]> git.tdb.fi Git - r2c2.git/blob - source/libr2c2/arducontrol.cpp
Add two new telemetry values to the 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&~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 success = (monitor.get_peak()>0.35f && monitor.get_current()<monitor.get_peak()-0.2f);
507                 Time::TimeStamp t = Time::now();
508                 if(t>off_timeout || success)
509                 {
510                         Accessory &acc = *active_accessory;
511
512                         unsigned bit = 1<<active_index;
513
514                         // Assume success if we were uncertain of the physical setting
515                         if(acc.uncertain&bit)
516                                 acc.uncertain &= ~bit;
517                         else if(acc.kind==Accessory::TURNOUT && !success)
518                         {
519                                 if(debug>=1)
520                                         IO::print("Peak current only %.2f A\n", monitor.get_peak());
521                                 signal_turnout_failed.emit(acc.address);
522                                 acc.state.rollback();
523                                 if(acc.valid_states&(1<<(acc.target^bit)))
524                                         acc.target ^= bit;
525                                 else
526                                         acc.target = acc.state;
527                         }
528
529                         off_timeout = Time::TimeStamp();
530                         PendingCommand cmd(acc, Accessory::DEACTIVATE, active_index);
531                         command_queue.push(cmd);
532                 }
533         }
534 }
535
536 void ArduControl::flush()
537 {
538         while(!command_queue.empty() || (power && !accessory_queue.empty()))
539                 tick();
540 }
541
542 void ArduControl::save_state() const
543 {
544         FS::RedirectedPath tmp_file(state_file);
545         IO::BufferedFile out(tmp_file.str(), IO::M_WRITE);
546         DataFile::Writer writer(out);
547
548         writer.write((DataFile::Statement("mfx_announce_serial"), mfx_announce.get_serial()));
549         for(MfxInfoArray::const_iterator i=mfx_info.begin(); i!=mfx_info.end(); ++i)
550         {
551                 DataFile::Statement st("mfx_locomotive");
552                 st.append(i->id);
553                 st.sub.push_back((DataFile::Statement("address"), i->address));
554                 st.sub.push_back((DataFile::Statement("name"), i->name));
555                 writer.write(st);
556         }
557 }
558
559
560 ArduControl::Tag::Tag():
561         type(NONE),
562         command(0),
563         serial(0),
564         id(0)
565 { }
566
567
568 ArduControl::Locomotive::Locomotive(Protocol p, unsigned a):
569         id((p<<16)|a),
570         proto(p),
571         address(a),
572         speed(0),
573         reverse(false),
574         funcs(0),
575         last_change_age(0)
576 { }
577
578 unsigned ArduControl::Locomotive::create_speed_dir_command(char *buffer) const
579 {
580         if(proto==MM)
581         {
582                 buffer[0] = MOTOROLA_SPEED_DIRECTION;
583                 buffer[1] = address;
584                 buffer[2] = funcs.pending&1;
585                 buffer[3] = speed.pending+reverse.pending*0x80;
586                 return 4;
587         }
588         else if(proto==MFX)
589         {
590                 buffer[0] = MFX_SPEED;
591                 buffer[1] = address>>8;
592                 buffer[2] = address;
593                 buffer[3] = speed.pending+reverse.pending*0x80;
594                 return 4;
595         }
596         else
597                 return 0;
598 }
599
600 unsigned ArduControl::Locomotive::create_speed_func_command(unsigned f, char *buffer) const
601 {
602         if(proto==MM)
603         {
604                 if(f<1 || f>4)
605                         throw invalid_argument("Locomotive::create_speed_func_command");
606
607                 buffer[0] = MOTOROLA_SPEED_FUNCTION;
608                 buffer[1] = address;
609                 buffer[2] = (f<<4)|(((funcs.pending>>f)&1)<<1)|(funcs.pending&1);
610                 buffer[3] = speed.pending;
611                 return 4;
612         }
613         else if(proto==MFX)
614         {
615                 bool f16 = (funcs.pending>0xFF);
616                 buffer[0] = (f16 ? MFX_SPEED_FUNCS16 : MFX_SPEED_FUNCS8);
617                 buffer[1] = address>>8;
618                 buffer[2] = address;
619                 buffer[3] = speed.pending+reverse.pending*0x80;
620                 if(f16)
621                 {
622                         buffer[4] = funcs.pending>>8;
623                         buffer[5] = funcs.pending;
624                         return 6;
625                 }
626                 else
627                 {
628                         buffer[4] = funcs.pending;
629                         return 5;
630                 }
631         }
632         else
633                 return 0;
634 }
635
636
637 ArduControl::Accessory::Accessory(Kind k, unsigned a, unsigned b, unsigned s):
638         kind(k),
639         address(a),
640         bits(b),
641         valid_states(s),
642         state(0),
643         uncertain((1<<bits)-1),
644         target(0),
645         active_time((bits*700)*Time::msec)
646 { }
647
648 unsigned ArduControl::Accessory::create_state_command(unsigned b, bool c, char *buffer) const
649 {
650         if(b>=bits)
651                 throw invalid_argument("Accessory::create_state_command");
652
653         unsigned a = (address+b+3)*2;
654         if(!((state.pending>>b)&1))
655                 ++a;
656         buffer[0] = MOTOROLA_SOLENOID;
657         buffer[1] = a>>3;
658         buffer[2] = ((a&7)<<4)|c;
659         return 3;
660 }
661
662
663 ArduControl::Sensor::Sensor(unsigned a):
664         address(a),
665         state(false)
666 { }
667
668
669 ArduControl::PendingCommand::PendingCommand():
670         length(0),
671         repeat_count(1)
672 { }
673
674 ArduControl::PendingCommand::PendingCommand(GeneralCommand cmd):
675         length(0),
676         repeat_count(1)
677 {
678         tag.type = Tag::GENERAL;
679         tag.command = cmd;
680 }
681
682 ArduControl::PendingCommand::PendingCommand(Locomotive &loco, Locomotive::Command cmd, unsigned index):
683         repeat_count(8)
684 {
685         tag.type = Tag::LOCOMOTIVE;
686         tag.command = cmd;
687         tag.id = loco.id;
688         if(cmd==Locomotive::SPEED)
689         {
690                 tag.serial = loco.speed.serial;
691                 length = loco.create_speed_dir_command(command);
692         }
693         else if(cmd==Locomotive::REVERSE)
694         {
695                 tag.serial = loco.reverse.serial;
696                 length = loco.create_speed_dir_command(command);
697         }
698         else if(cmd==Locomotive::FUNCTIONS)
699         {
700                 tag.serial = loco.funcs.serial;
701                 length = loco.create_speed_func_command(index, command);
702         }
703         else
704                 throw invalid_argument("PendingCommand");
705 }
706
707 ArduControl::PendingCommand::PendingCommand(Accessory &acc, Accessory::Command cmd, unsigned index):
708         repeat_count(1)
709 {
710         tag.type = Tag::ACCESSORY;
711         tag.command = cmd;
712         tag.id = acc.address;
713         if(cmd==Accessory::ACTIVATE || cmd==Accessory::DEACTIVATE)
714         {
715                 tag.serial = acc.state.serial;
716                 length = acc.create_state_command(index, (cmd==Accessory::ACTIVATE), command);
717         }
718         else
719                 throw invalid_argument("PendingCommand");
720 }
721
722
723 template<typename T>
724 void ArduControl::Queue<T>::push(const T &item)
725 {
726         MutexLock lock(mutex);
727         items.push_back(item);
728 }
729
730 template<typename T>
731 bool ArduControl::Queue<T>::pop(T &item)
732 {
733         MutexLock lock(mutex);
734         if(items.empty())
735                 return false;
736
737         item = items.front();
738         items.pop_front();
739         return true;
740 }
741
742 template<typename T>
743 unsigned ArduControl::Queue<T>::size() const
744 {
745         return items.size();
746 }
747
748 template<typename T>
749 bool ArduControl::Queue<T>::empty() const
750 {
751         return items.empty();
752 }
753
754
755 bool ArduControl::CommandQueueTask::get_work(PendingCommand &cmd)
756 {
757         return queue.pop(cmd);
758 }
759
760 void ArduControl::CommandQueueTask::push(const PendingCommand &cmd)
761 {
762         queue.push(cmd);
763 }
764
765
766 ArduControl::Task::Task(const string &n, unsigned p):
767         name(n),
768         priority(p)
769 { }
770
771 void ArduControl::Task::sleep(const Time::TimeDelta &dt)
772 {
773         sleep_timeout = Time::now()+dt;
774 }
775
776
777 ArduControl::CommandQueueTask::CommandQueueTask():
778         Task("CommandQueue")
779 { }
780
781
782 ArduControl::RefreshTask::RefreshTask():
783         Task("Refresh", 2),
784         next(cycle.end()),
785         round(0),
786         loco(0),
787         phase(0)
788 { }
789
790 bool ArduControl::RefreshTask::get_work(PendingCommand &cmd)
791 {
792         if(loco && loco->proto==MM && phase==0)
793         {
794                 cmd.length = loco->create_speed_func_command(round%4+1, cmd.command);
795                 cmd.repeat_count = 2;
796                 ++phase;
797                 return true;
798         }
799
800         loco = get_next_loco();
801         if(!loco)
802                 return false;
803
804         phase = 0;
805         if(loco->proto==MM)
806         {
807                 cmd.length = loco->create_speed_dir_command(cmd.command);
808                 cmd.repeat_count = 2;
809         }
810         else if(loco->proto==MFX)
811                 cmd.length = loco->create_speed_func_command(0, cmd.command);
812         else
813                 return false;
814
815         return true;
816 }
817
818 void ArduControl::RefreshTask::add_loco(Locomotive &l)
819 {
820         MutexLock lock(mutex);
821         cycle.push_back(&l);
822         if(cycle.size()>15)
823         {
824                 LocomotivePtrList::iterator oldest = cycle.begin();
825                 for(LocomotivePtrList::iterator i=cycle.begin(); ++i!=cycle.end(); )
826                         if((*i)->last_change_age>(*oldest)->last_change_age)
827                                 oldest = i;
828                 if(oldest==next)
829                         advance();
830                 cycle.erase(oldest);
831         }
832         if(next==cycle.end())
833                 next = cycle.begin();
834 }
835
836 void ArduControl::RefreshTask::remove_loco(Locomotive &l)
837 {
838         MutexLock lock(mutex);
839         for(LocomotivePtrList::iterator i=cycle.begin(); i!=cycle.end(); ++i)
840                 if(*i==&l)
841                 {
842                         if(i==next)
843                         {
844                                 if(cycle.size()>1)
845                                         advance();
846                                 else
847                                         next = cycle.end();
848                         }
849                         cycle.erase(i);
850                         return;
851                 }
852 }
853
854 ArduControl::Locomotive *ArduControl::RefreshTask::get_next_loco()
855 {
856         MutexLock lock(mutex);
857         if(cycle.empty())
858                 return 0;
859
860         Locomotive *l = *next;
861         advance();
862         return l;
863 }
864
865 void ArduControl::RefreshTask::advance()
866 {
867         ++next;
868         if(next==cycle.end())
869         {
870                 next = cycle.begin();
871                 ++round;
872         }
873 }
874
875
876 ArduControl::S88Task::S88Task(ArduControl &c):
877         Task("S88"),
878         control(c),
879         n_octets(0),
880         octets_remaining(0)
881 { }
882
883 bool ArduControl::S88Task::get_work(PendingCommand &cmd)
884 {
885         if(octets_remaining || !n_octets)
886                 return false;
887
888         Time::TimeStamp t = Time::now();
889         if(last_poll)
890                 latency = t-last_poll;
891         last_poll = t;
892
893         octets_remaining = n_octets;
894         cmd.command[0] = S88_READ;
895         cmd.command[1] = octets_remaining;
896         cmd.length = 2;
897
898         sleep(100*Time::msec);
899
900         return true;
901 }
902
903 void ArduControl::S88Task::process_reply(const char *reply, unsigned length)
904 {
905         unsigned char type = reply[0];
906         if(type==S88_DATA && length>2)
907         {
908                 unsigned offset = static_cast<unsigned char>(reply[1]);
909                 unsigned count = length-2;
910
911                 SensorMap::iterator begin = control.sensors.lower_bound(offset*8+1);
912                 SensorMap::iterator end = control.sensors.upper_bound((offset+count)*8);
913                 for(SensorMap::iterator i=begin; i!=end; ++i)
914                 {
915                         unsigned bit_index = i->first-1-offset*8;
916                         bool state = (reply[2+bit_index/8]>>(7-bit_index%8))&1;
917                         i->second.state.set(state);
918
919                         Tag tag;
920                         tag.type = Tag::SENSOR;
921                         tag.command = Sensor::STATE;
922                         tag.serial = i->second.state.serial;
923                         tag.id = i->first;
924                         control.completed_commands.push(tag);
925                 }
926
927                 if(count>octets_remaining)
928                         octets_remaining = 0;
929                 else
930                         octets_remaining -= count;
931         }
932 }
933
934 void ArduControl::S88Task::set_n_octets(unsigned n)
935 {
936         n_octets = n;
937 }
938
939 void ArduControl::S88Task::grow_n_octets(unsigned n)
940 {
941         if(n>n_octets)
942                 n_octets = n;
943 }
944
945
946 ArduControl::MfxAnnounceTask::MfxAnnounceTask():
947         Task("MfxAnnounce", 1),
948         serial(0)
949 { }
950
951 bool ArduControl::MfxAnnounceTask::get_work(PendingCommand &cmd)
952 {
953         cmd.command[0] = MFX_ANNOUNCE;
954         cmd.command[1] = serial>>8;
955         cmd.command[2] = serial;
956         cmd.length = 3;
957
958         sleep(400*Time::msec);
959
960         return true;
961 }
962
963 void ArduControl::MfxAnnounceTask::set_serial(unsigned s)
964 {
965         serial = s;
966 }
967
968
969 ArduControl::MfxSearchTask::MfxSearchTask(ArduControl &c):
970         Task("MfxSearch", 1),
971         control(c),
972         next_address(1),
973         size(0),
974         bits(0),
975         misses(0),
976         pending_info(0),
977         read_array(0),
978         read_offset(0),
979         read_length(0),
980         block_size(0)
981 { }
982
983 bool ArduControl::MfxSearchTask::get_work(PendingCommand &cmd)
984 {
985         if(read_length>0)
986         {
987                 cmd.command[0] = MFX_READ;
988                 cmd.command[1] = pending_info->address>>8;
989                 cmd.command[2] = pending_info->address;
990                 unsigned index = read_array*0x40+read_offset;
991                 cmd.command[3] = index>>8;
992                 cmd.command[4] = index;
993                 unsigned length = (read_length>=4 ? 4 : read_length>=2 ? 2 : 1);
994                 cmd.command[5] = length;
995                 cmd.length = 6;
996
997                 sleep(100*Time::msec);
998
999                 return true;
1000         }
1001         else if(pending_info)
1002         {
1003                 queue.push(*pending_info);
1004                 Tag tag;
1005                 tag.type = Tag::GENERAL;
1006                 tag.command = NEW_LOCO;
1007                 tag.id = pending_info->id;
1008                 control.completed_commands.push(tag);
1009
1010                 if(control.debug>=1)
1011                         IO::print("Completed processing locomotive %s at address %d\n", pending_info->name, pending_info->address);
1012
1013                 delete pending_info;
1014                 pending_info = 0;
1015         }
1016
1017         if(size>32)
1018         {
1019                 unsigned address = 0;
1020                 if(MfxInfo *existing = control.find_mfx_info(bits))
1021                         address = existing->address;
1022                 else
1023                         address = next_address++;
1024
1025                 if(control.debug>=1)
1026                         IO::print("Assigning MFX address %d to decoder %08X\n", address, bits);
1027
1028                 pending_info = new MfxInfo;
1029                 pending_info->protocol = "MFX";
1030                 pending_info->address = address;
1031                 pending_info->name = format("%08X", bits);
1032                 pending_info->id = bits;
1033
1034                 cmd.command[0] = MFX_ASSIGN_ADDRESS;
1035                 cmd.command[1] = address>>8;
1036                 cmd.command[2] = address;
1037                 for(unsigned i=0; i<4; ++i)
1038                         cmd.command[3+i] = bits>>(24-i*8);
1039                 cmd.length = 7;
1040
1041                 size = 0;
1042                 bits = 0;
1043                 misses = 0;
1044
1045                 read_array = 0;
1046                 read_offset = 0;
1047                 read_length = 6;
1048
1049                 return true;
1050         }
1051
1052         cmd.command[0] = MFX_SEARCH;
1053         for(unsigned i=0; i<4; ++i)
1054                 cmd.command[1+i] = bits>>(24-i*8);
1055         cmd.command[5] = size;
1056         cmd.length = 6;
1057
1058         sleep(100*Time::msec);
1059
1060         if(control.debug>=1)
1061                 IO::print("Search %08X/%d\n", bits, size);
1062
1063         return true;
1064 }
1065
1066 void ArduControl::MfxSearchTask::process_reply(const char *reply, unsigned length)
1067 {
1068         unsigned char type = reply[0];
1069         if(type==MFX_SEARCH_FEEDBACK && length==2)
1070         {
1071                 if(reply[1])
1072                 {
1073                         misses = 0;
1074                         ++size;
1075                 }
1076                 else if(size>0 && misses<6)
1077                 {
1078                         ++misses;
1079                         bits ^= 1<<(32-size);
1080                 }
1081                 else
1082                 {
1083                         sleep(2*Time::sec);
1084                         bits = 0;
1085                         size = 0;
1086                         misses = 0;
1087                 }
1088         }
1089         else if(type==MFX_READ_FEEDBACK && length>=3)
1090         {
1091                 if(reply[1])
1092                 {
1093                         misses = 0;
1094
1095                         for(unsigned i=2; i<length; ++i)
1096                                 read_data[read_offset+i-2] = reply[i];
1097                         read_offset += length-2;
1098                         read_length -= length-2;
1099
1100                         if(!read_length)
1101                         {
1102                                 if(read_array==0)
1103                                         block_size = static_cast<unsigned char>(read_data[4])*static_cast<unsigned char>(read_data[5]);
1104
1105                                 bool array_handled = false;
1106                                 if(read_data[0]==0x18)
1107                                 {
1108                                         for(unsigned i=1; i<read_offset; ++i)
1109                                                 if(!read_data[i])
1110                                                 {
1111                                                         pending_info->name = string(read_data+1, i-1);
1112                                                         array_handled = true;
1113                                                         break;
1114                                                 }
1115
1116                                         if(!array_handled)
1117                                                 read_length = 4;
1118                                 }
1119                                 else
1120                                         array_handled = true;
1121
1122                                 if(array_handled && control.debug>=1)
1123                                 {
1124                                         IO::print("MFX CA %03X:", read_array);
1125                                         for(unsigned i=0; i<read_offset; ++i)
1126                                                 IO::print(" %02X", static_cast<unsigned char>(read_data[i]));
1127                                         IO::print("\n");
1128                                 }
1129
1130                                 if(array_handled && read_array<block_size)
1131                                 {
1132                                         ++read_array;
1133                                         read_offset = 0;
1134                                         read_length = 1;
1135                                 }
1136                         }
1137                 }
1138                 else
1139                 {
1140                         ++misses;
1141                         if(misses>=10)
1142                         {
1143                                 if(control.debug>=1)
1144                                         IO::print("Failed to read MFX configuration from %d\n", pending_info->address);
1145                                 read_length = 0;
1146                         }
1147                 }
1148         }
1149 }
1150
1151 void ArduControl::MfxSearchTask::set_next_address(unsigned a)
1152 {
1153         next_address = a;
1154 }
1155
1156 bool ArduControl::MfxSearchTask::pop_info(MfxInfo &info)
1157 {
1158         return queue.pop(info);
1159 }
1160
1161
1162 ArduControl::MonitorTask::MonitorTask():
1163         Task("Monitor"),
1164         voltage(0),
1165         current(0),
1166         base_level(0),
1167         peak_level(0),
1168         next_type(0)
1169 { }
1170
1171 bool ArduControl::MonitorTask::get_work(PendingCommand &cmd)
1172 {
1173         if(next_type==0)
1174                 cmd.command[0] = READ_INPUT_VOLTAGE;
1175         else
1176                 cmd.command[0] = READ_TRACK_CURRENT;
1177         cmd.length = 1;
1178
1179         sleep(200*Time::msec);
1180         next_type = (next_type+1)%5;
1181
1182         return true;
1183 }
1184
1185 void ArduControl::MonitorTask::process_reply(const char *reply, unsigned length)
1186 {
1187         unsigned char type = reply[0];
1188         if(type==INPUT_VOLTAGE && length==3)
1189                 voltage = ((static_cast<unsigned char>(reply[1])<<8) | static_cast<unsigned char>(reply[2]))/1000.0f;
1190         else if(type==TRACK_CURRENT && length==5)
1191         {
1192                 current = ((static_cast<unsigned char>(reply[1])<<8) | static_cast<unsigned char>(reply[2]))/1000.0f;
1193                 float peak = ((static_cast<unsigned char>(reply[3])<<8) | static_cast<unsigned char>(reply[4]))/1000.0f;
1194                 peak_level = max(peak_level, peak);
1195                 base_level = min(base_level, current);
1196         }
1197 }
1198
1199 void ArduControl::MonitorTask::reset_peak()
1200 {
1201         base_level = current;
1202         peak_level = current;
1203 }
1204
1205
1206 ArduControl::ControlThread::ControlThread(ArduControl &c):
1207         control(c),
1208         done(false),
1209         cmd_rate(20),
1210         cmd_count(0)
1211 {
1212         tasks.push_back(&control.command_queue);
1213         tasks.push_back(&control.monitor);
1214         tasks.push_back(&control.mfx_announce);
1215         tasks.push_back(&control.mfx_search);
1216         tasks.push_back(&control.s88);
1217         tasks.push_back(&control.refresh);
1218
1219         launch();
1220 }
1221
1222 void ArduControl::ControlThread::exit()
1223 {
1224         done = true;
1225         join();
1226 }
1227
1228 void ArduControl::ControlThread::main()
1229 {
1230         init_baud_rate();
1231         cmd_rate_start = Time::now();
1232
1233         while(!done)
1234         {
1235                 PendingCommand cmd;
1236                 if(get_work(cmd))
1237                 {
1238                         bool success = true;
1239                         bool resync = false;
1240                         for(unsigned i=0; (success && i<cmd.repeat_count); ++i)
1241                         {
1242                                 unsigned result = do_command(cmd, control.command_timeout);
1243                                 success = (result==COMMAND_OK);
1244                                 resync = (result==0);
1245                         }
1246
1247                         if(success && cmd.tag)
1248                                 control.completed_commands.push(cmd.tag);
1249
1250                         if(resync)
1251                         {
1252                                 if(control.debug>=1)
1253                                         IO::print("Synchronization with ArduControl lost, attempting to recover\n");
1254                                 for(unsigned i=0; (resync && i<16); ++i)
1255                                 {
1256                                         control.serial.put('\xFF');
1257                                         while(IO::poll(control.serial, IO::P_INPUT, control.command_timeout))
1258                                                 resync = (control.serial.get()!=0xFF);
1259                                 }
1260                                 if(resync)
1261                                 {
1262                                         if(control.debug>=1)
1263                                                 IO::print("Resynchronization failed, giving up\n");
1264                                         done = true;
1265                                 }
1266                                 else
1267                                 {
1268                                         if(control.debug>=1)
1269                                                 IO::print("Resynchronization successful\n");
1270                                         if(cmd.tag)
1271                                                 control.command_queue.push(cmd);
1272                                 }
1273                         }
1274
1275                         if(cmd_count>=cmd_rate)
1276                         {
1277                                 Time::TimeStamp t = Time::now();
1278                                 cmd_rate = cmd_count/((t-cmd_rate_start)/Time::sec);
1279                                 cmd_rate_start = t;
1280                                 cmd_count = 0;
1281                         }
1282                 }
1283                 else
1284                         Time::sleep(10*Time::msec);
1285         }
1286 }
1287
1288 void ArduControl::ControlThread::init_baud_rate()
1289 {
1290         static unsigned rates[] = { 57600, 9600, 19200, 38400, 0 };
1291         unsigned rate = 0;
1292         control.serial.set_data_bits(8);
1293         control.serial.set_parity(IO::Serial::NONE);
1294         control.serial.set_stop_bits(1);
1295         for(unsigned i=0; rates[i]; ++i)
1296         {
1297                 control.serial.set_baud_rate(rates[i]);
1298                 control.serial.put('\xFF');
1299                 if(IO::poll(control.serial, IO::P_INPUT, 500*Time::msec))
1300                 {
1301                         int c = control.serial.get();
1302                         if(c==0xFF)
1303                         {
1304                                 rate = rates[i];
1305                                 break;
1306                         }
1307                 }
1308         }
1309
1310         if(!rate)
1311         {
1312                 if(control.debug>=1)
1313                         IO::print("ArduControl detection failed\n");
1314                 done = true;
1315                 return;
1316         }
1317
1318         if(control.debug>=1)
1319                 IO::print("ArduControl detected at %d bits/s\n", rate);
1320
1321         if(rate!=rates[0])
1322         {
1323                 PendingCommand cmd;
1324                 cmd.command[0] = SET_BAUD_RATE;
1325                 cmd.command[1] = rates[0]>>8;
1326                 cmd.command[2] = rates[0];
1327                 cmd.length = 3;
1328                 if(do_command(cmd, Time::sec)==COMMAND_OK)
1329                 {
1330                         control.serial.set_baud_rate(rates[0]);
1331                         Time::sleep(Time::sec);
1332                         if(do_command(cmd, Time::sec)==COMMAND_OK)
1333                         {
1334                                 if(control.debug>=1)
1335                                         IO::print("Rate changed to %d bits/s\n", rates[0]);
1336                         }
1337                 }
1338         }
1339 }
1340
1341 bool ArduControl::ControlThread::get_work(PendingCommand &cmd)
1342 {
1343         Time::TimeStamp t = Time::now();
1344
1345         unsigned count = 0;
1346         for(; (count<tasks.size() && tasks[count]->get_sleep_timeout()<=t); ++count) ;
1347
1348         for(; count>0; --count)
1349         {
1350                 unsigned i = 0;
1351                 for(unsigned j=1; j<count; ++j)
1352                         if(tasks[j]->get_priority()<tasks[i]->get_priority())
1353                                 i = j;
1354
1355                 Task *task = tasks[i];
1356                 bool result = task->get_work(cmd);
1357
1358                 Time::TimeStamp st = max(task->get_sleep_timeout(), t);
1359                 for(; (i+1<tasks.size() && tasks[i+1]->get_sleep_timeout()<=st); ++i)
1360                         tasks[i] = tasks[i+1];
1361                 tasks[i] = task;
1362
1363                 if(result)
1364                 {
1365                         if(control.debug>=2)
1366                                 IO::print("Scheduled task %s\n", task->get_name());
1367                         return true;
1368                 }
1369         }
1370
1371         // As fallback, send an idle packet for the MM protocol
1372         cmd.command[0] = MOTOROLA_SPEED;
1373         cmd.command[1] = 80;
1374         cmd.command[2] = 0;
1375         cmd.command[3] = 0;
1376         cmd.length = 4;
1377
1378         return true;
1379 }
1380
1381 unsigned ArduControl::ControlThread::do_command(const PendingCommand &cmd, const Time::TimeDelta &timeout)
1382 {
1383         if(control.debug>=2)
1384         {
1385                 string cmd_hex;
1386                 for(unsigned i=0; i<cmd.length; ++i)
1387                         cmd_hex += format(" %02X", static_cast<unsigned char>(cmd.command[i]));
1388                 IO::print("< %02X%s\n", cmd.length^0xFF, cmd_hex);
1389         }
1390
1391         control.serial.put(cmd.length^0xFF);
1392         control.serial.write(cmd.command, cmd.length);
1393
1394         unsigned result = 0;
1395         while(1)
1396         {
1397                 bool got_data;
1398                 if(result)
1399                         got_data = IO::poll(control.serial, IO::P_INPUT, Time::zero);
1400                 else
1401                         got_data = IO::poll(control.serial, IO::P_INPUT, timeout);
1402
1403                 if(!got_data)
1404                         break;
1405
1406                 unsigned rlength = control.serial.get()^0xFF;
1407                 if(rlength>15)
1408                 {
1409                         IO::print("Invalid length %02X\n", rlength);
1410                         continue;
1411                 }
1412
1413                 char reply[15];
1414                 unsigned pos = 0;
1415                 while(pos<rlength)
1416                 {
1417                         if(!IO::poll(control.serial, IO::P_INPUT, timeout))
1418                                 return 0;
1419                         pos += control.serial.read(reply+pos, rlength-pos);
1420                 }
1421
1422                 if(control.debug>=2)
1423                 {
1424                         string reply_hex;
1425                         for(unsigned i=0; i<rlength; ++i)
1426                                 reply_hex += format(" %02X", static_cast<unsigned char>(reply[i]));
1427                         IO::print("> %02X%s\n", rlength^0xFF, reply_hex);
1428                 }
1429
1430                 unsigned r = process_reply(reply, rlength);
1431                 if(r && !result)
1432                         result = r;
1433         }
1434
1435         ++cmd_count;
1436
1437         return result;
1438 }
1439
1440 unsigned ArduControl::ControlThread::process_reply(const char *reply, unsigned rlength)
1441 {
1442         unsigned char type = reply[0];
1443         if((type&0xE0)==0x80)
1444         {
1445                 if(type!=COMMAND_OK)
1446                         IO::print("Error %02X\n", type);
1447                 return type;
1448         }
1449         else if(type==POWER_STATE && rlength==2)
1450                 set_power(reply[1]);
1451         else if(type==OVERCURRENT)
1452         {
1453                 set_power(false);
1454                 IO::print("Overcurrent detected!\n");
1455         }
1456         else
1457         {
1458                 for(vector<Task *>::iterator i=tasks.begin(); i!=tasks.end(); ++i)
1459                         (*i)->process_reply(reply, rlength);
1460         }
1461
1462         return 0;
1463 }
1464
1465 void ArduControl::ControlThread::set_power(bool p)
1466 {
1467         control.power.set(p);
1468
1469         Tag tag;
1470         tag.type = Tag::GENERAL;
1471         tag.command = POWER;
1472         tag.serial = control.power.serial;
1473         control.completed_commands.push(tag);
1474 }
1475
1476
1477 ArduControl::Loader::Loader(ArduControl &c):
1478         DataFile::ObjectLoader<ArduControl>(c)
1479 {
1480         add("mfx_announce_serial", &Loader::mfx_announce_serial);
1481         add("mfx_locomotive", &Loader::mfx_locomotive);
1482 }
1483
1484 void ArduControl::Loader::mfx_announce_serial(unsigned s)
1485 {
1486         obj.mfx_announce.set_serial(s);
1487 }
1488
1489 void ArduControl::Loader::mfx_locomotive(unsigned id)
1490 {
1491         MfxInfo info;
1492         info.id = id;
1493         info.protocol = "MFX";
1494         load_sub(info);
1495         obj.add_mfx_info(info);
1496 }
1497
1498
1499 ArduControl::MfxInfo::Loader::Loader(MfxInfo &i):
1500         DataFile::ObjectLoader<MfxInfo>(i)
1501 {
1502         add("address", static_cast<unsigned MfxInfo::*>(&MfxInfo::address));
1503         add("name", static_cast<string MfxInfo::*>(&MfxInfo::name));
1504 }
1505
1506 } // namespace R2C2