]> git.tdb.fi Git - r2c2.git/blob - source/libr2c2/centralstation.cpp
Detect curved turnouts and assign appropriate symbols to them
[r2c2.git] / source / libr2c2 / centralstation.cpp
1 #include <algorithm>
2 #include <msp/core/refptr.h>
3 #include <msp/io/print.h>
4 #include <msp/net/resolve.h>
5 #include <msp/strings/utils.h>
6 #include <msp/time/units.h>
7 #include <msp/time/utils.h>
8 #include "centralstation.h"
9 #include "tracktype.h"
10 #include "vehicletype.h"
11
12 using namespace std;
13 using namespace Msp;
14
15 namespace R2C2 {
16
17 CentralStation::CentralStation(const string &host):
18         socket(Net::INET),
19         pending_commands(0),
20         power(false),
21         halted(false),
22         locos_synced(false),
23         turnouts_synced(false),
24         sensors_synced(false)
25 {
26         RefPtr<Net::SockAddr> addr = Net::resolve(host+":15471");
27         socket.connect(*addr);
28
29         IO::print("Connected to central station at %s\n", addr->str());
30
31         command("get(1, status)");
32         command("request(1, view)");
33         command("queryObjects(10, addr, name)");
34         command("queryObjects(11, addr)");
35         command("queryObjects(26)");
36 }
37
38 CentralStation::~CentralStation()
39 {
40         command("release(1, view)", true);
41         for(LocoMap::iterator i=locos.begin(); (i!=locos.end() && !(i->first&0x10000)); ++i)
42                 command(format("release(%d, view, control)", i->first));
43         for(TurnoutMap::iterator i=turnouts.begin(); (i!=turnouts.end() && !(i->first&0x10000)); ++i)
44                 command(format("release(%d, view, control)", i->first));
45         while(IO::poll(socket, IO::P_INPUT, 100*Time::msec))
46                 while(receive()) ;
47 }
48
49 void CentralStation::set_power(bool p)
50 {
51         power = p;
52         command(format("set(1, %s)", (power ? "go" : "stop")));
53 }
54
55 void CentralStation::halt(bool h)
56 {
57         halted = h;
58         if(halted)
59         {
60                 for(LocoMap::iterator i=locos.begin(); i!=locos.end(); ++i)
61                         if(i->second.speed)
62                                 set_loco_speed(i->first, 0);
63         }
64
65         signal_halt.emit(halted);
66 }
67
68 const char *CentralStation::enumerate_protocols(unsigned index) const
69 {
70         if(index==MM)
71                 return "MM";
72         else if(index==MM_27)
73                 return "MM-27";
74         else if(index==MFX)
75                 return "MFX";
76         else
77                 return 0;
78 }
79
80 unsigned CentralStation::get_protocol_speed_steps(const string &name) const
81 {
82         switch(map_protocol(name))
83         {
84         case MM: return 14;
85         case MM_27: return 27;
86         case MFX: return 126;
87         default: return 0;
88         }
89 }
90
91 void CentralStation::add_loco(unsigned addr, const string &proto_name, const VehicleType &type)
92 {
93         Protocol proto = map_protocol(proto_name);
94
95         unsigned id = map_address(locos, loco_addr, addr);
96         if(!id)
97         {
98                 Locomotive &loco = locos[addr|0x10000];
99                 loco.name = type.get_name();
100                 loco.protocol = proto;
101                 loco.address = addr;
102
103                 const VehicleType::FunctionMap &type_funcs = type.get_functions();
104                 for(VehicleType::FunctionMap::const_iterator i=type_funcs.begin(); i!=type_funcs.end(); ++i)
105                         loco.func_mask |= 1<<i->first;
106
107                 if(locos_synced && proto!=MFX)
108                         command("create(10)");
109         }
110         else
111                 command(format("request(%d, view, control, force)", id));
112 }
113
114 void CentralStation::set_loco_speed(unsigned addr, unsigned speed)
115 {
116         if(speed && halted)
117                 return;
118
119         unsigned id = map_address(locos, loco_addr, addr);
120         if(id)
121         {
122                 Locomotive &loco = locos[id];
123                 if(loco.protocol==MFX && speed)
124                         ++speed;
125                 command(format("set(%d, speedstep[%d])", id, speed));
126         }
127 }
128
129 void CentralStation::set_loco_reverse(unsigned addr, bool rev)
130 {
131         unsigned id = map_address(locos, loco_addr, addr);
132         if(id)
133                 command(format("set(%d, dir[%d])", id, rev));
134 }
135
136 void CentralStation::set_loco_function(unsigned addr, unsigned func, bool state)
137 {
138         unsigned id = map_address(locos, loco_addr, addr);
139         if(id)
140                 command(format("set(%d, func[%d, %d])", id, func, state));
141 }
142
143 void CentralStation::add_turnout(unsigned addr, const TrackType &type)
144 {
145         unsigned straight = type.get_paths();
146         bool left = false;
147         bool right = false;
148         bool cross = false;
149
150         const vector<TrackPart> &parts = type.get_parts();
151         for(vector<TrackPart>::const_iterator i=parts.begin(); i!=parts.end(); ++i)
152         {
153                 TrackPoint start = i->get_point(0);
154                 TrackPoint end = i->get_point(i->get_length());
155                 if(end.dir>start.dir+0.01 || end.dir<start.dir-0.01)
156                 {
157                         (end.dir>start.dir ? left : right) = true;
158                         straight &= ~(1<<i->get_path());
159                 }
160                 else if(start.dir<-0.01 || start.dir>0.01)
161                         cross = true;
162         }
163
164         unsigned symbol = Turnout::LEFT;
165         if(cross)
166                 symbol = Turnout::DOUBLESLIP;
167         else if(left && right)
168                 symbol = Turnout::THREEWAY;
169         else if(left)
170                 symbol = (straight ? Turnout::LEFT : Turnout::CURVED_LEFT);
171         else if(right)
172                 symbol = (straight ? Turnout::RIGHT : Turnout::CURVED_RIGHT);
173
174         unsigned id = map_address(turnouts, turnout_addr, addr);
175         if(!id)
176         {
177                 id = addr|0x10000;
178
179                 Turnout &turnout = turnouts[id];
180                 turnout.address = addr;
181                 turnout.bits = type.get_state_bits();
182                 turnout.symbol = symbol;
183
184                 turnout_addr[addr] = id;
185
186                 if(turnouts_synced)
187                         command("create(11, append)");
188         }
189         else
190         {
191                 Turnout &turnout = turnouts[id];
192                 command(format("request(%d, view, control)", id));
193                 if(turnout.symbol!=symbol)
194                         command(format("set(%d, symbol[%d])", symbol));
195         }
196 }
197
198 void CentralStation::set_turnout(unsigned addr, unsigned state)
199 {
200         unsigned id = map_address(turnouts, turnout_addr, addr);
201         if(id)
202         {
203                 Turnout &turnout = turnouts[id];
204                 unsigned mask = (1<<turnout.bits)-1;
205
206                 if(((state^turnout.state)&mask)==0 || !turnout.synced)
207                 {
208                         turnout.state = state;
209                         signal_turnout.emit(addr, turnout.state);
210                         return;
211                 }
212
213                 turnout.state = (turnout.state&mask) | (state&~mask);
214
215                 command(format("set(%d, state[%d])", id, state&mask));
216         }
217 }
218
219 unsigned CentralStation::get_turnout(unsigned addr) const
220 {
221         unsigned id = map_address(turnouts, turnout_addr, addr);
222         if(id)
223         {
224                 TurnoutMap::const_iterator i = turnouts.find(id);
225                 if(i!=turnouts.end())
226                         return i->second.state;
227         }
228         return 0;
229 }
230
231 void CentralStation::add_sensor(unsigned addr)
232 {
233         sensors.insert(SensorMap::value_type(addr, Sensor()));
234
235         if(sensors_synced)
236         {
237                 if(addr>s88.size()*16)
238                         command("create(26, add[0])");
239         }
240 }
241
242 bool CentralStation::get_sensor(unsigned addr) const
243 {
244         SensorMap::const_iterator i = sensors.find(addr);
245         if(i!=sensors.end())
246                 return i->second.state;
247         return false;
248 }
249
250 void CentralStation::tick()
251 {
252         while(Message msg = receive())
253         {
254                 if(msg.footer.code)
255                         IO::print("\033[31m*** ERROR: %s: %d %s ***\033[0m\n", msg.header.value, msg.footer.code, msg.footer.value);
256
257                 if(msg.header.type=="REPLY")
258                         process_reply(msg);
259                 else if(msg.header.type=="EVENT")
260                         process_event(msg);
261         }
262 }
263
264 void CentralStation::flush()
265 {
266 }
267
268 void CentralStation::command(const string &cmd, bool force)
269 {
270         if(pending_commands<10 || force)
271         {
272                 socket.write(cmd+"\r\n");
273                 ++pending_commands;
274         }
275         else
276                 cmd_queue.push_back(cmd);
277 }
278
279 CentralStation::Message CentralStation::receive()
280 {
281         while(IO::poll(socket, IO::P_INPUT, Time::zero))
282         {
283                 char rbuf[1024];
284                 unsigned len = socket.read(rbuf, sizeof(rbuf));
285                 if(!len)
286                         return Message();
287
288                 in_buffer.append(rbuf, len);
289         }
290
291         if(!in_buffer.empty())
292         {
293                 string::iterator iter = in_buffer.begin();
294                 if(Message msg = parse_message(iter, in_buffer.end()))
295                 {
296                         skip(iter, in_buffer.end(), "\r\n");
297                         in_buffer.erase(in_buffer.begin(), iter);
298
299                         if(msg.header.type=="REPLY" && pending_commands>0)
300                         {
301                                 --pending_commands;
302                                 if(!cmd_queue.empty())
303                                 {
304                                         command(cmd_queue.front());
305                                         cmd_queue.pop_front();
306                                 }
307                         }
308
309                         return msg;
310                 }
311         }
312
313         return Message();
314 }
315
316 void CentralStation::process_reply(const Message &msg)
317 {
318         if(!msg.header.value.compare(0, 4, "get("))
319         {
320                 for(Message::ObjectMap::const_iterator i=msg.content.begin(); i!=msg.content.end(); ++i)
321                 {
322                         if(turnouts.count(i->first))
323                                 turnouts[i->first].synced = true;
324
325                         process_object(i->first, i->second);
326                 }
327         }
328         else if(!msg.header.value.compare(0, 16, "queryObjects(10,"))
329         {
330                 for(Message::ObjectMap::const_iterator i=msg.content.begin(); i!=msg.content.end(); ++i)
331                 {
332                         LocoMap::iterator j = locos.find(i->first);
333                         if(j==locos.end())
334                         {
335                                 bool found = false;
336                                 Message::AttribMap::const_iterator k = i->second.find("addr");
337                                 if(k!=i->second.end())
338                                 {
339                                         unsigned addr = lexical_cast<unsigned>(k->second);
340
341                                         j = locos.find(addr|0x10000);
342                                         if(j!=locos.end())
343                                         {
344                                                 command(format("request(%d, view, control, force)", i->first));
345                                                 string cmd = format("get(%d, dir", i->first);
346                                                 for(unsigned l=0; j->second.func_mask>>l; ++l)
347                                                         if((j->second.func_mask>>l)&1)
348                                                                 cmd += format(", func[%d]", l);
349                                                 cmd += ')';
350                                                 command(cmd);
351
352                                                 locos.insert(LocoMap::value_type(i->first, j->second));
353                                                 locos.erase(j);
354
355                                                 found = true;
356                                         }
357                                 }
358
359                                 if(!found)
360                                         locos.insert(LocoMap::value_type(i->first, Locomotive()));
361                         }
362
363                         process_object(i->first, i->second);
364                 }
365
366                 locos_synced = true;
367
368                 if(locos.lower_bound(0x10000)!=locos.end())
369                         command("create(10)");
370         }
371         else if(!msg.header.value.compare(0, 16, "queryObjects(11,"))
372         {
373                 for(Message::ObjectMap::const_iterator i=msg.content.begin(); i!=msg.content.end(); ++i)
374                 {
375                         TurnoutMap::iterator j = turnouts.find(i->first);
376                         if(j==turnouts.end())
377                         {
378                                 bool found = false;
379                                 Message::AttribMap::const_iterator k = i->second.find("addr");
380                                 if(k!=i->second.end())
381                                 {
382                                         unsigned addr = lexical_cast<unsigned>(k->second);
383
384                                         j = turnouts.find(addr|0x10000);
385                                         if(j!=turnouts.end())
386                                         {
387                                                 command(format("request(%d, view, control)", i->first));
388                                                 command(format("set(%d, symbol[%d])", i->first, j->second.symbol));
389                                                 command(format("get(%d, state)", i->first));
390
391                                                 turnouts.insert(TurnoutMap::value_type(i->first, j->second));
392                                                 turnouts.erase(j);
393
394                                                 found = true;
395                                         }
396                                 }
397
398                                 if(!found)
399                                         turnouts.insert(TurnoutMap::value_type(i->first, Turnout()));
400                         }
401
402                         process_object(i->first, i->second);
403                 }
404
405                 turnouts_synced = true;
406
407                 for(TurnoutMap::const_iterator i=turnouts.lower_bound(0x10000); i!=turnouts.end(); ++i)
408                         command("create(11, append)");
409         }
410         else if(msg.header.value=="queryObjects(26)")
411         {
412                 s88.clear();
413                 for(Message::ObjectMap::const_iterator i=msg.content.begin(); i!=msg.content.end(); ++i)
414                 {
415                         s88.push_back(i->first);
416                         command(format("request(%d, view)", i->first));
417                         command(format("get(%d, state)", i->first));
418                 }
419
420                 sensors_synced = true;
421
422                 if(!sensors.empty())
423                 {
424                         unsigned high_addr = (--sensors.end())->first;
425                         if(high_addr>16*s88.size())
426                                 command("create(26, add[0])");
427                 }
428         }
429         else if(msg.header.value=="create(10)")
430         {
431                 Message::ObjectMap::const_iterator i = msg.content.find(10);
432                 if(i!=msg.content.end())
433                 {
434                         Message::AttribMap::const_iterator j = i->second.find("id");
435                         if(j!=i->second.end())
436                         {
437                                 unsigned id = lexical_cast<unsigned>(j->second);
438                                 LocoMap::iterator k = locos.lower_bound(0x10000);
439                                 if(k!=locos.end())
440                                 {
441                                         command(format("request(%d, view, control)", id));
442                                         command(format("set(%d, addr[%d], protocol[%s], name[\"%s\"])",
443                                                 id, k->second.address, (k->second.protocol==MM_27 ? "MM27" : "MM14"), k->second.name));
444                                         command("create(10, append)");
445
446                                         locos.insert(LocoMap::value_type(id, k->second));
447                                         locos.erase(k);
448                                 }
449                         }
450                 }
451
452                 if(locos.lower_bound(0x10000)!=locos.end())
453                         command("create(10)");
454         }
455         else if(!msg.header.value.compare(0, 10, "create(11,"))
456         {
457                 Message::ObjectMap::const_iterator i = msg.content.find(11);
458                 if(i!=msg.content.end())
459                 {
460                         Message::AttribMap::const_iterator j = i->second.find("id");
461                         if(j!=i->second.end())
462                         {
463                                 unsigned id = lexical_cast<unsigned>(j->second);
464                                 TurnoutMap::iterator k = turnouts.lower_bound(0x10000);
465                                 if(k!=turnouts.end())
466                                 {
467                                         command(format("request(%d, view, control)", id));
468                                         command(format("set(%d, addr[%d], symbol[%d], name1[\"Switch\"], name2[\"%d\"], name3[\"\"])",
469                                                 id, k->second.address, k->second.symbol, k->second.address));
470                                         command(format("set(%d, state[%d])", id, k->second.state&((1<<k->second.bits)-1)));
471
472                                         k->second.synced = true;
473                                         turnouts.insert(TurnoutMap::value_type(id, k->second));
474                                         turnouts.erase(k);
475                                 }
476                         }
477                 }
478         }
479         else if(!msg.header.value.compare(0, 10, "create(26,"))
480                 command("queryObjects(26)");
481 }
482
483 void CentralStation::process_event(const Message &msg)
484 {
485         for(Message::ObjectMap::const_iterator i=msg.content.begin(); i!=msg.content.end(); ++i)
486                 process_object(i->first, i->second);
487 }
488
489 void CentralStation::process_object(unsigned id, const Message::AttribMap &attribs)
490 {
491         if(id==1)
492         {
493                 for(Message::AttribMap::const_iterator i=attribs.begin(); i!=attribs.end(); ++i)
494                         if(i->first=="status")
495                         {
496                                 power = (i->second=="GO");
497                                 signal_power.emit(power);
498                         }
499         }
500         else if(locos.count(id))
501         {
502                 Locomotive &loco = locos[id];
503                 bool speed_changed = false;
504                 unsigned funcs_changed = 0;
505                 for(Message::AttribMap::const_iterator i=attribs.begin(); i!=attribs.end(); ++i)
506                 {
507                         if(i->first=="name")
508                                 loco.name = i->second.substr(1, i->second.size()-2);
509                         else if(i->first=="addr")
510                         {
511                                 loco_addr.erase(loco.address);
512                                 loco.address = lexical_cast<unsigned>(i->second);
513                                 loco_addr[loco.address] = id;
514                         }
515                         else if(i->first=="protocol")
516                         {
517                                 if(i->second=="MM")
518                                         loco.protocol = MM;
519                                 else if(i->second=="MM27")
520                                         loco.protocol = MM_27;
521                                 else if(i->second=="MFX")
522                                         loco.protocol = MFX;
523                         }
524                         else if(i->first=="speedstep")
525                         {
526                                 loco.speed = lexical_cast<unsigned>(i->second);
527                                 if(loco.protocol==MFX && loco.speed)
528                                         --loco.speed;
529                                 speed_changed = true;
530                         }
531                         else if(i->first=="dir")
532                         {
533                                 loco.reverse = i->second[0]!='0';
534                                 speed_changed = true;
535                         }
536                         else if(i->first=="func")
537                         {
538                                 vector<string> parts = split(i->second, ", ");
539                                 unsigned func = lexical_cast<unsigned>(parts[0]);
540                                 bool value = lexical_cast<unsigned>(parts[1]);
541                                 loco.funcs &= ~(1<<func);
542                                 if(value)
543                                         loco.funcs |= 1<<func;
544                                 funcs_changed |= 1<<func;
545                         }
546                         else if(i->first=="msg")
547                         {
548                                 if(i->second=="CONTROL_LOST")
549                                         command(format("request(%d, control, force)", id));
550                         }
551                 }
552
553                 if(speed_changed)
554                         signal_loco_speed.emit(loco.address, loco.speed, loco.reverse);
555                 for(unsigned i=0; funcs_changed>>i; ++i)
556                         if(funcs_changed&(1<<i))
557                                 signal_loco_function.emit(loco.address, i, loco.funcs&(1<<i));
558         }
559         else if(turnouts.count(id))
560         {
561                 Turnout &turnout = turnouts[id];
562                 bool state_changed = false;
563                 for(Message::AttribMap::const_iterator i=attribs.begin(); i!=attribs.end(); ++i)
564                 {
565                         if(i->first=="addr")
566                         {
567                                 turnout_addr.erase(turnout.address);
568                                 turnout.address = lexical_cast<unsigned>(i->second);
569                                 turnout_addr[turnout.address] = id;
570                         }
571                         else if(i->first=="state")
572                         {
573                                 unsigned state = lexical_cast<unsigned>(i->second);
574                                 unsigned mask = (1<<turnout.bits)-1;
575                                 turnout.state = (turnout.state&~mask) | (state&mask);
576                                 state_changed = true;
577                         }
578                 }
579
580                 if(state_changed)
581                         signal_turnout.emit(turnout.address, turnout.state);
582         }
583         else if(find(s88.begin(), s88.end(), id)!=s88.end())
584         {
585                 unsigned base = 0;
586                 for(; (base<s88.size() && s88[base]!=id); ++base) ;
587
588                 for(Message::AttribMap::const_iterator i=attribs.begin(); i!=attribs.end(); ++i)
589                 {
590                         if(i->first=="state")
591                         {
592                                 unsigned state = lexical_cast<unsigned>(i->second, "%i");
593                                 for(unsigned j=0; j<16; ++j)
594                                 {
595                                         unsigned addr = base*16+j+1;
596                                         Sensor &sensor = sensors[addr];
597                                         bool s = state&(1<<j);
598                                         if(s!=sensor.state)
599                                         {
600                                                 sensor.state = s;
601                                                 signal_sensor.emit(addr, sensor.state);
602                                         }
603                                 }
604                         }
605                 }
606         }
607 }
608
609 CentralStation::Protocol CentralStation::map_protocol(const string &name) const
610 {
611         if(name=="MM")
612                 return MM;
613         else if(name=="MM-27")
614                 return MM_27;
615         else if(name=="MFX")
616                 return MFX;
617         else
618                 throw invalid_argument("CentralStation::map_protocol");
619 }
620
621 template<typename T>
622 unsigned CentralStation::map_address(const map<unsigned, T> &omap, const AddressMap &amap, unsigned addr) const
623 {
624         if(omap.count(addr))
625                 return addr;
626         else
627         {
628                 AddressMap::const_iterator i = amap.find(addr);
629                 if(i!=amap.end())
630                         return i->second;
631                 else
632                         return 0;
633         }
634 }
635
636 void CentralStation::skip(string::iterator &iter, const string::iterator &end, const string &what) const
637 {
638         for(; (iter!=end && what.find(*iter)!=string::npos); ++iter) ;
639 }
640
641 string CentralStation::parse_token(string::iterator &iter, const string::iterator &end, const string &stop) const
642 {
643         vector<char> parens;
644         bool quote = false;
645         string token;
646
647         skip(iter, end, stop);
648
649         for(; iter!=end; ++iter)
650         {
651                 if(stop.find(*iter)!=string::npos && parens.empty() && !quote)
652                         break;
653                 else if(*iter=='(' || *iter=='[')
654                         parens.push_back(*iter);
655                 else if((*iter==')' || *iter==']') && !parens.empty())
656                 {
657                         if((*iter==')' && parens.back()!='(') || (*iter==']' && parens.back()!='['))
658                                 IO::print("Mismatched parentheses\n");
659                         parens.pop_back();
660                 }
661                 else if(*iter=='"')
662                         quote = !quote;
663
664                 token += *iter;
665         }
666
667         return token;
668 }
669
670 CentralStation::Tag CentralStation::parse_tag(string::iterator &iter, const string::iterator &end) const
671 {
672         Tag tag;
673
674         for(; (iter!=end && *iter!='<'); ++iter) ;
675         if(iter==end)
676                 return Tag();
677
678         tag.type = parse_token(++iter, end, " >");
679         if(tag.type=="END")
680         {
681                 string code = parse_token(iter, end, " >");
682                 tag.code = lexical_cast<unsigned>(code);
683         }
684         skip(iter, end, " ");
685         tag.value = parse_token(iter, end, ">");
686         if(iter==end)
687                 return Tag();
688         ++iter;
689
690         return tag;
691 }
692
693 CentralStation::Message CentralStation::parse_message(string::iterator &iter, const string::iterator &end) const
694 {
695         Message msg;
696
697         msg.header = parse_tag(iter, end);
698
699         while(iter!=end)
700         {
701                 skip(iter, end, "\r\n");
702                 if(*iter=='<')
703                         break;
704
705                 string id = parse_token(iter, end, " \r\n<");
706                 Message::AttribMap &attribs = msg.content[lexical_cast<unsigned>(id)];
707                 while(iter!=end && *iter!='\n' && *iter!='\r')
708                 {
709                         string attr = parse_token(iter, end, " \r\n<");
710                         string::size_type open_bracket = attr.find('[');
711                         if(open_bracket!=string::npos)
712                         {
713                                 string::size_type close_bracket = attr.rfind(']');
714                                 string attr_name = attr.substr(0, open_bracket);
715                                 string attr_value = attr.substr(open_bracket+1, close_bracket-open_bracket-1);
716                                 attribs.insert(Message::AttribMap::value_type(attr_name, attr_value));
717                         }
718                         else
719                                 attribs.insert(Message::AttribMap::value_type(attr, string()));
720                 }
721         }
722
723         msg.footer = parse_tag(iter, end);
724         if(msg.footer.type.empty())
725                 return Message();
726
727         return msg;
728 }
729
730
731 CentralStation::Tag::Tag():
732         code(0)
733 { }
734
735 CentralStation::Tag::operator bool() const
736 {
737         return !type.empty();
738 }
739
740
741 CentralStation::Message::operator bool() const
742 {
743         return header && footer;
744 }
745
746
747 CentralStation::Locomotive::Locomotive():
748         address(0),
749         speed(0),
750         reverse(false),
751         func_mask(0),
752         funcs(0),
753         control(false)
754 { }
755
756
757 CentralStation::Turnout::Turnout():
758         address(0),
759         symbol(0),
760         state(0),
761         bits(0),
762         synced(false)
763 { }
764
765
766 CentralStation::Sensor::Sensor():
767         state(false)
768 { }
769
770 } // namespace R2C2