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