+/* $Id$
+
+This file is part of R²C²
+Copyright © 2010 Mikkosoft Productions, Mikko Rasa
+Distributed under the GPL
+*/
+
+#ifndef LIBR2C2_INTELLIBOX_H_
+#define LIBR2C2_INTELLIBOX_H_
+
+#include <map>
+#include <msp/time/timestamp.h>
+#include "driver.h"
+
+namespace R2C2 {
+
+/**
+Driver for Uhlenbrock Intellibox. Uses the P50X binary protocol over RS232.
+
+Motorola decoders with 27 speed steps are supported by manually generating the
+commands necessary to reach the "half-steps". However, sending a rapid stream
+of speed changes to the same locomotive seems to cause excessive lag, so we
+cheat a bit; instead of sending the half-step command immediately, we send it
+with a 500ms delay, but only if no new set_loco_speed calls have occurred. As
+a downside from this accelerations and decelerations are still jerky.
+*/
+class Intellibox: public Driver
+{
+private:
+ enum Command
+ {
+ CMD_LOK=0x80,
+ CMD_LOK_STATUS=0x84,
+ CMD_LOK_CONFIG=0x85,
+ CMD_FUNC=0x88,
+ CMD_FUNC_STATUS=0x8C,
+ CMD_TURNOUT=0x90,
+ CMD_TURNOUT_FREE=0x93,
+ CMD_TURNOUT_STATUS=0x94,
+ CMD_TURNOUT_GROUP_STATUS=0x95,
+ CMD_SENSOR_STATUS=0x98,
+ CMD_SENSOR_REPORT=0x99,
+ CMD_SENSOR_PARAM_SET=0x9D,
+ CMD_STATUS=0xA2,
+ CMD_POWER_OFF=0xA6,
+ CMD_POWER_ON=0xA7,
+ CMD_NOP=0xC4,
+ CMD_EVENT=0xC8,
+ CMD_EVENT_LOK=0xC9,
+ CMD_EVENT_TURNOUT=0xCA,
+ CMD_EVENT_SENSOR=0xCB
+ };
+
+ enum Error
+ {
+ ERR_NO_ERROR=0,
+ ERR_SYS_ERROR,
+ ERR_BAD_PARAM,
+ ERR_POWER_OFF=0x6,
+ ERR_NO_LOK_SPACE=0x8, // No space in lok command buffer
+ ERR_NO_TURNOUT_SPACE, // No space in turnout command buffer
+ ERR_NO_DATA, // "no Lok status available (Lok is not in a slot)"
+ ERR_NO_SLOT, // "there is no slot available"
+ ERR_BAD_LOK_ADDR,
+ ERR_LOK_BUSY,
+ ERR_BAD_TURNOUT_ADDR,
+ ERR_BAD_SO_VALUE,
+ ERR_NO_I2C_SPACE,
+ ERR_LOW_TURNOUT_SPACE=0x40,
+ ERR_LOK_HALTED,
+ ERR_LOK_POWER_OFF,
+ };
+
+ enum Protocol
+ {
+ MM,
+ MM_27
+ };
+
+ struct Locomotive
+ {
+ Protocol protocol;
+ unsigned speed;
+ bool reverse;
+ unsigned funcs;
+ int pending_half_step;
+ Msp::Time::TimeStamp half_step_delay;
+
+ Locomotive();
+ };
+
+ struct Turnout
+ {
+ bool state;
+ bool active;
+ bool pending;
+ Msp::Time::TimeStamp off_timeout;
+
+ Turnout();
+ };
+
+ struct Sensor
+ {
+ bool state;
+ Msp::Time::TimeStamp off_timeout;
+
+ Sensor();
+ };
+
+ struct CommandSlot
+ {
+ Command cmd;
+ unsigned addr;
+ unsigned char data[8];
+ unsigned length;
+ };
+
+ int serial_fd;
+ bool power;
+ bool halted;
+ std::map<unsigned, Locomotive> locos;
+ std::map<unsigned, Turnout> turnouts;
+ std::map<unsigned, Sensor> sensors;
+ bool update_sensors;
+ std::list<CommandSlot> queue;
+ bool command_sent;
+ Msp::Time::TimeStamp next_event_query;
+
+public:
+ Intellibox(const std::string &);
+
+ virtual void set_power(bool);
+ virtual bool get_power() const { return power; }
+ virtual void halt(bool);
+ virtual bool is_halted() const { return halted; }
+
+ virtual const char *enumerate_protocols(unsigned) const;
+ virtual unsigned get_protocol_speed_steps(const std::string &) const;
+ virtual void add_loco(unsigned, const std::string &);
+ virtual void set_loco_speed(unsigned, unsigned);
+ virtual void set_loco_reverse(unsigned, bool);
+ virtual void set_loco_function(unsigned, unsigned, bool);
+
+ virtual void add_turnout(unsigned);
+ virtual void set_turnout(unsigned, bool);
+ virtual bool get_turnout(unsigned) const;
+
+ virtual void add_sensor(unsigned);
+ virtual void set_sensor(unsigned, bool) { }
+ virtual bool get_sensor(unsigned) const;
+
+ virtual void tick();
+ virtual void flush();
+
+private:
+ Protocol map_protocol(const std::string &) const;
+ void command(Command);
+ void command(Command, const unsigned char *, unsigned);
+ void command(Command, unsigned, const unsigned char *, unsigned);
+ void loco_command(unsigned, unsigned, bool, unsigned);
+ void turnout_command(unsigned, bool, bool);
+ void process_reply(const Msp::Time::TimeStamp &);
+ unsigned read_all(unsigned char *, unsigned);
+ unsigned read_status(Error *);
+ void error(Command, Error);
+};
+
+} // namespace R2C2
+
+#endif