]> git.tdb.fi Git - r2c2.git/blob - source/libr2c2/arducontrol.cpp
Implement halt mode to 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(!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 }
460
461 void ArduControl::save_state() const
462 {
463         FS::RedirectedPath tmp_file(state_file);
464         IO::BufferedFile out(tmp_file.str(), IO::M_WRITE);
465         DataFile::Writer writer(out);
466
467         writer.write((DataFile::Statement("mfx_announce_serial"), mfx_announce.get_serial()));
468         for(MfxInfoArray::const_iterator i=mfx_info.begin(); i!=mfx_info.end(); ++i)
469         {
470                 DataFile::Statement st("mfx_locomotive");
471                 st.append(i->id);
472                 st.sub.push_back((DataFile::Statement("address"), i->address));
473                 st.sub.push_back((DataFile::Statement("name"), i->name));
474                 writer.write(st);
475         }
476 }
477
478
479 ArduControl::Tag::Tag():
480         type(NONE),
481         command(0),
482         serial(0),
483         id(0)
484 { }
485
486
487 ArduControl::Locomotive::Locomotive(Protocol p, unsigned a):
488         id((p<<16)|a),
489         proto(p),
490         address(a),
491         speed(0),
492         reverse(false),
493         funcs(0),
494         last_change_age(0)
495 { }
496
497 unsigned ArduControl::Locomotive::create_speed_dir_command(char *buffer) const
498 {
499         if(proto==MM)
500         {
501                 buffer[0] = MOTOROLA_SPEED_DIRECTION;
502                 buffer[1] = address;
503                 buffer[2] = funcs.pending&1;
504                 buffer[3] = speed.pending+reverse.pending*0x80;
505                 return 4;
506         }
507         else if(proto==MFX)
508         {
509                 buffer[0] = MFX_SPEED;
510                 buffer[1] = address>>8;
511                 buffer[2] = address;
512                 buffer[3] = speed.pending+reverse.pending*0x80;
513                 return 4;
514         }
515         else
516                 return 0;
517 }
518
519 unsigned ArduControl::Locomotive::create_speed_func_command(unsigned f, char *buffer) const
520 {
521         if(proto==MM)
522         {
523                 if(f<1 || f>4)
524                         throw invalid_argument("Locomotive::create_speed_func_command");
525
526                 buffer[0] = MOTOROLA_SPEED_FUNCTION;
527                 buffer[1] = address;
528                 buffer[2] = (f<<4)|(((funcs.pending>>f)&1)<<1)|(funcs.pending&1);
529                 buffer[3] = speed.pending;
530                 return 4;
531         }
532         else if(proto==MFX)
533         {
534                 bool f16 = (funcs.pending>0xFF);
535                 buffer[0] = (f16 ? MFX_SPEED_FUNCS16 : MFX_SPEED_FUNCS8);
536                 buffer[1] = address>>8;
537                 buffer[2] = address;
538                 buffer[3] = speed.pending+reverse.pending*0x80;
539                 if(f16)
540                 {
541                         buffer[4] = funcs.pending>>8;
542                         buffer[5] = funcs.pending;
543                         return 6;
544                 }
545                 else
546                 {
547                         buffer[4] = funcs.pending;
548                         return 5;
549                 }
550         }
551         else
552                 return 0;
553 }
554
555
556 ArduControl::Accessory::Accessory(Kind k, unsigned a, unsigned b):
557         kind(k),
558         address(a),
559         bits(b),
560         state(0),
561         uncertain((1<<bits)-1),
562         target(0),
563         active_time(500*Time::msec)
564 { }
565
566 unsigned ArduControl::Accessory::create_state_command(unsigned b, bool c, char *buffer) const
567 {
568         if(b>=bits)
569                 throw invalid_argument("Accessory::create_state_command");
570
571         unsigned a = (address+b+3)*2;
572         if(!((state.pending>>b)&1))
573                 ++a;
574         buffer[0] = MOTOROLA_SOLENOID;
575         buffer[1] = a>>3;
576         buffer[2] = ((a&7)<<4)|c;
577         return 3;
578 }
579
580
581 ArduControl::Sensor::Sensor(unsigned a):
582         address(a),
583         state(false)
584 { }
585
586
587 ArduControl::PendingCommand::PendingCommand():
588         length(0),
589         repeat_count(1)
590 { }
591
592 ArduControl::PendingCommand::PendingCommand(GeneralCommand cmd):
593         length(0),
594         repeat_count(1)
595 {
596         tag.type = Tag::GENERAL;
597         tag.command = cmd;
598 }
599
600 ArduControl::PendingCommand::PendingCommand(Locomotive &loco, Locomotive::Command cmd, unsigned index):
601         repeat_count(8)
602 {
603         tag.type = Tag::LOCOMOTIVE;
604         tag.command = cmd;
605         tag.id = loco.id;
606         if(cmd==Locomotive::SPEED)
607         {
608                 tag.serial = loco.speed.serial;
609                 length = loco.create_speed_dir_command(command);
610         }
611         else if(cmd==Locomotive::REVERSE)
612         {
613                 tag.serial = loco.reverse.serial;
614                 length = loco.create_speed_dir_command(command);
615         }
616         else if(cmd==Locomotive::FUNCTIONS)
617         {
618                 tag.serial = loco.funcs.serial;
619                 length = loco.create_speed_func_command(index, command);
620         }
621         else
622                 throw invalid_argument("PendingCommand");
623 }
624
625 ArduControl::PendingCommand::PendingCommand(Accessory &acc, Accessory::Command cmd, unsigned index):
626         repeat_count(1)
627 {
628         tag.type = Tag::ACCESSORY;
629         tag.command = cmd;
630         tag.id = acc.address;
631         if(cmd==Accessory::ACTIVATE || cmd==Accessory::DEACTIVATE)
632         {
633                 tag.serial = acc.state.serial;
634                 length = acc.create_state_command(index, (cmd==Accessory::ACTIVATE), command);
635         }
636         else
637                 throw invalid_argument("PendingCommand");
638 }
639
640
641 template<typename T>
642 void ArduControl::Queue<T>::push(const T &item)
643 {
644         MutexLock lock(mutex);
645         items.push_back(item);
646 }
647
648 template<typename T>
649 bool ArduControl::Queue<T>::pop(T &item)
650 {
651         MutexLock lock(mutex);
652         if(items.empty())
653                 return false;
654
655         item = items.front();
656         items.pop_front();
657         return true;
658 }
659
660
661 ArduControl::RefreshTask::RefreshTask():
662         next(cycle.end()),
663         round(0),
664         loco(0),
665         phase(0)
666 { }
667
668 bool ArduControl::RefreshTask::get_work(PendingCommand &cmd)
669 {
670         if(loco && loco->proto==MM && phase==0)
671         {
672                 cmd.length = loco->create_speed_func_command(round%4+1, cmd.command);
673                 cmd.repeat_count = 2;
674                 ++phase;
675                 return true;
676         }
677
678         loco = get_next_loco();
679         if(!loco)
680                 return false;
681
682         phase = 0;
683         if(loco->proto==MM)
684         {
685                 cmd.length = loco->create_speed_dir_command(cmd.command);
686                 cmd.repeat_count = 2;
687         }
688         else if(loco->proto==MFX)
689                 cmd.length = loco->create_speed_func_command(0, cmd.command);
690         else
691                 return false;
692
693         return true;
694 }
695
696 void ArduControl::RefreshTask::add_loco(Locomotive &l)
697 {
698         MutexLock lock(mutex);
699         cycle.push_back(&l);
700         if(cycle.size()>15)
701         {
702                 LocomotivePtrList::iterator oldest = cycle.begin();
703                 for(LocomotivePtrList::iterator i=cycle.begin(); ++i!=cycle.end(); )
704                         if((*i)->last_change_age>(*oldest)->last_change_age)
705                                 oldest = i;
706                 if(oldest==next)
707                         advance();
708                 cycle.erase(oldest);
709         }
710         if(next==cycle.end())
711                 next = cycle.begin();
712 }
713
714 void ArduControl::RefreshTask::remove_loco(Locomotive &l)
715 {
716         MutexLock lock(mutex);
717         for(LocomotivePtrList::iterator i=cycle.begin(); i!=cycle.end(); ++i)
718                 if(*i==&l)
719                 {
720                         if(i==next)
721                         {
722                                 if(cycle.size()>1)
723                                         advance();
724                                 else
725                                         next = cycle.end();
726                         }
727                         cycle.erase(i);
728                         return;
729                 }
730 }
731
732 ArduControl::Locomotive *ArduControl::RefreshTask::get_next_loco()
733 {
734         MutexLock lock(mutex);
735         if(cycle.empty())
736                 return 0;
737
738         Locomotive *l = *next;
739         advance();
740         return l;
741 }
742
743 void ArduControl::RefreshTask::advance()
744 {
745         ++next;
746         if(next==cycle.end())
747         {
748                 next= cycle.begin();
749                 ++round;
750         }
751 }
752
753
754 ArduControl::S88Task::S88Task(ArduControl &c):
755         control(c),
756         n_octets(0),
757         octets_remaining(0),
758         delay(0)
759 { }
760
761 bool ArduControl::S88Task::get_work(PendingCommand &cmd)
762 {
763         if(delay)
764         {
765                 --delay;
766                 return false;
767         }
768         if(octets_remaining || !n_octets)
769                 return false;
770
771         octets_remaining = n_octets;
772         cmd.command[0] = S88_READ;
773         cmd.command[1] = octets_remaining;
774         cmd.length = 2;
775
776         delay = 4;
777
778         return true;
779 }
780
781 void ArduControl::S88Task::process_reply(const char *reply, unsigned length)
782 {
783         unsigned char type = reply[0];
784         if(type==S88_DATA && length>2)
785         {
786                 unsigned offset = static_cast<unsigned char>(reply[1]);
787                 unsigned count = length-2;
788
789                 SensorMap::iterator begin = control.sensors.lower_bound(offset*8+1);
790                 SensorMap::iterator end = control.sensors.upper_bound((offset+count)*8);
791                 for(SensorMap::iterator i=begin; i!=end; ++i)
792                 {
793                         unsigned bit_index = i->first-1-offset*8;
794                         bool state = (reply[2+bit_index/8]>>(7-bit_index%8))&1;
795                         i->second.state.set(state);
796
797                         Tag tag;
798                         tag.type = Tag::SENSOR;
799                         tag.command = Sensor::STATE;
800                         tag.serial = i->second.state.serial;
801                         tag.id = i->first;
802                         control.completed_commands.push(tag);
803                 }
804
805                 if(count>octets_remaining)
806                         octets_remaining = 0;
807                 else
808                         octets_remaining -= count;
809         }
810 }
811
812 void ArduControl::S88Task::set_n_octets(unsigned n)
813 {
814         n_octets = n;
815 }
816
817 void ArduControl::S88Task::grow_n_octets(unsigned n)
818 {
819         if(n>n_octets)
820                 n_octets = n;
821 }
822
823
824 ArduControl::MfxAnnounceTask::MfxAnnounceTask():
825         serial(0)
826 { }
827
828 bool ArduControl::MfxAnnounceTask::get_work(PendingCommand &cmd)
829 {
830         Time::TimeStamp t = Time::now();
831         if(t<next)
832                 return false;
833
834         cmd.command[0] = MFX_ANNOUNCE;
835         cmd.command[1] = serial>>8;
836         cmd.command[2] = serial;
837         cmd.length = 3;
838         next = t+400*Time::msec;
839
840         return true;
841 }
842
843 void ArduControl::MfxAnnounceTask::set_serial(unsigned s)
844 {
845         serial = s;
846 }
847
848
849 ArduControl::MfxSearchTask::MfxSearchTask(ArduControl &c):
850         control(c),
851         next_address(1),
852         size(0),
853         bits(0),
854         misses(0)
855 { }
856
857 bool ArduControl::MfxSearchTask::get_work(PendingCommand &cmd)
858 {
859         if(size>32)
860         {
861                 if(control.debug>=1)
862                         IO::print("Assigning MFX address %d to decoder %08X\n", next_address, bits);
863
864                 MfxInfo info;
865                 info.protocol = "MFX";
866                 info.address = next_address;
867                 info.name = format("%08X", bits);
868                 info.id = bits;
869                 queue.push(info);
870
871                 cmd.command[0] = MFX_ASSIGN_ADDRESS;
872                 cmd.command[1] = next_address>>8;
873                 cmd.command[2] = next_address;
874                 for(unsigned i=0; i<4; ++i)
875                         cmd.command[3+i] = bits>>(24-i*8);
876                 cmd.length = 7;
877
878                 cmd.tag.type = Tag::GENERAL;
879                 cmd.tag.command = NEW_LOCO;
880                 cmd.tag.id = bits;
881
882                 size = 0;
883                 bits = 0;
884                 ++next_address;
885
886                 return true;
887         }
888
889         Time::TimeStamp t = Time::now();
890         if(t<next)
891                 return false;
892
893         cmd.command[0] = MFX_SEARCH;
894         for(unsigned i=0; i<4; ++i)
895                 cmd.command[1+i] = bits>>(24-i*8);
896         cmd.command[5] = size;
897         cmd.length = 6;
898
899         next = t+200*Time::msec;
900
901         if(control.debug>=1)
902                 IO::print("Search %08X/%d\n", bits, size);
903
904         return true;
905 }
906
907 void ArduControl::MfxSearchTask::process_reply(const char *reply, unsigned length)
908 {
909         unsigned char type = reply[0];
910         if(type==MFX_SEARCH_FEEDBACK && length==2)
911         {
912                 if(reply[1])
913                 {
914                         misses = 0;
915                         ++size;
916                 }
917                 else if(size>0 && misses<6)
918                 {
919                         ++misses;
920                         bits ^= 1<<(32-size);
921                 }
922                 else
923                 {
924                         next = Time::now()+2*Time::sec;
925                         bits = 0;
926                         size = 0;
927                         misses = 0;
928                 }
929         }
930 }
931
932 void ArduControl::MfxSearchTask::set_next_address(unsigned a)
933 {
934         next_address = a;
935 }
936
937 bool ArduControl::MfxSearchTask::pop_info(MfxInfo &info)
938 {
939         return queue.pop(info);
940 }
941
942
943 ArduControl::MonitorTask::MonitorTask():
944         voltage(0),
945         current(0),
946         base_level(0),
947         peak_level(0),
948         next_type(0)
949 { }
950
951 bool ArduControl::MonitorTask::get_work(PendingCommand &cmd)
952 {
953         Time::TimeStamp t = Time::now();
954         if(t<next_poll)
955                 return false;
956
957         if(next_type==0)
958                 cmd.command[0] = READ_INPUT_VOLTAGE;
959         else
960                 cmd.command[0] = READ_TRACK_CURRENT;
961         cmd.length = 1;
962
963         next_poll = t+200*Time::msec;
964         next_type = (next_type+1)%5;
965
966         return true;
967 }
968
969 void ArduControl::MonitorTask::process_reply(const char *reply, unsigned length)
970 {
971         unsigned char type = reply[0];
972         if(type==INPUT_VOLTAGE && length==3)
973                 voltage = ((static_cast<unsigned char>(reply[1])<<8) | static_cast<unsigned char>(reply[2]))/1000.0f;
974         else if(type==TRACK_CURRENT && length==5)
975         {
976                 current = ((static_cast<unsigned char>(reply[1])<<8) | static_cast<unsigned char>(reply[2]))/1000.0f;
977                 float peak = ((static_cast<unsigned char>(reply[3])<<8) | static_cast<unsigned char>(reply[4]))/1000.0f;
978                 peak_level = max(peak_level, peak);
979                 base_level = min(base_level, current);
980         }
981 }
982
983 void ArduControl::MonitorTask::reset_peak()
984 {
985         base_level = current;
986         peak_level = current;
987 }
988
989
990 ArduControl::ControlThread::ControlThread(ArduControl &c):
991         control(c),
992         done(false)
993 {
994         tasks.push_back(&control.monitor);
995         tasks.push_back(&control.mfx_announce);
996         tasks.push_back(&control.mfx_search);
997         tasks.push_back(&control.s88);
998         tasks.push_back(&control.refresh);
999
1000         launch();
1001 }
1002
1003 void ArduControl::ControlThread::exit()
1004 {
1005         done = true;
1006         join();
1007 }
1008
1009 void ArduControl::ControlThread::main()
1010 {
1011         init_baud_rate();
1012
1013         while(!done)
1014         {
1015                 PendingCommand cmd;
1016                 if(get_work(cmd))
1017                 {
1018                         bool success = true;
1019                         for(unsigned i=0; (success && i<cmd.repeat_count); ++i)
1020                                 success = (do_command(cmd)==COMMAND_OK);
1021                         if(success && cmd.tag)
1022                                 control.completed_commands.push(cmd.tag);
1023                 }
1024                 else
1025                         Time::sleep(10*Time::msec);
1026         }
1027 }
1028
1029 void ArduControl::ControlThread::init_baud_rate()
1030 {
1031         static unsigned rates[] = { 57600, 9600, 19200, 38400, 0 };
1032         unsigned rate = 0;
1033         control.serial.set_data_bits(8);
1034         control.serial.set_parity(IO::Serial::NONE);
1035         control.serial.set_stop_bits(1);
1036         for(unsigned i=0; rates[i]; ++i)
1037         {
1038                 control.serial.set_baud_rate(rates[i]);
1039                 control.serial.put('\xFF');
1040                 if(IO::poll(control.serial, IO::P_INPUT, 500*Time::msec))
1041                 {
1042                         int c = control.serial.get();
1043                         if(c==0xFF)
1044                         {
1045                                 rate = rates[i];
1046                                 break;
1047                         }
1048                 }
1049         }
1050
1051         if(!rate)
1052         {
1053                 if(control.debug>=1)
1054                         IO::print("ArduControl detection failed\n");
1055                 done = true;
1056                 return;
1057         }
1058
1059         if(control.debug>=1)
1060                 IO::print("ArduControl detected at %d bits/s\n", rate);
1061
1062         if(rate!=rates[0])
1063         {
1064                 PendingCommand cmd;
1065                 cmd.command[0] = SET_BAUD_RATE;
1066                 cmd.command[1] = rates[0]>>8;
1067                 cmd.command[2] = rates[0];
1068                 cmd.length = 3;
1069                 if(do_command(cmd)==COMMAND_OK)
1070                 {
1071                         control.serial.set_baud_rate(rates[0]);
1072                         Time::sleep(Time::sec);
1073                         if(do_command(cmd)==COMMAND_OK)
1074                         {
1075                                 if(control.debug>=1)
1076                                         IO::print("Rate changed to %d bits/s\n", rates[0]);
1077                         }
1078                 }
1079         }
1080 }
1081
1082 bool ArduControl::ControlThread::get_work(PendingCommand &cmd)
1083 {
1084         if(control.command_queue.pop(cmd))
1085                 return true;
1086
1087         for(vector<Task *>::iterator i=tasks.begin(); i!=tasks.end(); ++i)
1088                 if((*i)->get_work(cmd))
1089                         return true;
1090
1091         // As fallback, send an idle packet for the MM protocol
1092         cmd.command[0] = MOTOROLA_SPEED;
1093         cmd.command[1] = 80;
1094         cmd.command[2] = 0;
1095         cmd.command[3] = 0;
1096         cmd.length = 4;
1097
1098         return true;
1099 }
1100
1101 unsigned ArduControl::ControlThread::do_command(const PendingCommand &cmd)
1102 {
1103         if(control.debug>=2)
1104         {
1105                 string cmd_hex;
1106                 for(unsigned i=0; i<cmd.length; ++i)
1107                         cmd_hex += format(" %02X", static_cast<unsigned char>(cmd.command[i]));
1108                 IO::print("< %02X%s\n", cmd.length^0xFF, cmd_hex);
1109         }
1110
1111         control.serial.put(cmd.length^0xFF);
1112         control.serial.write(cmd.command, cmd.length);
1113
1114         unsigned result = 0;
1115         while(1)
1116         {
1117                 bool got_data;
1118                 if(result)
1119                         got_data = IO::poll(control.serial, IO::P_INPUT, Time::zero);
1120                 else
1121                         got_data = IO::poll(control.serial, IO::P_INPUT);
1122
1123                 if(!got_data)
1124                         break;
1125
1126                 unsigned rlength = control.serial.get()^0xFF;
1127                 if(rlength>15)
1128                 {
1129                         IO::print("Invalid length %02X\n", rlength);
1130                         continue;
1131                 }
1132
1133                 char reply[15];
1134                 unsigned pos = 0;
1135                 while(pos<rlength)
1136                         pos += control.serial.read(reply+pos, rlength-pos);
1137
1138                 if(control.debug>=2)
1139                 {
1140                         string reply_hex;
1141                         for(unsigned i=0; i<rlength; ++i)
1142                                 reply_hex += format(" %02X", static_cast<unsigned char>(reply[i]));
1143                         IO::print("> %02X%s\n", rlength^0xFF, reply_hex);
1144                 }
1145
1146                 unsigned r = process_reply(reply, rlength);
1147                 if(r && !result)
1148                         result = r;
1149         }
1150
1151         return result;
1152 }
1153
1154 unsigned ArduControl::ControlThread::process_reply(const char *reply, unsigned rlength)
1155 {
1156         unsigned char type = reply[0];
1157         if((type&0xE0)==0x80)
1158         {
1159                 if(type!=COMMAND_OK)
1160                         IO::print("Error %02X\n", type);
1161                 return type;
1162         }
1163         else if(type==POWER_STATE && rlength==2)
1164         {
1165                 control.power.set(reply[1]);
1166
1167                 Tag tag;
1168                 tag.type = Tag::GENERAL;
1169                 tag.command = POWER;
1170                 tag.serial = control.power.serial;
1171                 control.completed_commands.push(tag);
1172         }
1173         else
1174         {
1175                 for(vector<Task *>::iterator i=tasks.begin(); i!=tasks.end(); ++i)
1176                         (*i)->process_reply(reply, rlength);
1177         }
1178
1179         return 0;
1180 }
1181
1182
1183 ArduControl::Loader::Loader(ArduControl &c):
1184         DataFile::ObjectLoader<ArduControl>(c)
1185 {
1186         add("mfx_announce_serial", &Loader::mfx_announce_serial);
1187         add("mfx_locomotive", &Loader::mfx_locomotive);
1188 }
1189
1190 void ArduControl::Loader::mfx_announce_serial(unsigned s)
1191 {
1192         obj.mfx_announce.set_serial(s);
1193 }
1194
1195 void ArduControl::Loader::mfx_locomotive(unsigned id)
1196 {
1197         MfxInfo info;
1198         info.id = id;
1199         info.protocol = "MFX";
1200         load_sub(info);
1201         obj.add_mfx_info(info);
1202 }
1203
1204
1205 ArduControl::MfxInfo::Loader::Loader(MfxInfo &i):
1206         DataFile::ObjectLoader<MfxInfo>(i)
1207 {
1208         add("address", static_cast<unsigned MfxInfo::*>(&MfxInfo::address));
1209         add("name", static_cast<string MfxInfo::*>(&MfxInfo::name));
1210 }
1211
1212 } // namespace R2C2