]> www.fi.muni.cz Git - slotcarman.git/blob - SCX/Reader.pm
Best lap handling.
[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         $self->track->race_setup($bytes[0] == 0x00
268                 ? 0
269                 : $bytes[1] & 0x0F * 256
270                         + $bytes[2] & 0x0F * 16
271                         + $bytes[3] & 0x0F);
272
273         return $msg; # FIXME - to be implemented
274 }
275
276
277 sub fuel_level_packet {
278         my ($self, @bytes) = @_;
279
280         my $msg = 'Strange fuel_level packet'
281                 if ($bytes[0] >> 4) > 8
282                 || ($bytes[0] & 0x0F) > 8
283                 || ($bytes[1] >> 4) > 8
284                 || ($bytes[1] & 0x0F) > 8
285                 || ($bytes[2] >> 4) > 8
286                 || ($bytes[2] & 0x0F) > 8
287                 || ($bytes[5] != 0xAA && $bytes[5] != 0xFF);
288
289         my @fuel = (
290                 $bytes[0] >> 4, $bytes[0] & 0x0f,
291                 $bytes[1] >> 4, $bytes[1] & 0x0f,
292                 $bytes[2] >> 4, $bytes[2] & 0x0f,
293         );
294
295         for my $car (0..5) {
296                 $self->track->car($car)->set_fuel($fuel[$car]);
297         }
298
299         return $msg;
300 }
301
302
303 sub brake_set_packet {
304         my ($self, @bytes) = @_;
305
306         return 'Unexpected brake_set packet (should be in the pit lane only)';
307 }
308
309
310 sub qualification_packet {
311         my ($self, @bytes) = @_;
312
313         my $msg = 'Strange qualification packet'
314                 if $bytes[0] & 0xF0
315                 || $bytes[1] & 0xF0
316                 || $bytes[2] & 0xF0
317                 || $bytes[3] > 5
318                 || $bytes[4] != 0xFF
319                 || $bytes[5] != 0xFF;
320
321         for my $car (0..5) {
322                 $self->track->car($car)->set_lap(undef);
323                 $self->track->car($car)->set_laptime(undef);
324         }
325
326         return $msg; # FIXME - to be implemented
327 }
328
329
330 sub end_of_race_packet {
331         my ($self, @bytes) = @_;
332
333         my $msg = 'Strange end_of_race packet'
334                 if $bytes[0] != 0xFF
335                 || $bytes[1] != 0xFF
336                 || $bytes[2] != 0xFF
337                 || $bytes[3] != 0xFF
338                 || $bytes[4] != 0xFF
339                 || $bytes[5] != 0xFF;
340
341         $self->track->race_end;
342
343         return $msg; # FIXME - to be implemented
344 }
345
346
347 sub race_start_packet {
348         my ($self, @bytes) = @_;
349
350         my $msg = 'Strange race_start packet'
351                 if $bytes[0] != 0x00
352                 || $bytes[1] != 0xAA
353                 || $bytes[2] != 0xAA
354                 || $bytes[3] != 0xAA
355                 || $bytes[4] != 0xAA
356                 || $bytes[5] != 0xAA;
357
358         $self->track->race_start;
359
360         return $msg; # FIXME - to be implemented
361 }
362
363
364 sub display_change_packet {
365         my ($self, @bytes) = @_;
366
367         my $msg = 'Strange display_change packet'
368                 if $bytes[0] & 0xFE
369                 || $bytes[1] != 0xFF
370                 || $bytes[2] != 0xFF
371                 || $bytes[3] != 0xFF
372                 || $bytes[4] != 0xFF
373                 || $bytes[5] != 0xFF;
374
375         return $msg; # FIXME - to be implemented
376 }
377
378
379 sub finish_line_packet {
380         my ($self, @bytes) = @_;
381
382         my $fail;
383         for my $byte (@bytes) {
384                 $fail = 1
385                         if $byte != 0xAA
386                         && $byte != 0xE7
387                         && $byte != 0xF0
388                         && $byte != 0xFE
389         }
390
391         my $msg = 'Strange finish_line packet'
392                 if $fail;
393
394         my $pit_lane_entry;
395         my @cars_finished;
396         for my $i (0..5) {
397                 my $byte = $bytes[$i];
398
399                 $pit_lane_entry = 1 if $byte == 0xF0;
400                 push @cars_finished, $i if $byte == 0xE7;
401         }
402
403         if ($pit_lane_entry) {
404                 for my $car (@cars_finished) {
405                         $self->track->car($car)->enter_pit_lane;
406                 }
407         }
408
409         return $msg; # FIXME - to be implemented
410 }
411
412 sub controller_status_packet {
413         my ($self, @bytes) = @_;
414
415         my $fail;
416         for my $byte (@bytes) {
417                 next if $byte == 0xAA;
418                 $fail = 1
419                         if ($byte & 0xC0) != 0xC0
420                         || ($byte & 0x0F) > 12
421         }
422
423         my $msg = 'Strange controller_status packet'
424                 if $fail;
425
426         my @fuel = (
427                 $bytes[1] >> 4, $bytes[1] & 0x0f,
428                 $bytes[2] >> 4, $bytes[2] & 0x0f,
429                 $bytes[3] >> 4, $bytes[3] & 0x0f,
430         );
431
432         for my $car (0..5) {
433                 my $byte = $bytes[$car];
434
435                 if ($byte == 0xAA) {
436                         $self->track->car($car)->set_throttle(undef);
437                         next;
438                 }
439
440                 my $light = !($byte & 0x20);
441                 my $backbutton = !($byte & 0x10);
442                 my $throttle = $byte & 0x0f;
443
444                 $self->track->car($car)->set_throttle($throttle);
445                 $self->track->car($car)->set_light($light);
446                 $self->track->car($car)->set_backbutton($backbutton);
447         }
448
449         return $msg;
450 }
451
452 1;
453