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