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