7 use Time::HiRes qw(gettimeofday tv_interval);
13 our $PACKET_SIZE = 9; # 9 bytes + 0x05
14 our $LOG_ROTATE = 600;
17 my ($class, $args) = @_;
19 my $portname = $args->{portname}
20 or die "portname not specified";
22 system 'stty', '-F', $portname, '115200', 'raw';
24 die "stty died with code $? (no permissions?)";
27 sysopen(my $fh, $portname, O_RDONLY|O_NONBLOCK)
28 or die "Can't open $portname: $!";
30 my $logfile = $args->{logfile};
33 open my $logfh, '>', "$logfile.$log_gen"
34 or die "Can't open $logfile.$log_gen: $!";
36 my $now = gettimeofday;
39 portname => $portname,
46 track => $args->{track},
55 sub fh { return shift->{fh}; }
57 sub track { return shift->{track}; }
63 my $bytes_read = sysread $self->fh, $data, $PACKET_SIZE;
64 die "Read error on $self->{portname}: $!"
67 my @bytes = unpack("C*", $data);
69 # print join(' ', map { sprintf(" %02x", $_) } @bytes), "\n";
70 push @{ $self->{bytes} }, @bytes;
71 @bytes = @{ $self->{bytes} };
75 while (@bytes > $PACKET_SIZE) {
76 if ($bytes[0] != 0x55) {
77 push @bad_bytes, shift @bytes;
82 if ($bytes[$PACKET_SIZE] != 0x05
83 || SCX::CRC::digest(@bytes[0..$PACKET_SIZE-2])
84 != $bytes[$PACKET_SIZE-1]) {
85 push @bad_bytes, shift @bytes;
89 if (@bad_bytes) { # Report previous bad bytes first
90 $self->log_bytes(\@bad_bytes, "Cannot parse packet");
94 my @packet = splice @bytes, 0, $PACKET_SIZE+1;
95 my $rv = $self->handle_packet(@packet);
96 $self->log_bytes(\@packet, $rv);
99 while (@bytes && $bytes[0] != 0x55) {
100 push @bad_bytes, shift @bytes;
102 $self->log_bytes(\@bad_bytes, "cannot parse packet");
105 @{ $self->{bytes} } = @bytes;
109 my ($self, $bytes, $msg) = @_;
113 $msg = defined $msg ? ' # ' . $msg : '';
115 my $now = gettimeofday;
117 if ($now - $self->{log_start} >= $LOG_ROTATE) {
118 close $self->{logfh};
119 $self->{log_gen} = $self->{log_gen} ? 0 : 1;
120 open my $fh, '>', $self->{logfile} . '.' . $self->{log_gen}
121 or die "Can't open $self->{logfile}.$self->{log_gen}: $!";
122 $self->{logfh} = $fh;
123 $self->{log_start} = $now;
126 $self->{logfh}->print(sprintf('% 10.3f', $now - $self->{starttime}),
127 (map { sprintf(" %02x", $_) } @$bytes),
129 $self->{logfh}->flush;
133 0xAA => \&bus_free_time_packet,
134 0xCC => \&car_programming_packet,
135 0xD0 => \&reset_packet,
136 0xD3 => \&standings_packet,
137 0xD4 => \&lap_time_packet,
138 0xD5 => \&race_setup_packet,
139 0xD6 => \&fuel_level_packet,
140 0xD7 => \&brake_set_packet,
141 0xDB => \&qualification_packet,
142 0xDC => \&end_of_race_packet,
143 0xDD => \&race_start_packet,
144 0xDE => \&display_change_packet,
145 0xEE => \&finish_line_packet,
146 0xFF => \&controller_status_packet,
150 my ($self, @data) = @_;
153 my @args = @data[2..7];
155 my $sub = $COMMANDS{$cmd};
156 return "Unknown packet"
159 return &$sub($self, @args);
162 sub bus_free_time_packet {
163 my ($self, @bytes) = @_;
165 my $msg = 'Strange bus free time packet'
169 || $bytes[5] != 0xF0;
171 return $msg; # No need to handle this, I think
174 sub car_programming_packet {
175 my ($self, @bytes) = @_;
177 my $msg = 'Strange car programming packet'
178 if ($bytes[0] & 0xF8) != 0 || ($bytes[0] & 0x07) > 5
183 || $bytes[5] != 0xFF;
189 my ($self, @bytes) = @_;
191 my $msg = 'Strange reset packet'
195 || $bytes[5] != 0xAA;
197 return $msg; # FIXME - to be implemented
200 sub standings_packet {
201 my ($self, @bytes) = @_;
203 my $msg = 'Strange standings packet'
204 if ($bytes[0] != 0xFF && ($bytes[0] & 0x07) > 5)
205 || ($bytes[1] != 0xFF && ($bytes[1] & 0x07) > 5)
206 || ($bytes[2] != 0xFF && ($bytes[2] & 0x07) > 5)
207 || ($bytes[3] != 0xFF && ($bytes[3] & 0x07) > 5)
208 || ($bytes[4] != 0xFF && ($bytes[4] & 0x07) > 5)
209 || ($bytes[5] != 0xFF && ($bytes[5] & 0x07) > 5);
213 push @standings, map { $_ != 0xFF ? $_ & (0x07) : () } @bytes;
215 return $msg; # FIXME - to be implemented
218 sub lap_time_packet {
219 my ($self, @bytes) = @_;
221 my $msg = 'Strange lap time packet'
225 || ($bytes[3] & 0xF0) != 0
229 return $msg; # FIXME - to be implemented
233 sub race_setup_packet {
234 my ($self, @bytes) = @_;
236 my $msg = 'Strange race setup packet'
237 if ($bytes[0] != 0x00 && $bytes[0] != 0xFF)
242 || $bytes[5] != 0xFF;
244 return $msg; # FIXME - to be implemented
248 sub fuel_level_packet {
249 my ($self, @bytes) = @_;
251 my $msg = 'Strange fuel_level packet'
252 if ($bytes[0] >> 4) > 8
253 || ($bytes[0] & 0x0F) > 8
254 || ($bytes[1] >> 4) > 8
255 || ($bytes[1] & 0x0F) > 8
256 || ($bytes[2] >> 4) > 8
257 || ($bytes[2] & 0x0F) > 8
258 || ($bytes[5] != 0xAA && $bytes[5] != 0xFF);
261 $bytes[0] >> 4, $bytes[0] & 0x0f,
262 $bytes[1] >> 4, $bytes[1] & 0x0f,
263 $bytes[2] >> 4, $bytes[2] & 0x0f,
267 $self->track->car($car)->set_fuel($fuel[$car]);
274 sub brake_set_packet {
275 my ($self, @bytes) = @_;
277 return 'Unexpected brake_set packet (should be in the pit lane only)';
281 sub qualification_packet {
282 my ($self, @bytes) = @_;
284 my $msg = 'Strange qualification packet'
290 || $bytes[5] != 0xFF;
292 return $msg; # FIXME - to be implemented
296 sub end_of_race_packet {
297 my ($self, @bytes) = @_;
299 my $msg = 'Strange end_of_race packet'
305 || $bytes[5] != 0xFF;
307 return $msg; # FIXME - to be implemented
311 sub race_start_packet {
312 my ($self, @bytes) = @_;
314 my $msg = 'Strange race_start packet'
320 || $bytes[5] != 0xAA;
322 return $msg; # FIXME - to be implemented
326 sub display_change_packet {
327 my ($self, @bytes) = @_;
329 my $msg = 'Strange display_change packet'
335 || $bytes[5] != 0xFF;
337 return $msg; # FIXME - to be implemented
341 sub finish_line_packet {
342 my ($self, @bytes) = @_;
345 for my $byte (@bytes) {
353 my $msg = 'Strange finish_line packet'
356 return $msg; # FIXME - to be implemented
360 sub controller_status_packet {
361 my ($self, @bytes) = @_;
364 for my $byte (@bytes) {
365 next if $byte == 0xA0;
367 if ($byte & 0xC0) != 0xC0
368 || ($byte & 0x0F) > 12
371 my $msg = 'Strange controller_status packet'
375 $bytes[1] >> 4, $bytes[1] & 0x0f,
376 $bytes[2] >> 4, $bytes[2] & 0x0f,
377 $bytes[3] >> 4, $bytes[3] & 0x0f,
381 my $byte = $bytes[$car];
384 $self->track->car($car)->set_throttle(undef);
388 my $light = !($byte & 0x20);
389 my $backbutton = !($byte & 0x10);
390 my $throttle = $byte & 0x0f;
392 $self->track->car($car)->set_throttle($throttle);
393 $self->track->car($car)->set_light($light);
394 $self->track->car($car)->set_backbutton($backbutton);