]> git.tdb.fi Git - r2c2.git/blob - source/libr2c2/intellibox.cpp
338aed2544127844dff4de200cf7bbffc90f2a2a
[r2c2.git] / source / libr2c2 / intellibox.cpp
1 #include <fcntl.h>
2 #include <termios.h>
3 #include <sys/poll.h>
4 #include <msp/io/print.h>
5 #include <msp/time/units.h>
6 #include <msp/time/utils.h>
7 #include "intellibox.h"
8 #include "tracktype.h"
9 #include "vehicletype.h"
10
11 using namespace std;
12 using namespace Msp;
13
14 namespace R2C2 {
15
16 Intellibox::Intellibox(const string &dev):
17         serial(dev),
18         power(false),
19         halted(false),
20         update_sensors(false),
21         command_sent(false)
22 {
23         static unsigned baud[]= { 2400, 4800, 9600, 19200, 0 };
24
25         serial.set_stop_bits(2);
26
27         bool ok = false;
28         bool p50 = false;
29         for(unsigned i=0; baud[i]; ++i)
30         {
31                 serial.set_baud_rate(baud[i]);
32                 serial.put('\xC4');
33
34                 if(IO::poll(serial, IO::P_INPUT, 500*Time::msec))
35                 {
36                         IO::print("IB detected at %d bits/s\n", baud[i]);
37                         char buf[2];
38                         p50 = (serial.read(buf, 2)==2);
39                         ok = true;
40                         break;
41                 }
42         }
43
44         if(!ok)
45                 throw runtime_error("IB not detected");
46
47         if(p50)
48                 serial.write("xZzA1\r", 6);
49
50         command(CMD_STATUS);
51 }
52
53 void Intellibox::set_power(bool p)
54 {
55         power = p;
56         if(power)
57                 command(CMD_POWER_ON);
58         else
59                 command(CMD_POWER_OFF);
60         signal_power.emit(power);
61 }
62
63 void Intellibox::halt(bool h)
64 {
65         halted = h;
66         if(halted)
67         {
68                 for(map<unsigned, Locomotive>::iterator i=locos.begin(); i!=locos.end(); ++i)
69                         if(i->second.speed)
70                                 set_loco_speed(i->first, 0);
71         }
72
73         signal_halt.emit(halted);
74 }
75
76 const char *Intellibox::enumerate_protocols(unsigned i) const
77 {
78         ++i;
79         if(i==MM)
80                 return "MM";
81         else if(i==MM_27)
82                 return "MM-27";
83         return 0;
84 }
85
86 unsigned Intellibox::get_protocol_speed_steps(const string &proto_name) const
87 {
88         Protocol proto = map_protocol(proto_name);
89         if(proto==MM)
90                 return 14;
91         else if(proto==MM_27)
92                 return 27;
93         return 0;
94 }
95
96 void Intellibox::add_loco(unsigned addr, const string &proto_name, const VehicleType &type)
97 {
98         Protocol proto = map_protocol(proto_name);
99
100         if(!locos.count(addr))
101         {
102                 Locomotive &loco = locos[addr];
103                 loco.protocol = proto;
104                 if(type.get_max_function()>4)
105                 {
106                         loco.ext_func = true;
107                         locos[addr+1].protocol = NONE;
108                 }
109
110                 unsigned char data[2];
111                 data[0] = addr&0xFF;
112                 data[1] = (addr>>8)&0xFF;
113                 command(CMD_LOK_STATUS, addr, data, 2);
114         }
115 }
116
117 void Intellibox::remove_loco(unsigned addr)
118 {
119         locos.erase(addr);
120 }
121
122 void Intellibox::set_loco_speed(unsigned addr, unsigned speed)
123 {
124         Locomotive &loco = locos[addr];
125         if(loco.protocol==NONE)
126                 return;
127
128         if(speed==loco.speed)
129         {
130                 if(loco.pending_half_step)
131                 {
132                         loco.pending_half_step = 0;
133                         loco.half_step_delay = Time::TimeStamp();
134                         signal_loco_speed.emit(addr, speed, loco.reverse);
135                 }
136                 return;
137         }
138         if(speed && halted)
139                 return;
140
141         if(loco.protocol==MM_27)
142         {
143                 if(speed>27)
144                         speed = 27;
145
146                 if(speed>loco.speed && !(speed&1))
147                 {
148                         loco.pending_half_step = -1;
149                         speed |= 1;
150                 }
151                 else if(speed<loco.speed && (speed&1))
152                 {
153                         loco.pending_half_step = 1;
154                         speed &= ~1;
155                 }
156                 else
157                         loco.pending_half_step = 0;
158                 loco.half_step_delay = Time::TimeStamp();
159
160                 loco_command(addr, (speed+1)/2, loco.reverse, loco.funcs, false);
161         }
162         else if(loco.protocol==MM)
163         {
164                 if(speed>14)
165                         speed = 14;
166
167                 loco_command(addr, speed, loco.reverse, loco.funcs, false);
168         }
169         loco.speed = speed;
170 }
171
172 void Intellibox::set_loco_reverse(unsigned addr, bool rev)
173 {
174         Locomotive &loco = locos[addr];
175         if(loco.protocol==NONE || rev==loco.reverse)
176                 return;
177
178         loco.speed = 0;
179         loco.reverse = rev;
180         loco_command(addr, 0, rev, loco.funcs, false);
181 }
182
183 void Intellibox::set_loco_function(unsigned addr, unsigned func, bool state)
184 {
185         Locomotive &loco = locos[addr];
186         if(loco.protocol==NONE)
187                 return;
188
189         if(state)
190                 loco.funcs |= 1<<func;
191         else
192                 loco.funcs &= ~(1<<func);
193         if(func<=4)
194                 loco_command(addr, loco.speed, loco.reverse, loco.funcs, true);
195         else if(loco.ext_func && func<=8)
196                 loco_command(addr+1, 0, false, (loco.funcs>>4)&0x1E, true);
197         signal_loco_function.emit(addr, func, state);
198 }
199
200 void Intellibox::add_turnout(unsigned addr, const TrackType &type)
201 {
202         add_turnout(addr, type.get_state_bits(), false);
203 }
204
205 void Intellibox::remove_turnout(unsigned addr)
206 {
207         turnouts.erase(addr);
208 }
209
210 void Intellibox::add_turnout(unsigned addr, unsigned bits, bool signal)
211 {
212         if(!turnouts.count(addr))
213         {
214                 Turnout &turnout = turnouts[addr];
215                 turnout.bits = bits;
216                 turnout.signal = signal;
217
218                 unsigned char data[2];
219                 data[0] = addr&0xFF;
220                 data[1] = (addr>>8)&0xFF;
221                 command(CMD_TURNOUT_STATUS, addr, data, 2);
222                 for(unsigned i=1; i<turnout.bits; ++i)
223                 {
224                         turnouts[addr+i].bits = 0;
225
226                         ++data[0];
227                         if(!data[0])
228                                 ++data[1];
229                         command(CMD_TURNOUT_STATUS, addr+i, data, 2);
230                 }
231         }
232 }
233
234 void Intellibox::turnout_state_changed(unsigned addr, const Turnout &turnout) const
235 {
236         if(turnout.signal)
237                 signal_signal.emit(addr, turnout.state);
238         else
239                 signal_turnout.emit(addr, turnout.state);
240 }
241
242 void Intellibox::set_turnout(unsigned addr, unsigned state)
243 {
244         Turnout &turnout = turnouts[addr];
245         unsigned mask = (1<<turnout.bits)-1;
246         if(((state^turnout.state)&mask)==0 || ((state^turnout.pending)&mask)==0 || !turnout.synced)
247         {
248                 turnout.state = state;
249                 turnout.pending = state;
250                 turnout_state_changed(addr, turnout);
251                 return;
252         }
253
254         turnout.state = (turnout.state&mask) | (state&~mask);
255         turnout.pending = state;
256         turnout.active = true;
257         turnout.off_timeout = Time::TimeStamp();
258
259         for(unsigned i=0; i<turnout.bits; ++i)
260                 if((state^turnout.state)&(1<<i))
261                         turnout_command(addr+i, !(state&(1<<i)), true);
262 }
263
264 unsigned Intellibox::get_turnout(unsigned addr) const
265 {
266         map<unsigned, Turnout>::const_iterator i = turnouts.find(addr);
267         if(i!=turnouts.end())
268                 return i->second.state;
269         return 0;
270 }
271
272 void Intellibox::add_signal(unsigned addr, const SignalType &)
273 {
274         add_turnout(addr, 1, true);
275 }
276
277 void Intellibox::remove_signal(unsigned addr)
278 {
279         remove_turnout(addr);
280 }
281
282 void Intellibox::set_signal(unsigned addr, unsigned state)
283 {
284         set_turnout(addr, state);
285 }
286
287 unsigned Intellibox::get_signal(unsigned addr) const
288 {
289         return get_turnout(addr);
290 }
291
292 void Intellibox::add_sensor(unsigned addr)
293 {
294         if(!sensors.count(addr))
295         {
296                 sensors[addr];
297                 update_sensors = true;
298         }
299 }
300
301 void Intellibox::remove_sensor(unsigned addr)
302 {
303         sensors.erase(addr);
304         update_sensors = true;
305 }
306
307 bool Intellibox::get_sensor(unsigned addr) const
308 {
309         map<unsigned, Sensor>::const_iterator i = sensors.find(addr);
310         if(i!=sensors.end())
311                 return i->second.state;
312         return false;
313 }
314
315 void Intellibox::tick()
316 {
317         const Time::TimeStamp t = Time::now();
318
319         if(t>next_event_query)
320         {
321                 next_event_query = t+200*Time::msec;
322                 command(CMD_EVENT);
323         }
324
325         for(map<unsigned, Locomotive>::iterator i=locos.begin(); i!=locos.end(); ++i)
326                 if(i->second.protocol==MM_27 && i->second.pending_half_step && i->second.half_step_delay && t>i->second.half_step_delay)
327                 {
328                         i->second.speed += i->second.pending_half_step;
329                         i->second.pending_half_step = 0;
330                         i->second.half_step_delay = Time::TimeStamp();
331                         loco_command(i->first, (i->second.speed+1)/2, i->second.reverse, i->second.funcs, false);
332                 }
333
334         for(map<unsigned, Turnout>::iterator i=turnouts.begin(); i!=turnouts.end(); ++i)
335                 if(i->second.active && i->second.off_timeout && t>i->second.off_timeout)
336                 {
337                         i->second.active = false;
338                         i->second.off_timeout = Time::TimeStamp();
339                         for(unsigned j=0; j<i->second.bits; ++j)
340                                 turnout_command(i->first+j, !(i->second.state&(1<<j)), false);
341                 }
342
343         for(map<unsigned, Sensor>::iterator i=sensors.begin(); i!=sensors.end(); ++i)
344                 if(i->second.off_timeout && t>i->second.off_timeout)
345                 {
346                         i->second.state = false;
347                         i->second.off_timeout = Time::TimeStamp();
348                         signal_sensor.emit(i->first, false);
349                 }
350
351         if(update_sensors)
352         {
353                 unsigned max_addr = (--sensors.end())->first;
354                 unsigned char data[2];
355                 data[0] = 0;
356                 data[1] = (max_addr+7)/8;
357                 command(CMD_SENSOR_PARAM_SET, data, 2);
358                 command(CMD_SENSOR_REPORT);
359                 update_sensors = false;
360         }
361
362         if(!queue.empty() && command_sent)
363         {
364                 if(IO::poll(serial, IO::P_INPUT, Time::zero))
365                 {
366                         process_reply(t);
367                         queue.erase(queue.begin());
368                         command_sent = false;
369                 }
370                 else
371                         return;
372         }
373
374         if(!queue.empty())
375         {
376                 const CommandSlot &slot = queue.front();
377                 serial.write(reinterpret_cast<const char *>(slot.data), slot.length);
378                 command_sent = true;
379         }
380 }
381
382 void Intellibox::flush()
383 {
384         for(list<CommandSlot>::iterator i=queue.begin(); i!=queue.end(); ++i)
385         {
386                 serial.write(reinterpret_cast<const char *>(i->data), i->length);
387                 bool first = true;
388                 while(first ? IO::poll(serial, IO::P_INPUT) : IO::poll(serial, IO::P_INPUT, Time::zero))
389                 {
390                         char data[16];
391                         serial.read(data, 16);
392                         first = false;
393                 }
394         }
395
396         queue.clear();
397         command_sent = false;
398 }
399
400 Intellibox::Protocol Intellibox::map_protocol(const string &name) const
401 {
402         if(name=="MM")
403                 return MM;
404         else if(name=="MM-27")
405                 return MM_27;
406         else
407                 throw invalid_argument("Intellibox::map_protocol");
408 }
409
410 void Intellibox::command(Command cmd)
411 {
412         command(cmd, 0, 0);
413 }
414
415 void Intellibox::command(Command cmd, const unsigned char *data, unsigned len)
416 {
417         command(cmd, 0, data, len);
418 }
419
420 void Intellibox::command(Command cmd, unsigned addr, const unsigned char *data, unsigned len)
421 {
422         CommandSlot slot;
423         slot.cmd = cmd;
424         slot.addr = addr;
425         slot.data[0] = cmd;
426         copy(data, data+len, slot.data+1);
427         slot.length = 1+len;
428         queue.push_back(slot);
429 }
430
431 void Intellibox::loco_command(unsigned addr, unsigned speed, bool rev, unsigned funcs, bool setf)
432 {
433         unsigned char data[4];
434         data[0] = addr&0xFF;
435         data[1] = (addr>>8)&0xFF;
436
437         if(speed==0)
438                 data[2] = 0;
439         else if(speed==1)
440                 data[2] = 2;
441         else
442                 data[2] = (speed*19-18)/2;
443         
444         data[3] = (rev ? 0 : 0x20) | ((funcs&1) ? 0x10 : 0);
445
446         if(setf)
447                 data[3] |= 0x80 | ((funcs>>1)&0xF);
448
449         command(CMD_LOK, addr, data, 4);
450 }
451
452 void Intellibox::turnout_command(unsigned addr, bool state, bool active)
453 {
454         unsigned char data[2];
455         data[0] = addr&0xFF;
456         data[1] = ((addr>>8)&0x7) | (active ? 0x40 : 0) | (state ? 0x80 : 0);
457         command(CMD_TURNOUT, addr, data, 2);
458 }
459
460 void Intellibox::process_reply(const Time::TimeStamp &t)
461 {
462         Command cmd = queue.front().cmd;
463
464         if(cmd==CMD_STATUS)
465         {
466                 unsigned char status;
467                 read_all(&status, 1);
468                 power = status&0x08;
469                 signal_power.emit(power);
470         }
471         else if(cmd==CMD_EVENT)
472         {
473                 for(unsigned i=0;; ++i)
474                 {
475                         unsigned char byte;
476                         read_all(&byte, 1);
477
478                         if(i==0)
479                         {
480                                 if(byte&0x01)
481                                         command(CMD_EVENT_LOK);
482                                 if(byte&0x20)
483                                         command(CMD_EVENT_TURNOUT);
484                                 if(byte&0x04)
485                                         command(CMD_EVENT_SENSOR);
486                         }
487                         else if(i==1)
488                         {
489                                 if(byte&0x40)
490                                         command(CMD_STATUS);
491                         }
492
493                         if(!(byte&0x80))
494                                 break;
495                 }
496         }
497         else if(cmd==CMD_EVENT_LOK)
498         {
499                 while(1)
500                 {
501                         unsigned char data[5];
502                         read_all(data, 1);
503                         if(data[0]==0x80)
504                                 break;
505                         read_all(data+1, 4);
506                 }
507         }
508         else if(cmd==CMD_EVENT_TURNOUT)
509         {
510                 unsigned char count;
511                 read_all(&count, 1);
512                 for(unsigned i=0; i<count; ++i)
513                 {
514                         unsigned char data[2];
515                         read_all(data, 2);
516
517                         unsigned addr = data[0]+((data[1]&7)<<8);
518                         unsigned mask = 1;
519                         for(; !turnouts[addr].bits; --addr, mask<<=1) ;
520                         Turnout &turnout = turnouts[addr];
521
522                         unsigned bit = !(data[1]&0x80);
523                         turnout.state = (turnout.state&~mask) | (bit*mask);
524                         turnout.pending = turnout.state;
525                         turnout_state_changed(addr,turnout);
526                 }
527         }
528         else if(cmd==CMD_EVENT_SENSOR)
529         {
530                 while(1)
531                 {
532                         unsigned char mod;
533                         read_all(&mod, 1);
534                         if(!mod)
535                                 break;
536
537                         unsigned char data[2];
538                         read_all(data, 2);
539                         for(unsigned i=0; i<16; ++i)
540                         {
541                                 unsigned addr = mod*16+i-15;
542                                 bool state = (data[i/8]>>(7-i%8))&1;
543
544                                 Sensor &sensor = sensors[addr];
545                                 if(state)
546                                 {
547                                         sensor.off_timeout = Time::TimeStamp();
548                                         if(!sensor.state)
549                                         {
550                                                 sensor.state = state;
551                                                 signal_sensor(addr, state);
552                                         }
553                                 }
554                                 else if(sensor.state)
555                                         sensor.off_timeout = t+700*Time::msec;
556                         }
557                 }
558         }
559         else if(cmd==CMD_LOK)
560         {
561                 Error err;
562                 read_status(&err);
563
564                 if(err==ERR_NO_ERROR)
565                 {
566                         unsigned addr = queue.front().addr;
567                         Locomotive &loco = locos[addr];
568                         if(loco.protocol)
569                         {
570                                 signal_loco_speed.emit(addr, loco.speed+loco.pending_half_step, loco.reverse);
571                                 if(loco.pending_half_step)
572                                         loco.half_step_delay = Time::now()+500*Time::msec;
573                         }
574                 }
575                 else
576                         error(cmd, err);
577         }
578         else if(cmd==CMD_TURNOUT)
579         {
580                 Error err;
581                 read_status(&err);
582
583                 unsigned addr = queue.front().addr;
584                 unsigned mask = 1;
585                 for(; !turnouts[addr].bits; --addr, mask<<=1) ;
586                 Turnout &turnout = turnouts[addr];
587
588                 if(err==ERR_NO_ERROR)
589                 {
590                         turnout.state = (turnout.state&~mask) | (turnout.pending&mask);
591                         if(turnout.active)
592                         {
593                                 if(turnout.state==turnout.pending)
594                                         turnout_state_changed(addr, turnout);
595                                 turnout.off_timeout = t+500*Time::msec;
596                         }
597                 }
598                 else if(err==ERR_NO_I2C_SPACE)
599                         queue.push_back(queue.front());
600                 else
601                 {
602                         turnout.pending = (turnout.pending&~mask) | (turnout.state&mask);
603                         error(cmd, err);
604                 }
605         }
606         else if(cmd==CMD_TURNOUT_STATUS)
607         {
608                 Error err;
609                 read_status(&err);
610
611                 if(err==ERR_NO_ERROR)
612                 {
613                         unsigned char data;
614                         read_all(&data, 1);
615
616                         unsigned addr = queue.front().addr;
617                         unsigned mask = 1;
618                         for(; !turnouts[addr].bits; --addr, mask<<=1) ;
619                         Turnout &turnout = turnouts[addr];
620
621                         bool bit = !(data&0x04);
622                         if(bit!=((turnout.state&mask)!=0))
623                         {
624                                 turnout.state = (turnout.state&~mask) | (bit*mask);
625                                 turnout.pending = turnout.state;
626                                 turnout_state_changed(addr, turnout);
627                         }
628
629                         turnout.synced = true;
630                 }
631                 else
632                         error(cmd, err);
633         }
634         else if(cmd==CMD_LOK_STATUS)
635         {
636                 Error err;
637                 read_status(&err);
638
639                 if(err==ERR_NO_ERROR)
640                 {
641                         unsigned char data[3];
642                         read_all(data, 3);
643
644                         unsigned addr = queue.front().addr;
645                         Locomotive &loco = locos[addr];
646
647                         unsigned speed = (data[0]<=1 ? 0 : data[0]*2/19+1);
648                         bool reverse = !(data[1]&0x20);
649                         bool speed_changed = (speed!=loco.speed || reverse!=loco.reverse);
650
651                         loco.speed = speed;
652                         loco.reverse = reverse;
653
654                         unsigned funcs = (data[1]&0xF)<<1;
655                         if(data[1]&0x10)
656                                 funcs |= 1;
657                         unsigned funcs_changed = loco.funcs^funcs;
658                         loco.funcs = funcs;
659
660                         if(speed_changed)
661                                 signal_loco_speed.emit(addr, loco.speed, loco.reverse);
662                         for(unsigned i=0; i<5; ++i)
663                                 if(funcs_changed&(1<<i))
664                                         signal_loco_function.emit(addr, i, loco.funcs&(1<<i));
665                 }
666                 else
667                         error(cmd, err);
668         }
669         else
670         {
671                 unsigned expected_bytes = 0;
672                 if(cmd==CMD_FUNC_STATUS)
673                         expected_bytes = 1;
674                 if(cmd==CMD_TURNOUT_GROUP_STATUS)
675                         expected_bytes = 2;
676                 if(cmd==CMD_LOK_CONFIG)
677                         expected_bytes = 4;
678
679                 Error err;
680                 read_status(&err);
681
682                 if(err==ERR_NO_ERROR)
683                 {
684                         unsigned char data[8];
685                         read_all(data, expected_bytes);
686                 }
687                 else
688                         error(cmd, err);
689         }
690 }
691
692 unsigned Intellibox::read_all(unsigned char *buf, unsigned len)
693 {
694         unsigned pos = 0;
695         while(pos<len)
696                 pos += serial.read(reinterpret_cast<char *>(buf+pos), len-pos);
697
698         return pos;
699 }
700
701 unsigned Intellibox::read_status(Error *err)
702 {
703         unsigned char c;
704         unsigned ret = read_all(&c, 1);
705         *err = static_cast<Error>(c);
706         return ret;
707 }
708
709 void Intellibox::error(Command cmd, Error err)
710 {
711         const char *cmd_str = 0;
712         switch(cmd)
713         {
714         case CMD_LOK: cmd_str = "CMD_LOK"; break;
715         case CMD_LOK_STATUS: cmd_str = "CMD_LOK_STATUS"; break;
716         case CMD_LOK_CONFIG: cmd_str = "CMD_LOK_CONFIG"; break;
717         case CMD_FUNC: cmd_str = "CMD_FUNC"; break;
718         case CMD_FUNC_STATUS: cmd_str = "CMD_FUNC_STATUS"; break;
719         case CMD_TURNOUT: cmd_str = "CMD_TURNOUT"; break;
720         case CMD_TURNOUT_FREE: cmd_str = "CMD_TURNOUT_FREE"; break;
721         case CMD_TURNOUT_STATUS: cmd_str = "CMD_TURNOUT_STATUS"; break;
722         case CMD_TURNOUT_GROUP_STATUS: cmd_str = "CMD_TURNOUT_GROUP_STATUS"; break;
723         case CMD_SENSOR_STATUS: cmd_str = "CMD_SENSOR_STATUS"; break;
724         case CMD_SENSOR_REPORT: cmd_str = "CMD_SENSOR_REPORT"; break;
725         case CMD_SENSOR_PARAM_SET: cmd_str = "CMD_SENSOR_PARAM_SET"; break;
726         case CMD_STATUS: cmd_str = "CMD_STATUS"; break;
727         case CMD_POWER_OFF: cmd_str = "CMD_POWER_OFF"; break;
728         case CMD_POWER_ON: cmd_str = "CMD_POWER_ON"; break;
729         case CMD_NOP: cmd_str = "CMD_NOP"; break;
730         case CMD_EVENT: cmd_str = "CMD_EVENT"; break;
731         case CMD_EVENT_LOK: cmd_str = "CMD_EVENT_LOK"; break;
732         case CMD_EVENT_TURNOUT: cmd_str = "CMD_EVENT_TURNOUT"; break;
733         case CMD_EVENT_SENSOR: cmd_str = "CMD_EVENT_SENSOR"; break;
734         default: cmd_str = "(unknown command)";
735         }
736
737         const char *err_str = 0;
738         switch(err)
739         {
740         case ERR_NO_ERROR: err_str = "ERR_NO_ERROR"; break;
741         case ERR_SYS_ERROR: err_str = "ERR_SYS_ERROR"; break;
742         case ERR_BAD_PARAM: err_str = "ERR_BAD_PARAM"; break;
743         case ERR_POWER_OFF: err_str = "ERR_POWER_OFF"; break;
744         case ERR_NO_LOK_SPACE: err_str = "ERR_NO_LOK_SPACE"; break;
745         case ERR_NO_TURNOUT_SPACE: err_str = "ERR_NO_TURNOUT_SPACE"; break;
746         case ERR_NO_DATA: err_str = "ERR_NO_DATA"; break;
747         case ERR_NO_SLOT: err_str = "ERR_NO_SLOT"; break;
748         case ERR_BAD_LOK_ADDR: err_str = "ERR_BAD_LOK_ADDR"; break;
749         case ERR_LOK_BUSY: err_str = "ERR_LOK_BUSY"; break;
750         case ERR_BAD_TURNOUT_ADDR: err_str = "ERR_BAD_TURNOUT_ADDR"; break;
751         case ERR_BAD_SO_VALUE: err_str = "ERR_BAD_SO_VALUE"; break;
752         case ERR_NO_I2C_SPACE: err_str = "ERR_NO_I2C_SPACE"; break;
753         case ERR_LOW_TURNOUT_SPACE: err_str = "ERR_LOW_TURNOUT_SPACE"; break;
754         case ERR_LOK_HALTED: err_str = "ERR_LOK_HALTED"; break;
755         case ERR_LOK_POWER_OFF: err_str = "ERR_LOK_POWER_OFF"; break;
756         default: cmd_str = "(unknown error)";
757         }
758
759         IO::print("Error: %s: %s\n", cmd_str, err_str);
760 }
761
762
763 Intellibox::Locomotive::Locomotive():
764         protocol(NONE),
765         ext_func(false),
766         speed(0),
767         reverse(false),
768         funcs(0),
769         pending_half_step(0)
770 { }
771
772
773 Intellibox::Turnout::Turnout():
774         bits(1),
775         state(0),
776         active(false),
777         synced(false),
778         pending(0),
779         signal(false)
780 { }
781
782
783 Intellibox::Sensor::Sensor():
784         state(false)
785 { }
786
787 } // namespace R2C2