From 6c1770adb57a99f638708c4679c7867fb8a4633b Mon Sep 17 00:00:00 2001 From: "Jan \"Yenya\" Kasprzak" Date: Sat, 12 Feb 2011 00:16:14 +0100 Subject: [PATCH] Factor out packet parsing into a subclassable module. --- SCX/Parser.pm | 422 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 422 insertions(+) create mode 100644 SCX/Parser.pm diff --git a/SCX/Parser.pm b/SCX/Parser.pm new file mode 100644 index 0000000..b71f26b --- /dev/null +++ b/SCX/Parser.pm @@ -0,0 +1,422 @@ +#!/usr/bin/perl -w + +package SCX::Parser; + +use strict; +use SCX::CRC; + +our $PACKET_SIZE = 9; # 9 bytes + 0x05 + +sub new { + my ($class, $args) = @_; + + my $self = { + bytes => [], + now => 0, + }; + + bless $self, $class; + + return $self; +} + +sub now { return shift->{now} } + +sub add_data { + my ($self, $time, @bytes) = @_; + + push @{ $self->{bytes} }, @bytes; + @bytes = @{ $self->{bytes} }; + + $self->{now} = $time; + + my @bad_bytes; + + while (@bytes > $PACKET_SIZE) { + if ($bytes[0] != 0x55) { + push @bad_bytes, shift @bytes; + next; + } + my $cmd = $bytes[1]; + + if ($bytes[$PACKET_SIZE] != 0x05 + || SCX::CRC::digest(@bytes[0..$PACKET_SIZE-2]) + != $bytes[$PACKET_SIZE-1]) { + push @bad_bytes, shift @bytes; + next; + } + + if (@bad_bytes) { # Report previous bad bytes first + $self->bad_bytes(@bad_bytes); + @bad_bytes = (); + } + + my @packet = splice @bytes, 0, $PACKET_SIZE+1; + $self->log_packet(@packet); + $self->parse_packet(@packet); + } + + if (@bad_bytes) { + while (@bytes && $bytes[0] != 0x55) { + push @bad_bytes, shift @bytes; + } + $self->bad_bytes(@bad_bytes); + } + + @{ $self->{bytes} } = @bytes; +} + +# Subclass these: + +sub log_packet { } +sub bad_bytes { } +sub unknown_packet { } +sub strange_packet { } + +sub bus_free_time { } +sub car_programming { } +sub reset { } +sub standings { } +sub car_lap_time { } +sub race_setup { } +sub fuel_level { } +sub brake_set { } +sub qualification { } +sub end_of_race { } +sub race_start { } +sub display_change { } +sub finish_line { } +sub controller_status { } + +our %COMMANDS = ( + 0xAA => \&bus_free_time_packet, + 0xCC => \&car_programming_packet, + 0xD0 => \&reset_packet, + 0xD3 => \&standings_packet, + 0xD4 => \&car_lap_time_packet, + 0xD5 => \&race_setup_packet, + 0xD6 => \&fuel_level_packet, + 0xD7 => \&brake_set_packet, + 0xDB => \&qualification_packet, + 0xDC => \&end_of_race_packet, + 0xDD => \&race_start_packet, + 0xDE => \&display_change_packet, + 0xEE => \&finish_line_packet, + 0xFF => \&controller_status_packet, +); + +sub parse_packet { + my ($self, @data) = @_; + + my $cmd = $data[1]; + my @args = @data[2..7]; + + my $sub = $COMMANDS{$cmd}; + + if (!defined $sub) { + $self->unknown_packet($cmd, @args); + return; + } + + return &$sub($self, @args); +} + +sub bus_free_time_packet { + my ($self, @bytes) = @_; + + $self->strange_packet('bus free time', @bytes) + if $bytes[2] != 0xF0 + || $bytes[3] != 0xF0 + || $bytes[4] != 0xF0 + || $bytes[5] != 0xF0; + + $self->bus_free_time($bytes[1], $bytes[0]); +} + +sub car_programming_packet { + my ($self, @bytes) = @_; + + $self->strange_packet('car programming', @bytes) + if ($bytes[0] & 0xF8) != 0 || ($bytes[0] & 0x07) > 5 + || $bytes[1] != 0xFE + || $bytes[2] != 0xFF + || $bytes[3] != 0xFF + || $bytes[4] != 0xFF + || $bytes[5] != 0xFF; + + $self->car_programming($bytes[0] & 0x07); +} + +sub reset_packet { + my ($self, @bytes) = @_; + + $self->strange_packet('reset', @bytes) + if $bytes[0] != 0xFF + || $bytes[3] != 0xAA + || $bytes[4] != 0xAA + || $bytes[5] != 0xAA; + + $self->reset($bytes[1], $bytes[2]); +} + +sub standings_packet { + my ($self, @bytes) = @_; + + self->strange_packet('standings', @bytes) + if ($bytes[0] != 0xFF && ($bytes[0] & 0x07) > 5) + || ($bytes[1] != 0xFF && ($bytes[1] & 0x07) > 5) + || ($bytes[2] != 0xFF && ($bytes[2] & 0x07) > 5) + || ($bytes[3] != 0xFF && ($bytes[3] & 0x07) > 5) + || ($bytes[4] != 0xFF && ($bytes[4] & 0x07) > 5) + || ($bytes[5] != 0xFF && ($bytes[5] & 0x07) > 5); + + $self->standings(map { $_ != 0xFF ? $_ & (0x07) : () } @bytes); +} + +sub car_lap_time_packet { + my ($self, @bytes) = @_; + + $self->strange_packet('car lap time', @bytes) + if $bytes[0] > 5 + || $bytes[1] & 0x01 + || $bytes[2] & 0x01 + || ($bytes[3] & 0xF0) != 0 + || $bytes[4] & 0x01 + || $bytes[5] & 0x01; + + $self->car_lap_time($bytes[0], + 256*$bytes[1] + $bytes[2] + ($bytes[3] & 0x01 ? 1 : 0), + sprintf('%.3f', 0.01024 * (256*$bytes[4] + $bytes[5] + + ($bytes[3] & 0x08 ? 1 : 0))), + sprintf('%04b', $bytes[3]) + ); +} + +sub race_setup_packet { + my ($self, @bytes) = @_; + + $self->strange_packet('race setup') + if ($bytes[0] != 0x00 && $bytes[0] != 0xFF) + || $bytes[1] & 0xF0 + || $bytes[2] & 0xF0 + || $bytes[3] & 0xF0 + || $bytes[4] != 0xFF + || $bytes[5] != 0xFF; + + my $rounds = $bytes[0] == 0x00 + ? 0 + : ($bytes[1] & 0x0F) * 256 + + ($bytes[2] & 0x0F) * 16 + + ($bytes[3] & 0x0F); + + $self->race_setup($rounds); +} + +sub fuel_level_packet { + my ($self, @bytes) = @_; + + $self->strange_packet('fuel level') + if ($bytes[0] >> 4) > 8 + || ($bytes[0] & 0x0F) > 8 + || ($bytes[1] >> 4) > 8 + || ($bytes[1] & 0x0F) > 8 + || ($bytes[2] >> 4) > 8 + || ($bytes[2] & 0x0F) > 8 + || ($bytes[5] != 0xAA && $bytes[5] != 0xFF); + + my @fuel = ( + $bytes[0] >> 4, $bytes[0] & 0x0f, + $bytes[1] >> 4, $bytes[1] & 0x0f, + $bytes[2] >> 4, $bytes[2] & 0x0f, + ); + + $self->fuel_level(@fuel); +} + +sub brake_set_packet { + my ($self, @bytes) = @_; + + $self->strange_packet('brake set') + if ($bytes[0] > 5) + || ($bytes[1] != 0x00 && $bytes[1] != 0x02 && $bytes[1] != 0x04) + || $bytes[2] != 0x83 + || $bytes[3] != 0x93 + || $bytes[4] != 0xDB + || $bytes[5] != 0xFF; + + $self->brake_set($bytes[0], + $bytes[1] == 0x00 ? 0 + : $bytes[1] == 0x02 ? 50 + : 100); +} + +sub qualification_packet { + my ($self, @bytes) = @_; + + $self->strange_packet('qualification') + if $bytes[0] & 0xF0 + || $bytes[1] & 0xF0 + || $bytes[2] & 0xF0 + || $bytes[3] > 6 + || $bytes[4] != 0xFF + || $bytes[5] != 0xFF; + + my $rounds = ($bytes[0] & 0x0F) * 256 + + ($bytes[1] & 0x0F) * 16 + + ($bytes[2] & 0x0F); + my $cars = $bytes[3]; + + $self->qualification($rounds, $cars); +} + +sub end_of_race_packet { + my ($self, @bytes) = @_; + + $self->strange_packet('end of race') + if $bytes[0] != 0xFF + || $bytes[1] != 0xFF + || $bytes[2] != 0xFF + || $bytes[3] != 0xFF + || $bytes[4] != 0xFF + || $bytes[5] != 0xFF; + + $self->end_of_race(); +} + +sub race_start_packet { + my ($self, @bytes) = @_; + + $self->strange_packet('race start') + if $bytes[0] != 0x00 + || $bytes[1] != 0xAA + || $bytes[2] != 0xAA + || $bytes[3] != 0xAA + || $bytes[4] != 0xAA + || $bytes[5] != 0xAA; + + $self->race_start(); +} + +sub display_change_packet { + my ($self, @bytes) = @_; + + $self->strange_packet('display change') + if $bytes[0] & 0xFE + || $bytes[1] != 0xFF + || $bytes[2] != 0xFF + || $bytes[3] != 0xFF + || $bytes[4] != 0xFF + || $bytes[5] != 0xFF; + + $self->display_change(); +} + +=comment + +# FIXME: we still do not know the meaning of the bytes +sub finish_line_packet { + my ($self, @bytes) = @_; + + my $fail; + for my $byte (@bytes) { + $fail = 1 + if $byte != 0xAA + && $byte != 0xE7 + && $byte != 0xF0 + && $byte != 0xFE + } + + my $msg = 'Strange finish_line packet' + if $fail; + + my $regular = 1; + my @cars_finished; + for my $i (0..5) { + my $byte = $bytes[$i]; + + $regular = 0 + if $byte != 0xAA && $byte != 0xE7 && $byte != 0xFE; + + push @cars_finished, $i if $byte == 0xE7; + } + + $self->log_cmd('finish_line', $regular, @cars_finished); + $self->track->finish_line( + $self->{last_read_time}, + $regular, + @cars_finished + ); + + return $msg; +} +=cut + +sub finish_line_packet { + my ($self, @bytes) = @_; + + my $fail; + for my $byte (@bytes) { + $fail = 1 + if $byte != 0xAA + && $byte != 0xE7 + && $byte != 0xF0 + && $byte != 0xFE + } + + $self->strange_packet('finish line') + if $fail; + + my $regular = 1; + my @cars_finished; + for my $i (0..5) { + my $byte = $bytes[$i]; + + $regular = 0 + if $byte != 0xAA && $byte != 0xE7 && $byte != 0xFE; + + push @cars_finished, $i if $byte == 0xE7; + } + + $self->finish_line($regular, @cars_finished); +} + +sub controller_status_packet { + my ($self, @bytes) = @_; + + my $fail; + for my $byte (@bytes) { + next if $byte == 0xAA; + $fail = 1 + if ($byte & 0xC0) != 0xC0 + || ($byte & 0x0F) > 12 + } + + $self->strange_packet('controller status') + if $fail; + + my @ctrl_data; + + for my $car (0..5) { + my $byte = $bytes[$car]; + + if ($byte == 0xAA) { + push @ctrl_data, undef; + next; + } + + my $light = ($byte & 0x20) ? 0 : 1; + my $backbutton = ($byte & 0x10) ? 0 : 1; + my $throttle = $byte & 0x0f; + + push @ctrl_data, { + throttle => $throttle, + button => $backbutton, + light => $light, + }; + } + + $self->controller_status(@ctrl_data); +} + +1; + -- 2.43.0