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