#!/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] & 0x02 ? 256 : 0) + ($bytes[3] & 0x01 ? 1 : 0), sprintf('%.3f', 0.01024 * (256*$bytes[4] + $bytes[5] + ($bytes[3] & 0x08 ? 256 : 0) + ($bytes[3] & 0x04 ? 1 : 0))), sprintf('%04b', $bytes[3]) ); } sub race_setup_packet { my ($self, @bytes) = @_; $self->strange_packet('race_setup', @bytes) 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', @bytes) 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', @bytes) 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', @bytes) 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', @bytes) 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', @bytes) 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', @bytes) 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', @bytes) 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', @bytes) 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;