]> git.tdb.fi Git - r2c2.git/blob - source/libr2c2/arducontrol.cpp
Add some more range checks in ArduControl and improve existing ones
[r2c2.git] / source / libr2c2 / arducontrol.cpp
1 #include <msp/core/maputils.h>
2 #include <msp/io/print.h>
3 #include <msp/time/utils.h>
4 #include "arducontrol.h"
5 #include "tracktype.h"
6
7 using namespace std;
8 using namespace Msp;
9
10 namespace R2C2 {
11
12 ArduControl::ProtocolInfo ArduControl::protocol_info[2] =
13 {
14         { 79, 14, 4 }  // MM
15 };
16
17 ArduControl::ArduControl(const string &dev):
18         serial(dev),
19         debug(true),
20         power(false),
21         next_refresh(refresh_cycle.end()),
22         refresh_counter(0),
23         active_accessory(0),
24         n_s88_octets(0),
25         thread(*this)
26 {
27 }
28
29 ArduControl::~ArduControl()
30 {
31         thread.exit();
32 }
33
34 void ArduControl::set_power(bool p)
35 {
36         if(p==power.pending)
37                 return;
38
39         power.pending = p;
40         ++power.serial;
41
42         QueuedCommand cmd(POWER);
43         cmd.tag.serial = power.serial;
44         cmd.command[0] = (p ? POWER_ON : POWER_OFF);
45         cmd.length = 1;
46         push_command(cmd);
47 }
48
49 void ArduControl::halt(bool)
50 {
51 }
52
53 const char *ArduControl::enumerate_protocols(unsigned i) const
54 {
55         if(i==0)
56                 return "MM";
57         else
58                 return 0;
59 }
60
61 ArduControl::Protocol ArduControl::map_protocol(const string &proto_name)
62 {
63         if(proto_name=="MM")
64                 return MM;
65         else if(proto_name=="MFX")
66                 return MFX;
67         else
68                 throw invalid_argument("ArduControl::map_protocol");
69 }
70
71 unsigned ArduControl::get_protocol_speed_steps(const string &proto_name) const
72 {
73         return protocol_info[map_protocol(proto_name)].max_speed;
74 }
75
76 unsigned ArduControl::add_loco(unsigned addr, const string &proto_name, const VehicleType &)
77 {
78         if(!addr)
79                 throw invalid_argument("ArduControl::add_loco");
80
81         Protocol proto = map_protocol(proto_name);
82         if(addr>protocol_info[proto].max_address)
83                 throw invalid_argument("ArduControl::add_loco");
84
85         Locomotive loco(proto, addr);
86         insert_unique(locomotives, loco.id, loco);
87
88         return loco.id;
89 }
90
91 void ArduControl::remove_loco(unsigned id)
92 {
93         Locomotive &loco = get_item(locomotives, id);
94         remove_loco_from_refresh(loco);
95         locomotives.erase(id);
96 }
97
98 void ArduControl::set_loco_speed(unsigned id, unsigned speed)
99 {
100         Locomotive &loco = get_item(locomotives, id);
101         if(speed>protocol_info[loco.proto].max_speed)
102                 throw invalid_argument("ArduControl::set_loco_speed");
103
104         if(loco.speed.set(speed))
105         {
106                 QueuedCommand cmd(loco, Locomotive::SPEED);
107                 push_command(cmd);
108
109                 add_loco_to_refresh(loco);
110         }
111 }
112
113 void ArduControl::set_loco_reverse(unsigned id, bool rev)
114 {
115         Locomotive &loco = get_item(locomotives, id);
116         if(loco.reverse.set(rev))
117         {
118                 QueuedCommand cmd(loco, Locomotive::REVERSE);
119                 push_command(cmd);
120
121                 add_loco_to_refresh(loco);
122         }
123 }
124
125 void ArduControl::set_loco_function(unsigned id, unsigned func, bool state)
126 {
127         Locomotive &loco = get_item(locomotives, id);
128         if(func>protocol_info[loco.proto].max_func)
129                 throw invalid_argument("ArduControl::set_loco_function");
130
131         unsigned mask = 1<<func;
132         if(loco.funcs.set((loco.funcs&~mask)|(mask*state)))
133         {
134                 if(func>0)
135                 {
136                         QueuedCommand cmd(loco, Locomotive::FUNCTIONS, func);
137                         push_command(cmd);
138                 }
139
140                 add_loco_to_refresh(loco);
141         }
142 }
143
144 void ArduControl::add_loco_to_refresh(Locomotive &loco)
145 {
146         MutexLock lock(mutex);
147         refresh_cycle.push_back(&loco);
148         if(refresh_cycle.size()>15)
149         {
150                 LocomotivePtrList::iterator oldest = refresh_cycle.begin();
151                 for(LocomotivePtrList::iterator i=refresh_cycle.begin(); ++i!=refresh_cycle.end(); )
152                         if((*i)->last_change_age>(*oldest)->last_change_age)
153                                 oldest = i;
154                 if(oldest==next_refresh)
155                         advance_next_refresh();
156                 refresh_cycle.erase(oldest);
157         }
158         if(next_refresh==refresh_cycle.end())
159                 next_refresh = refresh_cycle.begin();
160 }
161
162 void ArduControl::remove_loco_from_refresh(Locomotive &loco)
163 {
164         MutexLock lock(mutex);
165         for(LocomotivePtrList::iterator i=refresh_cycle.begin(); i!=refresh_cycle.end(); ++i)
166                 if(*i==&loco)
167                 {
168                         if(i==next_refresh)
169                         {
170                                 if(refresh_cycle.size()>1)
171                                         advance_next_refresh();
172                                 else
173                                         next_refresh = refresh_cycle.end();
174                         }
175                         refresh_cycle.erase(i);
176                         return;
177                 }
178 }
179
180 ArduControl::Locomotive *ArduControl::get_loco_to_refresh()
181 {
182         MutexLock lock(mutex);
183         if(refresh_cycle.empty())
184                 return 0;
185
186         Locomotive *loco = *next_refresh;
187         advance_next_refresh();
188         return loco;
189 }
190
191 void ArduControl::advance_next_refresh()
192 {
193         ++next_refresh;
194         if(next_refresh==refresh_cycle.end())
195         {
196                 next_refresh = refresh_cycle.begin();
197                 ++refresh_counter;
198         }
199 }
200
201 unsigned ArduControl::add_turnout(unsigned addr, const TrackType &type)
202 {
203         if(!addr || !type.is_turnout())
204                 throw invalid_argument("ArduControl::add_turnout");
205
206         return add_accessory(Accessory::TURNOUT, addr, type.get_state_bits());
207 }
208
209 void ArduControl::remove_turnout(unsigned addr)
210 {
211         remove_accessory(Accessory::TURNOUT, addr);
212 }
213
214 void ArduControl::set_turnout(unsigned addr, unsigned state)
215 {
216         set_accessory(Accessory::TURNOUT, addr, state);
217 }
218
219 unsigned ArduControl::get_turnout(unsigned addr) const
220 {
221         return get_accessory(Accessory::TURNOUT, addr);
222 }
223
224 unsigned ArduControl::add_signal(unsigned addr, const SignalType &)
225 {
226         return add_accessory(Accessory::SIGNAL, addr, 1);
227 }
228
229 void ArduControl::remove_signal(unsigned addr)
230 {
231         remove_accessory(Accessory::SIGNAL, addr);
232 }
233
234 void ArduControl::set_signal(unsigned addr, unsigned state)
235 {
236         set_accessory(Accessory::SIGNAL, addr, state);
237 }
238
239 unsigned ArduControl::get_signal(unsigned addr) const
240 {
241         return get_accessory(Accessory::SIGNAL, addr);
242 }
243
244 unsigned ArduControl::add_accessory(Accessory::Kind kind, unsigned addr, unsigned bits)
245 {
246         AccessoryMap::iterator i = accessories.lower_bound(addr);
247         AccessoryMap::iterator j = accessories.upper_bound(addr+bits-1);
248         if(i!=j)
249                 throw key_error(addr);
250         if(i!=accessories.begin())
251         {
252                 --i;
253                 if(i->first+i->second.bits>addr)
254                         throw key_error(addr);
255         }
256
257         insert_unique(accessories, addr, Accessory(kind, addr, bits));
258         return addr;
259 }
260
261 void ArduControl::remove_accessory(Accessory::Kind kind, unsigned addr)
262 {
263         Accessory &acc = get_item(accessories, addr);
264         if(acc.kind!=kind)
265                 throw key_error(addr);
266         accessories.erase(addr);
267 }
268
269 void ArduControl::set_accessory(Accessory::Kind kind, unsigned addr, unsigned state)
270 {
271         Accessory &acc = get_item(accessories, addr);
272         if(acc.kind!=kind)
273                 throw key_error(addr);
274
275         if(state!=acc.target)
276         {
277                 acc.target = state;
278                 accessory_queue.push_back(&acc);
279         }
280 }
281
282 unsigned ArduControl::get_accessory(Accessory::Kind kind, unsigned addr) const
283 {
284         const Accessory &acc = get_item(accessories, addr);
285         if(acc.kind!=kind)
286                 throw key_error(addr);
287         return acc.state;
288 }
289
290 unsigned ArduControl::add_sensor(unsigned addr)
291 {
292         if(!addr)
293                 throw invalid_argument("ArduControl::add_sensor");
294
295         insert_unique(sensors, addr, Sensor(addr));
296         unsigned octet_index = (addr-1)/8;
297         if(octet_index>=n_s88_octets)
298                 n_s88_octets = octet_index+1;
299
300         return addr;
301 }
302
303 void ArduControl::remove_sensor(unsigned addr)
304 {
305         remove_existing(sensors, addr);
306         // TODO update n_s88_octets
307 }
308
309 bool ArduControl::get_sensor(unsigned addr) const
310 {
311         return get_item(sensors, addr).state;
312 }
313
314 void ArduControl::tick()
315 {
316         while(Tag tag = pop_completed_tag())
317         {
318                 if(tag.type==Tag::GENERAL)
319                 {
320                         if(tag.command==POWER)
321                         {
322                                 if(power.commit(tag.serial))
323                                         signal_power.emit(power.current);
324                         }
325                 }
326                 else if(tag.type==Tag::LOCOMOTIVE)
327                 {
328                         LocomotiveMap::iterator i = locomotives.find(tag.id);
329                         if(i==locomotives.end())
330                                 continue;
331
332                         Locomotive &loco = i->second;
333                         if(tag.command==Locomotive::SPEED)
334                         {
335                                 if(loco.speed.commit(tag.serial))
336                                         signal_loco_speed.emit(loco.id, loco.speed, loco.reverse);
337                         }
338                         else if(tag.command==Locomotive::REVERSE)
339                         {
340                                 if(loco.reverse.commit(tag.serial))
341                                         signal_loco_speed.emit(loco.id, loco.speed, loco.reverse);
342                         }
343                         else if(tag.command==Locomotive::FUNCTIONS)
344                         {
345                                 unsigned old = loco.funcs;
346                                 if(loco.funcs.commit(tag.serial))
347                                 {
348                                         unsigned changed = old^loco.funcs;
349                                         for(unsigned j=0; changed>>j; ++j)
350                                                 if((changed>>j)&1)
351                                                         signal_loco_function.emit(loco.id, j, (loco.funcs>>j)&1);
352                                 }
353                         }
354                 }
355                 else if(tag.type==Tag::ACCESSORY)
356                 {
357                         AccessoryMap::iterator i = accessories.find(tag.id);
358                         if(i==accessories.end())
359                                 continue;
360
361                         Accessory &acc = i->second;
362                         if(tag.command==Accessory::ACTIVATE)
363                         {
364                                 off_timeout = Time::now()+acc.active_time;
365                         }
366                         else if(tag.command==Accessory::DEACTIVATE)
367                         {
368                                 if(acc.state.commit(tag.serial))
369                                 {
370                                         if(acc.state==acc.target)
371                                         {
372                                                 if(acc.kind==Accessory::TURNOUT)
373                                                         signal_turnout.emit(acc.address, acc.state);
374                                                 else if(acc.kind==Accessory::SIGNAL)
375                                                         signal_signal.emit(acc.address, acc.state);
376                                         }
377                                         if(&acc==active_accessory)
378                                                 active_accessory = 0;
379                                 }
380                         }
381                 }
382                 else if(tag.type==Tag::SENSOR)
383                 {
384                         SensorMap::iterator i = sensors.find(tag.id);
385                         if(i==sensors.end())
386                                 continue;
387
388                         Sensor &sensor = i->second;
389                         if(tag.command==Sensor::STATE)
390                         {
391                                 if(sensor.state.commit(tag.serial))
392                                         signal_sensor.emit(sensor.address, sensor.state);
393                         }
394                 }
395         }
396
397         while(!active_accessory && !accessory_queue.empty())
398         {
399                 Accessory &acc = *accessory_queue.front();
400
401                 if(acc.state!=acc.target)
402                 {
403                         active_accessory = &acc;
404
405                         unsigned changes = acc.state^acc.target;
406                         unsigned lowest_bit = changes&~(changes-1);
407                         unsigned i;
408                         for(i=0; (lowest_bit>>i)>1; ++i) ;
409                         acc.state.set(acc.state^lowest_bit);
410                         QueuedCommand cmd(acc, Accessory::ACTIVATE, i);
411                         push_command(cmd);
412                 }
413                 else
414                         accessory_queue.pop_front();
415         }
416
417         if(active_accessory && off_timeout)
418         {
419                 Time::TimeStamp t = Time::now();
420                 if(t>off_timeout)
421                 {
422                         off_timeout = Time::TimeStamp();
423                         QueuedCommand cmd(*active_accessory, Accessory::DEACTIVATE);
424                         push_command(cmd);
425                 }
426         }
427 }
428
429 void ArduControl::flush()
430 {
431 }
432
433 void ArduControl::push_command(const QueuedCommand &cmd)
434 {
435         MutexLock lock(mutex);
436         command_queue.push_back(cmd);
437 }
438
439 bool ArduControl::pop_command(QueuedCommand &cmd)
440 {
441         MutexLock lock(mutex);
442         if(command_queue.empty())
443                 return false;
444         cmd = command_queue.front();
445         command_queue.pop_front();
446         return true;
447 }
448
449 void ArduControl::push_completed_tag(const Tag &tag)
450 {
451         MutexLock lock(mutex);
452         completed_commands.push_back(tag);
453 }
454
455 ArduControl::Tag ArduControl::pop_completed_tag()
456 {
457         MutexLock lock(mutex);
458         if(completed_commands.empty())
459                 return Tag();
460         Tag tag = completed_commands.front();
461         completed_commands.pop_front();
462         return tag;
463 }
464
465
466 ArduControl::Tag::Tag():
467         type(NONE),
468         command(0),
469         serial(0),
470         id(0)
471 { }
472
473
474 ArduControl::Locomotive::Locomotive(Protocol p, unsigned a):
475         id((p<<16)|a),
476         proto(p),
477         address(a),
478         speed(0),
479         reverse(false),
480         funcs(0),
481         last_change_age(0)
482 { }
483
484 unsigned ArduControl::Locomotive::create_speed_dir_command(char *buffer) const
485 {
486         if(proto==MM)
487         {
488                 buffer[0] = MOTOROLA_SPEED_DIRECTION;
489                 buffer[1] = address;
490                 buffer[2] = funcs.pending&1;
491                 buffer[3] = speed.pending+reverse.pending*0x80;
492                 return 4;
493         }
494         else
495                 return 0;
496 }
497
498 unsigned ArduControl::Locomotive::create_speed_func_command(unsigned f, char *buffer) const
499 {
500         if(proto==MM)
501         {
502                 if(f<1 || f>4)
503                         throw invalid_argument("Locomotive::create_speed_func_command");
504
505                 buffer[0] = MOTOROLA_SPEED_FUNCTION;
506                 buffer[1] = address;
507                 buffer[2] = (f<<4)|(((funcs.pending>>f)&1)<<1)|(funcs.pending&1);
508                 buffer[3] = speed.pending;
509                 return 4;
510         }
511         else
512                 return 0;
513 }
514
515
516 ArduControl::Accessory::Accessory(Kind k, unsigned a, unsigned b):
517         kind(k),
518         address(a),
519         bits(b),
520         state(0),
521         active_time(500*Time::msec)
522 { }
523
524 unsigned ArduControl::Accessory::create_state_command(unsigned b, bool c, char *buffer) const
525 {
526         if(b>=bits)
527                 throw invalid_argument("Accessory::create_state_command");
528
529         unsigned a = (address+b+3)*2;
530         if(!((state.pending>>b)&1))
531                 ++a;
532         buffer[0] = MOTOROLA_SOLENOID;
533         buffer[1] = a>>3;
534         buffer[2] = ((a&7)<<4)|c;
535         return 3;
536 }
537
538
539 ArduControl::Sensor::Sensor(unsigned a):
540         address(a),
541         state(false)
542 { }
543
544
545 ArduControl::ControlThread::ControlThread(ArduControl &c):
546         control(c),
547         done(false)
548 {
549         launch();
550 }
551
552 void ArduControl::ControlThread::exit()
553 {
554         done = true;
555         join();
556 }
557
558 void ArduControl::ControlThread::main()
559 {
560         char command[15];
561         unsigned length = 0;
562         unsigned repeat_count = 0;
563         Tag tag;
564         Locomotive *loco = 0;
565         unsigned phase = 0;
566         unsigned s88_octets_remaining = 0;
567         while(!done)
568         {
569                 if(!repeat_count)
570                 {
571                         tag = Tag();
572                         length = 0;
573                         repeat_count = 1;
574                         QueuedCommand qcmd;
575                         if(control.pop_command(qcmd))
576                         {
577                                 length = qcmd.length;
578                                 copy(qcmd.command, qcmd.command+length, command);
579                                 if(qcmd.tag.type==Tag::LOCOMOTIVE)
580                                         repeat_count = 8;
581                                 tag = qcmd.tag;
582                         }
583                         else if(loco && phase==0)
584                         {
585                                 length = loco->create_speed_func_command(control.refresh_counter%4+1, command);
586                                 repeat_count = 2;
587                                 ++phase;
588                         }
589                         else if(!s88_octets_remaining && control.n_s88_octets)
590                         {
591                                 command[0] = S88_READ;
592                                 command[1] = control.n_s88_octets;
593                                 length = 2;
594                                 s88_octets_remaining = control.n_s88_octets;
595                         }
596                         else if((loco = control.get_loco_to_refresh()))
597                         {
598                                 length = loco->create_speed_dir_command(command);
599                                 repeat_count = 2;
600                                 phase = 0;
601                         }
602                         else
603                         {
604                                 // Send an idle packet for the MM protocol
605                                 command[0] = MOTOROLA_SPEED;
606                                 command[1] = 80;
607                                 command[2] = 0;
608                                 command[3] = 0;
609                                 length = 4;
610                         }
611                 }
612
613                 if(control.debug)
614                 {
615                         string cmd_hex;
616                         for(unsigned i=0; i<length; ++i)
617                                 cmd_hex += format(" %02X", static_cast<unsigned char>(command[i]));
618                         IO::print("< %02X%s\n", length^0xFF, cmd_hex);
619                 }
620
621                 control.serial.put(length^0xFF);
622                 control.serial.write(command, length);
623                 --repeat_count;
624
625                 bool got_reply = false;
626                 bool got_data = false;
627                 while(!got_reply || got_data)
628                 {
629                         if(got_reply)
630                                 got_data = IO::poll(control.serial, IO::P_INPUT, Time::zero);
631                         else
632                                 got_data = IO::poll(control.serial, IO::P_INPUT);
633
634                         if(got_data)
635                         {
636                                 unsigned rlength = control.serial.get()^0xFF;
637                                 if(rlength>15)
638                                 {
639                                         IO::print("Invalid length %02X\n", rlength);
640                                         continue;
641                                 }
642
643                                 char reply[15];
644                                 unsigned pos = 0;
645                                 while(pos<rlength)
646                                         pos += control.serial.read(reply+pos, rlength-pos);
647
648                                 if(control.debug)
649                                 {
650                                         string reply_hex;
651                                         for(unsigned i=0; i<rlength; ++i)
652                                                 reply_hex += format(" %02X", static_cast<unsigned char>(reply[i]));
653                                         IO::print("> %02X%s\n", rlength^0xFF, reply_hex);
654                                 }
655
656                                 unsigned char type = reply[0];
657                                 if((type&0xE0)==0x80)
658                                 {
659                                         got_reply = true;
660                                         if(type!=COMMAND_OK)
661                                                 IO::print("Error %02X\n", type);
662                                         else if(tag && !repeat_count)
663                                                 control.push_completed_tag(tag);
664                                 }
665                                 else if(type==S88_DATA && rlength>2)
666                                 {
667                                         unsigned offset = static_cast<unsigned char>(reply[1]);
668                                         unsigned count = rlength-2;
669
670                                         SensorMap::iterator begin = control.sensors.lower_bound(offset*8+1);
671                                         SensorMap::iterator end = control.sensors.upper_bound((offset+count)*8);
672                                         for(SensorMap::iterator i=begin; i!=end; ++i)
673                                         {
674                                                 unsigned bit_index = i->first-1-offset*8;
675                                                 bool state = (reply[2+bit_index/8]>>(7-bit_index%8))&1;
676                                                 i->second.state.set(state);
677
678                                                 Tag stag;
679                                                 stag.type = Tag::SENSOR;
680                                                 stag.command = Sensor::STATE;
681                                                 stag.serial = i->second.state.serial;
682                                                 stag.id = i->first;
683                                                 control.push_completed_tag(stag);
684                                         }
685
686                                         if(count>s88_octets_remaining)
687                                                 s88_octets_remaining = 0;
688                                         else
689                                                 s88_octets_remaining -= count;
690                                 }
691                         }
692                 }
693         }
694 }
695
696
697 ArduControl::QueuedCommand::QueuedCommand():
698         length(0)
699 { }
700
701 ArduControl::QueuedCommand::QueuedCommand(GeneralCommand cmd):
702         length(0)
703 {
704         tag.type = Tag::GENERAL;
705         tag.command = cmd;
706 }
707
708 ArduControl::QueuedCommand::QueuedCommand(Locomotive &loco, Locomotive::Command cmd, unsigned index)
709 {
710         tag.type = Tag::LOCOMOTIVE;
711         tag.command = cmd;
712         tag.id = loco.id;
713         if(cmd==Locomotive::SPEED)
714         {
715                 tag.serial = loco.speed.serial;
716                 length = loco.create_speed_dir_command(command);
717         }
718         else if(cmd==Locomotive::REVERSE)
719         {
720                 tag.serial = loco.reverse.serial;
721                 length = loco.create_speed_dir_command(command);
722         }
723         else if(cmd==Locomotive::FUNCTIONS)
724         {
725                 tag.serial = loco.funcs.serial;
726                 length = loco.create_speed_func_command(index, command);
727         }
728         else
729                 throw invalid_argument("QueuedCommand");
730 }
731
732 ArduControl::QueuedCommand::QueuedCommand(Accessory &acc, Accessory::Command cmd, unsigned index)
733 {
734         tag.type = Tag::ACCESSORY;
735         tag.command = cmd;
736         tag.id = acc.address;
737         if(cmd==Accessory::ACTIVATE || cmd==Accessory::DEACTIVATE)
738         {
739                 tag.serial = acc.state.serial;
740                 length = acc.create_state_command(index, (cmd==Accessory::ACTIVATE), command);
741         }
742         else
743                 throw invalid_argument("QueuedCommand");
744 }
745
746 } // namespace R2C2