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