]> git.tdb.fi Git - r2c2.git/commitdiff
Add driver for my custom Arduino-based control device
authorMikko Rasa <tdb@tdb.fi>
Thu, 31 Oct 2013 23:10:57 +0000 (01:10 +0200)
committerMikko Rasa <tdb@tdb.fi>
Thu, 31 Oct 2013 23:10:57 +0000 (01:10 +0200)
source/libr2c2/arducontrol.cpp [new file with mode: 0644]
source/libr2c2/arducontrol.h [new file with mode: 0644]
source/libr2c2/driver.cpp

diff --git a/source/libr2c2/arducontrol.cpp b/source/libr2c2/arducontrol.cpp
new file mode 100644 (file)
index 0000000..a1d2381
--- /dev/null
@@ -0,0 +1,734 @@
+#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
diff --git a/source/libr2c2/arducontrol.h b/source/libr2c2/arducontrol.h
new file mode 100644 (file)
index 0000000..cbdecad
--- /dev/null
@@ -0,0 +1,265 @@
+#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
index 3b6d64995f56a04c24e52472800e9d6ec8fe24ba..04df5f4e82465f9c8d57ab28b0e24ec222e122bb 100644 (file)
@@ -1,3 +1,4 @@
+#include "arducontrol.h"
 #include "centralstation.h"
 #include "driver.h"
 #include "dummy.h"
@@ -20,6 +21,8 @@ Driver *Driver::create(const string &str)
                return new Intellibox(params);
        else if(type=="cs" || type=="centralstation")
                return new CentralStation(params);
+       else if(type=="ac" || type=="arducontrol")
+               return new ArduControl(params);
        else if(type=="dummy")
                return new Dummy(params);