+/* $Id$
+
+This file is part of the MSP Märklin suite
+Copyright © 2010 Mikkosoft Productions, Mikko Rasa
+Distributed under the GPL
+*/
+
+#include <fcntl.h>
+#include <termios.h>
+#include <sys/poll.h>
+#include <msp/io/print.h>
+#include <msp/time/units.h>
+#include <msp/time/utils.h>
+#include "intellibox.h"
+
+using namespace std;
+using namespace Msp;
+
+namespace Marklin {
+
+Intellibox::Intellibox(const string &dev):
+ power(false),
+ update_sensors(false),
+ command_sent(false)
+{
+ serial_fd = ::open(dev.c_str(), O_RDWR);
+ if(serial_fd<0)
+ throw Exception("Couldn't open serial port\n");
+
+ static unsigned baud[]=
+ {
+ 2400, B2400,
+ 4800, B4800,
+ 9600, B9600,
+ 19200, B19200,
+ 0
+ };
+
+ termios attr;
+ tcgetattr(serial_fd, &attr);
+ cfmakeraw(&attr);
+ attr.c_cflag |= CSTOPB;
+
+ bool ok = false;
+ bool p50 = false;
+ for(unsigned i=0; baud[i]; i+=2)
+ {
+ cfsetospeed(&attr, baud[i+1]);
+ tcsetattr(serial_fd, TCSADRAIN, &attr);
+
+ write(serial_fd, "\xC4", 1);
+
+ pollfd pfd = { serial_fd, POLLIN, 0 };
+ if(poll(&pfd, 1, 500)>0)
+ {
+ IO::print("IB detected at %d bits/s\n", baud[i]);
+ char buf[2];
+ p50 = (read(serial_fd, buf, 2)==2);
+ ok = true;
+ break;
+ }
+ }
+
+ if(!ok)
+ throw Exception("IB not detected");
+
+ if(p50)
+ write(serial_fd, "xZzA1\r", 6);
+
+ command(CMD_STATUS);
+}
+
+void Intellibox::set_power(bool p)
+{
+ power = p;
+ if(power)
+ command(CMD_POWER_ON);
+ else
+ command(CMD_POWER_OFF);
+ signal_power.emit(power);
+}
+
+void Intellibox::add_loco(unsigned addr)
+{
+ if(!locos.count(addr))
+ {
+ locos[addr];
+
+ unsigned char data[2];
+ data[0] = addr&0xFF;
+ data[1] = (addr>>8)&0xFF;
+ command(CMD_LOK_STATUS, addr, data, 2);
+ }
+}
+
+void Intellibox::set_loco_speed(unsigned addr, unsigned speed)
+{
+ Locomotive &loco = locos[addr];
+ loco.speed = speed;
+ loco_command(addr, speed, loco.reverse, loco.funcs|0x100);
+ signal_loco_speed.emit(addr, speed, loco.reverse);
+}
+
+void Intellibox::set_loco_reverse(unsigned addr, bool rev)
+{
+ Locomotive &loco = locos[addr];
+ loco.reverse = rev;
+ loco_command(addr, loco.speed, rev, loco.funcs|0x100);
+ signal_loco_speed.emit(addr, loco.speed, rev);
+}
+
+void Intellibox::set_loco_function(unsigned addr, unsigned func, bool state)
+{
+ Locomotive &loco = locos[addr];
+ if(state)
+ loco.funcs |= 1<<func;
+ else
+ loco.funcs &= ~(1<<func);
+ loco_command(addr, loco.speed, loco.reverse, loco.funcs);
+ signal_loco_function.emit(addr, func, state);
+}
+
+void Intellibox::add_turnout(unsigned addr)
+{
+ if(!turnouts.count(addr))
+ {
+ turnouts[addr];
+
+ unsigned char data[2];
+ data[0] = addr&0xFF;
+ data[1] = (addr>>8)&0xFF;
+ command(CMD_TURNOUT_STATUS, addr, data, 2);
+ }
+}
+
+void Intellibox::set_turnout(unsigned addr, bool state)
+{
+ Turnout &turnout = turnouts[addr];
+ if(state==turnout.state || state==turnout.pending)
+ return;
+
+ turnout.pending = state;
+ turnout.active = true;
+
+ turnout_command(addr, state, true);
+}
+
+bool Intellibox::get_turnout(unsigned addr) const
+{
+ map<unsigned, Turnout>::const_iterator i = turnouts.find(addr);
+ if(i!=turnouts.end())
+ return i->second.state;
+ return false;
+}
+
+void Intellibox::add_sensor(unsigned addr)
+{
+ if(!sensors.count(addr))
+ {
+ sensors[addr];
+ update_sensors = true;
+ }
+}
+
+bool Intellibox::get_sensor(unsigned addr) const
+{
+ map<unsigned, Sensor>::const_iterator i = sensors.find(addr);
+ if(i!=sensors.end())
+ return i->second.state;
+ return false;
+}
+
+void Intellibox::tick()
+{
+ const Time::TimeStamp t = Time::now();
+
+ if(t>next_event_query)
+ {
+ next_event_query = t+200*Time::msec;
+ command(CMD_EVENT);
+ }
+
+ for(map<unsigned, Turnout>::iterator i=turnouts.begin(); i!=turnouts.end(); ++i)
+ if(i->second.active && i->second.off_timeout && t>i->second.off_timeout)
+ {
+ i->second.active = false;
+ i->second.off_timeout = Time::TimeStamp();
+ turnout_command(i->first, i->second.state, false);
+ }
+
+ for(map<unsigned, Sensor>::iterator i=sensors.begin(); i!=sensors.end(); ++i)
+ if(i->second.off_timeout && t>i->second.off_timeout)
+ {
+ i->second.state = false;
+ i->second.off_timeout = Time::TimeStamp();
+ signal_sensor.emit(i->first, false);
+ }
+
+ if(update_sensors)
+ {
+ unsigned max_addr = (--sensors.end())->first;
+ unsigned char data[2];
+ data[0] = 0;
+ data[1] = (max_addr+7)/8;
+ command(CMD_SENSOR_PARAM_SET, data, 2);
+ command(CMD_SENSOR_REPORT);
+ update_sensors = false;
+ }
+
+ if(!queue.empty() && command_sent)
+ {
+ pollfd pfd = { serial_fd, POLLIN, 0 };
+ if(poll(&pfd, 1, 0)>0)
+ {
+ process_reply(t);
+ queue.erase(queue.begin());
+ command_sent = false;
+ }
+ else
+ return;
+ }
+
+ if(!queue.empty())
+ {
+ const CommandSlot &slot = queue.front();
+ write(serial_fd, slot.data, slot.length);
+ command_sent = true;
+ }
+}
+
+void Intellibox::flush()
+{
+ for(list<CommandSlot>::iterator i=queue.begin(); i!=queue.end(); ++i)
+ write(serial_fd, i->data, i->length);
+}
+
+void Intellibox::command(Command cmd)
+{
+ command(cmd, 0, 0);
+}
+
+void Intellibox::command(Command cmd, const unsigned char *data, unsigned len)
+{
+ command(cmd, 0, data, len);
+}
+
+void Intellibox::command(Command cmd, unsigned addr, const unsigned char *data, unsigned len)
+{
+ CommandSlot slot;
+ slot.cmd = cmd;
+ slot.addr = addr;
+ slot.data[0] = cmd;
+ copy(data, data+len, slot.data+1);
+ slot.length = 1+len;
+ queue.push_back(slot);
+}
+
+void Intellibox::loco_command(unsigned addr, unsigned speed, bool rev, unsigned funcs)
+{
+ unsigned char data[4];
+ data[0] = addr&0xFF;
+ data[1] = (addr>>8)&0xFF;
+
+ if(speed==0)
+ data[2] = 0;
+ else if(speed==1)
+ data[2] = 2;
+ else
+ data[2] = (speed*19-18)/2;
+
+ data[3] = (rev ? 0 : 0x20) | ((funcs&1) ? 0x10 : 0);
+
+ if(!(funcs&0x100))
+ data[3] |= 0x80 | ((funcs>>1)&0xF);
+
+ command(CMD_LOK, addr, data, 4);
+}
+
+void Intellibox::turnout_command(unsigned addr, bool state, bool active)
+{
+ unsigned char data[2];
+ data[0] = addr&0xFF;
+ data[1] = ((addr>>8)&0x7) | (active ? 0x40 : 0) | (state ? 0x80 : 0);
+ command(CMD_TURNOUT, addr, data, 2);
+}
+
+void Intellibox::process_reply(const Time::TimeStamp &t)
+{
+ Command cmd = queue.front().cmd;
+
+ if(cmd==CMD_STATUS)
+ {
+ unsigned char status;
+ read_all(&status, 1);
+ power = status&0x08;
+ signal_power.emit(power);
+ }
+ else if(cmd==CMD_EVENT)
+ {
+ for(unsigned i=0;; ++i)
+ {
+ unsigned char byte;
+ read_all(&byte, 1);
+
+ if(i==0)
+ {
+ if(byte&0x01)
+ command(CMD_EVENT_LOK);
+ if(byte&0x20)
+ command(CMD_EVENT_TURNOUT);
+ if(byte&0x04)
+ command(CMD_EVENT_SENSOR);
+ }
+ else if(i==1)
+ {
+ if(byte&0x40)
+ command(CMD_STATUS);
+ }
+
+ if(!(byte&0x80))
+ break;
+ }
+ }
+ else if(cmd==CMD_EVENT_LOK)
+ {
+ while(1)
+ {
+ unsigned char data[5];
+ read_all(data, 1);
+ if(data[0]==0x80)
+ break;
+ read_all(data+1, 4);
+ }
+ }
+ else if(cmd==CMD_EVENT_TURNOUT)
+ {
+ unsigned char count;
+ read_all(&count, 1);
+ for(unsigned i=0; i<count; ++i)
+ {
+ unsigned char data[2];
+ read_all(data, 2);
+
+ unsigned addr = data[0]+((data[1]&7)<<8);
+ signal_turnout.emit(addr, (data[1]&0x80)!=0);
+ }
+ }
+ else if(cmd==CMD_EVENT_SENSOR)
+ {
+ while(1)
+ {
+ unsigned char mod;
+ read_all(&mod, 1);
+ if(!mod)
+ break;
+
+ unsigned char data[2];
+ read_all(data, 2);
+ for(unsigned i=0; i<16; ++i)
+ {
+ unsigned addr = mod*16+i-15;
+ bool state = (data[i/8]>>(7-i%8))&1;
+
+ Sensor &sensor = sensors[addr];
+ if(state)
+ {
+ sensor.off_timeout = Time::TimeStamp();
+ if(!sensor.state)
+ {
+ sensor.state = state;
+ signal_sensor(addr, state);
+ }
+ }
+ else if(sensor.state)
+ sensor.off_timeout = t+700*Time::msec;
+ }
+ }
+ }
+ else if(cmd==CMD_TURNOUT)
+ {
+ unsigned char err;
+ read_all(&err, 1);
+
+ if(err==ERR_NO_ERROR)
+ {
+ unsigned addr = queue.front().addr;
+ Turnout &turnout = turnouts[addr];
+ turnout.state = turnout.pending;
+ if(turnout.active)
+ {
+ signal_turnout.emit(addr, turnout.state);
+ turnout.off_timeout = t+500*Time::msec;
+ }
+ }
+ else if(err==ERR_NO_I2C_SPACE)
+ queue.push_back(queue.front());
+ }
+ else if(cmd==CMD_TURNOUT_STATUS)
+ {
+ unsigned char err;
+ read_all(&err, 1);
+
+ if(err==ERR_NO_ERROR)
+ {
+ unsigned char data;
+ read_all(&data, 1);
+
+ unsigned addr = queue.front().addr;
+ bool state = data&0x04;
+
+ Turnout &turnout = turnouts[addr];
+ if(state!=turnout.state)
+ {
+ turnout.state = state;
+ signal_turnout.emit(addr, turnout.state);
+ }
+ }
+ }
+ else if(cmd==CMD_LOK_STATUS)
+ {
+ unsigned char err;
+ read_all(&err, 1);
+
+ if(err==ERR_NO_ERROR)
+ {
+ unsigned char data[3];
+ read_all(data, 3);
+
+ unsigned addr = queue.front().addr;
+ Locomotive &loco = locos[addr];
+
+ unsigned speed = (data[0]<=1 ? 0 : data[0]*2/19+1);
+ bool reverse = !(data[1]&0x20);
+ if(speed!=loco.speed || reverse!=loco.reverse)
+ {
+ loco.speed = speed;
+ loco.reverse = reverse;
+ signal_loco_speed.emit(addr, loco.speed, loco.reverse);
+ }
+
+ unsigned funcs = (data[1]&0xF)<<1;
+ if(data[1]&0x10)
+ funcs |= 1;
+ if(funcs!=loco.funcs)
+ {
+ unsigned changed = loco.funcs^funcs;
+ loco.funcs = funcs;
+ for(unsigned i=0; i<5; ++i)
+ if(changed&(1<<i))
+ signal_loco_function.emit(addr, i, loco.funcs&(1<<i));
+ }
+ }
+ }
+ else
+ {
+ unsigned expected_bytes = 0;
+ if(cmd==CMD_FUNC_STATUS)
+ expected_bytes = 1;
+ if(cmd==CMD_TURNOUT_GROUP_STATUS)
+ expected_bytes = 2;
+ if(cmd==CMD_LOK_CONFIG)
+ expected_bytes = 4;
+
+ unsigned char err;
+ read_all(&err, 1);
+
+ if(err==ERR_NO_ERROR)
+ {
+ unsigned char data[8];
+ read_all(data, expected_bytes);
+ }
+ }
+}
+
+unsigned Intellibox::read_all(unsigned char *buf, unsigned len)
+{
+ unsigned pos = 0;
+ while(pos<len)
+ pos += read(serial_fd, buf+pos, len-pos);
+
+ return pos;
+}
+
+
+Intellibox::Locomotive::Locomotive():
+ speed(0),
+ reverse(false),
+ funcs(0)
+{ }
+
+
+Intellibox::Turnout::Turnout():
+ state(false),
+ active(false)
+{ }
+
+
+Intellibox::Sensor::Sensor():
+ state(false)
+{ }
+
+} // namespace Marklin