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