Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- use v6;
- use Data::Dump;
- use experimental :pack;
- use JSON::Tiny;
- use Redis::Async;
- constant $SOCKET_PORT = 7000;
- constant $SOCKET_ADDR = '0.0.0.0';
- constant $REDIS_PORT = 6379;
- constant $REDIS_ADDR = '127.0.0.1';
- constant $REDIS_AUTH = 'xxxxxxxxx';
- constant $REDIS_PATH = '/usr/bin/redis-cli';
- constant $IDLING_PERIOD_MIN = 178;
- constant $CACHE_EXPIRE_IN = 86400;
- # create socket
- my $socket = IO::Socket::Async.listen($SOCKET_ADDR, $SOCKET_PORT);
- # connnect to Redis ...
- my $redis;
- try {
- my $error-code = "110";
- $redis = Redis::Async.new("127.0.0.1:6379");
- $redis.auth($REDIS_AUTH);
- CATCH {
- default {
- say "Error $error-code ", .^name, ': Failed to initiate connection to Redis';
- exit;
- }
- }
- }
- # react whenever there is connection
- react {
- whenever $socket -> $conn {
- # do something when the connection wants to talk
- whenever $conn.Supply(:bin) {
- if $_.decode('utf-8').chars == 108 or $_.decode('utf-8').chars == 116 {
- say "Received --> "~$_.decode('utf-8');
- my $ack = generateAck($_.decode('utf-8'));
- if $ack {
- $conn.print: $ack;
- }else{
- say "No ack. Closing connection";
- $conn.close;
- }
- }
- }
- }
- CATCH {
- default {
- say .^name, ': ', .Str;
- say "handled in $?LINE";
- }
- }
- }
- sub generateAck($raw) {
- my $ack = 0;
- $raw ~~ s/^\s+|\s+$//; # remove heading/trailing whitespace
- # split device and vehicle data
- my ($device_data, $vehicle_data) = split('|', $raw);
- my ($header, $length, $datatype, $imei) = @($device_data ~~ /(..)(....)(..)(.+)/);
- unless $length eq '0116' or $length eq '0108' { return 0};
- my $veh_serial = substr($vehicle_data, 86, 4) || substr($vehicle_data, 78, 4);
- # at the same time, translate the data in parallel
- my $translated = start { # this is expensive as we will save the translated data to Redis as well
- my %vehicle_data_hash;
- try {
- my $error-code = "120";
- %vehicle_data_hash = translate($imei.Str, $vehicle_data);
- CATCH {
- default {
- say "Error $error-code ", .^name, ': Failed to translate data';
- return 0;
- }
- }
- }
- print "Publishing data..";
- try {
- my $error-code = "112";
- $redis.publish('data_json', to-json %vehicle_data_hash);
- CATCH {
- default {
- say "Error $error-code ", .^name, ': Failed to publish data to Redis';
- exit;
- }
- }
- }
- say "OK!";
- print "Storing data..";
- try {
- my $error-code = "113";
- $redis.set($imei~'.'~time, to-json %vehicle_data_hash);
- CATCH {
- default {
- say "Error $error-code ", .^name, ': Failed to set data to Redis';
- exit;
- }
- }
- }
- say "OK!";
- #say Dump %vehicle_data_hash;
- 1;
- };
- # check data checksum
- my $our_checksum = generateChecksum(substr($raw, 0, *-2));
- my $data_checksum = substr($raw, *-2);
- if $datatype ~~ /E\d/ or $datatype eq 'BB' {
- return 0;
- } else {
- $datatype = 'AA';
- }
- # if checksum ok, generate ACK
- if $our_checksum eq $data_checksum {
- $ack = '$$0014'~$datatype~$veh_serial~generateChecksum('$$0014'~$datatype~$veh_serial);
- say "Replied --> "~$ack;
- }
- await Promise.anyof($translated, Promise.in(1)); #in 1 second, if the data is not translated yet, kill it
- if $translated {
- return $ack;
- }
- else {
- return 0;
- }
- }
- sub translate($imei, $vehicle_data) {
- my %vehicle_data_hash;
- $redis.auth($REDIS_AUTH);
- if $vehicle_data.chars == 92 {
- %vehicle_data_hash =
- "veh_status" => substr($vehicle_data, 0, 8),
- "veh_datetime" => substr($vehicle_data, 8, 12),
- "veh_batt_voltage" => substr($vehicle_data, 20, 2),
- "veh_supp_voltage" => substr($vehicle_data, 22, 2),
- "veh_ADC1" => substr($vehicle_data, 24, 4),
- "veh_temp1" => substr($vehicle_data, 28, 4),
- "veh_temp2" => substr($vehicle_data, 32, 4),
- "veh_loc" => substr($vehicle_data, 36, 4),
- "veh_cellID" => substr($vehicle_data, 40, 4),
- "veh_sat" => substr($vehicle_data, 44, 2),
- "veh_signal" => substr($vehicle_data, 46, 2),
- "veh_angle" => substr($vehicle_data, 48, 3),
- "veh_speed" => substr($vehicle_data, 51, 3),
- "veh_hdop" => substr($vehicle_data, 54, 4),
- "veh_mileage" => substr($vehicle_data, 58, 7),
- "veh_lat" => substr($vehicle_data, 65, 9),
- "veh_ns" => substr($vehicle_data, 74, 1),
- "veh_long" => substr($vehicle_data, 75, 10),
- "veh_ew" => substr($vehicle_data, 85, 1),
- "dev_IMEI" => $imei,
- ;
- } elsif $vehicle_data.chars == 84 {
- %vehicle_data_hash =
- "veh_status" => substr($vehicle_data, 0, 8),
- "veh_datetime" => substr($vehicle_data, 8, 12),
- "veh_batt_voltage" => substr($vehicle_data, 20, 2),
- "veh_supp_voltage" => substr($vehicle_data, 22, 2),
- "veh_ADC1" => substr($vehicle_data, 24, 4),
- "veh_loc" => substr($vehicle_data, 28, 4),
- "veh_cellID" => substr($vehicle_data, 32, 4),
- "veh_sat" => substr($vehicle_data, 36, 2),
- "veh_signal" => substr($vehicle_data, 38, 2),
- "veh_angle" => substr($vehicle_data, 40, 3),
- "veh_speed" => substr($vehicle_data, 43, 3),
- "veh_hdop" => substr($vehicle_data, 46, 4),
- "veh_mileage" => substr($vehicle_data, 50, 7),
- "veh_lat" => substr($vehicle_data, 57, 9),
- "veh_ns" => substr($vehicle_data, 66, 1),
- "veh_long" => substr($vehicle_data, 67, 10),
- "veh_ew" => substr($vehicle_data, 78, 1),
- "dev_IMEI" => $imei,
- ;
- }
- my $veh_lat_degree = substr %vehicle_data_hash{"veh_lat"}, 0, 2;
- my $veh_lat_minute = substr %vehicle_data_hash{"veh_lat"}, 2;
- my $veh_long_degree = substr %vehicle_data_hash{"veh_long"}, 0, 3;
- my $veh_long_minute = substr %vehicle_data_hash{"veh_long"}, 3;
- my @bin = split('',sprintf("%b",:16(%vehicle_data_hash{"veh_status"}).base(10))) :skip-empty; #convert hex to 32bit binary and each bit to array
- unshift @bin, 0 while @bin.elems != 32; #make sure we have 32 elems!
- my $isIdle = @bin[14].Int; # motherfuc##r!
- my $isEngineStarted = @bin[1].Int;
- my $isOverSpeed = @bin[2].Int;
- my $isAntennaDrop = @bin[6].Int;
- if $isIdle {
- #vehicle is in idle state
- %vehicle_data_hash{"veh_status"} = "IDLE";
- } elsif $isOverSpeed {
- #vehicle is in overspeed state
- %vehicle_data_hash{"veh_status"} = "OSPD";
- } elsif $isAntennaDrop {
- #gps device not running
- %vehicle_data_hash{"veh_status"} = "NSIG";
- } else {
- #if vehicle is not in idle state, not in overspeed, gps is working fine and engine is running
- if $isEngineStarted {
- #vehicle is moving
- %vehicle_data_hash{"veh_status"} = "MOVE";
- } else {
- %vehicle_data_hash{"veh_status"} = "STOP";
- }
- }
- %vehicle_data_hash{"veh_lat_coordinate"} = sprintf("%.7f", $veh_lat_degree + sprintf("%.7f", $veh_lat_minute/60));
- %vehicle_data_hash{"veh_long_coordinate"} = sprintf("%.7f", $veh_long_degree + sprintf("%.7f", $veh_long_minute/60));
- my ($orig_year,$orig_mon,$orig_day,$orig_hour,$orig_min,$orig_sec) = %vehicle_data_hash{"veh_datetime"}.comb(2);
- my $format = sub ($self) { sprintf "%04d-%02d-%02d %02d:%02d:%02d" , .year, .month, .day, .hour, .minute, .second given $self; };
- my $dt = DateTime.new(
- year => $orig_year + 2000,
- month => $orig_mon,
- day => $orig_day,
- hour => $orig_hour,
- minute => $orig_min,
- second => $orig_sec,
- formatter => $format,
- ).utc;
- %vehicle_data_hash{"veh_datetime_parsed"} = $dt.local.Str;
- %vehicle_data_hash{"veh_datetime_epoch"} = $dt.posix.Str;;
- %vehicle_data_hash{"veh_breached"} = 0;
- ## Logic for IDLING starts here
- state $accumulatedStopPeriod = 0;
- my $stopPeriod = 0;
- my $cached_speed = $redis.get('cache.'~%vehicle_data_hash{'dev_IMEI'}~'.speed');
- my $cached_datetime = $redis.get('cache.'~%vehicle_data_hash{'dev_IMEI'}~'.datetime');
- my $cached_status = $redis.get('cache.'~%vehicle_data_hash{'dev_IMEI'}~'.status');
- if $cached_speed and $cached_datetime and $cached_status {
- #if was and is MOVE but speed was and is 0
- if (($cached_status eq 'MOVE' or $cached_status eq 'IDLE') and $cached_speed == '0' and %vehicle_data_hash{'veh_status'} eq 'MOVE' and %vehicle_data_hash{'veh_speed'} == '0') {
- $stopPeriod = %vehicle_data_hash{'veh_datetime_epoch'} - $cached_datetime;
- $accumulatedStopPeriod += $stopPeriod;
- say("total: $accumulatedStopPeriod");
- say("current stop period: $stopPeriod");
- if ($accumulatedStopPeriod > $IDLING_PERIOD_MIN) {
- %vehicle_data_hash{'veh_status'} = 'IDLE';
- }
- }
- }
- # $log->debug("S: $stopPeriod");
- if ($cached_status) {
- if ($cached_status eq 'IDLE' and (%vehicle_data_hash{'veh_status'} eq 'STOP' or %vehicle_data_hash{'veh_speed'} > '0' ) ) {
- $redis.del('cache.'~%vehicle_data_hash{'dev_IMEI'}~'.datetime') if $cached_datetime;
- $redis.del('cache.'~%vehicle_data_hash{'dev_IMEI'}~'.status') if $cached_status;
- $redis.del('cache.'~%vehicle_data_hash{'dev_IMEI'}~'.speed') if $cached_speed;
- $accumulatedStopPeriod = 0;
- }
- }
- $redis.set('cache.'~%vehicle_data_hash{'dev_IMEI'}~'.datetime',%vehicle_data_hash{'veh_datetime_epoch'});
- $redis.set('cache.'~%vehicle_data_hash{'dev_IMEI'}~'.status',%vehicle_data_hash{'veh_status'});
- $redis.set('cache.'~%vehicle_data_hash{'dev_IMEI'}~'.speed',%vehicle_data_hash{'veh_speed'});
- ## Logic for IDLING ends here
- return %vehicle_data_hash;
- }
- sub generateChecksum($minus_checksum) {
- # turn string into Blob
- my Blob $blob = $minus_checksum.encode('utf-8');
- # unpack Blob into ascii list
- my @array = $blob.unpack("C*");
- # perform bitwise operation for each ascii in the list
- my $dec +^= $_ for $blob.unpack("C*");
- # only take 2 digits
- $dec = sprintf("%02d", $dec) if $dec ~~ /^\d$/;
- $dec = '0'.$dec if $dec ~~ /^[a..fA..F]$/;
- $dec = uc $dec;
- # convert it to hex
- my $hex = sprintf '%02x', $dec;
- return uc $hex;
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement