]> www.fi.muni.cz Git - slotcarman.git/blob - SCX/Reader.pm
Race setup, car reordering.
[slotcarman.git] / SCX / Reader.pm
1 #!/usr/bin/perl -w
2
3 package SCX::Reader;
4
5 use strict;
6
7 use Time::HiRes qw(gettimeofday tv_interval);
8 use FileHandle;
9 use IO::Handle;
10 use POSIX;
11 use SCX::CRC;
12
13 our $PACKET_SIZE = 9; # 9 bytes + 0x05
14 our $LOG_ROTATE  = 600;
15
16 sub new {
17         my ($class, $args) = @_;
18
19         my $portname = $args->{portname}
20                 or die "portname not specified";
21         
22         system 'stty', '-F', $portname, '115200', 'raw';
23         if ($?) {
24                 die "stty died with code $? (no permissions?)";
25         }
26
27         sysopen(my $fh, $portname, O_RDONLY|O_NONBLOCK)
28                 or die "Can't open $portname: $!";
29
30         my $logfile = $args->{logfile};
31         my $log_gen = 0;
32
33         open my $logfh, '>', "$logfile.$log_gen"
34         or die "Can't open $logfile.$log_gen: $!";
35
36         my $now = gettimeofday;
37
38         my $self = {
39                 portname  => $portname,
40                 fh        => $fh,
41                 logfile   => $logfile,
42                 logfh     => $logfh,
43                 log_gen   => $log_gen,
44                 log_start => $now,
45                 starttime => $now,
46                 track     => $args->{track},
47                 bytes     => [],
48         };
49
50         bless $self, $class;
51
52         return $self;
53 }
54
55 sub fh { return shift->{fh}; }
56
57 sub track { return shift->{track}; }
58
59 sub read {
60         my ($self) = @_;
61
62         my $data;
63         my $bytes_read = sysread $self->fh, $data, $PACKET_SIZE;
64         die "Read error on $self->{portname}: $!"
65                 if !$bytes_read;
66
67         my @bytes = unpack("C*", $data);
68
69         # print join(' ', map { sprintf(" %02x", $_) } @bytes), "\n";
70         push @{ $self->{bytes} }, @bytes;
71         @bytes = @{ $self->{bytes} };
72
73         my @bad_bytes;
74
75         while (@bytes > $PACKET_SIZE) {
76                 if ($bytes[0] != 0x55) {
77                         push @bad_bytes, shift @bytes;
78                         next;
79                 }
80                 my $cmd = $bytes[1];
81
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;
86                         next;
87                 }
88                 
89                 if (@bad_bytes) { # Report previous bad bytes first
90                         $self->log_bytes(\@bad_bytes, "Cannot parse packet");
91                         @bad_bytes = ();
92                 }
93
94                 my @packet = splice @bytes, 0, $PACKET_SIZE+1;
95                 my $rv = $self->handle_packet(@packet);
96                 $self->log_bytes(\@packet, $rv);
97         }
98         if (@bad_bytes) {
99                 while (@bytes && $bytes[0] != 0x55) {
100                         push @bad_bytes, shift @bytes;
101                 }
102                 $self->log_bytes(\@bad_bytes, "cannot parse packet");
103         }
104
105         @{ $self->{bytes} } = @bytes;
106 }
107
108 sub log_bytes {
109         my ($self, $bytes, $msg) = @_;
110
111         return if !@$bytes;
112
113         $msg = defined $msg ? ' # ' . $msg : '';
114
115         my $now = gettimeofday;
116
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;
124         }
125
126         $self->{logfh}->print(sprintf('% 10.3f', $now - $self->{starttime}),
127                 (map { sprintf(" %02x", $_) } @$bytes),
128                 $msg, "\n");
129         $self->{logfh}->flush;
130 }
131
132 our %COMMANDS = (
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,
147 );
148
149 sub handle_packet {
150         my ($self, @data) = @_;
151
152         my $cmd = $data[1];
153         my @args = @data[2..7];
154
155         my $sub = $COMMANDS{$cmd};
156         return "Unknown packet"
157                 if !defined $sub;
158
159         return &$sub($self, @args);
160 }
161
162 sub bus_free_time_packet {
163         my ($self, @bytes) = @_;
164
165         my $msg = 'Strange bus free time packet'
166                 if $bytes[2] != 0xF0
167                 || $bytes[3] != 0xF0
168                 || $bytes[4] != 0xF0
169                 || $bytes[5] != 0xF0;
170
171         return $msg; # No need to handle this, I think
172 }
173
174 sub car_programming_packet {
175         my ($self, @bytes) = @_;
176
177         my $msg = 'Strange car programming packet'
178                 if ($bytes[0] & 0xF8) != 0 || ($bytes[0] & 0x07) > 5
179                 || $bytes[1] != 0xFE
180                 || $bytes[2] != 0xFF
181                 || $bytes[3] != 0xFF
182                 || $bytes[4] != 0xFF
183                 || $bytes[5] != 0xFF;
184
185         return $msg;
186 }
187
188 sub reset_packet {
189         my ($self, @bytes) = @_;
190
191         my $msg = 'Strange reset packet'
192                 if $bytes[0] != 0xFF
193                 || $bytes[3] != 0xAA
194                 || $bytes[4] != 0xAA
195                 || $bytes[5] != 0xAA;
196
197         return $msg; # FIXME - to be implemented
198 }
199
200 sub standings_packet {
201         my ($self, @bytes) = @_;
202
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);
210
211         my @standings;
212
213         push @standings, map { $_ != 0xFF ? $_ & (0x07) : () } @bytes;
214
215         return $msg; # FIXME - to be implemented
216 }
217
218 sub lap_time_packet {
219         my ($self, @bytes) = @_;
220
221         my $msg = 'Strange lap time packet'
222                 if $bytes[0] > 5
223                 || $bytes[1] & 0x01
224                 || $bytes[2] & 0x01
225                 || ($bytes[3] & 0xF0) != 0
226                 || $bytes[4] & 0x01
227                 || $bytes[5] & 0x01;
228
229         my $nonzero = grep { $_ != 0 } @bytes;
230
231         my $car = $bytes[0];
232         my $round = 256*$bytes[1] + $bytes[2]
233                 + ($bytes[3] & 2 ? 256 : 0)
234                 + ($bytes[3] & 1 ? 1 : 0);
235         my $time = 256*$bytes[4] + $bytes[5]
236                 + ($bytes[3] & 8 ? 256 : 0)
237                 + ($bytes[3] & 4 ? 1 : 0);
238         if ($time == 65535) {
239                 $self->track->car($car)->enter_pit_lane;
240         } else {
241                 $time *= 0.01024;
242
243                 if ($nonzero) {
244                         $self->track->car($car)->set_lap($round);
245                         $self->track->car($car)->set_laptime($time);
246                 } else {
247                         # FIXME - probably reset race time or whatever
248                         # all-zeros packet is sent after the race setup
249                 }
250         }
251
252         return $msg;
253 }
254
255
256 sub race_setup_packet {
257         my ($self, @bytes) = @_;
258
259         my $msg = 'Strange race setup packet'
260                 if ($bytes[0] != 0x00 && $bytes[0] != 0xFF)
261                 || $bytes[1] & 0xF0
262                 || $bytes[2] & 0xF0
263                 || $bytes[3] & 0xF0
264                 || $bytes[4] != 0xFF
265                 || $bytes[5] != 0xFF;
266
267         for my $car (0..5) {
268                 $self->track->car($car)->set_lap(0);
269                 $self->track->car($car)->set_laptime(undef);
270         }
271
272         $self->track->race_setup($bytes[0] == 0x00
273                 ? 0
274                 : $bytes[1] & 0x0F * 256
275                         + $bytes[2] & 0x0F * 16
276                         + $bytes[3] & 0x0F);
277
278         return $msg; # FIXME - to be implemented
279 }
280
281
282 sub fuel_level_packet {
283         my ($self, @bytes) = @_;
284
285         my $msg = 'Strange fuel_level packet'
286                 if ($bytes[0] >> 4) > 8
287                 || ($bytes[0] & 0x0F) > 8
288                 || ($bytes[1] >> 4) > 8
289                 || ($bytes[1] & 0x0F) > 8
290                 || ($bytes[2] >> 4) > 8
291                 || ($bytes[2] & 0x0F) > 8
292                 || ($bytes[5] != 0xAA && $bytes[5] != 0xFF);
293
294         my @fuel = (
295                 $bytes[0] >> 4, $bytes[0] & 0x0f,
296                 $bytes[1] >> 4, $bytes[1] & 0x0f,
297                 $bytes[2] >> 4, $bytes[2] & 0x0f,
298         );
299
300         for my $car (0..5) {
301                 $self->track->car($car)->set_fuel($fuel[$car]);
302         }
303
304         return $msg;
305 }
306
307
308 sub brake_set_packet {
309         my ($self, @bytes) = @_;
310
311         return 'Unexpected brake_set packet (should be in the pit lane only)';
312 }
313
314
315 sub qualification_packet {
316         my ($self, @bytes) = @_;
317
318         my $msg = 'Strange qualification packet'
319                 if $bytes[0] & 0xF0
320                 || $bytes[1] & 0xF0
321                 || $bytes[2] & 0xF0
322                 || $bytes[3] > 5
323                 || $bytes[4] != 0xFF
324                 || $bytes[5] != 0xFF;
325
326         for my $car (0..5) {
327                 $self->track->car($car)->set_lap(undef);
328                 $self->track->car($car)->set_laptime(undef);
329         }
330
331         return $msg; # FIXME - to be implemented
332 }
333
334
335 sub end_of_race_packet {
336         my ($self, @bytes) = @_;
337
338         my $msg = 'Strange end_of_race packet'
339                 if $bytes[0] != 0xFF
340                 || $bytes[1] != 0xFF
341                 || $bytes[2] != 0xFF
342                 || $bytes[3] != 0xFF
343                 || $bytes[4] != 0xFF
344                 || $bytes[5] != 0xFF;
345
346         $self->track->race_end;
347
348         return $msg; # FIXME - to be implemented
349 }
350
351
352 sub race_start_packet {
353         my ($self, @bytes) = @_;
354
355         my $msg = 'Strange race_start packet'
356                 if $bytes[0] != 0x00
357                 || $bytes[1] != 0xAA
358                 || $bytes[2] != 0xAA
359                 || $bytes[3] != 0xAA
360                 || $bytes[4] != 0xAA
361                 || $bytes[5] != 0xAA;
362
363         $self->track->race_start;
364
365         return $msg; # FIXME - to be implemented
366 }
367
368
369 sub display_change_packet {
370         my ($self, @bytes) = @_;
371
372         my $msg = 'Strange display_change packet'
373                 if $bytes[0] & 0xFE
374                 || $bytes[1] != 0xFF
375                 || $bytes[2] != 0xFF
376                 || $bytes[3] != 0xFF
377                 || $bytes[4] != 0xFF
378                 || $bytes[5] != 0xFF;
379
380         return $msg; # FIXME - to be implemented
381 }
382
383
384 sub finish_line_packet {
385         my ($self, @bytes) = @_;
386
387         my $fail;
388         for my $byte (@bytes) {
389                 $fail = 1
390                         if $byte != 0xAA
391                         && $byte != 0xE7
392                         && $byte != 0xF0
393                         && $byte != 0xFE
394         }
395
396         my $msg = 'Strange finish_line packet'
397                 if $fail;
398
399         my $pit_lane_entry;
400         my @cars_finished;
401         for my $i (0..5) {
402                 my $byte = $bytes[$i];
403
404                 $pit_lane_entry = 1 if $byte == 0xF0;
405                 push @cars_finished, $i if $byte == 0xE7;
406         }
407
408         if ($pit_lane_entry) {
409                 for my $car (@cars_finished) {
410                         $self->track->car($car)->enter_pit_lane;
411                 }
412         }
413
414         return $msg; # FIXME - to be implemented
415 }
416
417 sub controller_status_packet {
418         my ($self, @bytes) = @_;
419
420         my $fail;
421         for my $byte (@bytes) {
422                 next if $byte == 0xAA;
423                 $fail = 1
424                         if ($byte & 0xC0) != 0xC0
425                         || ($byte & 0x0F) > 12
426         }
427
428         my $msg = 'Strange controller_status packet'
429                 if $fail;
430
431         my @fuel = (
432                 $bytes[1] >> 4, $bytes[1] & 0x0f,
433                 $bytes[2] >> 4, $bytes[2] & 0x0f,
434                 $bytes[3] >> 4, $bytes[3] & 0x0f,
435         );
436
437         for my $car (0..5) {
438                 my $byte = $bytes[$car];
439
440                 if ($byte == 0xAA) {
441                         $self->track->car($car)->set_throttle(undef);
442                         next;
443                 }
444
445                 my $light = !($byte & 0x20);
446                 my $backbutton = !($byte & 0x10);
447                 my $throttle = $byte & 0x0f;
448
449                 $self->track->car($car)->set_throttle($throttle);
450                 $self->track->car($car)->set_light($light);
451                 $self->track->car($car)->set_backbutton($backbutton);
452         }
453
454         return $msg;
455 }
456
457 1;
458