]> git.tdb.fi Git - r2c2.git/blob - source/libr2c2/arducontrol.cpp
Add telemetry framework for drivers
[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         if(debug>=1)
300                 IO::print("Setting accessory %d bit %d, state=%d\n", acc.address, active_index, acc.state.pending);
301         PendingCommand cmd(acc, Accessory::ACTIVATE, active_index);
302         command_queue.push(cmd);
303         active_accessory = &acc;
304
305         monitor.reset_peak();
306 }
307
308 unsigned ArduControl::add_sensor(unsigned addr)
309 {
310         if(!addr)
311                 throw invalid_argument("ArduControl::add_sensor");
312
313         insert_unique(sensors, addr, Sensor(addr));
314         s88.grow_n_octets((addr+7)/8);
315
316         return addr;
317 }
318
319 void ArduControl::remove_sensor(unsigned addr)
320 {
321         remove_existing(sensors, addr);
322         // TODO update s88.n_octets
323 }
324
325 bool ArduControl::get_sensor(unsigned addr) const
326 {
327         return get_item(sensors, addr).state;
328 }
329
330 float ArduControl::get_telemetry_value(const string &name) const
331 {
332         throw key_error(name);
333 }
334
335 void ArduControl::tick()
336 {
337         Tag tag;
338         while(completed_commands.pop(tag))
339         {
340                 if(tag.type==Tag::GENERAL)
341                 {
342                         if(tag.command==POWER)
343                         {
344                                 if(power.commit(tag.serial))
345                                         signal_power.emit(power.current);
346                         }
347                         else if(tag.command==NEW_LOCO)
348                         {
349                                 MfxInfo info;
350                                 if(mfx_search.pop_info(info))
351                                 {
352                                         MfxInfoArray::iterator i = add_mfx_info(info);
353                                         save_state();
354                                         signal_locomotive_detected.emit(*i);
355                                 }
356                         }
357                 }
358                 else if(tag.type==Tag::LOCOMOTIVE)
359                 {
360                         LocomotiveMap::iterator i = locomotives.find(tag.id);
361                         if(i==locomotives.end())
362                                 continue;
363
364                         Locomotive &loco = i->second;
365                         if(tag.command==Locomotive::SPEED)
366                         {
367                                 if(loco.speed.commit(tag.serial))
368                                         signal_loco_speed.emit(loco.id, loco.speed, loco.reverse);
369                         }
370                         else if(tag.command==Locomotive::REVERSE)
371                         {
372                                 if(loco.reverse.commit(tag.serial))
373                                         signal_loco_speed.emit(loco.id, loco.speed, loco.reverse);
374                         }
375                         else if(tag.command==Locomotive::FUNCTIONS)
376                         {
377                                 unsigned old = loco.funcs;
378                                 if(loco.funcs.commit(tag.serial))
379                                 {
380                                         unsigned changed = old^loco.funcs;
381                                         for(unsigned j=0; changed>>j; ++j)
382                                                 if((changed>>j)&1)
383                                                         signal_loco_function.emit(loco.id, j, (loco.funcs>>j)&1);
384                                 }
385                         }
386                 }
387                 else if(tag.type==Tag::ACCESSORY)
388                 {
389                         AccessoryMap::iterator i = accessories.find(tag.id);
390                         if(i==accessories.end())
391                                 continue;
392
393                         Accessory &acc = i->second;
394                         if(tag.command==Accessory::ACTIVATE)
395                                 off_timeout = Time::now()+acc.active_time;
396                         else if(tag.command==Accessory::DEACTIVATE)
397                         {
398                                 if(acc.state.commit(tag.serial))
399                                 {
400                                         if(&acc==active_accessory)
401                                                 active_accessory = 0;
402                                 }
403                         }
404                 }
405                 else if(tag.type==Tag::SENSOR)
406                 {
407                         SensorMap::iterator i = sensors.find(tag.id);
408                         if(i==sensors.end())
409                                 continue;
410
411                         Sensor &sensor = i->second;
412                         if(tag.command==Sensor::STATE)
413                         {
414                                 if(sensor.state.commit(tag.serial))
415                                         signal_sensor.emit(sensor.address, sensor.state);
416                         }
417                 }
418         }
419
420         while(power && !active_accessory && !accessory_queue.empty())
421         {
422                 Accessory &acc = *accessory_queue.front();
423
424                 if(acc.uncertain)
425                 {
426                         unsigned zeroes = acc.uncertain&~acc.target;
427                         if(zeroes)
428                                 activate_accessory_by_mask(acc, zeroes);
429                         else
430                                 activate_accessory_by_mask(acc, acc.uncertain);
431                 }
432                 else if(acc.state!=acc.target)
433                 {
434                         unsigned changes = acc.state^acc.target;
435                         if(!(changes&((1<<acc.bits)-1)))
436                         {
437                                 // All remaining changes are in non-physical bits
438                                 acc.state.set(acc.state^changes);
439                                 acc.state.commit(acc.state.serial);
440                         }
441                         else
442                         {
443                                 unsigned toggle_bit = 0;
444                                 for(unsigned bit=1; (!toggle_bit && bit<=changes); bit<<=1)
445                                         if((changes&bit) && (acc.valid_states&(1<<(acc.state^bit))))
446                                                 toggle_bit = bit;
447
448                                 activate_accessory_by_mask(acc, toggle_bit);
449                         }
450                 }
451                 else
452                 {
453                         accessory_queue.pop_front();
454
455                         if(acc.state==acc.target)
456                         {
457                                 if(acc.kind==Accessory::TURNOUT)
458                                         signal_turnout.emit(acc.address, acc.state);
459                                 else if(acc.kind==Accessory::SIGNAL)
460                                         signal_signal.emit(acc.address, acc.state);
461                         }
462                 }
463         }
464
465         if(active_accessory && off_timeout)
466         {
467                 bool success = (monitor.get_peak()>0.35f && monitor.get_current()<monitor.get_peak()-0.2f);
468                 Time::TimeStamp t = Time::now();
469                 if(t>off_timeout || success)
470                 {
471                         Accessory &acc = *active_accessory;
472
473                         unsigned bit = 1<<active_index;
474
475                         // Assume success if we were uncertain of the physical setting
476                         if(acc.uncertain&bit)
477                                 acc.uncertain &= ~bit;
478                         else if(acc.kind==Accessory::TURNOUT && !success)
479                         {
480                                 if(debug>=1)
481                                         IO::print("Peak current only %.2f A\n", monitor.get_peak());
482                                 signal_turnout_failed.emit(acc.address);
483                                 acc.state.rollback();
484                                 if(acc.valid_states&(1<<(acc.target^bit)))
485                                         acc.target ^= bit;
486                                 else
487                                         acc.target = acc.state;
488                         }
489
490                         off_timeout = Time::TimeStamp();
491                         PendingCommand cmd(acc, Accessory::DEACTIVATE, active_index);
492                         command_queue.push(cmd);
493                 }
494         }
495 }
496
497 void ArduControl::flush()
498 {
499         while(!command_queue.empty() || (power && !accessory_queue.empty()))
500                 tick();
501 }
502
503 void ArduControl::save_state() const
504 {
505         FS::RedirectedPath tmp_file(state_file);
506         IO::BufferedFile out(tmp_file.str(), IO::M_WRITE);
507         DataFile::Writer writer(out);
508
509         writer.write((DataFile::Statement("mfx_announce_serial"), mfx_announce.get_serial()));
510         for(MfxInfoArray::const_iterator i=mfx_info.begin(); i!=mfx_info.end(); ++i)
511         {
512                 DataFile::Statement st("mfx_locomotive");
513                 st.append(i->id);
514                 st.sub.push_back((DataFile::Statement("address"), i->address));
515                 st.sub.push_back((DataFile::Statement("name"), i->name));
516                 writer.write(st);
517         }
518 }
519
520
521 ArduControl::Tag::Tag():
522         type(NONE),
523         command(0),
524         serial(0),
525         id(0)
526 { }
527
528
529 ArduControl::Locomotive::Locomotive(Protocol p, unsigned a):
530         id((p<<16)|a),
531         proto(p),
532         address(a),
533         speed(0),
534         reverse(false),
535         funcs(0),
536         last_change_age(0)
537 { }
538
539 unsigned ArduControl::Locomotive::create_speed_dir_command(char *buffer) const
540 {
541         if(proto==MM)
542         {
543                 buffer[0] = MOTOROLA_SPEED_DIRECTION;
544                 buffer[1] = address;
545                 buffer[2] = funcs.pending&1;
546                 buffer[3] = speed.pending+reverse.pending*0x80;
547                 return 4;
548         }
549         else if(proto==MFX)
550         {
551                 buffer[0] = MFX_SPEED;
552                 buffer[1] = address>>8;
553                 buffer[2] = address;
554                 buffer[3] = speed.pending+reverse.pending*0x80;
555                 return 4;
556         }
557         else
558                 return 0;
559 }
560
561 unsigned ArduControl::Locomotive::create_speed_func_command(unsigned f, char *buffer) const
562 {
563         if(proto==MM)
564         {
565                 if(f<1 || f>4)
566                         throw invalid_argument("Locomotive::create_speed_func_command");
567
568                 buffer[0] = MOTOROLA_SPEED_FUNCTION;
569                 buffer[1] = address;
570                 buffer[2] = (f<<4)|(((funcs.pending>>f)&1)<<1)|(funcs.pending&1);
571                 buffer[3] = speed.pending;
572                 return 4;
573         }
574         else if(proto==MFX)
575         {
576                 bool f16 = (funcs.pending>0xFF);
577                 buffer[0] = (f16 ? MFX_SPEED_FUNCS16 : MFX_SPEED_FUNCS8);
578                 buffer[1] = address>>8;
579                 buffer[2] = address;
580                 buffer[3] = speed.pending+reverse.pending*0x80;
581                 if(f16)
582                 {
583                         buffer[4] = funcs.pending>>8;
584                         buffer[5] = funcs.pending;
585                         return 6;
586                 }
587                 else
588                 {
589                         buffer[4] = funcs.pending;
590                         return 5;
591                 }
592         }
593         else
594                 return 0;
595 }
596
597
598 ArduControl::Accessory::Accessory(Kind k, unsigned a, unsigned b, unsigned s):
599         kind(k),
600         address(a),
601         bits(b),
602         valid_states(s),
603         state(0),
604         uncertain((1<<bits)-1),
605         target(0),
606         active_time((bits*700)*Time::msec)
607 { }
608
609 unsigned ArduControl::Accessory::create_state_command(unsigned b, bool c, char *buffer) const
610 {
611         if(b>=bits)
612                 throw invalid_argument("Accessory::create_state_command");
613
614         unsigned a = (address+b+3)*2;
615         if(!((state.pending>>b)&1))
616                 ++a;
617         buffer[0] = MOTOROLA_SOLENOID;
618         buffer[1] = a>>3;
619         buffer[2] = ((a&7)<<4)|c;
620         return 3;
621 }
622
623
624 ArduControl::Sensor::Sensor(unsigned a):
625         address(a),
626         state(false)
627 { }
628
629
630 ArduControl::PendingCommand::PendingCommand():
631         length(0),
632         repeat_count(1)
633 { }
634
635 ArduControl::PendingCommand::PendingCommand(GeneralCommand cmd):
636         length(0),
637         repeat_count(1)
638 {
639         tag.type = Tag::GENERAL;
640         tag.command = cmd;
641 }
642
643 ArduControl::PendingCommand::PendingCommand(Locomotive &loco, Locomotive::Command cmd, unsigned index):
644         repeat_count(8)
645 {
646         tag.type = Tag::LOCOMOTIVE;
647         tag.command = cmd;
648         tag.id = loco.id;
649         if(cmd==Locomotive::SPEED)
650         {
651                 tag.serial = loco.speed.serial;
652                 length = loco.create_speed_dir_command(command);
653         }
654         else if(cmd==Locomotive::REVERSE)
655         {
656                 tag.serial = loco.reverse.serial;
657                 length = loco.create_speed_dir_command(command);
658         }
659         else if(cmd==Locomotive::FUNCTIONS)
660         {
661                 tag.serial = loco.funcs.serial;
662                 length = loco.create_speed_func_command(index, command);
663         }
664         else
665                 throw invalid_argument("PendingCommand");
666 }
667
668 ArduControl::PendingCommand::PendingCommand(Accessory &acc, Accessory::Command cmd, unsigned index):
669         repeat_count(1)
670 {
671         tag.type = Tag::ACCESSORY;
672         tag.command = cmd;
673         tag.id = acc.address;
674         if(cmd==Accessory::ACTIVATE || cmd==Accessory::DEACTIVATE)
675         {
676                 tag.serial = acc.state.serial;
677                 length = acc.create_state_command(index, (cmd==Accessory::ACTIVATE), command);
678         }
679         else
680                 throw invalid_argument("PendingCommand");
681 }
682
683
684 template<typename T>
685 void ArduControl::Queue<T>::push(const T &item)
686 {
687         MutexLock lock(mutex);
688         items.push_back(item);
689 }
690
691 template<typename T>
692 bool ArduControl::Queue<T>::pop(T &item)
693 {
694         MutexLock lock(mutex);
695         if(items.empty())
696                 return false;
697
698         item = items.front();
699         items.pop_front();
700         return true;
701 }
702
703 template<typename T>
704 bool ArduControl::Queue<T>::empty() const
705 {
706         return items.empty();
707 }
708
709
710 bool ArduControl::CommandQueueTask::get_work(PendingCommand &cmd)
711 {
712         return queue.pop(cmd);
713 }
714
715 void ArduControl::CommandQueueTask::push(const PendingCommand &cmd)
716 {
717         queue.push(cmd);
718 }
719
720
721 ArduControl::Task::Task(const string &n, unsigned p):
722         name(n),
723         priority(p)
724 { }
725
726 void ArduControl::Task::sleep(const Time::TimeDelta &dt)
727 {
728         sleep_timeout = Time::now()+dt;
729 }
730
731
732 ArduControl::CommandQueueTask::CommandQueueTask():
733         Task("CommandQueue")
734 { }
735
736
737 ArduControl::RefreshTask::RefreshTask():
738         Task("Refresh", 2),
739         next(cycle.end()),
740         round(0),
741         loco(0),
742         phase(0)
743 { }
744
745 bool ArduControl::RefreshTask::get_work(PendingCommand &cmd)
746 {
747         if(loco && loco->proto==MM && phase==0)
748         {
749                 cmd.length = loco->create_speed_func_command(round%4+1, cmd.command);
750                 cmd.repeat_count = 2;
751                 ++phase;
752                 return true;
753         }
754
755         loco = get_next_loco();
756         if(!loco)
757                 return false;
758
759         phase = 0;
760         if(loco->proto==MM)
761         {
762                 cmd.length = loco->create_speed_dir_command(cmd.command);
763                 cmd.repeat_count = 2;
764         }
765         else if(loco->proto==MFX)
766                 cmd.length = loco->create_speed_func_command(0, cmd.command);
767         else
768                 return false;
769
770         return true;
771 }
772
773 void ArduControl::RefreshTask::add_loco(Locomotive &l)
774 {
775         MutexLock lock(mutex);
776         cycle.push_back(&l);
777         if(cycle.size()>15)
778         {
779                 LocomotivePtrList::iterator oldest = cycle.begin();
780                 for(LocomotivePtrList::iterator i=cycle.begin(); ++i!=cycle.end(); )
781                         if((*i)->last_change_age>(*oldest)->last_change_age)
782                                 oldest = i;
783                 if(oldest==next)
784                         advance();
785                 cycle.erase(oldest);
786         }
787         if(next==cycle.end())
788                 next = cycle.begin();
789 }
790
791 void ArduControl::RefreshTask::remove_loco(Locomotive &l)
792 {
793         MutexLock lock(mutex);
794         for(LocomotivePtrList::iterator i=cycle.begin(); i!=cycle.end(); ++i)
795                 if(*i==&l)
796                 {
797                         if(i==next)
798                         {
799                                 if(cycle.size()>1)
800                                         advance();
801                                 else
802                                         next = cycle.end();
803                         }
804                         cycle.erase(i);
805                         return;
806                 }
807 }
808
809 ArduControl::Locomotive *ArduControl::RefreshTask::get_next_loco()
810 {
811         MutexLock lock(mutex);
812         if(cycle.empty())
813                 return 0;
814
815         Locomotive *l = *next;
816         advance();
817         return l;
818 }
819
820 void ArduControl::RefreshTask::advance()
821 {
822         ++next;
823         if(next==cycle.end())
824         {
825                 next= cycle.begin();
826                 ++round;
827         }
828 }
829
830
831 ArduControl::S88Task::S88Task(ArduControl &c):
832         Task("S88"),
833         control(c),
834         n_octets(0),
835         octets_remaining(0)
836 { }
837
838 bool ArduControl::S88Task::get_work(PendingCommand &cmd)
839 {
840         if(octets_remaining || !n_octets)
841                 return false;
842
843         octets_remaining = n_octets;
844         cmd.command[0] = S88_READ;
845         cmd.command[1] = octets_remaining;
846         cmd.length = 2;
847
848         sleep(100*Time::msec);
849
850         return true;
851 }
852
853 void ArduControl::S88Task::process_reply(const char *reply, unsigned length)
854 {
855         unsigned char type = reply[0];
856         if(type==S88_DATA && length>2)
857         {
858                 unsigned offset = static_cast<unsigned char>(reply[1]);
859                 unsigned count = length-2;
860
861                 SensorMap::iterator begin = control.sensors.lower_bound(offset*8+1);
862                 SensorMap::iterator end = control.sensors.upper_bound((offset+count)*8);
863                 for(SensorMap::iterator i=begin; i!=end; ++i)
864                 {
865                         unsigned bit_index = i->first-1-offset*8;
866                         bool state = (reply[2+bit_index/8]>>(7-bit_index%8))&1;
867                         i->second.state.set(state);
868
869                         Tag tag;
870                         tag.type = Tag::SENSOR;
871                         tag.command = Sensor::STATE;
872                         tag.serial = i->second.state.serial;
873                         tag.id = i->first;
874                         control.completed_commands.push(tag);
875                 }
876
877                 if(count>octets_remaining)
878                         octets_remaining = 0;
879                 else
880                         octets_remaining -= count;
881         }
882 }
883
884 void ArduControl::S88Task::set_n_octets(unsigned n)
885 {
886         n_octets = n;
887 }
888
889 void ArduControl::S88Task::grow_n_octets(unsigned n)
890 {
891         if(n>n_octets)
892                 n_octets = n;
893 }
894
895
896 ArduControl::MfxAnnounceTask::MfxAnnounceTask():
897         Task("MfxAnnounce", 1),
898         serial(0)
899 { }
900
901 bool ArduControl::MfxAnnounceTask::get_work(PendingCommand &cmd)
902 {
903         cmd.command[0] = MFX_ANNOUNCE;
904         cmd.command[1] = serial>>8;
905         cmd.command[2] = serial;
906         cmd.length = 3;
907
908         sleep(400*Time::msec);
909
910         return true;
911 }
912
913 void ArduControl::MfxAnnounceTask::set_serial(unsigned s)
914 {
915         serial = s;
916 }
917
918
919 ArduControl::MfxSearchTask::MfxSearchTask(ArduControl &c):
920         Task("MfxSearch", 1),
921         control(c),
922         next_address(1),
923         size(0),
924         bits(0),
925         misses(0)
926 { }
927
928 bool ArduControl::MfxSearchTask::get_work(PendingCommand &cmd)
929 {
930         if(size>32)
931         {
932                 if(control.debug>=1)
933                         IO::print("Assigning MFX address %d to decoder %08X\n", next_address, bits);
934
935                 MfxInfo info;
936                 info.protocol = "MFX";
937                 info.address = next_address;
938                 info.name = format("%08X", bits);
939                 info.id = bits;
940                 queue.push(info);
941
942                 cmd.command[0] = MFX_ASSIGN_ADDRESS;
943                 cmd.command[1] = next_address>>8;
944                 cmd.command[2] = next_address;
945                 for(unsigned i=0; i<4; ++i)
946                         cmd.command[3+i] = bits>>(24-i*8);
947                 cmd.length = 7;
948
949                 cmd.tag.type = Tag::GENERAL;
950                 cmd.tag.command = NEW_LOCO;
951                 cmd.tag.id = bits;
952
953                 size = 0;
954                 bits = 0;
955                 ++next_address;
956
957                 return true;
958         }
959
960         cmd.command[0] = MFX_SEARCH;
961         for(unsigned i=0; i<4; ++i)
962                 cmd.command[1+i] = bits>>(24-i*8);
963         cmd.command[5] = size;
964         cmd.length = 6;
965
966         sleep(200*Time::msec);
967
968         if(control.debug>=1)
969                 IO::print("Search %08X/%d\n", bits, size);
970
971         return true;
972 }
973
974 void ArduControl::MfxSearchTask::process_reply(const char *reply, unsigned length)
975 {
976         unsigned char type = reply[0];
977         if(type==MFX_SEARCH_FEEDBACK && length==2)
978         {
979                 if(reply[1])
980                 {
981                         misses = 0;
982                         ++size;
983                 }
984                 else if(size>0 && misses<6)
985                 {
986                         ++misses;
987                         bits ^= 1<<(32-size);
988                 }
989                 else
990                 {
991                         sleep(2*Time::sec);
992                         bits = 0;
993                         size = 0;
994                         misses = 0;
995                 }
996         }
997 }
998
999 void ArduControl::MfxSearchTask::set_next_address(unsigned a)
1000 {
1001         next_address = a;
1002 }
1003
1004 bool ArduControl::MfxSearchTask::pop_info(MfxInfo &info)
1005 {
1006         return queue.pop(info);
1007 }
1008
1009
1010 ArduControl::MonitorTask::MonitorTask():
1011         Task("Monitor"),
1012         voltage(0),
1013         current(0),
1014         base_level(0),
1015         peak_level(0),
1016         next_type(0)
1017 { }
1018
1019 bool ArduControl::MonitorTask::get_work(PendingCommand &cmd)
1020 {
1021         if(next_type==0)
1022                 cmd.command[0] = READ_INPUT_VOLTAGE;
1023         else
1024                 cmd.command[0] = READ_TRACK_CURRENT;
1025         cmd.length = 1;
1026
1027         sleep(200*Time::msec);
1028         next_type = (next_type+1)%5;
1029
1030         return true;
1031 }
1032
1033 void ArduControl::MonitorTask::process_reply(const char *reply, unsigned length)
1034 {
1035         unsigned char type = reply[0];
1036         if(type==INPUT_VOLTAGE && length==3)
1037                 voltage = ((static_cast<unsigned char>(reply[1])<<8) | static_cast<unsigned char>(reply[2]))/1000.0f;
1038         else if(type==TRACK_CURRENT && length==5)
1039         {
1040                 current = ((static_cast<unsigned char>(reply[1])<<8) | static_cast<unsigned char>(reply[2]))/1000.0f;
1041                 float peak = ((static_cast<unsigned char>(reply[3])<<8) | static_cast<unsigned char>(reply[4]))/1000.0f;
1042                 peak_level = max(peak_level, peak);
1043                 base_level = min(base_level, current);
1044         }
1045 }
1046
1047 void ArduControl::MonitorTask::reset_peak()
1048 {
1049         base_level = current;
1050         peak_level = current;
1051 }
1052
1053
1054 ArduControl::ControlThread::ControlThread(ArduControl &c):
1055         control(c),
1056         done(false)
1057 {
1058         tasks.push_back(&control.command_queue);
1059         tasks.push_back(&control.monitor);
1060         tasks.push_back(&control.mfx_announce);
1061         tasks.push_back(&control.mfx_search);
1062         tasks.push_back(&control.s88);
1063         tasks.push_back(&control.refresh);
1064
1065         launch();
1066 }
1067
1068 void ArduControl::ControlThread::exit()
1069 {
1070         done = true;
1071         join();
1072 }
1073
1074 void ArduControl::ControlThread::main()
1075 {
1076         init_baud_rate();
1077
1078         while(!done)
1079         {
1080                 PendingCommand cmd;
1081                 if(get_work(cmd))
1082                 {
1083                         bool success = true;
1084                         bool resync = false;
1085                         for(unsigned i=0; (success && i<cmd.repeat_count); ++i)
1086                         {
1087                                 unsigned result = do_command(cmd, control.command_timeout);
1088                                 success = (result==COMMAND_OK);
1089                                 resync = (result==0);
1090                         }
1091
1092                         if(success && cmd.tag)
1093                                 control.completed_commands.push(cmd.tag);
1094
1095                         if(resync)
1096                         {
1097                                 if(control.debug>=1)
1098                                         IO::print("Synchronization with ArduControl lost, attempting to recover\n");
1099                                 for(unsigned i=0; (resync && i<16); ++i)
1100                                 {
1101                                         control.serial.put('\xFF');
1102                                         while(IO::poll(control.serial, IO::P_INPUT, control.command_timeout))
1103                                                 resync = (control.serial.get()!=0xFF);
1104                                 }
1105                                 if(resync)
1106                                 {
1107                                         if(control.debug>=1)
1108                                                 IO::print("Resynchronization failed, giving up\n");
1109                                         done = true;
1110                                 }
1111                                 else
1112                                 {
1113                                         if(control.debug>=1)
1114                                                 IO::print("Resynchronization successful\n");
1115                                         if(cmd.tag)
1116                                                 control.command_queue.push(cmd);
1117                                 }
1118                         }
1119                 }
1120                 else
1121                         Time::sleep(10*Time::msec);
1122         }
1123 }
1124
1125 void ArduControl::ControlThread::init_baud_rate()
1126 {
1127         static unsigned rates[] = { 57600, 9600, 19200, 38400, 0 };
1128         unsigned rate = 0;
1129         control.serial.set_data_bits(8);
1130         control.serial.set_parity(IO::Serial::NONE);
1131         control.serial.set_stop_bits(1);
1132         for(unsigned i=0; rates[i]; ++i)
1133         {
1134                 control.serial.set_baud_rate(rates[i]);
1135                 control.serial.put('\xFF');
1136                 if(IO::poll(control.serial, IO::P_INPUT, 500*Time::msec))
1137                 {
1138                         int c = control.serial.get();
1139                         if(c==0xFF)
1140                         {
1141                                 rate = rates[i];
1142                                 break;
1143                         }
1144                 }
1145         }
1146
1147         if(!rate)
1148         {
1149                 if(control.debug>=1)
1150                         IO::print("ArduControl detection failed\n");
1151                 done = true;
1152                 return;
1153         }
1154
1155         if(control.debug>=1)
1156                 IO::print("ArduControl detected at %d bits/s\n", rate);
1157
1158         if(rate!=rates[0])
1159         {
1160                 PendingCommand cmd;
1161                 cmd.command[0] = SET_BAUD_RATE;
1162                 cmd.command[1] = rates[0]>>8;
1163                 cmd.command[2] = rates[0];
1164                 cmd.length = 3;
1165                 if(do_command(cmd, Time::sec)==COMMAND_OK)
1166                 {
1167                         control.serial.set_baud_rate(rates[0]);
1168                         Time::sleep(Time::sec);
1169                         if(do_command(cmd, Time::sec)==COMMAND_OK)
1170                         {
1171                                 if(control.debug>=1)
1172                                         IO::print("Rate changed to %d bits/s\n", rates[0]);
1173                         }
1174                 }
1175         }
1176 }
1177
1178 bool ArduControl::ControlThread::get_work(PendingCommand &cmd)
1179 {
1180         Time::TimeStamp t = Time::now();
1181
1182         unsigned count = 0;
1183         for(; (count<tasks.size() && tasks[count]->get_sleep_timeout()<=t); ++count) ;
1184
1185         for(; count>0; --count)
1186         {
1187                 unsigned i = 0;
1188                 for(unsigned j=1; j<count; ++j)
1189                         if(tasks[j]->get_priority()<tasks[i]->get_priority())
1190                                 i = j;
1191
1192                 Task *task = tasks[i];
1193                 bool result = task->get_work(cmd);
1194
1195                 Time::TimeStamp st = max(task->get_sleep_timeout(), t);
1196                 for(; (i+1<tasks.size() && tasks[i+1]->get_sleep_timeout()<=st); ++i)
1197                         tasks[i] = tasks[i+1];
1198                 tasks[i] = task;
1199
1200                 if(result)
1201                 {
1202                         if(control.debug>=2)
1203                                 IO::print("Scheduled task %s\n", task->get_name());
1204                         return true;
1205                 }
1206         }
1207
1208         // As fallback, send an idle packet for the MM protocol
1209         cmd.command[0] = MOTOROLA_SPEED;
1210         cmd.command[1] = 80;
1211         cmd.command[2] = 0;
1212         cmd.command[3] = 0;
1213         cmd.length = 4;
1214
1215         return true;
1216 }
1217
1218 unsigned ArduControl::ControlThread::do_command(const PendingCommand &cmd, const Time::TimeDelta &timeout)
1219 {
1220         if(control.debug>=2)
1221         {
1222                 string cmd_hex;
1223                 for(unsigned i=0; i<cmd.length; ++i)
1224                         cmd_hex += format(" %02X", static_cast<unsigned char>(cmd.command[i]));
1225                 IO::print("< %02X%s\n", cmd.length^0xFF, cmd_hex);
1226         }
1227
1228         control.serial.put(cmd.length^0xFF);
1229         control.serial.write(cmd.command, cmd.length);
1230
1231         unsigned result = 0;
1232         while(1)
1233         {
1234                 bool got_data;
1235                 if(result)
1236                         got_data = IO::poll(control.serial, IO::P_INPUT, Time::zero);
1237                 else
1238                         got_data = IO::poll(control.serial, IO::P_INPUT, timeout);
1239
1240                 if(!got_data)
1241                         break;
1242
1243                 unsigned rlength = control.serial.get()^0xFF;
1244                 if(rlength>15)
1245                 {
1246                         IO::print("Invalid length %02X\n", rlength);
1247                         continue;
1248                 }
1249
1250                 char reply[15];
1251                 unsigned pos = 0;
1252                 while(pos<rlength)
1253                 {
1254                         if(!IO::poll(control.serial, IO::P_INPUT, timeout))
1255                                 return 0;
1256                         pos += control.serial.read(reply+pos, rlength-pos);
1257                 }
1258
1259                 if(control.debug>=2)
1260                 {
1261                         string reply_hex;
1262                         for(unsigned i=0; i<rlength; ++i)
1263                                 reply_hex += format(" %02X", static_cast<unsigned char>(reply[i]));
1264                         IO::print("> %02X%s\n", rlength^0xFF, reply_hex);
1265                 }
1266
1267                 unsigned r = process_reply(reply, rlength);
1268                 if(r && !result)
1269                         result = r;
1270         }
1271
1272         return result;
1273 }
1274
1275 unsigned ArduControl::ControlThread::process_reply(const char *reply, unsigned rlength)
1276 {
1277         unsigned char type = reply[0];
1278         if((type&0xE0)==0x80)
1279         {
1280                 if(type!=COMMAND_OK)
1281                         IO::print("Error %02X\n", type);
1282                 return type;
1283         }
1284         else if(type==POWER_STATE && rlength==2)
1285                 set_power(reply[1]);
1286         else if(type==OVERCURRENT)
1287         {
1288                 set_power(false);
1289                 IO::print("Overcurrent detected!\n");
1290         }
1291         else
1292         {
1293                 for(vector<Task *>::iterator i=tasks.begin(); i!=tasks.end(); ++i)
1294                         (*i)->process_reply(reply, rlength);
1295         }
1296
1297         return 0;
1298 }
1299
1300 void ArduControl::ControlThread::set_power(bool p)
1301 {
1302         control.power.set(p);
1303
1304         Tag tag;
1305         tag.type = Tag::GENERAL;
1306         tag.command = POWER;
1307         tag.serial = control.power.serial;
1308         control.completed_commands.push(tag);
1309 }
1310
1311
1312 ArduControl::Loader::Loader(ArduControl &c):
1313         DataFile::ObjectLoader<ArduControl>(c)
1314 {
1315         add("mfx_announce_serial", &Loader::mfx_announce_serial);
1316         add("mfx_locomotive", &Loader::mfx_locomotive);
1317 }
1318
1319 void ArduControl::Loader::mfx_announce_serial(unsigned s)
1320 {
1321         obj.mfx_announce.set_serial(s);
1322 }
1323
1324 void ArduControl::Loader::mfx_locomotive(unsigned id)
1325 {
1326         MfxInfo info;
1327         info.id = id;
1328         info.protocol = "MFX";
1329         load_sub(info);
1330         obj.add_mfx_info(info);
1331 }
1332
1333
1334 ArduControl::MfxInfo::Loader::Loader(MfxInfo &i):
1335         DataFile::ObjectLoader<MfxInfo>(i)
1336 {
1337         add("address", static_cast<unsigned MfxInfo::*>(&MfxInfo::address));
1338         add("name", static_cast<string MfxInfo::*>(&MfxInfo::name));
1339 }
1340
1341 } // namespace R2C2