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