Skip to content

Commit

Permalink
payload-parsing: port frontend to new frame shape
Browse files Browse the repository at this point in the history
add addtl serial tab filters and prettier rendering
  • Loading branch information
nebulous committed Sep 27, 2024
1 parent 323331b commit f412074
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 78 deletions.
38 changes: 11 additions & 27 deletions infinitude
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -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 };
}
}

Expand All @@ -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");
});
};

Expand Down
5 changes: 3 additions & 2 deletions lib/CarBus.pm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -108,6 +108,7 @@ package CarBus::Bridge;
use Moo;

has buslist => (is=>'ro');
has routes => (is=>'rw', default=>sub{{}});

sub drive {
my $self = shift;
Expand Down
27 changes: 15 additions & 12 deletions lib/CarBus/Frame.pm
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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} }

Expand Down Expand Up @@ -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}
);
}

Expand Down Expand Up @@ -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'}) })),


Expand All @@ -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')
),
Expand Down
52 changes: 34 additions & 18 deletions public/app/scripts/controllers/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@ function wsu(s) {
}

var toHex = function (str) {
var hex = '';
for(var i=0;i<str.length;i++) {
hex += ' '+('0' + str.charCodeAt(i).toString(16).toUpperCase()).substr(-2,2);
}
return hex;
return str.split("")
.map(c => 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')
Expand Down Expand Up @@ -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;
Expand All @@ -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 = [];
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand All @@ -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));
Expand Down
2 changes: 1 addition & 1 deletion public/app/views/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ <h3>Status</h3>
<strong>{{ notifications.notification[0].timestamp[0] | date:"EEE yyyy-MM-dd 'at' h:mma" }}</strong>
</blockquote>


<h4>Global</h4>
Operating Mode: <strong>{{status.mode[0]}}</strong>

Expand Down Expand Up @@ -62,7 +63,6 @@ <h4>{{zone.name[0]}}</h4>
</div>
</div>

<div>{{ status.zones[0].zone[selectedZone].name[0] }}</div>
<div class="row">
<div class="col-md-6">
<div class="table-responsive">
Expand Down
21 changes: 21 additions & 0 deletions public/app/views/node_tree.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<div style="display: none">
{{ depth = typeof(depth) == 'number' ? depth : 0 }}
</div>
<table class="table table-striped table-bordered table-rounded">
<thead ng-if="title">
<tr>
<th class="text-center" colspan="2">{{title}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="(k,v) in node track by depth+k">
<th class="text-right">{{ k }}</th>
<td ng-if="typeof(v) == 'string' || typeof(v) == 'number'">
{{v}}
</td>
<td ng-if="typeof(v) == 'object' && depth<=4">
<div ng-include="'views/node_tree.html'" ng-init="node=v; depth=depth+1; title='';"></div>
</td>
</tr>
</tbody>
</table>
39 changes: 21 additions & 18 deletions public/app/views/serial.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,22 @@ <h4>Virtual SAM Request</h4>
<div class="row">
<div class="col-md-12">
<h3>Stream</h3>
<table class="table table-bordered table-hover">
<table class="table table-bordered table-hover" style="table-layout: fixed">
<thead>
<tr class="active">
<th>src</th>
<th>dst</th>
<th>function</th>
<th>address</th>
<th>src <input type="text" ng-model="srcFilter" /></th>
<th>dst <input type="text" ng-model="dstFilter" /></th>
<th>cmd <input type="text" ng-model="cmdFilter" /></th>
<th>reg <input type="text" ng-model="regFilter" /></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="frame in frames" ng-class="{'reply':'success', 'write':'warning', 'exception':'danger'}[frame.Function]">
<td>{{ frame.SrcClass }}</td>
<td>{{ frame.DstClass }}</td>
<td>{{ frame.Function }}</td>
<td style="white-space: nowrap;">{{ frame.data | limitTo:3 | toHex }}</td>
<!-- <td>{{ (frame.data | strings) || (frame.data | toHex) }}</td> -->
<tr ng-repeat="frame in frames | filter: { src:srcFilter, dst:dstFilter, cmd: cmdFilter, reg_string:regFilter }" ng-class="{'reply':'success', 'write':'warning', 'exception':'danger'}[frame.cmd]">
<td>{{ frame.src }}</td>
<td>{{ frame.dst }}</td>
<td>{{ frame.cmd }}</td>
<td style="white-space: nowrap;">{{ frame.payload_raw | limitTo:3 | toHex }}</td>
<!-- <td>{{ (frame.payload_raw | strings) || (frame.payload_raw | toHex) }}</td> -->
</tr>
</tbody>
</table>
Expand All @@ -40,26 +40,29 @@ <h3>State</h3>
<thead>
<tr>
<th>Updated</th>
<th>Device</th>
<th>Address</th>
<th>Src</th>
<th>Register</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="frame in state | toList | orderBy: ['-Device','Function','data|limitTo:3']" ng-class="{'read':'success', 'write':'danger', 'reply':'warning'}[frame.Function]">
<tr ng-repeat="frame in state | toList | orderBy: ['-Device','cmd','reg_string']" ng-class="{'read':'success', 'write':'danger', 'reply':'warning'}[frame.cmd]">
<td>{{ frame.timestamp*1000 | timeAgo }}</td>
<td>{{ frame.Device }}</td>
<td style="white-space: pre-line">
{{ frame.data | limitTo:3 | toHex }}
{{ frame.payload_raw | limitTo:3 | toHex }}
<p class="text-primary" ng-if="frame.field.name">{{ frame.field.name }}</p>
</td>
<td style="font-family: monospace">
<strong ng-bind-html="frame.data | subStr:3 | toHex | markDiff:(frame.history[0]|subStr:3|toHex)"></strong>
<br /><pre class="text-primary" style="white-space: pre" ng-if="frame.data|strings">{{ frame.data | strings }}</pre>
<strong ng-bind-html="frame.payload_raw | subStr:3 | toHex | markDiff:(frame.history[0]|subStr:3|toHex)"></strong>
<br /><pre class="text-primary" style="white-space: pre" ng-if="frame.payload_raw|strings">{{ frame.payload_raw | strings }}</pre>
<hr>
<div ng-repeat="log in frame.history track by $index">
<p ng-if="!$last" ng-bind-html="(log|toHex)|markDiff:(frame.history[$index+1]|toHex)|subStr:9"></p>
<p style="opacity: {{1-$index/20}}" ng-if="!$last" ng-bind-html="(log|toHex)|markDiff:(frame.history[$index+1]|toHex)|subStr:9"></p>
</div>

<p class="text-primary" style="white-space: pre-wrap" ng-if="frame.field">{{ frame.field }}</p>
<div ng-if="frame.field" ng-include="'views/node_tree.html'" ng-init="node=frame.field"></div>
</td>
</tr>
</tbody>
Expand Down

0 comments on commit f412074

Please sign in to comment.