Improve error handling and reporting in the LED pattern parser
[led-pattern-ed] / src / led-pattern-rx51.vala
1 /* This file is part of LED Pattern Editor.
2  *
3  * Copyright (C) 2010 Philipp Zabel
4  *
5  * LED Pattern Editor is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * LED Pattern Editor is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with LED Pattern Editor. If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 class LedPatternRX51 : LedPattern {
20         public LedColor color1;
21         public LedColor color2;
22         public List<LedCommandRX51> engine1;
23         public List<LedCommandRX51> engine2;
24
25         public override void parse (string line) throws LedPatternError {
26                 string[] key_value = line.split ("=");
27
28                 if (key_value.length != 2)
29                         throw new LedPatternError.INVALID_PATTERN ("pattern line does not contain '=': " + line);
30
31                 name = key_value[0];
32
33                 string[] p = key_value[1].split (";");
34                 if (p.length != 6)
35                         throw new LedPatternError.INVALID_PATTERN ("%s does not contain 6 components: %d".printf (name, p.length));
36
37                 if (p[4].length > 16*4 || p[5].length > 16*4)
38                         throw new LedPatternError.INVALID_PATTERN ("%s engine pattern too long!".printf (name));
39
40                 if (p[4].length % 4 != 0 || p[5].length % 4 != 0)
41                         throw new LedPatternError.INVALID_PATTERN ("%s engine pattern not an even number of bytes!".printf (name));
42
43                 priority = p[0].to_int ();
44                 screen_on = p[1].to_int ();
45                 timeout = p[2].to_int ();
46                 parse_led_map (p[3], out color1, out color2);
47                 engine1 = parse_pattern (p[4]);
48                 engine2 = parse_pattern (p[5]);
49
50                 if (engine1.first ().data.code != 0x9d80) {
51                         print ("engine1 pattern doesn't start with refresh mux command\n");
52                 }
53                 if (engine2.first ().data.code != 0x9d80) {
54                         print ("engine2 pattern doesn't start with refresh mux command\n");
55                 }
56
57                 on_changed ();
58         }
59
60         private void parse_led_map (string led_map, out LedColor color1, out LedColor color2) {
61                 color1 = LedColor.OFF;
62                 color2 = LedColor.OFF;
63                 if ("r" in led_map)
64                         color1 |= LedColor.R;
65                 if ("g" in led_map)
66                         color1 |= LedColor.G;
67                 if ("b" in led_map)
68                         color1 |= LedColor.B;
69                 if ("R" in led_map)
70                         color2 |= LedColor.R;
71                 if ("G" in led_map)
72                         color2 |= LedColor.G;
73                 if ("B" in led_map)
74                         color2 |= LedColor.B;
75         }
76
77         private List<LedCommandRX51> parse_pattern (string pattern) {
78                 var list = new List<LedCommandRX51> ();
79                 var length = pattern.length;
80
81                 if (length % 4 != 0)
82                         return list;
83
84                 char *p = ((char*) pattern) + length - 4;
85                 while (p >= (char*) pattern) {
86                         var command = new LedCommandRX51.with_code ((uint16) ((string) p).to_ulong (null, 16));
87                         command.changed.connect (on_changed);
88                         list.prepend (command);
89                         p[0] = '\0';
90                         p -= 4;
91                 }
92
93                 return list;
94         }
95
96         public override string dump () {
97                 return "%s=%d;%d;%d;%s;%s;%s".printf (name, priority, screen_on, timeout, dump_led_map (),
98                                                       dump_pattern (engine1), dump_pattern (engine2));
99         }
100
101         private string dump_led_map () {
102                 string led_map = "";
103                 if (LedColor.R in color1)
104                         led_map += "r";
105                 if (LedColor.R in color2)
106                         led_map += "R";
107                 if (LedColor.G in color1)
108                         led_map += "g";
109                 if (LedColor.G in color2)
110                         led_map += "G";
111                 if (LedColor.B in color1)
112                         led_map += "b";
113                 if (LedColor.B in color2)
114                         led_map += "B";
115                 return led_map;
116         }
117
118         private string dump_pattern (List<LedCommandRX51> list) {
119                 string result = "";
120                 foreach (LedCommandRX51 command in list) {
121                         result += "%04x".printf (command.code);
122                 }
123                 return result;
124         }
125
126         public LedPatternRX51 copy () {
127                 var pattern = new LedPatternRX51 ();
128
129                 pattern.name = name.dup ();
130                 pattern.priority = priority;
131                 pattern.screen_on = screen_on;
132                 pattern.timeout = timeout;
133
134                 pattern.duration = duration;
135
136                 pattern.color1 = color1;
137                 pattern.color2 = color2;
138                 pattern.engine1 = deep_copy (pattern, engine1);
139                 pattern.engine2 = deep_copy (pattern, engine2);
140
141                 return pattern;
142         }
143
144         public List<LedCommandRX51> deep_copy (LedPatternRX51 pattern, List<LedCommandRX51> list) {
145                 var list2 = new List<LedCommandRX51> ();
146
147                 foreach (LedCommandRX51 command in list) {
148                         var command2 = command.copy ();
149                         command2.changed.connect (pattern.on_changed);
150                         list2.append (command2);
151                 }
152
153                 return list2;
154         }
155
156         public void replace_with (LedPatternRX51 pattern) {
157                 name = pattern.name;
158                 priority = pattern.priority;
159                 screen_on = pattern.screen_on;
160                 timeout = pattern.timeout;
161
162                 duration = pattern.duration;
163
164                 color1 = pattern.color1;
165                 color2 = pattern.color2;
166                 engine1 = deep_copy (this, pattern.engine1);
167                 engine2 = deep_copy (this, pattern.engine2);
168
169                 changed ();
170         }
171
172         public void on_changed () {
173                 bool unresolved = calculate_timing ();
174                 if (unresolved) {
175                         unresolved = calculate_timing ();
176                 }
177                 if (unresolved) {
178                         Hildon.Banner.show_information (null, null, "Timing unresolved");
179                 }
180                 changed ();
181         }
182
183         private bool calculate_timing () {
184                 bool unresolved = false;
185                 // Calculate timing and level info for engine 1
186                 double time = 0;
187                 int level = 0;
188                 foreach (LedCommandRX51 command in engine1) {
189                         command.time = time;
190                         if (command.type == CommandType.SET_PWM) {
191                                 level = command.level;
192                         } else {
193                                 command.level = level;
194                                 level += command.steps;
195                         }
196                         if (command.type == CommandType.TRIGGER &&
197                             (command.code & 0x0180) != 0) {
198                                 command.duration = wait_for_trigger (time, engine2);
199                                 if (command.duration == 0)
200                                         unresolved = true;
201                                 command.duration += 16 * LedCommandRX51.CYCLE_TIME_MS;
202                         }
203                         time += command.duration;
204                         if (level < 0)
205                                 level = 0;
206                         if (level > 255)
207                                 level = 255;
208                 }
209                 duration = time;
210                 // Calculate timing and level info for engine 2
211                 time = 0;
212                 level = 0;
213                 foreach (LedCommandRX51 command in engine2) {
214                         command.time = time;
215                         if (command.type == CommandType.SET_PWM) {
216                                 level = command.level;
217                         } else {
218                                 command.level = level;
219                                 level += command.steps;
220                         }
221                         if (command.type == CommandType.TRIGGER &&
222                             (command.code & 0x0180) != 0) {
223                                 command.duration = wait_for_trigger (time, engine1);
224                                 if (command.duration == 0)
225                                         unresolved = true;
226                                 command.duration += 16 * LedCommandRX51.CYCLE_TIME_MS;
227                         }
228                         time += command.duration;
229                         if (level < 0)
230                                 level = 0;
231                         if (level > 255)
232                                 level = 255;
233                 }
234                 if (time > duration)
235                         duration = time;
236                 return unresolved;
237         }
238
239         double wait_for_trigger (double time, List<LedCommandRX51> engine) {
240                 double duration = 0;
241                 bool repeat = false;
242                 foreach (LedCommandRX51 command in engine) {
243                         duration = command.time + command.duration;
244                         if (command.type == CommandType.TRIGGER &&
245                             (command.code & 0x0006) != 0 && command.time > time) {
246                                 return command.time - time;
247                         }
248                         if (command.type == CommandType.GO_TO_START) {
249                                 repeat = true;
250                                 break;
251                         }
252                 }
253                 if (repeat) foreach (LedCommandRX51 command in engine) {
254                         if (command.type == CommandType.TRIGGER &&
255                             (command.code & 0x0006) != 0 && (duration + command.time) > time) {
256                                 return duration + command.time - time;
257                         }
258                 }
259                 return 0;
260         }
261 }
262
263 class LedCommandRX51 : LedCommand {
264         internal const double CYCLE_TIME_MS = 1000.0 / 32768.0;
265
266         public uint16 code;
267
268         public LedCommandRX51 () {
269         }
270
271         public LedCommandRX51.with_code (uint16 _code) {
272                 code = _code;
273                 duration = 16 * CYCLE_TIME_MS;
274                 if ((code & 0x8000) == 0) {
275                         if (code == 0x0000) {
276                                 type = CommandType.GO_TO_START;
277                         } else if ((code & 0x3e00) != 0) {
278                                 type = CommandType.RAMP_WAIT;
279                                 steps = code & 0xff;
280                                 step_time = code >> 9;
281                                 if ((code & 0x4000) == 0)
282                                         step_time = (code >> 9) * 16 * CYCLE_TIME_MS;
283                                 else {
284                                         step_time = ((code & 0x3e00) >> 9) * 512 * CYCLE_TIME_MS;
285                                 }
286                                 duration = step_time * (steps + 1);
287                                 if ((code & 0x100) != 0)
288                                         steps = -steps;
289                         } else {
290                                 type = CommandType.SET_PWM;
291                                 level = code & 0xff;
292                         }
293                 } else {
294                         if (code == 0x9d80) {
295                                 type = CommandType.RESET_MUX;
296                         } else if ((code & ~0x1f8f) == 0xa000) {
297                                 type = CommandType.BRANCH;
298                                 // 0x1f80: (loop count - 1) << 7
299                                 // 0x000f: step number
300                         } else if ((code & ~0x1800) == 0xc000) {
301                                 type = CommandType.END;
302                                 // 0x1000: interrupt
303                                 if ((code & 0x0800) != 0) // Reset
304                                         steps = -255;
305                         } else if ((code & ~ 0x13fe) == 0xe000) {
306                                 type = CommandType.TRIGGER;
307                                 // 0x1000: wait ext
308                                 // 0x0380: wait B G R
309                                 // 0x0040: set ext
310                                 // 0x000e: set B G R
311                         }
312                 }
313         }
314
315         public override void set_pwm (int _level) {
316                 code = 0x4000 | _level;
317                 base.set_pwm (_level);
318         }
319
320         public override void ramp_wait (double _step_time, int _steps) requires (_step_time >= (16 * CYCLE_TIME_MS)) {
321                 int step_time;
322                 if (_step_time < 32 * (16 * CYCLE_TIME_MS)) {
323                         step_time = (int) ((_step_time + 0.001) / (16 * CYCLE_TIME_MS));
324                         code = (uint16) step_time << 9;
325                         _step_time = step_time * (16 * CYCLE_TIME_MS);
326                 } else if (_step_time < 32*(512 * CYCLE_TIME_MS)) {
327                         step_time = (int) ((_step_time + 0.001) / (512 * CYCLE_TIME_MS));
328                         code = 0x4000 | (step_time << 9);
329                         _step_time = step_time * (512 * CYCLE_TIME_MS);
330                 } else {
331                         return;
332                 }
333                 if (_steps < 0) {
334                         code |= 0x100 | (-_steps);
335                 } else {
336                         code |= _steps;
337                 }
338                 base.ramp_wait (_step_time, _steps);
339         }
340
341         public override void go_to_start () {
342                 code = 0x0000;
343                 base.go_to_start ();
344         }
345
346         public override void end (bool reset) {
347                 code = 0xc000;
348                 if (reset)
349                         code |= 0x0800;
350                 base.end (reset);
351         }
352
353         public LedCommandRX51 copy () {
354                 var command = new LedCommandRX51 ();
355
356                 command.type = type;
357                 command.time = time;
358                 command.step_time = step_time;
359                 command.duration = duration;
360                 command.level = level;
361                 command.steps = steps;
362
363                 command.code = code;
364
365                 return command;
366         }
367 }