--- /dev/null
+#include <msp/core/maputils.h>
+#include <msp/io/print.h>
+#include <msp/time/utils.h>
+#include "arducontrol.h"
+#include "tracktype.h"
+
+using namespace std;
+using namespace Msp;
+
+namespace R2C2 {
+
+ArduControl::ArduControl(const string &dev):
+ serial(dev),
+ debug(true),
+ power(false),
+ next_refresh(refresh_cycle.end()),
+ refresh_counter(0),
+ active_accessory(0),
+ n_s88_octets(0),
+ thread(*this)
+{
+}
+
+ArduControl::~ArduControl()
+{
+ thread.exit();
+}
+
+void ArduControl::set_power(bool p)
+{
+ if(p==power.pending)
+ return;
+
+ power.pending = p;
+ ++power.serial;
+
+ QueuedCommand cmd(POWER);
+ cmd.tag.serial = power.serial;
+ cmd.command[0] = (p ? POWER_ON : POWER_OFF);
+ cmd.length = 1;
+ push_command(cmd);
+}
+
+void ArduControl::halt(bool)
+{
+}
+
+const char *ArduControl::enumerate_protocols(unsigned i) const
+{
+ if(i==0)
+ return "MM";
+ else
+ return 0;
+}
+
+ArduControl::Protocol ArduControl::map_protocol(const string &proto_name)
+{
+ if(proto_name=="MM")
+ return MM;
+ else
+ throw invalid_argument("ArduControl::map_protocol");
+}
+
+unsigned ArduControl::get_protocol_speed_steps(const string &proto_name) const
+{
+ Protocol proto = map_protocol(proto_name);
+ if(proto==MM)
+ return 14;
+ else
+ return 0;
+}
+
+void ArduControl::add_loco(unsigned addr, const string &proto_name, const VehicleType &)
+{
+ if(!addr)
+ throw invalid_argument("ArduControl::add_loco");
+
+ Protocol proto = map_protocol(proto_name);
+
+ if(proto==MM)
+ {
+ if(addr>=80)
+ throw invalid_argument("ArduControl::add_loco");
+ }
+
+ insert_unique(locomotives, addr, Locomotive(proto, addr));
+}
+
+void ArduControl::remove_loco(unsigned addr)
+{
+ Locomotive &loco = get_item(locomotives, addr);
+ remove_loco_from_refresh(loco);
+ locomotives.erase(addr);
+}
+
+void ArduControl::set_loco_speed(unsigned addr, unsigned speed)
+{
+ Locomotive &loco = get_item(locomotives, addr);
+ if(loco.speed.set(speed))
+ {
+ QueuedCommand cmd(loco, Locomotive::SPEED);
+ push_command(cmd);
+
+ add_loco_to_refresh(loco);
+ }
+}
+
+void ArduControl::set_loco_reverse(unsigned addr, bool rev)
+{
+ Locomotive &loco = get_item(locomotives, addr);
+ if(loco.reverse.set(rev))
+ {
+ QueuedCommand cmd(loco, Locomotive::REVERSE);
+ push_command(cmd);
+
+ add_loco_to_refresh(loco);
+ }
+}
+
+void ArduControl::set_loco_function(unsigned addr, unsigned func, bool state)
+{
+ Locomotive &loco = get_item(locomotives, addr);
+ unsigned mask = 1<<func;
+ if(loco.funcs.set((loco.funcs&~mask)|(mask*state)))
+ {
+ if(func>0)
+ {
+ QueuedCommand cmd(loco, Locomotive::FUNCTIONS, func);
+ push_command(cmd);
+ }
+
+ add_loco_to_refresh(loco);
+ }
+}
+
+void ArduControl::add_loco_to_refresh(Locomotive &loco)
+{
+ MutexLock lock(mutex);
+ refresh_cycle.push_back(&loco);
+ if(refresh_cycle.size()>15)
+ {
+ LocomotivePtrList::iterator oldest = refresh_cycle.begin();
+ for(LocomotivePtrList::iterator i=refresh_cycle.begin(); ++i!=refresh_cycle.end(); )
+ if((*i)->last_change_age>(*oldest)->last_change_age)
+ oldest = i;
+ if(oldest==next_refresh)
+ advance_next_refresh();
+ refresh_cycle.erase(oldest);
+ }
+ if(next_refresh==refresh_cycle.end())
+ next_refresh = refresh_cycle.begin();
+}
+
+void ArduControl::remove_loco_from_refresh(Locomotive &loco)
+{
+ MutexLock lock(mutex);
+ for(LocomotivePtrList::iterator i=refresh_cycle.begin(); i!=refresh_cycle.end(); ++i)
+ if(*i==&loco)
+ {
+ if(i==next_refresh)
+ {
+ if(refresh_cycle.size()>1)
+ advance_next_refresh();
+ else
+ next_refresh = refresh_cycle.end();
+ }
+ refresh_cycle.erase(i);
+ return;
+ }
+}
+
+ArduControl::Locomotive *ArduControl::get_loco_to_refresh()
+{
+ MutexLock lock(mutex);
+ if(refresh_cycle.empty())
+ return 0;
+
+ Locomotive *loco = *next_refresh;
+ advance_next_refresh();
+ return loco;
+}
+
+void ArduControl::advance_next_refresh()
+{
+ ++next_refresh;
+ if(next_refresh==refresh_cycle.end())
+ {
+ next_refresh = refresh_cycle.begin();
+ ++refresh_counter;
+ }
+}
+
+void ArduControl::add_turnout(unsigned addr, const TrackType &type)
+{
+ if(!addr || !type.is_turnout())
+ throw invalid_argument("ArduControl::add_turnout");
+
+ add_accessory(Accessory::TURNOUT, addr, type.get_state_bits());
+}
+
+void ArduControl::remove_turnout(unsigned addr)
+{
+ remove_accessory(Accessory::TURNOUT, addr);
+}
+
+void ArduControl::set_turnout(unsigned addr, unsigned state)
+{
+ set_accessory(Accessory::TURNOUT, addr, state);
+}
+
+unsigned ArduControl::get_turnout(unsigned addr) const
+{
+ return get_accessory(Accessory::TURNOUT, addr);
+}
+
+void ArduControl::add_signal(unsigned addr, const SignalType &)
+{
+ add_accessory(Accessory::SIGNAL, addr, 1);
+}
+
+void ArduControl::remove_signal(unsigned addr)
+{
+ remove_accessory(Accessory::SIGNAL, addr);
+}
+
+void ArduControl::set_signal(unsigned addr, unsigned state)
+{
+ set_accessory(Accessory::SIGNAL, addr, state);
+}
+
+unsigned ArduControl::get_signal(unsigned addr) const
+{
+ return get_accessory(Accessory::SIGNAL, addr);
+}
+
+void ArduControl::add_accessory(Accessory::Kind kind, unsigned addr, unsigned bits)
+{
+ AccessoryMap::iterator i = accessories.lower_bound(addr);
+ AccessoryMap::iterator j = accessories.upper_bound(addr+bits-1);
+ if(i!=j)
+ throw key_error(addr);
+ if(i!=accessories.begin())
+ {
+ --i;
+ if(i->first+i->second.bits>addr)
+ throw key_error(addr);
+ }
+
+ insert_unique(accessories, addr, Accessory(kind, addr, bits));
+}
+
+void ArduControl::remove_accessory(Accessory::Kind kind, unsigned addr)
+{
+ Accessory &acc = get_item(accessories, addr);
+ if(acc.kind!=kind)
+ throw key_error(addr);
+ accessories.erase(addr);
+}
+
+void ArduControl::set_accessory(Accessory::Kind kind, unsigned addr, unsigned state)
+{
+ Accessory &acc = get_item(accessories, addr);
+ if(acc.kind!=kind)
+ throw key_error(addr);
+
+ if(state!=acc.target)
+ {
+ acc.target = state;
+ accessory_queue.push_back(&acc);
+ }
+}
+
+unsigned ArduControl::get_accessory(Accessory::Kind kind, unsigned addr) const
+{
+ const Accessory &acc = get_item(accessories, addr);
+ if(acc.kind!=kind)
+ throw key_error(addr);
+ return acc.state;
+}
+
+void ArduControl::add_sensor(unsigned addr)
+{
+ if(!addr)
+ throw invalid_argument("ArduControl::add_sensor");
+
+ insert_unique(sensors, addr, Sensor(addr));
+ unsigned octet_index = (addr-1)/8;
+ if(octet_index>=n_s88_octets)
+ n_s88_octets = octet_index+1;
+}
+
+void ArduControl::remove_sensor(unsigned addr)
+{
+ remove_existing(sensors, addr);
+ // TODO update n_s88_octets
+}
+
+bool ArduControl::get_sensor(unsigned addr) const
+{
+ return get_item(sensors, addr).state;
+}
+
+void ArduControl::tick()
+{
+ while(Tag tag = pop_completed_tag())
+ {
+ if(tag.type==Tag::GENERAL)
+ {
+ if(tag.command==POWER)
+ {
+ if(power.commit(tag.serial))
+ signal_power.emit(power.current);
+ }
+ }
+ else if(tag.type==Tag::LOCOMOTIVE)
+ {
+ LocomotiveMap::iterator i = locomotives.find(tag.address);
+ if(i==locomotives.end())
+ continue;
+
+ Locomotive &loco = i->second;
+ if(tag.command==Locomotive::SPEED)
+ {
+ if(loco.speed.commit(tag.serial))
+ signal_loco_speed.emit(loco.address, loco.speed, loco.reverse);
+ }
+ else if(tag.command==Locomotive::REVERSE)
+ {
+ if(loco.reverse.commit(tag.serial))
+ signal_loco_speed.emit(loco.address, loco.speed, loco.reverse);
+ }
+ else if(tag.command==Locomotive::FUNCTIONS)
+ {
+ unsigned old = loco.funcs;
+ if(loco.funcs.commit(tag.serial))
+ {
+ unsigned changed = old^loco.funcs;
+ for(unsigned j=0; changed>>j; ++j)
+ if((changed>>j)&1)
+ signal_loco_function.emit(loco.address, j, (loco.funcs>>j)&1);
+ }
+ }
+ }
+ else if(tag.type==Tag::ACCESSORY)
+ {
+ AccessoryMap::iterator i = accessories.find(tag.address);
+ if(i==accessories.end())
+ continue;
+
+ Accessory &acc = i->second;
+ if(tag.command==Accessory::ACTIVATE)
+ {
+ off_timeout = Time::now()+acc.active_time;
+ }
+ else if(tag.command==Accessory::DEACTIVATE)
+ {
+ if(acc.state.commit(tag.serial))
+ {
+ if(acc.state==acc.target)
+ {
+ if(acc.kind==Accessory::TURNOUT)
+ signal_turnout.emit(acc.address, acc.state);
+ else if(acc.kind==Accessory::SIGNAL)
+ signal_signal.emit(acc.address, acc.state);
+ }
+ if(&acc==active_accessory)
+ active_accessory = 0;
+ }
+ }
+ }
+ else if(tag.type==Tag::SENSOR)
+ {
+ SensorMap::iterator i = sensors.find(tag.address);
+ if(i==sensors.end())
+ continue;
+
+ Sensor &sensor = i->second;
+ if(tag.command==Sensor::STATE)
+ {
+ if(sensor.state.commit(tag.serial))
+ signal_sensor.emit(sensor.address, sensor.state);
+ }
+ }
+ }
+
+ while(!active_accessory && !accessory_queue.empty())
+ {
+ Accessory &acc = *accessory_queue.front();
+
+ if(acc.state!=acc.target)
+ {
+ active_accessory = &acc;
+
+ unsigned changes = acc.state^acc.target;
+ unsigned lowest_bit = changes&~(changes-1);
+ unsigned i;
+ for(i=0; (lowest_bit>>i)>1; ++i) ;
+ acc.state.set(acc.state^lowest_bit);
+ QueuedCommand cmd(acc, Accessory::ACTIVATE, i);
+ push_command(cmd);
+ }
+ else
+ accessory_queue.pop_front();
+ }
+
+ if(active_accessory && off_timeout)
+ {
+ Time::TimeStamp t = Time::now();
+ if(t>off_timeout)
+ {
+ off_timeout = Time::TimeStamp();
+ QueuedCommand cmd(*active_accessory, Accessory::DEACTIVATE);
+ push_command(cmd);
+ }
+ }
+}
+
+void ArduControl::flush()
+{
+}
+
+void ArduControl::push_command(const QueuedCommand &cmd)
+{
+ MutexLock lock(mutex);
+ command_queue.push_back(cmd);
+}
+
+bool ArduControl::pop_command(QueuedCommand &cmd)
+{
+ MutexLock lock(mutex);
+ if(command_queue.empty())
+ return false;
+ cmd = command_queue.front();
+ command_queue.pop_front();
+ return true;
+}
+
+void ArduControl::push_completed_tag(const Tag &tag)
+{
+ MutexLock lock(mutex);
+ completed_commands.push_back(tag);
+}
+
+ArduControl::Tag ArduControl::pop_completed_tag()
+{
+ MutexLock lock(mutex);
+ if(completed_commands.empty())
+ return Tag();
+ Tag tag = completed_commands.front();
+ completed_commands.pop_front();
+ return tag;
+}
+
+
+ArduControl::Tag::Tag():
+ type(NONE),
+ command(0),
+ serial(0),
+ address(0)
+{ }
+
+
+ArduControl::Locomotive::Locomotive(Protocol p, unsigned a):
+ proto(p),
+ address(a),
+ speed(0),
+ reverse(false),
+ funcs(0),
+ last_change_age(0)
+{ }
+
+unsigned ArduControl::Locomotive::create_speed_dir_command(char *buffer) const
+{
+ if(proto==MM)
+ {
+ buffer[0] = MOTOROLA_SPEED_DIRECTION;
+ buffer[1] = address;
+ buffer[2] = funcs.pending&1;
+ buffer[3] = speed.pending+reverse.pending*0x80;
+ return 4;
+ }
+ else
+ return 0;
+}
+
+unsigned ArduControl::Locomotive::create_speed_func_command(unsigned f, char *buffer) const
+{
+ if(proto==MM)
+ {
+ if(f<1 || f>4)
+ throw invalid_argument("Locomotive::create_speed_func_command");
+
+ buffer[0] = MOTOROLA_SPEED_FUNCTION;
+ buffer[1] = address;
+ buffer[2] = (f<<4)|(((funcs.pending>>f)&1)<<1)|(funcs.pending&1);
+ buffer[3] = speed.pending;
+ return 4;
+ }
+ else
+ return 0;
+}
+
+
+ArduControl::Accessory::Accessory(Kind k, unsigned a, unsigned b):
+ kind(k),
+ address(a),
+ bits(b),
+ state(0),
+ active_time(500*Time::msec)
+{ }
+
+unsigned ArduControl::Accessory::create_state_command(unsigned b, bool c, char *buffer) const
+{
+ if(b>=bits)
+ throw invalid_argument("Accessory::create_state_command");
+
+ unsigned a = (address+b+3)*2;
+ if((state.pending>>b)&1)
+ ++a;
+ buffer[0] = MOTOROLA_SOLENOID;
+ buffer[1] = a>>3;
+ buffer[2] = ((a&7)<<4)|c;
+ return 3;
+}
+
+
+ArduControl::Sensor::Sensor(unsigned a):
+ address(a),
+ state(false)
+{ }
+
+
+ArduControl::ControlThread::ControlThread(ArduControl &c):
+ control(c),
+ done(false)
+{
+ launch();
+}
+
+void ArduControl::ControlThread::exit()
+{
+ done = true;
+ join();
+}
+
+void ArduControl::ControlThread::main()
+{
+ char command[15];
+ unsigned length = 0;
+ unsigned repeat_count = 0;
+ Tag tag;
+ Locomotive *loco = 0;
+ unsigned phase = 0;
+ unsigned s88_octets_remaining = 0;
+ while(!done)
+ {
+ if(!repeat_count)
+ {
+ tag = Tag();
+ length = 0;
+ repeat_count = 1;
+ QueuedCommand qcmd;
+ if(control.pop_command(qcmd))
+ {
+ length = qcmd.length;
+ copy(qcmd.command, qcmd.command+length, command);
+ if(qcmd.tag.type==Tag::LOCOMOTIVE)
+ repeat_count = 8;
+ tag = qcmd.tag;
+ }
+ else if(loco && phase==0)
+ {
+ length = loco->create_speed_func_command(control.refresh_counter%4+1, command);
+ repeat_count = 2;
+ ++phase;
+ }
+ else if(!s88_octets_remaining && control.n_s88_octets)
+ {
+ command[0] = S88_READ;
+ command[1] = control.n_s88_octets;
+ length = 2;
+ s88_octets_remaining = control.n_s88_octets;
+ }
+ else if((loco = control.get_loco_to_refresh()))
+ {
+ length = loco->create_speed_dir_command(command);
+ repeat_count = 2;
+ phase = 0;
+ }
+ else
+ {
+ // Send an idle packet for the MM protocol
+ command[0] = MOTOROLA_SPEED;
+ command[1] = 80;
+ command[2] = 0;
+ command[3] = 0;
+ length = 4;
+ }
+ }
+
+ if(control.debug)
+ {
+ string cmd_hex;
+ for(unsigned i=0; i<length; ++i)
+ cmd_hex += format(" %02X", static_cast<unsigned char>(command[i]));
+ IO::print("< %02X%s\n", length^0xFF, cmd_hex);
+ }
+
+ control.serial.put(length^0xFF);
+ control.serial.write(command, length);
+ --repeat_count;
+
+ bool got_reply = false;
+ bool got_data = false;
+ while(!got_reply || got_data)
+ {
+ if(got_reply)
+ got_data = IO::poll(control.serial, IO::P_INPUT, Time::zero);
+ else
+ got_data = IO::poll(control.serial, IO::P_INPUT);
+
+ if(got_data)
+ {
+ unsigned rlength = control.serial.get()^0xFF;
+ if(rlength>15)
+ {
+ IO::print("Invalid length %02X\n", rlength);
+ continue;
+ }
+
+ char reply[15];
+ unsigned pos = 0;
+ while(pos<rlength)
+ pos += control.serial.read(reply+pos, rlength-pos);
+
+ if(control.debug)
+ {
+ string reply_hex;
+ for(unsigned i=0; i<rlength; ++i)
+ reply_hex += format(" %02X", static_cast<unsigned char>(reply[i]));
+ IO::print("> %02X%s\n", rlength^0xFF, reply_hex);
+ }
+
+ unsigned char type = reply[0];
+ if((type&0xE0)==0x80)
+ {
+ got_reply = true;
+ if(type!=COMMAND_OK)
+ IO::print("Error %02X\n", type);
+ else if(tag && !repeat_count)
+ control.push_completed_tag(tag);
+ }
+ else if(type==S88_DATA && rlength>2)
+ {
+ unsigned offset = static_cast<unsigned char>(reply[1]);
+ unsigned count = rlength-2;
+
+ SensorMap::iterator begin = control.sensors.lower_bound(offset*8+1);
+ SensorMap::iterator end = control.sensors.upper_bound((offset+count)*8);
+ for(SensorMap::iterator i=begin; i!=end; ++i)
+ {
+ unsigned bit_index = i->first-1-offset*8;
+ bool state = (reply[2+bit_index/8]>>(7-bit_index%8))&1;
+ i->second.state.set(state);
+
+ Tag stag;
+ stag.type = Tag::SENSOR;
+ stag.command = Sensor::STATE;
+ stag.serial = i->second.state.serial;
+ stag.address = i->first;
+ control.push_completed_tag(stag);
+ }
+
+ if(count>s88_octets_remaining)
+ s88_octets_remaining = 0;
+ else
+ s88_octets_remaining -= count;
+ }
+ }
+ }
+ }
+}
+
+
+ArduControl::QueuedCommand::QueuedCommand():
+ length(0)
+{ }
+
+ArduControl::QueuedCommand::QueuedCommand(GeneralCommand cmd):
+ length(0)
+{
+ tag.type = Tag::GENERAL;
+ tag.command = cmd;
+}
+
+ArduControl::QueuedCommand::QueuedCommand(Locomotive &loco, Locomotive::Command cmd, unsigned index)
+{
+ tag.type = Tag::LOCOMOTIVE;
+ tag.command = cmd;
+ tag.address = loco.address;
+ if(cmd==Locomotive::SPEED)
+ {
+ tag.serial = loco.speed.serial;
+ length = loco.create_speed_dir_command(command);
+ }
+ else if(cmd==Locomotive::REVERSE)
+ {
+ tag.serial = loco.reverse.serial;
+ length = loco.create_speed_dir_command(command);
+ }
+ else if(cmd==Locomotive::FUNCTIONS)
+ {
+ tag.serial = loco.funcs.serial;
+ length = loco.create_speed_func_command(index, command);
+ }
+ else
+ throw invalid_argument("QueuedCommand");
+}
+
+ArduControl::QueuedCommand::QueuedCommand(Accessory &acc, Accessory::Command cmd, unsigned index)
+{
+ tag.type = Tag::ACCESSORY;
+ tag.command = cmd;
+ tag.address = acc.address;
+ if(cmd==Accessory::ACTIVATE || cmd==Accessory::DEACTIVATE)
+ {
+ tag.serial = acc.state.serial;
+ length = acc.create_state_command(index, (cmd==Accessory::ACTIVATE), command);
+ }
+ else
+ throw invalid_argument("QueuedCommand");
+}
+
+} // namespace R2C2
--- /dev/null
+#ifndef LIBR2C2_ARDUCONTROL_H_
+#define LIBR2C2_ARDUCONTROL_H_
+
+#include <msp/core/mutex.h>
+#include <msp/core/thread.h>
+#include <msp/io/serial.h>
+#include <msp/time/timedelta.h>
+#include <msp/time/timestamp.h>
+#include "driver.h"
+
+namespace R2C2 {
+
+class ArduControl: public Driver
+{
+private:
+ enum Command
+ {
+ POWER_ON = 0x01,
+ POWER_OFF = 0x02,
+ READ_TRACK_CURRENT = 0x08,
+ SET_OVERCURRENT_LIMIT = 0x09,
+ READ_INPUT_VOLTAGE = 0x0A,
+ MOTOROLA_SPEED = 0x11,
+ MOTOROLA_REVERSE = 0x12,
+ MOTOROLA_SPEED_DIRECTION = 0x13,
+ MOTOROLA_SPEED_FUNCTION = 0x14,
+ MOTOROLA_SOLENOID = 0x15,
+ S88_READ = 0x30,
+ COMMAND_OK = 0x80,
+ RECEIVE_OVERRUN = 0x81,
+ FRAMING_ERROR = 0x82,
+ INVALID_COMMAND = 0x83,
+ LENGTH_ERROR = 0x84,
+ INVALID_VALUE = 0x85,
+ OVERCURRENT = 0xA0,
+ TRACK_CURRENT = 0xC0,
+ INPUT_VOLTAGE = 0xC1,
+ S88_DATA = 0xD0
+ };
+
+ struct Tag
+ {
+ enum Type
+ {
+ NONE,
+ GENERAL,
+ LOCOMOTIVE,
+ ACCESSORY,
+ SENSOR
+ };
+
+ Type type;
+ unsigned char command;
+ unsigned short serial;
+ unsigned address;
+
+ Tag();
+
+ operator bool() const { return type!=NONE; }
+ };
+
+ enum GeneralCommand
+ {
+ POWER
+ };
+
+ enum Protocol
+ {
+ NONE,
+ MM
+ };
+
+ template<typename T>
+ struct ControlledVariable
+ {
+ T current;
+ T pending;
+ unsigned short serial;
+
+ ControlledVariable(): current(), pending(), serial(0) { }
+ ControlledVariable(T v): current(v), pending(v), serial(0) { }
+
+ bool set(T v) { if(v==pending) return false; pending = v; ++serial; return true; }
+ bool commit(unsigned short s) { if(s!=serial) return false; current = pending; return true; }
+
+ operator T() const { return current; }
+ };
+
+ struct Locomotive
+ {
+ enum Command
+ {
+ SPEED,
+ REVERSE,
+ FUNCTIONS
+ };
+
+ Protocol proto;
+ unsigned address;
+ ControlledVariable<unsigned> speed;
+ ControlledVariable<bool> reverse;
+ ControlledVariable<unsigned> funcs;
+ unsigned last_change_age;
+
+ Locomotive(Protocol, unsigned);
+
+ unsigned create_speed_dir_command(char *) const;
+ unsigned create_speed_func_command(unsigned, char *) const;
+ };
+
+ struct Accessory
+ {
+ enum Kind
+ {
+ TURNOUT,
+ SIGNAL
+ };
+
+ enum Command
+ {
+ ACTIVATE,
+ DEACTIVATE
+ };
+
+ Kind kind;
+ unsigned address;
+ unsigned bits;
+ ControlledVariable<unsigned> state;
+ unsigned target;
+ Msp::Time::TimeDelta active_time;
+
+ Accessory(Kind, unsigned, unsigned);
+
+ unsigned create_state_command(unsigned, bool, char *) const;
+ };
+
+ struct Sensor
+ {
+ enum Command
+ {
+ STATE
+ };
+
+ unsigned address;
+ ControlledVariable<bool> state;
+
+ Sensor(unsigned);
+ };
+
+ class ControlThread: public Msp::Thread
+ {
+ private:
+ ArduControl &control;
+ bool done;
+
+ public:
+ ControlThread(ArduControl &);
+
+ void exit();
+ private:
+ virtual void main();
+ };
+
+ struct QueuedCommand
+ {
+ Tag tag;
+ char command[15];
+ unsigned char length;
+
+ QueuedCommand();
+ QueuedCommand(GeneralCommand);
+ QueuedCommand(Locomotive &, Locomotive::Command, unsigned = 0);
+ QueuedCommand(Accessory &, Accessory::Command, unsigned = 0);
+ };
+
+ typedef std::map<unsigned, Locomotive> LocomotiveMap;
+ typedef std::list<Locomotive *> LocomotivePtrList;
+ typedef std::map<unsigned, Accessory> AccessoryMap;
+ typedef std::list<Accessory *> AccessoryPtrList;
+ typedef std::map<unsigned, Sensor> SensorMap;
+
+ Msp::IO::Serial serial;
+ bool debug;
+
+ ControlledVariable<bool> power;
+
+ LocomotiveMap locomotives;
+ LocomotivePtrList refresh_cycle;
+ LocomotivePtrList::iterator next_refresh;
+ unsigned refresh_counter;
+ AccessoryMap accessories;
+ AccessoryPtrList accessory_queue;
+ Accessory *active_accessory;
+ Msp::Time::TimeStamp off_timeout;
+ std::list<QueuedCommand> command_queue;
+ std::list<Tag> completed_commands;
+
+ SensorMap sensors;
+ unsigned n_s88_octets;
+
+ Msp::Mutex mutex;
+ ControlThread thread;
+
+public:
+ ArduControl(const std::string &);
+ ~ArduControl();
+
+ virtual void set_power(bool);
+ virtual bool get_power() const { return power; }
+ virtual void halt(bool);
+ virtual bool is_halted() const { return false; }
+
+ virtual const char *enumerate_protocols(unsigned) const;
+private:
+ static Protocol map_protocol(const std::string &);
+public:
+ virtual unsigned get_protocol_speed_steps(const std::string &) const;
+
+ virtual void add_loco(unsigned, const std::string &, const VehicleType &);
+ virtual void remove_loco(unsigned);
+ virtual void set_loco_speed(unsigned, unsigned);
+ virtual void set_loco_reverse(unsigned, bool);
+ virtual void set_loco_function(unsigned, unsigned, bool);
+private:
+ void add_loco_to_refresh(Locomotive &);
+ void remove_loco_from_refresh(Locomotive &);
+ Locomotive *get_loco_to_refresh();
+ void advance_next_refresh();
+
+public:
+ virtual void add_turnout(unsigned, const TrackType &);
+ virtual void remove_turnout(unsigned);
+ virtual void set_turnout(unsigned, unsigned);
+ virtual unsigned get_turnout(unsigned) const;
+
+ virtual void add_signal(unsigned, const SignalType &);
+ virtual void remove_signal(unsigned);
+ virtual void set_signal(unsigned, unsigned);
+ virtual unsigned get_signal(unsigned) const;
+
+private:
+ void add_accessory(Accessory::Kind, unsigned, unsigned);
+ void remove_accessory(Accessory::Kind, unsigned);
+ void set_accessory(Accessory::Kind, unsigned, unsigned);
+ unsigned get_accessory(Accessory::Kind, unsigned) const;
+
+public:
+ virtual void add_sensor(unsigned);
+ virtual void remove_sensor(unsigned);
+ virtual void set_sensor(unsigned, bool) { }
+ virtual bool get_sensor(unsigned) const;
+
+ virtual void tick();
+ virtual void flush();
+
+private:
+ void push_command(const QueuedCommand &);
+ bool pop_command(QueuedCommand &);
+ void push_completed_tag(const Tag &);
+ Tag pop_completed_tag();
+};
+
+} // namespace R2C2
+
+#endif