]> git.tdb.fi Git - model-railway-devices.git/blob - arduprogram/main.cpp
Add decoder programming utility
[model-railway-devices.git] / arduprogram / main.cpp
1 #include <msp/core/application.h>
2 #include <msp/core/getopt.h>
3 #include <msp/io/console.h>
4 #include <msp/io/eventdispatcher.h>
5 #include <msp/io/print.h>
6 #include <msp/io/serial.h>
7 #include <msp/time/timestamp.h>
8 #include <msp/time/utils.h>
9
10 using namespace std;
11 using namespace Msp;
12
13 class ArduProgram: public RegisteredApplication<ArduProgram>
14 {
15 private:
16         struct Options
17         {
18                 string serial_port;
19
20                 Options(int, char **);
21         };
22
23         enum OutputState
24         {
25                 INIT,
26                 IDLE,
27                 SEND_REG,
28                 SEND_VALUE
29         };
30
31         enum InputState
32         {
33                 WAIT,
34                 MENU,
35                 PROG_ADDR,
36                 PROG_REG,
37                 PROG_VALUE
38         };
39
40         Options options;
41
42         IO::Serial serial;
43         unsigned out_addr;
44         bool out_active;
45         OutputState out_state;
46         Time::TimeStamp timeout;
47
48         InputState in_state;
49         unsigned value;
50
51         IO::EventDispatcher ev_disp;
52
53 public:
54         ArduProgram(int, char **);
55
56         virtual int main();
57
58 private:
59         void send_command(const string &);
60         void set_output();
61         void set_input_state(InputState);
62         void user_input_available();
63         void set_output_state(OutputState);
64         void serial_input_available();
65 };
66
67
68 ArduProgram::Options::Options(int argc, char **argv)
69 {
70         GetOpt getopt;
71         getopt.add_argument("port", serial_port).set_help("Serial port to use");
72         getopt(argc, argv);
73 }
74
75
76 ArduProgram::ArduProgram(int argc, char **argv):
77         options(argc, argv),
78         serial(options.serial_port)
79 {
80         IO::cin.signal_data_available.connect(sigc::mem_fun(this, &ArduProgram::user_input_available));
81         ev_disp.add(IO::cin);
82         serial.signal_data_available.connect(sigc::mem_fun(this, &ArduProgram::serial_input_available));
83         ev_disp.add(serial);
84 }
85
86 int ArduProgram::main()
87 {
88         IO::print("Putting locomotive in programming mode (this will take a few seconds)\n");
89
90         send_command("\x02");
91         Time::sleep(Time::sec);
92         send_command("\x01");
93
94         set_output_state(INIT);
95         set_input_state(WAIT);
96
97         while(!done)
98         {
99                 Time::TimeStamp t = Time::now();
100                 if(timeout)
101                 {
102                         if(t>=timeout)
103                         {
104                                 timeout = Time::TimeStamp();
105
106                                 if(out_active)
107                                 {
108                                         out_active = false;
109                                         timeout = t+Time::sec;
110                                 }
111                                 else if(out_state==SEND_REG)
112                                 {
113                                         if(in_state==WAIT)
114                                                 set_output_state(SEND_VALUE);
115                                 }
116                                 else
117                                 {
118                                         set_output_state(IDLE);
119                                         set_input_state(MENU);
120                                 }
121                         }
122                         else
123                                 ev_disp.tick(timeout-t);
124                 }
125                 else
126                         ev_disp.tick();
127         }
128
129         IO::print("Resetting locomotive\n");
130
131         send_command("\x02");
132         Time::sleep(Time::sec);
133         send_command("\x01");
134
135         return 0;
136 }
137
138 void ArduProgram::send_command(const string &cmd)
139 {
140         serial.put(cmd.size()^0xFF);
141         serial.write(cmd);
142 }
143
144 void ArduProgram::set_output()
145 {
146         string cmd;
147         cmd.reserve(4);
148         cmd += (out_active ? 0x12 : 0x11);
149         cmd += out_addr;
150         cmd += '\0';
151         if(!out_active)
152                 cmd += '\0';
153         send_command(cmd);
154 }
155
156 void ArduProgram::set_input_state(InputState s)
157 {
158         in_state = s;
159         switch(in_state)
160         {
161         case MENU:
162                 IO::print("Do what [a/r/p/q/?] ");
163                 break;
164         case PROG_ADDR:
165                 IO::print("Enter new address: ");
166                 break;
167         case PROG_REG:
168                 IO::print("Enter register: ");
169                 break;
170         case PROG_VALUE:
171                 IO::print("Enter value: ");
172                 break;
173         case WAIT:
174                 IO::print("Please wait...\n");
175                 break;
176         default:;
177         }
178 }
179
180 void ArduProgram::user_input_available()
181 {
182         string line;
183         if(!IO::cin.getline(line))
184                 return;
185
186         if(in_state==MENU)
187         {
188                 if(line.size()==1)
189                 {
190                         int c = tolower(line[0]);
191                         if(c=='q')
192                                 exit(0);
193                         else if(c=='a')
194                         {
195                                 value = 1;
196                                 set_output_state(SEND_REG);
197                                 set_input_state(PROG_ADDR);
198                         }
199                         else if(c=='p')
200                                 set_input_state(PROG_REG);
201                         else if(c=='r')
202                         {
203                                 value = 8;
204                                 set_output_state(SEND_REG);
205                                 set_input_state(WAIT);
206                         }
207                         else if(c=='?')
208                         {
209                                 IO::print("a: Enter new address\n"
210                                         "r: Reset to factory settings\n"
211                                         "p: Program other CV\n"
212                                         "q: Quit\n");
213                                 set_input_state(MENU);
214                         }
215                 }
216                 else
217                         set_input_state(in_state);
218         }
219         else
220         {
221                 try
222                 {
223                         value = lexical_cast<unsigned>(line);
224
225                         if(in_state==PROG_REG)
226                         {
227                                 set_output_state(SEND_REG);
228                                 set_input_state(PROG_VALUE);
229                         }
230                         else if(in_state==PROG_ADDR || in_state==PROG_VALUE)
231                         {
232                                 value = lexical_cast<unsigned>(line);
233                                 if(!out_active)
234                                         set_output_state(SEND_VALUE);
235                                 set_input_state(WAIT);
236                         }
237                 }
238                 catch(const lexical_error &e)
239                 {
240                         IO::print("Bad input, try again");
241                         set_input_state(in_state);
242                 }
243         }
244 }
245
246 void ArduProgram::set_output_state(OutputState s)
247 {
248         out_state = s;
249
250         switch(out_state)
251         {
252         case INIT:
253                 out_addr = 0;
254                 out_active = true;
255                 timeout = Time::now()+2*Time::sec;
256                 set_output();
257                 break;
258         case IDLE:
259                 out_addr = 0;
260                 out_active = false;
261                 break;
262         case SEND_REG:
263         case SEND_VALUE:
264                 out_addr = value;
265                 out_active = true;
266                 timeout = Time::now()+Time::sec;
267                 break;
268         default:;
269         }
270 }
271
272 void ArduProgram::serial_input_available()
273 {
274         int c = serial.get();
275         if(c==0x80)
276                 set_output();
277         else
278                 throw runtime_error(format("Command error: %x\n", c));
279 }