+#include <msp/core/application.h>
+#include <msp/core/getopt.h>
+#include <msp/io/console.h>
+#include <msp/io/eventdispatcher.h>
+#include <msp/io/print.h>
+#include <msp/io/serial.h>
+#include <msp/time/timestamp.h>
+#include <msp/time/utils.h>
+
+using namespace std;
+using namespace Msp;
+
+class ArduProgram: public RegisteredApplication<ArduProgram>
+{
+private:
+ struct Options
+ {
+ string serial_port;
+
+ Options(int, char **);
+ };
+
+ enum OutputState
+ {
+ INIT,
+ IDLE,
+ SEND_REG,
+ SEND_VALUE
+ };
+
+ enum InputState
+ {
+ WAIT,
+ MENU,
+ PROG_ADDR,
+ PROG_REG,
+ PROG_VALUE
+ };
+
+ Options options;
+
+ IO::Serial serial;
+ unsigned out_addr;
+ bool out_active;
+ OutputState out_state;
+ Time::TimeStamp timeout;
+
+ InputState in_state;
+ unsigned value;
+
+ IO::EventDispatcher ev_disp;
+
+public:
+ ArduProgram(int, char **);
+
+ virtual int main();
+
+private:
+ void send_command(const string &);
+ void set_output();
+ void set_input_state(InputState);
+ void user_input_available();
+ void set_output_state(OutputState);
+ void serial_input_available();
+};
+
+
+ArduProgram::Options::Options(int argc, char **argv)
+{
+ GetOpt getopt;
+ getopt.add_argument("port", serial_port).set_help("Serial port to use");
+ getopt(argc, argv);
+}
+
+
+ArduProgram::ArduProgram(int argc, char **argv):
+ options(argc, argv),
+ serial(options.serial_port)
+{
+ IO::cin.signal_data_available.connect(sigc::mem_fun(this, &ArduProgram::user_input_available));
+ ev_disp.add(IO::cin);
+ serial.signal_data_available.connect(sigc::mem_fun(this, &ArduProgram::serial_input_available));
+ ev_disp.add(serial);
+}
+
+int ArduProgram::main()
+{
+ IO::print("Putting locomotive in programming mode (this will take a few seconds)\n");
+
+ send_command("\x02");
+ Time::sleep(Time::sec);
+ send_command("\x01");
+
+ set_output_state(INIT);
+ set_input_state(WAIT);
+
+ while(!done)
+ {
+ Time::TimeStamp t = Time::now();
+ if(timeout)
+ {
+ if(t>=timeout)
+ {
+ timeout = Time::TimeStamp();
+
+ if(out_active)
+ {
+ out_active = false;
+ timeout = t+Time::sec;
+ }
+ else if(out_state==SEND_REG)
+ {
+ if(in_state==WAIT)
+ set_output_state(SEND_VALUE);
+ }
+ else
+ {
+ set_output_state(IDLE);
+ set_input_state(MENU);
+ }
+ }
+ else
+ ev_disp.tick(timeout-t);
+ }
+ else
+ ev_disp.tick();
+ }
+
+ IO::print("Resetting locomotive\n");
+
+ send_command("\x02");
+ Time::sleep(Time::sec);
+ send_command("\x01");
+
+ return 0;
+}
+
+void ArduProgram::send_command(const string &cmd)
+{
+ serial.put(cmd.size()^0xFF);
+ serial.write(cmd);
+}
+
+void ArduProgram::set_output()
+{
+ string cmd;
+ cmd.reserve(4);
+ cmd += (out_active ? 0x12 : 0x11);
+ cmd += out_addr;
+ cmd += '\0';
+ if(!out_active)
+ cmd += '\0';
+ send_command(cmd);
+}
+
+void ArduProgram::set_input_state(InputState s)
+{
+ in_state = s;
+ switch(in_state)
+ {
+ case MENU:
+ IO::print("Do what [a/r/p/q/?] ");
+ break;
+ case PROG_ADDR:
+ IO::print("Enter new address: ");
+ break;
+ case PROG_REG:
+ IO::print("Enter register: ");
+ break;
+ case PROG_VALUE:
+ IO::print("Enter value: ");
+ break;
+ case WAIT:
+ IO::print("Please wait...\n");
+ break;
+ default:;
+ }
+}
+
+void ArduProgram::user_input_available()
+{
+ string line;
+ if(!IO::cin.getline(line))
+ return;
+
+ if(in_state==MENU)
+ {
+ if(line.size()==1)
+ {
+ int c = tolower(line[0]);
+ if(c=='q')
+ exit(0);
+ else if(c=='a')
+ {
+ value = 1;
+ set_output_state(SEND_REG);
+ set_input_state(PROG_ADDR);
+ }
+ else if(c=='p')
+ set_input_state(PROG_REG);
+ else if(c=='r')
+ {
+ value = 8;
+ set_output_state(SEND_REG);
+ set_input_state(WAIT);
+ }
+ else if(c=='?')
+ {
+ IO::print("a: Enter new address\n"
+ "r: Reset to factory settings\n"
+ "p: Program other CV\n"
+ "q: Quit\n");
+ set_input_state(MENU);
+ }
+ }
+ else
+ set_input_state(in_state);
+ }
+ else
+ {
+ try
+ {
+ value = lexical_cast<unsigned>(line);
+
+ if(in_state==PROG_REG)
+ {
+ set_output_state(SEND_REG);
+ set_input_state(PROG_VALUE);
+ }
+ else if(in_state==PROG_ADDR || in_state==PROG_VALUE)
+ {
+ value = lexical_cast<unsigned>(line);
+ if(!out_active)
+ set_output_state(SEND_VALUE);
+ set_input_state(WAIT);
+ }
+ }
+ catch(const lexical_error &e)
+ {
+ IO::print("Bad input, try again");
+ set_input_state(in_state);
+ }
+ }
+}
+
+void ArduProgram::set_output_state(OutputState s)
+{
+ out_state = s;
+
+ switch(out_state)
+ {
+ case INIT:
+ out_addr = 0;
+ out_active = true;
+ timeout = Time::now()+2*Time::sec;
+ set_output();
+ break;
+ case IDLE:
+ out_addr = 0;
+ out_active = false;
+ break;
+ case SEND_REG:
+ case SEND_VALUE:
+ out_addr = value;
+ out_active = true;
+ timeout = Time::now()+Time::sec;
+ break;
+ default:;
+ }
+}
+
+void ArduProgram::serial_input_available()
+{
+ int c = serial.get();
+ if(c==0x80)
+ set_output();
+ else
+ throw runtime_error(format("Command error: %x\n", c));
+}