]> www.fi.muni.cz Git - slotcarman.git/blob - SCX/Reader.pm
873ffa058a09a451b217df87ed47196bd1efc37a
[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 $car = $bytes[0];
230         my $round = 256*$bytes[1] + $bytes[2]
231                 + ($bytes[3] & 2 ? 256 : 0)
232                 + ($bytes[3] & 1 ? 1 : 0);
233         my $time = 256*$bytes[4] + $bytes[5]
234                 + ($bytes[3] & 8 ? 256 : 0)
235                 + ($bytes[3] & 4 ? 1 : 0);
236         $time *= 0.01024;
237
238         $self->track->car($car)->set_lap($round);
239         $self->track->car($car)->set_laptime($time);
240
241         return $msg;
242 }
243
244
245 sub race_setup_packet {
246         my ($self, @bytes) = @_;
247
248         my $msg = 'Strange race setup packet'
249                 if ($bytes[0] != 0x00 && $bytes[0] != 0xFF)
250                 || $bytes[1] & 0xF0
251                 || $bytes[2] & 0xF0
252                 || $bytes[3] & 0xF0
253                 || $bytes[4] != 0xFF
254                 || $bytes[5] != 0xFF;
255
256         return $msg; # FIXME - to be implemented
257 }
258
259
260 sub fuel_level_packet {
261         my ($self, @bytes) = @_;
262
263         my $msg = 'Strange fuel_level packet'
264                 if ($bytes[0] >> 4) > 8
265                 || ($bytes[0] & 0x0F) > 8
266                 || ($bytes[1] >> 4) > 8
267                 || ($bytes[1] & 0x0F) > 8
268                 || ($bytes[2] >> 4) > 8
269                 || ($bytes[2] & 0x0F) > 8
270                 || ($bytes[5] != 0xAA && $bytes[5] != 0xFF);
271
272         my @fuel = (
273                 $bytes[0] >> 4, $bytes[0] & 0x0f,
274                 $bytes[1] >> 4, $bytes[1] & 0x0f,
275                 $bytes[2] >> 4, $bytes[2] & 0x0f,
276         );
277
278         for my $car (0..5) {
279                 $self->track->car($car)->set_fuel($fuel[$car]);
280         }
281
282         return $msg;
283 }
284
285
286 sub brake_set_packet {
287         my ($self, @bytes) = @_;
288
289         return 'Unexpected brake_set packet (should be in the pit lane only)';
290 }
291
292
293 sub qualification_packet {
294         my ($self, @bytes) = @_;
295
296         my $msg = 'Strange qualification packet'
297                 if $bytes[0] & 0xF0
298                 || $bytes[1] & 0xF0
299                 || $bytes[2] & 0xF0
300                 || $bytes[3] > 5
301                 || $bytes[4] != 0xFF
302                 || $bytes[5] != 0xFF;
303
304         return $msg; # FIXME - to be implemented
305 }
306
307
308 sub end_of_race_packet {
309         my ($self, @bytes) = @_;
310
311         my $msg = 'Strange end_of_race packet'
312                 if $bytes[0] != 0xFF
313                 || $bytes[1] != 0xFF
314                 || $bytes[2] != 0xFF
315                 || $bytes[3] != 0xFF
316                 || $bytes[4] != 0xFF
317                 || $bytes[5] != 0xFF;
318
319         return $msg; # FIXME - to be implemented
320 }
321
322
323 sub race_start_packet {
324         my ($self, @bytes) = @_;
325
326         my $msg = 'Strange race_start packet'
327                 if $bytes[0] != 0x00
328                 || $bytes[1] != 0xAA
329                 || $bytes[2] != 0xAA
330                 || $bytes[3] != 0xAA
331                 || $bytes[4] != 0xAA
332                 || $bytes[5] != 0xAA;
333
334         return $msg; # FIXME - to be implemented
335 }
336
337
338 sub display_change_packet {
339         my ($self, @bytes) = @_;
340
341         my $msg = 'Strange display_change packet'
342                 if $bytes[0] & 0xFE
343                 || $bytes[1] != 0xFF
344                 || $bytes[2] != 0xFF
345                 || $bytes[3] != 0xFF
346                 || $bytes[4] != 0xFF
347                 || $bytes[5] != 0xFF;
348
349         return $msg; # FIXME - to be implemented
350 }
351
352
353 sub finish_line_packet {
354         my ($self, @bytes) = @_;
355
356         my $fail;
357         for my $byte (@bytes) {
358                 $fail = 1
359                         if $byte != 0xAA
360                         && $byte != 0xE7
361                         && $byte != 0xF0
362                         && $byte != 0xFE
363         }
364
365         my $msg = 'Strange finish_line packet'
366                 if $fail;
367
368         return $msg; # FIXME - to be implemented
369 }
370
371
372 sub controller_status_packet {
373         my ($self, @bytes) = @_;
374
375         my $fail;
376         for my $byte (@bytes) {
377                 next if $byte == 0xA0;
378                 $fail = 1
379                         if ($byte & 0xC0) != 0xC0
380                         || ($byte & 0x0F) > 12
381         }
382
383         my $msg = 'Strange controller_status packet'
384                 if $fail;
385
386         my @fuel = (
387                 $bytes[1] >> 4, $bytes[1] & 0x0f,
388                 $bytes[2] >> 4, $bytes[2] & 0x0f,
389                 $bytes[3] >> 4, $bytes[3] & 0x0f,
390         );
391
392         for my $car (0..5) {
393                 my $byte = $bytes[$car];
394
395                 if ($byte == 0xAA) {
396                         $self->track->car($car)->set_throttle(undef);
397                         next;
398                 }
399
400                 my $light = !($byte & 0x20);
401                 my $backbutton = !($byte & 0x10);
402                 my $throttle = $byte & 0x0f;
403
404                 $self->track->car($car)->set_throttle($throttle);
405                 $self->track->car($car)->set_light($light);
406                 $self->track->car($car)->set_backbutton($backbutton);
407         }
408
409         return $msg;
410 }
411
412 1;
413