]> git.tdb.fi Git - r2c2.git/blob - source/libmarklin/intellibox.cpp
Halt all trains in various unexpected situations
[r2c2.git] / source / libmarklin / intellibox.cpp
1 /* $Id$
2
3 This file is part of the MSP Märklin suite
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
16 using namespace std;
17 using namespace Msp;
18
19 namespace Marklin {
20
21 Intellibox::Intellibox(const string &dev):
22         power(false),
23         halted(false),
24         update_sensors(false),
25         command_sent(false)
26 {
27         serial_fd = ::open(dev.c_str(), O_RDWR);
28         if(serial_fd<0)
29                 throw Exception("Couldn't open serial port\n");
30
31         static unsigned baud[]=
32         {
33                  2400, B2400,
34                  4800, B4800,
35                  9600, B9600,
36                 19200, B19200,
37                 0
38         };
39
40         termios attr;
41         tcgetattr(serial_fd, &attr);
42         cfmakeraw(&attr);
43         attr.c_cflag |= CSTOPB;
44
45         bool ok = false;
46         bool p50 = false;
47         for(unsigned i=0; baud[i]; i+=2)
48         {
49                 cfsetospeed(&attr, baud[i+1]);
50                 tcsetattr(serial_fd, TCSADRAIN, &attr);
51
52                 write(serial_fd, "\xC4", 1);
53
54                 pollfd pfd = { serial_fd, POLLIN, 0 };
55                 if(poll(&pfd, 1, 500)>0)
56                 {
57                         IO::print("IB detected at %d bits/s\n", baud[i]);
58                         char buf[2];
59                         p50 = (read(serial_fd, buf, 2)==2);
60                         ok = true;
61                         break;
62                 }
63         }
64
65         if(!ok)
66                 throw Exception("IB not detected");
67
68         if(p50)
69                 write(serial_fd, "xZzA1\r", 6);
70
71         command(CMD_STATUS);
72 }
73
74 void Intellibox::set_power(bool p)
75 {
76         power = p;
77         if(power)
78                 command(CMD_POWER_ON);
79         else
80                 command(CMD_POWER_OFF);
81         signal_power.emit(power);
82 }
83
84 void Intellibox::halt(bool h)
85 {
86         halted = h;
87         if(halted)
88         {
89                 for(map<unsigned, Locomotive>::iterator i=locos.begin(); i!=locos.end(); ++i)
90                         if(i->second.speed)
91                                 set_loco_speed(i->first, 0);
92         }
93
94         signal_halt.emit(halted);
95 }
96
97 void Intellibox::add_loco(unsigned addr)
98 {
99         if(!locos.count(addr))
100         {
101                 locos[addr];
102
103                 unsigned char data[2];
104                 data[0] = addr&0xFF;
105                 data[1] = (addr>>8)&0xFF;
106                 command(CMD_LOK_STATUS, addr, data, 2);
107         }
108 }
109
110 void Intellibox::set_loco_speed(unsigned addr, unsigned speed)
111 {
112         Locomotive &loco = locos[addr];
113         if(speed==loco.speed)
114                 return;
115         if(speed && halted)
116                 return;
117
118         loco.speed = speed;
119         loco_command(addr, speed, loco.reverse, loco.funcs|0x100);
120 }
121
122 void Intellibox::set_loco_reverse(unsigned addr, bool rev)
123 {
124         Locomotive &loco = locos[addr];
125         if(rev==loco.reverse)
126                 return;
127
128         loco.reverse = rev;
129         loco_command(addr, loco.speed, rev, loco.funcs|0x100);
130 }
131
132 void Intellibox::set_loco_function(unsigned addr, unsigned func, bool state)
133 {
134         Locomotive &loco = locos[addr];
135         if(state)
136                 loco.funcs |= 1<<func;
137         else
138                 loco.funcs &= ~(1<<func);
139         loco_command(addr, loco.speed, loco.reverse, loco.funcs);
140         signal_loco_function.emit(addr, func, state);
141 }
142
143 void Intellibox::add_turnout(unsigned addr)
144 {
145         if(!turnouts.count(addr))
146         {
147                 turnouts[addr];
148
149                 unsigned char data[2];
150                 data[0] = addr&0xFF;
151                 data[1] = (addr>>8)&0xFF;
152                 command(CMD_TURNOUT_STATUS, addr, data, 2);
153         }
154 }
155
156 void Intellibox::set_turnout(unsigned addr, bool state)
157 {
158         Turnout &turnout = turnouts[addr];
159         if(state==turnout.state || state==turnout.pending)
160                 return;
161
162         turnout.pending = state;
163         turnout.active = true;
164
165         turnout_command(addr, state, true);
166 }
167
168 bool Intellibox::get_turnout(unsigned addr) const
169 {
170         map<unsigned, Turnout>::const_iterator i = turnouts.find(addr);
171         if(i!=turnouts.end())
172                 return i->second.state;
173         return false;
174 }
175
176 void Intellibox::add_sensor(unsigned addr)
177 {
178         if(!sensors.count(addr))
179         {
180                 sensors[addr];
181                 update_sensors = true;
182         }
183 }
184
185 bool Intellibox::get_sensor(unsigned addr) const
186 {
187         map<unsigned, Sensor>::const_iterator i = sensors.find(addr);
188         if(i!=sensors.end())
189                 return i->second.state;
190         return false;
191 }
192
193 void Intellibox::tick()
194 {
195         const Time::TimeStamp t = Time::now();
196
197         if(t>next_event_query)
198         {
199                 next_event_query = t+200*Time::msec;
200                 command(CMD_EVENT);
201         }
202
203         for(map<unsigned, Turnout>::iterator i=turnouts.begin(); i!=turnouts.end(); ++i)
204                 if(i->second.active && i->second.off_timeout && t>i->second.off_timeout)
205                 {
206                         i->second.active = false;
207                         i->second.off_timeout = Time::TimeStamp();
208                         turnout_command(i->first, i->second.state, false);
209                 }
210
211         for(map<unsigned, Sensor>::iterator i=sensors.begin(); i!=sensors.end(); ++i)
212                 if(i->second.off_timeout && t>i->second.off_timeout)
213                 {
214                         i->second.state = false;
215                         i->second.off_timeout = Time::TimeStamp();
216                         signal_sensor.emit(i->first, false);
217                 }
218
219         if(update_sensors)
220         {
221                 unsigned max_addr = (--sensors.end())->first;
222                 unsigned char data[2];
223                 data[0] = 0;
224                 data[1] = (max_addr+7)/8;
225                 command(CMD_SENSOR_PARAM_SET, data, 2);
226                 command(CMD_SENSOR_REPORT);
227                 update_sensors = false;
228         }
229
230         if(!queue.empty() && command_sent)
231         {
232                 pollfd pfd = { serial_fd, POLLIN, 0 };
233                 if(poll(&pfd, 1, 0)>0)
234                 {
235                         process_reply(t);
236                         queue.erase(queue.begin());
237                         command_sent = false;
238                 }
239                 else
240                         return;
241         }
242
243         if(!queue.empty())
244         {
245                 const CommandSlot &slot = queue.front();
246                 write(serial_fd, slot.data, slot.length);
247                 command_sent = true;
248         }
249 }
250
251 void Intellibox::flush()
252 {
253         Time::TimeStamp t = Time::now();
254         for(list<CommandSlot>::iterator i=queue.begin(); i!=queue.end(); ++i)
255         {
256                 write(serial_fd, i->data, i->length);
257                 pollfd pfd = { serial_fd, POLLIN, 0 };
258                 bool first = true;
259                 while(poll(&pfd, 1, (first ? -1 : 0))>0)
260                 {
261                         char data[16];
262                         read(serial_fd, data, 16);
263                         first = false;
264                 }
265         }
266
267         queue.clear();
268         command_sent = false;
269 }
270
271 void Intellibox::command(Command cmd)
272 {
273         command(cmd, 0, 0);
274 }
275
276 void Intellibox::command(Command cmd, const unsigned char *data, unsigned len)
277 {
278         command(cmd, 0, data, len);
279 }
280
281 void Intellibox::command(Command cmd, unsigned addr, const unsigned char *data, unsigned len)
282 {
283         CommandSlot slot;
284         slot.cmd = cmd;
285         slot.addr = addr;
286         slot.data[0] = cmd;
287         copy(data, data+len, slot.data+1);
288         slot.length = 1+len;
289         queue.push_back(slot);
290 }
291
292 void Intellibox::loco_command(unsigned addr, unsigned speed, bool rev, unsigned funcs)
293 {
294         unsigned char data[4];
295         data[0] = addr&0xFF;
296         data[1] = (addr>>8)&0xFF;
297
298         if(speed==0)
299                 data[2] = 0;
300         else if(speed==1)
301                 data[2] = 2;
302         else
303                 data[2] = (speed*19-18)/2;
304         
305         data[3] = (rev ? 0 : 0x20) | ((funcs&1) ? 0x10 : 0);
306
307         if(!(funcs&0x100))
308                 data[3] |= 0x80 | ((funcs>>1)&0xF);
309
310         command(CMD_LOK, addr, data, 4);
311 }
312
313 void Intellibox::turnout_command(unsigned addr, bool state, bool active)
314 {
315         unsigned char data[2];
316         data[0] = addr&0xFF;
317         data[1] = ((addr>>8)&0x7) | (active ? 0x40 : 0) | (state ? 0x80 : 0);
318         command(CMD_TURNOUT, addr, data, 2);
319 }
320
321 void Intellibox::process_reply(const Time::TimeStamp &t)
322 {
323         Command cmd = queue.front().cmd;
324
325         if(cmd==CMD_STATUS)
326         {
327                 unsigned char status;
328                 read_all(&status, 1);
329                 power = status&0x08;
330                 signal_power.emit(power);
331         }
332         else if(cmd==CMD_EVENT)
333         {
334                 for(unsigned i=0;; ++i)
335                 {
336                         unsigned char byte;
337                         read_all(&byte, 1);
338
339                         if(i==0)
340                         {
341                                 if(byte&0x01)
342                                         command(CMD_EVENT_LOK);
343                                 if(byte&0x20)
344                                         command(CMD_EVENT_TURNOUT);
345                                 if(byte&0x04)
346                                         command(CMD_EVENT_SENSOR);
347                         }
348                         else if(i==1)
349                         {
350                                 if(byte&0x40)
351                                         command(CMD_STATUS);
352                         }
353
354                         if(!(byte&0x80))
355                                 break;
356                 }
357         }
358         else if(cmd==CMD_EVENT_LOK)
359         {
360                 while(1)
361                 {
362                         unsigned char data[5];
363                         read_all(data, 1);
364                         if(data[0]==0x80)
365                                 break;
366                         read_all(data+1, 4);
367                 }
368         }
369         else if(cmd==CMD_EVENT_TURNOUT)
370         {
371                 unsigned char count;
372                 read_all(&count, 1);
373                 for(unsigned i=0; i<count; ++i)
374                 {
375                         unsigned char data[2];
376                         read_all(data, 2);
377
378                         unsigned addr = data[0]+((data[1]&7)<<8);
379                         signal_turnout.emit(addr, (data[1]&0x80)!=0);
380                 }
381         }
382         else if(cmd==CMD_EVENT_SENSOR)
383         {
384                 while(1)
385                 {
386                         unsigned char mod;
387                         read_all(&mod, 1);
388                         if(!mod)
389                                 break;
390
391                         unsigned char data[2];
392                         read_all(data, 2);
393                         for(unsigned i=0; i<16; ++i)
394                         {
395                                 unsigned addr = mod*16+i-15;
396                                 bool state = (data[i/8]>>(7-i%8))&1;
397
398                                 Sensor &sensor = sensors[addr];
399                                 if(state)
400                                 {
401                                         sensor.off_timeout = Time::TimeStamp();
402                                         if(!sensor.state)
403                                         {
404                                                 sensor.state = state;
405                                                 signal_sensor(addr, state);
406                                         }
407                                 }
408                                 else if(sensor.state)
409                                         sensor.off_timeout = t+700*Time::msec;
410                         }
411                 }
412         }
413         else if(cmd==CMD_LOK)
414         {
415                 unsigned char err;
416                 read_all(&err, 1);
417
418                 if(err==ERR_NO_ERROR)
419                 {
420                         unsigned addr = queue.front().addr;
421                         Locomotive &loco = locos[addr];
422                         signal_loco_speed.emit(addr, loco.speed, loco.reverse);
423                 }
424         }
425         else if(cmd==CMD_TURNOUT)
426         {
427                 unsigned char err;
428                 read_all(&err, 1);
429
430                 unsigned addr = queue.front().addr;
431                 Turnout &turnout = turnouts[addr];
432
433                 if(err==ERR_NO_ERROR)
434                 {
435                         turnout.state = turnout.pending;
436                         if(turnout.active)
437                         {
438                                 signal_turnout.emit(addr, turnout.state);
439                                 turnout.off_timeout = t+500*Time::msec;
440                         }
441                 }
442                 else if(err==ERR_NO_I2C_SPACE)
443                         queue.push_back(queue.front());
444                 else
445                         turnout.pending = turnout.state;
446         }
447         else if(cmd==CMD_TURNOUT_STATUS)
448         {
449                 unsigned char err;
450                 read_all(&err, 1);
451
452                 if(err==ERR_NO_ERROR)
453                 {
454                         unsigned char data;
455                         read_all(&data, 1);
456
457                         unsigned addr = queue.front().addr;
458                         bool state = data&0x04;
459
460                         Turnout &turnout = turnouts[addr];
461                         if(state!=turnout.state)
462                         {
463                                 turnout.state = state;
464                                 signal_turnout.emit(addr, turnout.state);
465                         }
466                 }
467         }
468         else if(cmd==CMD_LOK_STATUS)
469         {
470                 unsigned char err;
471                 read_all(&err, 1);
472
473                 if(err==ERR_NO_ERROR)
474                 {
475                         unsigned char data[3];
476                         read_all(data, 3);
477
478                         unsigned addr = queue.front().addr;
479                         Locomotive &loco = locos[addr];
480
481                         unsigned speed = (data[0]<=1 ? 0 : data[0]*2/19+1);
482                         bool reverse = !(data[1]&0x20);
483                         if(speed!=loco.speed || reverse!=loco.reverse)
484                         {
485                                 loco.speed = speed;
486                                 loco.reverse = reverse;
487                                 signal_loco_speed.emit(addr, loco.speed, loco.reverse);
488                         }
489
490                         unsigned funcs = (data[1]&0xF)<<1;
491                         if(data[1]&0x10)
492                                 funcs |= 1;
493                         if(funcs!=loco.funcs)
494                         {
495                                 unsigned changed = loco.funcs^funcs;
496                                 loco.funcs = funcs;
497                                 for(unsigned i=0; i<5; ++i)
498                                         if(changed&(1<<i))
499                                                 signal_loco_function.emit(addr, i, loco.funcs&(1<<i));
500                         }
501                 }
502         }
503         else
504         {
505                 unsigned expected_bytes = 0;
506                 if(cmd==CMD_FUNC_STATUS)
507                         expected_bytes = 1;
508                 if(cmd==CMD_TURNOUT_GROUP_STATUS)
509                         expected_bytes = 2;
510                 if(cmd==CMD_LOK_CONFIG)
511                         expected_bytes = 4;
512
513                 unsigned char err;
514                 read_all(&err, 1);
515
516                 if(err==ERR_NO_ERROR)
517                 {
518                         unsigned char data[8];
519                         read_all(data, expected_bytes);
520                 }
521         }
522 }
523
524 unsigned Intellibox::read_all(unsigned char *buf, unsigned len)
525 {
526         unsigned pos = 0;
527         while(pos<len)
528                 pos += read(serial_fd, buf+pos, len-pos);
529
530         return pos;
531 }
532
533
534 Intellibox::Locomotive::Locomotive():
535         speed(0),
536         reverse(false),
537         funcs(0)
538 { }
539
540
541 Intellibox::Turnout::Turnout():
542         state(false),
543         active(false)
544 { }
545
546
547 Intellibox::Sensor::Sensor():
548         state(false)
549 { }
550
551 } // namespace Marklin