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