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