From f412074b350e24165da2686b0f6cfbe14824bebf Mon Sep 17 00:00:00 2001 From: nebulous Date: Fri, 27 Sep 2024 16:30:19 -0400 Subject: [PATCH] payload-parsing: port frontend to new frame shape add addtl serial tab filters and prettier rendering --- infinitude | 38 ++++++------------- lib/CarBus.pm | 5 ++- lib/CarBus/Frame.pm | 27 +++++++------ public/app/scripts/controllers/main.js | 52 +++++++++++++++++--------- public/app/views/main.html | 2 +- public/app/views/node_tree.html | 21 +++++++++++ public/app/views/serial.html | 39 ++++++++++--------- 7 files changed, 106 insertions(+), 78 deletions(-) create mode 100644 public/app/views/node_tree.html diff --git a/infinitude b/infinitude index bc89911..a2af9bf 100755 --- a/infinitude +++ b/infinitude @@ -83,26 +83,6 @@ sub serial_init { } serial_init(); -my @inbox = (); -if ($use_serial) { - my $last_frame_time = time; - my $frame_inbox_id = Mojo::IOLoop->recurring(0.0625 => sub { - my $loop = shift; - $loop->on(finish => sub { warn "Frame filler loop FINISH"; }); - $loop->on(reset => sub { warn "Frame filler loop RESET"; }); - if ((time - $last_frame_time) < 10) { - return unless $carbus; - if (my $frame = $carbus->get_frame) { - push(@inbox, $frame); - $last_frame_time = time; - } - shift @inbox if scalar(@inbox)>128; - } else { - serial_init(); - } - }); -} - app->secrets([$config->{app_secret}]); push (@{app->static->paths}, ('development' eq ($ENV{MOJO_MODE}//'')) ? 'public/app' : 'public/dist'); @@ -431,26 +411,28 @@ any '/systems/:system_id/:part' => sub { my $scantab = 0; my $scanrow = 0; my $scansec = 0; +my $lastframe = 0; websocket '/serial' => sub { my $c = shift; unless ($use_serial) { - $c->app->log->info("Websocket opened, but no streaming source is available"); + $c->app->log->info("Websocket opened, but no streaming source is configured"); return; } - my $socketloop_id = Mojo::IOLoop->recurring(0.03125 => sub { + my $socketloop_id = Mojo::IOLoop->recurring(0.0625 => sub { my $loop = shift; - if (my $frame = shift @inbox) { + serial_init() if (!$carbus or time>($lastframe+10)); + if (my $frame = $carbus->get_frame) { return if (!$frame or !$frame->struct->{cmd}); my $fstruc = $frame->frame_hash; - $fstruc->{timestamp} = time; - if ($fstruc->{function} eq 'reply') { + $fstruc->{timestamp} = $lastframe = time; + if ($fstruc->{cmd} eq 'reply') { my $payf = $fstruc->{payload}; if ($payf) { if ($payf->{rows} and $ENV{SCAN_THERMOSTAT}) { $scanrow = $payf->{rows}+1; warn sprintf(">>>>>>>>>>> table %02x has %d rows <<<<<<<<<<<<<<<<<<<", $scantab, $payf->{rows}); } - $fstruc->{field} = { $fstruc->{type}=>$fstruc->{payload} }; + $fstruc->{field} = { $fstruc->{reg_name}||'unknown' => $payf }; } } @@ -469,14 +451,16 @@ websocket '/serial' => sub { $carbus->samreq($scantab, $scanrow); } } + #warn $frame->frame_log; $c->send({json=>$fstruc}); } }); + $c->app->log->info("Websocket $socketloop_id Established"); $c->on('finish' => sub { my ($c, $code) = @_; Mojo::IOLoop->remove($socketloop_id); - $c->app->log->info("Websocket Closed: $code"); + $c->app->log->info("Websocket $socketloop_id Closed: $code"); }); }; diff --git a/lib/CarBus.pm b/lib/CarBus.pm index 7452be7..88023c0 100644 --- a/lib/CarBus.pm +++ b/lib/CarBus.pm @@ -88,8 +88,8 @@ sub samreq { my ($table, $row, $frameopts) = @_; $frameopts //= {}; my $samframe = CarBus::Frame->new( - src=>'SAM', - dst=>'Thermostat', + src=>'FakeSAM', src_bus=>1, + dst=>'Thermostat', dst_bus=>1, cmd=>'read', payload_raw=>pack("C*", 0, $table, $row), %$frameopts @@ -108,6 +108,7 @@ package CarBus::Bridge; use Moo; has buslist => (is=>'ro'); +has routes => (is=>'rw', default=>sub{{}}); sub drive { my $self = shift; diff --git a/lib/CarBus/Frame.pm b/lib/CarBus/Frame.pm index ae1b655..b415058 100644 --- a/lib/CarBus/Frame.pm +++ b/lib/CarBus/Frame.pm @@ -54,7 +54,17 @@ my $fp = Struct("CarFrame", Value("gensum", sub { crc16(substr($_->ctx->{raw},0,-2)) }), Value("valid", sub { $_->ctx->{gensum} == $_->ctx->{checksum} }), Value("payload", sub { length($_->ctx->{payload_raw})<=3 ? undef - : subparser($_->ctx->{reg_string})->parse(substr($_->ctx->{payload_raw},3)) }) + : subparser($_->ctx->{reg_string})->parse(substr($_->ctx->{payload_raw},3)) }), + + Value("reg_name", sub { + my $fh = $_->ctx; + my $subp = subparser($fh->{reg_string}); + my $regname = $fh->{reg_string} // ''; + $regname = $subp->{Name}."($regname)" if $subp; + return $regname; + }) + + ); around BUILDARGS => sub { @@ -74,11 +84,6 @@ around BUILDARGS => sub { has parser => (is=>'ro', default=> sub { $fp }); has struct => (is=>'rw'); -sub payload_parser { - my $self = shift; - my $pp = subparser($self->struct->{reg_string}); -} - sub valid { shift->struct->{valid} } @@ -114,14 +119,11 @@ sub frame_hash { sub frame_log { my $self = shift; my $fh = $self->frame_hash; - my $subp = subparser($fh->{reg_string}); - my $regname = $fh->{reg_string} // ''; - $regname = $subp->{Name}."($regname)" if $subp; return join(' ', $fh->{src}, $fh->{cmd}, $fh->{dst}, - $regname + $fh->{reg_name} ); } @@ -154,7 +156,8 @@ my $parsers = { PaddedString('reference', 24, paddir=>'right'), ), - '0202' => Struct('time', Byte('hour'), Byte('minute'), Byte('unknown')), + '0202' => Struct('time', Byte('hour'), Byte('minute'), Enum(Byte('weekday'), 0=>'Sunday', 1=>'Monday', 2=>'Tuesday', 3=>'Wednesday', 4=>'Thursday', 5=>'Friday', 6=>'Saturday')), + '0203' => Struct('date', Byte('day'), Byte('month'), Byte('20xx'), Value('year', sub { 2000+int($_->ctx->{'20xx'}) })), @@ -175,7 +178,7 @@ my $parsers = { Enum(Nibble('mode'), heat=>0, cool=>1, auto=>2, eheat=>3, off=>4) ), Array(2, Byte('unknown')), - Byte('day_of_week'), + Enum(Byte('weekday'), 0=>'Sunday', 1=>'Monday', 2=>'Tuesday', 3=>'Wednesday', 4=>'Thursday', 5=>'Friday', 6=>'Saturday'), UBInt16('minutes_since_midnight'), Byte('displayed_zone') ), diff --git a/public/app/scripts/controllers/main.js b/public/app/scripts/controllers/main.js index 986661f..3737371 100644 --- a/public/app/scripts/controllers/main.js +++ b/public/app/scripts/controllers/main.js @@ -6,11 +6,17 @@ function wsu(s) { } var toHex = function (str) { - var hex = ''; - for(var i=0;i c.charCodeAt(0).toString(16).padStart(2, "0")) + .join(" "); +}; + +var fromHex = function (hexstr) { + return hexstr.replace(" ","") + .split(/(\w\w)/g) + .filter(p => !!p) + .map(c => String.fromCharCode(parseInt(c, 16))) + .join("") }; angular.module('infinitude') @@ -42,6 +48,7 @@ angular.module('infinitude') }) .filter('strings', function() { return function (str, min) { + if (!str) { return "" } min = min || 4; var cnt = 0; var instring = false; @@ -66,6 +73,9 @@ angular.module('infinitude') .filter('toHex', function() { return toHex; }) + .filter('fromHex', function() { + return fromHex; + }) .filter('toList', function() { return function(items) { var filtered = []; @@ -89,6 +99,8 @@ angular.module('infinitude') // True means the data has been copied and the user has edited it $scope.systemsEdited = null; + $scope.typeof = function(variable) { return typeof(variable) }; + $scope.mkTime = function(input) { if (angular.equals({}, input)) { return '00:00'; } return input; @@ -227,22 +239,26 @@ angular.module('infinitude') var transferTimer; serial.onmessage = function(m) { var frame = angular.fromJson(m.data); + if (typeof(frame.cmd) != 'string') { console.log(frame) } $scope.transferColor = '#4F4'; $timeout.cancel(transferTimer); $timeout(function() { $scope.transferColor = '#5E5'; }, 2000); /* jshint ignore:start */ - var dataView = new jDataView(frame.data); + var dataView = new jDataView(frame.payload_raw); if (typeof($scope.carbus) == 'undefined') { $scope.carbus = {}; } $scope.history = angular.fromJson(window.localStorage.getItem('tmpdat')) || {}; - if (frame.Function.match(/write|reply/)) { - var address = toHex(frame.data.substring(0,3)); - var id = frame.Function + frame.SrcClass + frame.DstClass + address; - frame.Device = frame.Function === 'reply' ? frame.SrcClass : frame.DstClass; + if (frame.cmd.match(/write|reply/)) { + var address = frame.reg_string; + address=address||""; + address=address.toUpperCase(); + + var id = frame.cmd + frame.src + frame.dst + address; + frame.Device = frame.cmd === 'reply' ? frame.src : frame.dst; $scope.devices[frame.Device] = 1; var busLog = function(key,value) { @@ -257,21 +273,21 @@ angular.module('infinitude') // Break this out into config once others publish their registers. // Are you reading this? Then you're probably one of those people. - if (frame.Function == 'reply' && frame.SrcClass.match(/IndoorUnit/)) { - if (address.match(/00 03 06/)) { + if (frame.cmd == 'reply' && frame.src.match(/IndoorUnit/)) { + if (address.match(/0306/)) { $scope.carbus.blowerRPM = dataView.getInt16(1 +3); busLog('blowerRPM', $scope.carbus.blowerRPM); } - if (address.match(/00 03 16/)) { + if (address.match(/0316/)) { $scope.carbus.airflowCFM = dataView.getInt16(4 +3); busLog('airflowCFM', $scope.carbus.airflowCFM); } } - if (frame.Function == 'reply' && frame.SrcClass.match(/OutdoorUnit/)) { - if (address.match(/00 03 02/)) { + if (frame.cmd == 'reply' && frame.src.match(/OutdoorUnit/)) { + if (address.match(/0302/)) { $scope.carbus.outsideTemp = dataView.getInt16(2 +3)/16; } - if (address.match(/00 3E 01/)) { + if (address.match(/3E01/)) { $scope.carbus.outsideTemp = dataView.getInt16(0 +3)/16; $scope.carbus.coilTemp = dataView.getInt16(2 +3)/16; busLog('coilTemp', $scope.carbus.coilTem); @@ -280,13 +296,13 @@ angular.module('infinitude') } var lastframe = $scope.state[id] || frame; - lastframe = lastframe.data; + lastframe = lastframe.payload_raw; $scope.state[id] = $scope.state[id] || {}; angular.extend($scope.state[id],frame); $scope.state[id].history = $scope.state[id].history || []; - if (lastframe !== frame.data) { $scope.state[id].history.unshift(lastframe); } + if (lastframe !== frame.payload_raw) { $scope.state[id].history.unshift(lastframe); } if ($scope.state[id].history.length>9) { $scope.state[id].history.pop(); } window.localStorage.setItem('infinitude-serial-state',angular.toJson($scope.state)); diff --git a/public/app/views/main.html b/public/app/views/main.html index 6fb09f5..b6cd2ea 100644 --- a/public/app/views/main.html +++ b/public/app/views/main.html @@ -4,6 +4,7 @@

Status

{{ notifications.notification[0].timestamp[0] | date:"EEE yyyy-MM-dd 'at' h:mma" }} +

Global

Operating Mode: {{status.mode[0]}} @@ -62,7 +63,6 @@

{{zone.name[0]}}

-
{{ status.zones[0].zone[selectedZone].name[0] }}
diff --git a/public/app/views/node_tree.html b/public/app/views/node_tree.html new file mode 100644 index 0000000..5dda821 --- /dev/null +++ b/public/app/views/node_tree.html @@ -0,0 +1,21 @@ +
+{{ depth = typeof(depth) == 'number' ? depth : 0 }} +
+ + + + + + + + + + + + + +
{{title}}
{{ k }} + {{v}} + +
+
diff --git a/public/app/views/serial.html b/public/app/views/serial.html index 72191ee..f803b3b 100644 --- a/public/app/views/serial.html +++ b/public/app/views/serial.html @@ -9,22 +9,22 @@

Virtual SAM Request

Stream

- +
- - - - + + + + - - - - - - + + + + + +
srcdstfunctionaddresssrc dst cmd reg
{{ frame.SrcClass }}{{ frame.DstClass }}{{ frame.Function }}{{ frame.data | limitTo:3 | toHex }}
{{ frame.src }}{{ frame.dst }}{{ frame.cmd }}{{ frame.payload_raw | limitTo:3 | toHex }}
@@ -40,26 +40,29 @@

State

Updated - Device - Address + Src + Register Value - + {{ frame.timestamp*1000 | timeAgo }} {{ frame.Device }} - {{ frame.data | limitTo:3 | toHex }} + {{ frame.payload_raw | limitTo:3 | toHex }}

{{ frame.field.name }}

- -
{{ frame.data | strings }}
+ +
{{ frame.payload_raw | strings }}
+
-

+

+

{{ frame.field }}

+