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