]> git.tdb.fi Git - r2c2.git/blob - source/libr2c2/arducontrol.cpp
Avoid invalid states when setting multi-bit accessories
[r2c2.git] / source / libr2c2 / arducontrol.cpp
1 #include <msp/core/maputils.h>
2 #include <msp/datafile/writer.h>
3 #include <msp/fs/redirectedpath.h>
4 #include <msp/fs/stat.h>
5 #include <msp/io/print.h>
6 #include <msp/time/utils.h>
7 #include "arducontrol.h"
8 #include "tracktype.h"
9
10 using namespace std;
11 using namespace Msp;
12
13 namespace R2C2 {
14
15 ArduControl::ProtocolInfo ArduControl::protocol_info[2] =
16 {
17         { 79, 14, 4 },       // MM
18         { 0x3FFF, 126, 15 }  // MFX
19 };
20
21 ArduControl::ArduControl(const Options &opts):
22         serial(opts.get<string>(string(), "ttyUSB0")),
23         debug(opts.get<unsigned>("debug")),
24         state_file("arducontrol.state"),
25         power(false),
26         halted(false),
27         active_accessory(0),
28         command_timeout(200*Time::msec),
29         s88(*this),
30         mfx_search(*this),
31         thread(*this)
32 {
33         if(FS::exists(state_file))
34                 DataFile::load(*this, state_file.str());
35
36         unsigned max_address = 0;
37         for(MfxInfoArray::const_iterator i=mfx_info.begin(); i!=mfx_info.end(); ++i)
38                 max_address = max(max_address, i->address);
39         mfx_search.set_next_address(max_address+1);
40
41         PendingCommand cmd;
42         cmd.command[0] = READ_POWER_STATE;
43         cmd.length = 1;
44         command_queue.push(cmd);
45
46         cmd.command[0] = MFX_SET_STATION_ID;
47         cmd.command[1] = 'R';
48         cmd.command[2] = '2';
49         cmd.command[3] = 'C';
50         cmd.command[4] = '2';
51         cmd.length = 5;
52         command_queue.push(cmd);
53 }
54
55 ArduControl::~ArduControl()
56 {
57         thread.exit();
58 }
59
60 void ArduControl::set_power(bool p)
61 {
62         if(power.set(p))
63         {
64                 PendingCommand cmd(POWER);
65                 cmd.tag.serial = power.serial;
66                 cmd.command[0] = (p ? POWER_ON : POWER_OFF);
67                 cmd.length = 1;
68                 command_queue.push(cmd);
69         }
70 }
71
72 void ArduControl::halt(bool h)
73 {
74         if(h==halted)
75                 return;
76
77         halted = h;
78         if(halted)
79         {
80                 for(LocomotiveMap::const_iterator i=locomotives.begin(); i!=locomotives.end(); ++i)
81                         set_loco_speed(i->first, 0);
82         }
83
84         signal_halt.emit(halted);
85 }
86
87 const char *ArduControl::enumerate_protocols(unsigned i) const
88 {
89         if(i==0)
90                 return "MM";
91         else if(i==1)
92                 return "MFX";
93         else
94                 return 0;
95 }
96
97 ArduControl::Protocol ArduControl::map_protocol(const string &proto_name)
98 {
99         if(proto_name=="MM")
100                 return MM;
101         else if(proto_name=="MFX")
102                 return MFX;
103         else
104                 throw invalid_argument("ArduControl::map_protocol");
105 }
106
107 unsigned ArduControl::get_protocol_speed_steps(const string &proto_name) const
108 {
109         return protocol_info[map_protocol(proto_name)].max_speed;
110 }
111
112 const Driver::DetectedLocomotive *ArduControl::enumerate_detected_locos(unsigned i) const
113 {
114         if(i>=mfx_info.size())
115                 return 0;
116
117         return &mfx_info[i];
118 }
119
120 unsigned ArduControl::add_loco(unsigned addr, const string &proto_name, const VehicleType &)
121 {
122         if(!addr)
123                 throw invalid_argument("ArduControl::add_loco");
124
125         Protocol proto = map_protocol(proto_name);
126         if(addr>protocol_info[proto].max_address)
127                 throw invalid_argument("ArduControl::add_loco");
128
129         Locomotive loco(proto, addr);
130         insert_unique(locomotives, loco.id, loco);
131
132         return loco.id;
133 }
134
135 ArduControl::MfxInfoArray::iterator ArduControl::add_mfx_info(const MfxInfo &info)
136 {
137         MfxInfoArray::iterator i;
138         for(i=mfx_info.begin(); (i!=mfx_info.end() && i->id!=info.id); ++i) ;
139         if(i==mfx_info.end())
140         {
141                 mfx_info.push_back(info);
142                 i = --mfx_info.end();
143         }
144         else
145                 *i = info;
146         return i;
147 }
148
149 void ArduControl::remove_loco(unsigned id)
150 {
151         Locomotive &loco = get_item(locomotives, id);
152         refresh.remove_loco(loco);
153         locomotives.erase(id);
154 }
155
156 void ArduControl::set_loco_speed(unsigned id, unsigned speed)
157 {
158         Locomotive &loco = get_item(locomotives, id);
159         if(speed>protocol_info[loco.proto].max_speed)
160                 throw invalid_argument("ArduControl::set_loco_speed");
161
162         if(speed && halted)
163                 return;
164
165         if(loco.speed.set(speed))
166         {
167                 PendingCommand cmd(loco, Locomotive::SPEED);
168                 command_queue.push(cmd);
169
170                 refresh.add_loco(loco);
171         }
172 }
173
174 void ArduControl::set_loco_reverse(unsigned id, bool rev)
175 {
176         Locomotive &loco = get_item(locomotives, id);
177         if(loco.reverse.set(rev))
178         {
179                 PendingCommand cmd(loco, Locomotive::REVERSE);
180                 command_queue.push(cmd);
181
182                 refresh.add_loco(loco);
183         }
184 }
185
186 void ArduControl::set_loco_function(unsigned id, unsigned func, bool state)
187 {
188         Locomotive &loco = get_item(locomotives, id);
189         if(func>protocol_info[loco.proto].max_func)
190                 throw invalid_argument("ArduControl::set_loco_function");
191
192         unsigned mask = 1<<func;
193         if(loco.funcs.set((loco.funcs&~mask)|(mask*state)))
194         {
195                 if(func>0 || loco.proto!=MM)
196                 {
197                         PendingCommand cmd(loco, Locomotive::FUNCTIONS, func);
198                         command_queue.push(cmd);
199                 }
200
201                 refresh.add_loco(loco);
202         }
203 }
204
205 unsigned ArduControl::add_turnout(unsigned addr, const TrackType &type)
206 {
207         if(!addr || !type.is_turnout())
208                 throw invalid_argument("ArduControl::add_turnout");
209
210         return add_accessory(Accessory::TURNOUT, addr, type.get_state_bits(), type.get_paths());
211 }
212
213 void ArduControl::remove_turnout(unsigned addr)
214 {
215         remove_accessory(Accessory::TURNOUT, addr);
216 }
217
218 void ArduControl::set_turnout(unsigned addr, unsigned state)
219 {
220         set_accessory(Accessory::TURNOUT, addr, state);
221 }
222
223 unsigned ArduControl::get_turnout(unsigned addr) const
224 {
225         return get_accessory(Accessory::TURNOUT, addr);
226 }
227
228 unsigned ArduControl::add_signal(unsigned addr, const SignalType &)
229 {
230         return add_accessory(Accessory::SIGNAL, addr, 1, 3);
231 }
232
233 void ArduControl::remove_signal(unsigned addr)
234 {
235         remove_accessory(Accessory::SIGNAL, addr);
236 }
237
238 void ArduControl::set_signal(unsigned addr, unsigned state)
239 {
240         set_accessory(Accessory::SIGNAL, addr, state);
241 }
242
243 unsigned ArduControl::get_signal(unsigned addr) const
244 {
245         return get_accessory(Accessory::SIGNAL, addr);
246 }
247
248 unsigned ArduControl::add_accessory(Accessory::Kind kind, unsigned addr, unsigned bits, unsigned states)
249 {
250         AccessoryMap::iterator i = accessories.lower_bound(addr);
251         AccessoryMap::iterator j = accessories.upper_bound(addr+bits-1);
252         if(i!=j)
253                 throw key_error(addr);
254         if(i!=accessories.begin())
255         {
256                 --i;
257                 if(i->first+i->second.bits>addr)
258                         throw key_error(addr);
259         }
260
261         insert_unique(accessories, addr, Accessory(kind, addr, bits, states));
262         return addr;
263 }
264
265 void ArduControl::remove_accessory(Accessory::Kind kind, unsigned addr)
266 {
267         Accessory &acc = get_item(accessories, addr);
268         if(acc.kind!=kind)
269                 throw key_error(addr);
270         accessories.erase(addr);
271 }
272
273 void ArduControl::set_accessory(Accessory::Kind kind, unsigned addr, unsigned state)
274 {
275         Accessory &acc = get_item(accessories, addr);
276         if(acc.kind!=kind)
277                 throw key_error(addr);
278
279         if(state!=acc.target || acc.uncertain)
280         {
281                 acc.target = state;
282                 accessory_queue.push_back(&acc);
283         }
284 }
285
286 unsigned ArduControl::get_accessory(Accessory::Kind kind, unsigned addr) const
287 {
288         const Accessory &acc = get_item(accessories, addr);
289         if(acc.kind!=kind)
290                 throw key_error(addr);
291         return acc.state;
292 }
293
294 void ArduControl::activate_accessory_by_mask(Accessory &acc, unsigned mask)
295 {
296         unsigned bit = mask&~(mask-1);
297         for(active_index=0; (bit>>active_index)>1; ++active_index) ;
298         acc.state.set((acc.state&~bit)|(acc.target&bit));
299         PendingCommand cmd(acc, Accessory::ACTIVATE, active_index);
300         command_queue.push(cmd);
301         active_accessory = &acc;
302
303         monitor.reset_peak();
304 }
305
306 unsigned ArduControl::add_sensor(unsigned addr)
307 {
308         if(!addr)
309                 throw invalid_argument("ArduControl::add_sensor");
310
311         insert_unique(sensors, addr, Sensor(addr));
312         s88.grow_n_octets((addr+7)/8);
313
314         return addr;
315 }
316
317 void ArduControl::remove_sensor(unsigned addr)
318 {
319         remove_existing(sensors, addr);
320         // TODO update s88.n_octets
321 }
322
323 bool ArduControl::get_sensor(unsigned addr) const
324 {
325         return get_item(sensors, addr).state;
326 }
327
328 void ArduControl::tick()
329 {
330         Tag tag;
331         while(completed_commands.pop(tag))
332         {
333                 if(tag.type==Tag::GENERAL)
334                 {
335                         if(tag.command==POWER)
336                         {
337                                 if(power.commit(tag.serial))
338                                         signal_power.emit(power.current);
339                         }
340                         else if(tag.command==NEW_LOCO)
341                         {
342                                 MfxInfo info;
343                                 if(mfx_search.pop_info(info))
344                                 {
345                                         MfxInfoArray::iterator i = add_mfx_info(info);
346                                         save_state();
347                                         signal_locomotive_detected.emit(*i);
348                                 }
349                         }
350                 }
351                 else if(tag.type==Tag::LOCOMOTIVE)
352                 {
353                         LocomotiveMap::iterator i = locomotives.find(tag.id);
354                         if(i==locomotives.end())
355                                 continue;
356
357                         Locomotive &loco = i->second;
358                         if(tag.command==Locomotive::SPEED)
359                         {
360                                 if(loco.speed.commit(tag.serial))
361                                         signal_loco_speed.emit(loco.id, loco.speed, loco.reverse);
362                         }
363                         else if(tag.command==Locomotive::REVERSE)
364                         {
365                                 if(loco.reverse.commit(tag.serial))
366                                         signal_loco_speed.emit(loco.id, loco.speed, loco.reverse);
367                         }
368                         else if(tag.command==Locomotive::FUNCTIONS)
369                         {
370                                 unsigned old = loco.funcs;
371                                 if(loco.funcs.commit(tag.serial))
372                                 {
373                                         unsigned changed = old^loco.funcs;
374                                         for(unsigned j=0; changed>>j; ++j)
375                                                 if((changed>>j)&1)
376                                                         signal_loco_function.emit(loco.id, j, (loco.funcs>>j)&1);
377                                 }
378                         }
379                 }
380                 else if(tag.type==Tag::ACCESSORY)
381                 {
382                         AccessoryMap::iterator i = accessories.find(tag.id);
383                         if(i==accessories.end())
384                                 continue;
385
386                         Accessory &acc = i->second;
387                         if(tag.command==Accessory::ACTIVATE)
388                                 off_timeout = Time::now()+acc.active_time;
389                         else if(tag.command==Accessory::DEACTIVATE)
390                         {
391                                 if(acc.state.commit(tag.serial))
392                                 {
393                                         if(&acc==active_accessory)
394                                                 active_accessory = 0;
395                                 }
396                         }
397                 }
398                 else if(tag.type==Tag::SENSOR)
399                 {
400                         SensorMap::iterator i = sensors.find(tag.id);
401                         if(i==sensors.end())
402                                 continue;
403
404                         Sensor &sensor = i->second;
405                         if(tag.command==Sensor::STATE)
406                         {
407                                 if(sensor.state.commit(tag.serial))
408                                         signal_sensor.emit(sensor.address, sensor.state);
409                         }
410                 }
411         }
412
413         while(power && !active_accessory && !accessory_queue.empty())
414         {
415                 Accessory &acc = *accessory_queue.front();
416
417                 if(acc.uncertain)
418                 {
419                         unsigned zeroes = acc.uncertain&~acc.target;
420                         if(zeroes)
421                                 activate_accessory_by_mask(acc, zeroes);
422                         else
423                                 activate_accessory_by_mask(acc, acc.uncertain);
424                 }
425                 else if(acc.state!=acc.target)
426                 {
427                         unsigned changes = acc.state^acc.target;
428                         if(!(changes&((1<<acc.bits)-1)))
429                         {
430                                 // All remaining changes are in non-physical bits
431                                 acc.state.set(acc.state^changes);
432                                 acc.state.commit(acc.state.serial);
433                         }
434                         else
435                         {
436                                 unsigned toggle_bit = 0;
437                                 for(unsigned bit=1; (!toggle_bit && bit<=changes); bit<<=1)
438                                         if((changes&bit) && (acc.valid_states&(1<<(acc.state^bit))))
439                                                 toggle_bit = bit;
440
441                                 activate_accessory_by_mask(acc, toggle_bit);
442                         }
443                 }
444                 else
445                 {
446                         accessory_queue.pop_front();
447
448                         if(acc.state==acc.target)
449                         {
450                                 if(acc.kind==Accessory::TURNOUT)
451                                         signal_turnout.emit(acc.address, acc.state);
452                                 else if(acc.kind==Accessory::SIGNAL)
453                                         signal_signal.emit(acc.address, acc.state);
454                         }
455                 }
456         }
457
458         if(active_accessory && off_timeout)
459         {
460                 Time::TimeStamp t = Time::now();
461                 if(t>off_timeout)
462                 {
463                         Accessory &acc = *active_accessory;
464
465                         unsigned bit = 1<<active_index;
466
467                         // Assume success if we were uncertain of the physical setting
468                         if(acc.uncertain&bit)
469                                 acc.uncertain &= ~bit;
470                         else if(acc.kind==Accessory::TURNOUT && monitor.get_peak()<0.5f)
471                         {
472                                 signal_turnout_failed.emit(acc.address);
473                                 acc.state.rollback();
474                                 if(acc.valid_states&(1<<(acc.target^bit)))
475                                         acc.target ^= bit;
476                                 else
477                                         acc.target = acc.state;
478                         }
479
480                         off_timeout = Time::TimeStamp();
481                         PendingCommand cmd(acc, Accessory::DEACTIVATE, active_index);
482                         command_queue.push(cmd);
483                 }
484         }
485 }
486
487 void ArduControl::flush()
488 {
489         while(!command_queue.empty() || (power && !accessory_queue.empty()))
490                 tick();
491 }
492
493 void ArduControl::save_state() const
494 {
495         FS::RedirectedPath tmp_file(state_file);
496         IO::BufferedFile out(tmp_file.str(), IO::M_WRITE);
497         DataFile::Writer writer(out);
498
499         writer.write((DataFile::Statement("mfx_announce_serial"), mfx_announce.get_serial()));
500         for(MfxInfoArray::const_iterator i=mfx_info.begin(); i!=mfx_info.end(); ++i)
501         {
502                 DataFile::Statement st("mfx_locomotive");
503                 st.append(i->id);
504                 st.sub.push_back((DataFile::Statement("address"), i->address));
505                 st.sub.push_back((DataFile::Statement("name"), i->name));
506                 writer.write(st);
507         }
508 }
509
510
511 ArduControl::Tag::Tag():
512         type(NONE),
513         command(0),
514         serial(0),
515         id(0)
516 { }
517
518
519 ArduControl::Locomotive::Locomotive(Protocol p, unsigned a):
520         id((p<<16)|a),
521         proto(p),
522         address(a),
523         speed(0),
524         reverse(false),
525         funcs(0),
526         last_change_age(0)
527 { }
528
529 unsigned ArduControl::Locomotive::create_speed_dir_command(char *buffer) const
530 {
531         if(proto==MM)
532         {
533                 buffer[0] = MOTOROLA_SPEED_DIRECTION;
534                 buffer[1] = address;
535                 buffer[2] = funcs.pending&1;
536                 buffer[3] = speed.pending+reverse.pending*0x80;
537                 return 4;
538         }
539         else if(proto==MFX)
540         {
541                 buffer[0] = MFX_SPEED;
542                 buffer[1] = address>>8;
543                 buffer[2] = address;
544                 buffer[3] = speed.pending+reverse.pending*0x80;
545                 return 4;
546         }
547         else
548                 return 0;
549 }
550
551 unsigned ArduControl::Locomotive::create_speed_func_command(unsigned f, char *buffer) const
552 {
553         if(proto==MM)
554         {
555                 if(f<1 || f>4)
556                         throw invalid_argument("Locomotive::create_speed_func_command");
557
558                 buffer[0] = MOTOROLA_SPEED_FUNCTION;
559                 buffer[1] = address;
560                 buffer[2] = (f<<4)|(((funcs.pending>>f)&1)<<1)|(funcs.pending&1);
561                 buffer[3] = speed.pending;
562                 return 4;
563         }
564         else if(proto==MFX)
565         {
566                 bool f16 = (funcs.pending>0xFF);
567                 buffer[0] = (f16 ? MFX_SPEED_FUNCS16 : MFX_SPEED_FUNCS8);
568                 buffer[1] = address>>8;
569                 buffer[2] = address;
570                 buffer[3] = speed.pending+reverse.pending*0x80;
571                 if(f16)
572                 {
573                         buffer[4] = funcs.pending>>8;
574                         buffer[5] = funcs.pending;
575                         return 6;
576                 }
577                 else
578                 {
579                         buffer[4] = funcs.pending;
580                         return 5;
581                 }
582         }
583         else
584                 return 0;
585 }
586
587
588 ArduControl::Accessory::Accessory(Kind k, unsigned a, unsigned b, unsigned s):
589         kind(k),
590         address(a),
591         bits(b),
592         valid_states(s),
593         state(0),
594         uncertain((1<<bits)-1),
595         target(0),
596         active_time(500*Time::msec)
597 { }
598
599 unsigned ArduControl::Accessory::create_state_command(unsigned b, bool c, char *buffer) const
600 {
601         if(b>=bits)
602                 throw invalid_argument("Accessory::create_state_command");
603
604         unsigned a = (address+b+3)*2;
605         if(!((state.pending>>b)&1))
606                 ++a;
607         buffer[0] = MOTOROLA_SOLENOID;
608         buffer[1] = a>>3;
609         buffer[2] = ((a&7)<<4)|c;
610         return 3;
611 }
612
613
614 ArduControl::Sensor::Sensor(unsigned a):
615         address(a),
616         state(false)
617 { }
618
619
620 ArduControl::PendingCommand::PendingCommand():
621         length(0),
622         repeat_count(1)
623 { }
624
625 ArduControl::PendingCommand::PendingCommand(GeneralCommand cmd):
626         length(0),
627         repeat_count(1)
628 {
629         tag.type = Tag::GENERAL;
630         tag.command = cmd;
631 }
632
633 ArduControl::PendingCommand::PendingCommand(Locomotive &loco, Locomotive::Command cmd, unsigned index):
634         repeat_count(8)
635 {
636         tag.type = Tag::LOCOMOTIVE;
637         tag.command = cmd;
638         tag.id = loco.id;
639         if(cmd==Locomotive::SPEED)
640         {
641                 tag.serial = loco.speed.serial;
642                 length = loco.create_speed_dir_command(command);
643         }
644         else if(cmd==Locomotive::REVERSE)
645         {
646                 tag.serial = loco.reverse.serial;
647                 length = loco.create_speed_dir_command(command);
648         }
649         else if(cmd==Locomotive::FUNCTIONS)
650         {
651                 tag.serial = loco.funcs.serial;
652                 length = loco.create_speed_func_command(index, command);
653         }
654         else
655                 throw invalid_argument("PendingCommand");
656 }
657
658 ArduControl::PendingCommand::PendingCommand(Accessory &acc, Accessory::Command cmd, unsigned index):
659         repeat_count(1)
660 {
661         tag.type = Tag::ACCESSORY;
662         tag.command = cmd;
663         tag.id = acc.address;
664         if(cmd==Accessory::ACTIVATE || cmd==Accessory::DEACTIVATE)
665         {
666                 tag.serial = acc.state.serial;
667                 length = acc.create_state_command(index, (cmd==Accessory::ACTIVATE), command);
668         }
669         else
670                 throw invalid_argument("PendingCommand");
671 }
672
673
674 template<typename T>
675 void ArduControl::Queue<T>::push(const T &item)
676 {
677         MutexLock lock(mutex);
678         items.push_back(item);
679 }
680
681 template<typename T>
682 bool ArduControl::Queue<T>::pop(T &item)
683 {
684         MutexLock lock(mutex);
685         if(items.empty())
686                 return false;
687
688         item = items.front();
689         items.pop_front();
690         return true;
691 }
692
693 template<typename T>
694 bool ArduControl::Queue<T>::empty() const
695 {
696         return items.empty();
697 }
698
699
700 ArduControl::RefreshTask::RefreshTask():
701         next(cycle.end()),
702         round(0),
703         loco(0),
704         phase(0)
705 { }
706
707 bool ArduControl::RefreshTask::get_work(PendingCommand &cmd)
708 {
709         if(loco && loco->proto==MM && phase==0)
710         {
711                 cmd.length = loco->create_speed_func_command(round%4+1, cmd.command);
712                 cmd.repeat_count = 2;
713                 ++phase;
714                 return true;
715         }
716
717         loco = get_next_loco();
718         if(!loco)
719                 return false;
720
721         phase = 0;
722         if(loco->proto==MM)
723         {
724                 cmd.length = loco->create_speed_dir_command(cmd.command);
725                 cmd.repeat_count = 2;
726         }
727         else if(loco->proto==MFX)
728                 cmd.length = loco->create_speed_func_command(0, cmd.command);
729         else
730                 return false;
731
732         return true;
733 }
734
735 void ArduControl::RefreshTask::add_loco(Locomotive &l)
736 {
737         MutexLock lock(mutex);
738         cycle.push_back(&l);
739         if(cycle.size()>15)
740         {
741                 LocomotivePtrList::iterator oldest = cycle.begin();
742                 for(LocomotivePtrList::iterator i=cycle.begin(); ++i!=cycle.end(); )
743                         if((*i)->last_change_age>(*oldest)->last_change_age)
744                                 oldest = i;
745                 if(oldest==next)
746                         advance();
747                 cycle.erase(oldest);
748         }
749         if(next==cycle.end())
750                 next = cycle.begin();
751 }
752
753 void ArduControl::RefreshTask::remove_loco(Locomotive &l)
754 {
755         MutexLock lock(mutex);
756         for(LocomotivePtrList::iterator i=cycle.begin(); i!=cycle.end(); ++i)
757                 if(*i==&l)
758                 {
759                         if(i==next)
760                         {
761                                 if(cycle.size()>1)
762                                         advance();
763                                 else
764                                         next = cycle.end();
765                         }
766                         cycle.erase(i);
767                         return;
768                 }
769 }
770
771 ArduControl::Locomotive *ArduControl::RefreshTask::get_next_loco()
772 {
773         MutexLock lock(mutex);
774         if(cycle.empty())
775                 return 0;
776
777         Locomotive *l = *next;
778         advance();
779         return l;
780 }
781
782 void ArduControl::RefreshTask::advance()
783 {
784         ++next;
785         if(next==cycle.end())
786         {
787                 next= cycle.begin();
788                 ++round;
789         }
790 }
791
792
793 ArduControl::S88Task::S88Task(ArduControl &c):
794         control(c),
795         n_octets(0),
796         octets_remaining(0),
797         delay(0)
798 { }
799
800 bool ArduControl::S88Task::get_work(PendingCommand &cmd)
801 {
802         if(delay)
803         {
804                 --delay;
805                 return false;
806         }
807         if(octets_remaining || !n_octets)
808                 return false;
809
810         octets_remaining = n_octets;
811         cmd.command[0] = S88_READ;
812         cmd.command[1] = octets_remaining;
813         cmd.length = 2;
814
815         delay = 4;
816
817         return true;
818 }
819
820 void ArduControl::S88Task::process_reply(const char *reply, unsigned length)
821 {
822         unsigned char type = reply[0];
823         if(type==S88_DATA && length>2)
824         {
825                 unsigned offset = static_cast<unsigned char>(reply[1]);
826                 unsigned count = length-2;
827
828                 SensorMap::iterator begin = control.sensors.lower_bound(offset*8+1);
829                 SensorMap::iterator end = control.sensors.upper_bound((offset+count)*8);
830                 for(SensorMap::iterator i=begin; i!=end; ++i)
831                 {
832                         unsigned bit_index = i->first-1-offset*8;
833                         bool state = (reply[2+bit_index/8]>>(7-bit_index%8))&1;
834                         i->second.state.set(state);
835
836                         Tag tag;
837                         tag.type = Tag::SENSOR;
838                         tag.command = Sensor::STATE;
839                         tag.serial = i->second.state.serial;
840                         tag.id = i->first;
841                         control.completed_commands.push(tag);
842                 }
843
844                 if(count>octets_remaining)
845                         octets_remaining = 0;
846                 else
847                         octets_remaining -= count;
848         }
849 }
850
851 void ArduControl::S88Task::set_n_octets(unsigned n)
852 {
853         n_octets = n;
854 }
855
856 void ArduControl::S88Task::grow_n_octets(unsigned n)
857 {
858         if(n>n_octets)
859                 n_octets = n;
860 }
861
862
863 ArduControl::MfxAnnounceTask::MfxAnnounceTask():
864         serial(0)
865 { }
866
867 bool ArduControl::MfxAnnounceTask::get_work(PendingCommand &cmd)
868 {
869         Time::TimeStamp t = Time::now();
870         if(t<next)
871                 return false;
872
873         cmd.command[0] = MFX_ANNOUNCE;
874         cmd.command[1] = serial>>8;
875         cmd.command[2] = serial;
876         cmd.length = 3;
877         next = t+400*Time::msec;
878
879         return true;
880 }
881
882 void ArduControl::MfxAnnounceTask::set_serial(unsigned s)
883 {
884         serial = s;
885 }
886
887
888 ArduControl::MfxSearchTask::MfxSearchTask(ArduControl &c):
889         control(c),
890         next_address(1),
891         size(0),
892         bits(0),
893         misses(0)
894 { }
895
896 bool ArduControl::MfxSearchTask::get_work(PendingCommand &cmd)
897 {
898         if(size>32)
899         {
900                 if(control.debug>=1)
901                         IO::print("Assigning MFX address %d to decoder %08X\n", next_address, bits);
902
903                 MfxInfo info;
904                 info.protocol = "MFX";
905                 info.address = next_address;
906                 info.name = format("%08X", bits);
907                 info.id = bits;
908                 queue.push(info);
909
910                 cmd.command[0] = MFX_ASSIGN_ADDRESS;
911                 cmd.command[1] = next_address>>8;
912                 cmd.command[2] = next_address;
913                 for(unsigned i=0; i<4; ++i)
914                         cmd.command[3+i] = bits>>(24-i*8);
915                 cmd.length = 7;
916
917                 cmd.tag.type = Tag::GENERAL;
918                 cmd.tag.command = NEW_LOCO;
919                 cmd.tag.id = bits;
920
921                 size = 0;
922                 bits = 0;
923                 ++next_address;
924
925                 return true;
926         }
927
928         Time::TimeStamp t = Time::now();
929         if(t<next)
930                 return false;
931
932         cmd.command[0] = MFX_SEARCH;
933         for(unsigned i=0; i<4; ++i)
934                 cmd.command[1+i] = bits>>(24-i*8);
935         cmd.command[5] = size;
936         cmd.length = 6;
937
938         next = t+200*Time::msec;
939
940         if(control.debug>=1)
941                 IO::print("Search %08X/%d\n", bits, size);
942
943         return true;
944 }
945
946 void ArduControl::MfxSearchTask::process_reply(const char *reply, unsigned length)
947 {
948         unsigned char type = reply[0];
949         if(type==MFX_SEARCH_FEEDBACK && length==2)
950         {
951                 if(reply[1])
952                 {
953                         misses = 0;
954                         ++size;
955                 }
956                 else if(size>0 && misses<6)
957                 {
958                         ++misses;
959                         bits ^= 1<<(32-size);
960                 }
961                 else
962                 {
963                         next = Time::now()+2*Time::sec;
964                         bits = 0;
965                         size = 0;
966                         misses = 0;
967                 }
968         }
969 }
970
971 void ArduControl::MfxSearchTask::set_next_address(unsigned a)
972 {
973         next_address = a;
974 }
975
976 bool ArduControl::MfxSearchTask::pop_info(MfxInfo &info)
977 {
978         return queue.pop(info);
979 }
980
981
982 ArduControl::MonitorTask::MonitorTask():
983         voltage(0),
984         current(0),
985         base_level(0),
986         peak_level(0),
987         next_type(0)
988 { }
989
990 bool ArduControl::MonitorTask::get_work(PendingCommand &cmd)
991 {
992         Time::TimeStamp t = Time::now();
993         if(t<next_poll)
994                 return false;
995
996         if(next_type==0)
997                 cmd.command[0] = READ_INPUT_VOLTAGE;
998         else
999                 cmd.command[0] = READ_TRACK_CURRENT;
1000         cmd.length = 1;
1001
1002         next_poll = t+200*Time::msec;
1003         next_type = (next_type+1)%5;
1004
1005         return true;
1006 }
1007
1008 void ArduControl::MonitorTask::process_reply(const char *reply, unsigned length)
1009 {
1010         unsigned char type = reply[0];
1011         if(type==INPUT_VOLTAGE && length==3)
1012                 voltage = ((static_cast<unsigned char>(reply[1])<<8) | static_cast<unsigned char>(reply[2]))/1000.0f;
1013         else if(type==TRACK_CURRENT && length==5)
1014         {
1015                 current = ((static_cast<unsigned char>(reply[1])<<8) | static_cast<unsigned char>(reply[2]))/1000.0f;
1016                 float peak = ((static_cast<unsigned char>(reply[3])<<8) | static_cast<unsigned char>(reply[4]))/1000.0f;
1017                 peak_level = max(peak_level, peak);
1018                 base_level = min(base_level, current);
1019         }
1020 }
1021
1022 void ArduControl::MonitorTask::reset_peak()
1023 {
1024         base_level = current;
1025         peak_level = current;
1026 }
1027
1028
1029 ArduControl::ControlThread::ControlThread(ArduControl &c):
1030         control(c),
1031         done(false)
1032 {
1033         tasks.push_back(&control.monitor);
1034         tasks.push_back(&control.mfx_announce);
1035         tasks.push_back(&control.mfx_search);
1036         tasks.push_back(&control.s88);
1037         tasks.push_back(&control.refresh);
1038
1039         launch();
1040 }
1041
1042 void ArduControl::ControlThread::exit()
1043 {
1044         done = true;
1045         join();
1046 }
1047
1048 void ArduControl::ControlThread::main()
1049 {
1050         init_baud_rate();
1051
1052         while(!done)
1053         {
1054                 PendingCommand cmd;
1055                 if(get_work(cmd))
1056                 {
1057                         bool success = true;
1058                         bool resync = false;
1059                         for(unsigned i=0; (success && i<cmd.repeat_count); ++i)
1060                         {
1061                                 unsigned result = do_command(cmd, control.command_timeout);
1062                                 success = (result==COMMAND_OK);
1063                                 resync = (result==0);
1064                         }
1065
1066                         if(success && cmd.tag)
1067                                 control.completed_commands.push(cmd.tag);
1068
1069                         if(resync)
1070                         {
1071                                 if(control.debug>=1)
1072                                         IO::print("Synchronization with ArduControl lost, attempting to recover\n");
1073                                 for(unsigned i=0; (resync && i<16); ++i)
1074                                 {
1075                                         control.serial.put('\xFF');
1076                                         while(IO::poll(control.serial, IO::P_INPUT, control.command_timeout))
1077                                                 resync = (control.serial.get()!=0xFF);
1078                                 }
1079                                 if(resync)
1080                                 {
1081                                         if(control.debug>=1)
1082                                                 IO::print("Resynchronization failed, giving up\n");
1083                                         done = true;
1084                                 }
1085                                 else
1086                                 {
1087                                         if(control.debug>=1)
1088                                                 IO::print("Resynchronization successful\n");
1089                                         if(cmd.tag)
1090                                                 control.command_queue.push(cmd);
1091                                 }
1092                         }
1093                 }
1094                 else
1095                         Time::sleep(10*Time::msec);
1096         }
1097 }
1098
1099 void ArduControl::ControlThread::init_baud_rate()
1100 {
1101         static unsigned rates[] = { 57600, 9600, 19200, 38400, 0 };
1102         unsigned rate = 0;
1103         control.serial.set_data_bits(8);
1104         control.serial.set_parity(IO::Serial::NONE);
1105         control.serial.set_stop_bits(1);
1106         for(unsigned i=0; rates[i]; ++i)
1107         {
1108                 control.serial.set_baud_rate(rates[i]);
1109                 control.serial.put('\xFF');
1110                 if(IO::poll(control.serial, IO::P_INPUT, 500*Time::msec))
1111                 {
1112                         int c = control.serial.get();
1113                         if(c==0xFF)
1114                         {
1115                                 rate = rates[i];
1116                                 break;
1117                         }
1118                 }
1119         }
1120
1121         if(!rate)
1122         {
1123                 if(control.debug>=1)
1124                         IO::print("ArduControl detection failed\n");
1125                 done = true;
1126                 return;
1127         }
1128
1129         if(control.debug>=1)
1130                 IO::print("ArduControl detected at %d bits/s\n", rate);
1131
1132         if(rate!=rates[0])
1133         {
1134                 PendingCommand cmd;
1135                 cmd.command[0] = SET_BAUD_RATE;
1136                 cmd.command[1] = rates[0]>>8;
1137                 cmd.command[2] = rates[0];
1138                 cmd.length = 3;
1139                 if(do_command(cmd, Time::sec)==COMMAND_OK)
1140                 {
1141                         control.serial.set_baud_rate(rates[0]);
1142                         Time::sleep(Time::sec);
1143                         if(do_command(cmd, Time::sec)==COMMAND_OK)
1144                         {
1145                                 if(control.debug>=1)
1146                                         IO::print("Rate changed to %d bits/s\n", rates[0]);
1147                         }
1148                 }
1149         }
1150 }
1151
1152 bool ArduControl::ControlThread::get_work(PendingCommand &cmd)
1153 {
1154         if(control.command_queue.pop(cmd))
1155                 return true;
1156
1157         for(vector<Task *>::iterator i=tasks.begin(); i!=tasks.end(); ++i)
1158                 if((*i)->get_work(cmd))
1159                         return true;
1160
1161         // As fallback, send an idle packet for the MM protocol
1162         cmd.command[0] = MOTOROLA_SPEED;
1163         cmd.command[1] = 80;
1164         cmd.command[2] = 0;
1165         cmd.command[3] = 0;
1166         cmd.length = 4;
1167
1168         return true;
1169 }
1170
1171 unsigned ArduControl::ControlThread::do_command(const PendingCommand &cmd, const Time::TimeDelta &timeout)
1172 {
1173         if(control.debug>=2)
1174         {
1175                 string cmd_hex;
1176                 for(unsigned i=0; i<cmd.length; ++i)
1177                         cmd_hex += format(" %02X", static_cast<unsigned char>(cmd.command[i]));
1178                 IO::print("< %02X%s\n", cmd.length^0xFF, cmd_hex);
1179         }
1180
1181         control.serial.put(cmd.length^0xFF);
1182         control.serial.write(cmd.command, cmd.length);
1183
1184         unsigned result = 0;
1185         while(1)
1186         {
1187                 bool got_data;
1188                 if(result)
1189                         got_data = IO::poll(control.serial, IO::P_INPUT, Time::zero);
1190                 else
1191                         got_data = IO::poll(control.serial, IO::P_INPUT, timeout);
1192
1193                 if(!got_data)
1194                         break;
1195
1196                 unsigned rlength = control.serial.get()^0xFF;
1197                 if(rlength>15)
1198                 {
1199                         IO::print("Invalid length %02X\n", rlength);
1200                         continue;
1201                 }
1202
1203                 char reply[15];
1204                 unsigned pos = 0;
1205                 while(pos<rlength)
1206                 {
1207                         if(!IO::poll(control.serial, IO::P_INPUT, timeout))
1208                                 return 0;
1209                         pos += control.serial.read(reply+pos, rlength-pos);
1210                 }
1211
1212                 if(control.debug>=2)
1213                 {
1214                         string reply_hex;
1215                         for(unsigned i=0; i<rlength; ++i)
1216                                 reply_hex += format(" %02X", static_cast<unsigned char>(reply[i]));
1217                         IO::print("> %02X%s\n", rlength^0xFF, reply_hex);
1218                 }
1219
1220                 unsigned r = process_reply(reply, rlength);
1221                 if(r && !result)
1222                         result = r;
1223         }
1224
1225         return result;
1226 }
1227
1228 unsigned ArduControl::ControlThread::process_reply(const char *reply, unsigned rlength)
1229 {
1230         unsigned char type = reply[0];
1231         if((type&0xE0)==0x80)
1232         {
1233                 if(type!=COMMAND_OK)
1234                         IO::print("Error %02X\n", type);
1235                 return type;
1236         }
1237         else if(type==POWER_STATE && rlength==2)
1238                 set_power(reply[1]);
1239         else if(type==OVERCURRENT)
1240         {
1241                 set_power(false);
1242                 IO::print("Overcurrent detected!\n");
1243         }
1244         else
1245         {
1246                 for(vector<Task *>::iterator i=tasks.begin(); i!=tasks.end(); ++i)
1247                         (*i)->process_reply(reply, rlength);
1248         }
1249
1250         return 0;
1251 }
1252
1253 void ArduControl::ControlThread::set_power(bool p)
1254 {
1255         control.power.set(p);
1256
1257         Tag tag;
1258         tag.type = Tag::GENERAL;
1259         tag.command = POWER;
1260         tag.serial = control.power.serial;
1261         control.completed_commands.push(tag);
1262 }
1263
1264
1265 ArduControl::Loader::Loader(ArduControl &c):
1266         DataFile::ObjectLoader<ArduControl>(c)
1267 {
1268         add("mfx_announce_serial", &Loader::mfx_announce_serial);
1269         add("mfx_locomotive", &Loader::mfx_locomotive);
1270 }
1271
1272 void ArduControl::Loader::mfx_announce_serial(unsigned s)
1273 {
1274         obj.mfx_announce.set_serial(s);
1275 }
1276
1277 void ArduControl::Loader::mfx_locomotive(unsigned id)
1278 {
1279         MfxInfo info;
1280         info.id = id;
1281         info.protocol = "MFX";
1282         load_sub(info);
1283         obj.add_mfx_info(info);
1284 }
1285
1286
1287 ArduControl::MfxInfo::Loader::Loader(MfxInfo &i):
1288         DataFile::ObjectLoader<MfxInfo>(i)
1289 {
1290         add("address", static_cast<unsigned MfxInfo::*>(&MfxInfo::address));
1291         add("name", static_cast<string MfxInfo::*>(&MfxInfo::name));
1292 }
1293
1294 } // namespace R2C2