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