Improve error handling and reporting in the LED pattern parser
[led-pattern-ed] / src / led-pattern-rx44.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 LedPatternRX44 : LedPattern {
20         public List<LedCommandRX44> engine_r;
21         public List<LedCommandRX44> engine_g;
22         public List<LedCommandRX44> engine_b;
23
24         public override void parse (string line) throws LedPatternError {
25                 string[] key_value = line.split ("=");
26
27                 if (key_value.length != 2)
28                         throw new LedPatternError.INVALID_PATTERN ("pattern line does not contain '=': " + line);
29
30                 name = key_value[0];
31
32                 string[] p = key_value[1].split (";");
33                 if (p.length != 6)
34                         throw new LedPatternError.INVALID_PATTERN ("%s does not contain 6 components: %d".printf (name, p.length));
35
36                 if (p[3].length > 16*4 || p[4].length > 16*4 || p[5].length > 16*4)
37                         throw new LedPatternError.INVALID_PATTERN ("%s engine pattern too long!".printf (name));
38
39                 if (p[3].length % 4 != 0 || p[4].length % 4 != 0 || p[5].length % 4 != 0)
40                         throw new LedPatternError.INVALID_PATTERN ("%s engine pattern not an even number of bytes!".printf (name));
41
42                 priority = p[0].to_int ();
43                 screen_on = p[1].to_int ();
44                 timeout = p[2].to_int ();
45                 engine_r = parse_pattern (p[3]);
46                 engine_g = parse_pattern (p[4]);
47                 engine_b = parse_pattern (p[5]);
48
49                 on_changed ();
50         }
51
52         private List<LedCommandRX44> parse_pattern (string pattern) {
53                 var list = new List<LedCommandRX44> ();
54                 var length = pattern.length;
55
56                 if (length % 4 != 0)
57                         return list;
58
59                 char *p = ((char*) pattern) + length - 4;
60                 while (p >= (char*) pattern) {
61                         var command = new LedCommandRX44.with_code ((uint16) ((string) p).to_ulong (null, 16));
62                         command.changed.connect (on_changed);
63                         list.prepend (command);
64                         p[0] = '\0';
65                         p -= 4;
66                 }
67
68                 return list;
69         }
70
71         public override string dump () {
72                 return "%s=%d;%d;%d;%s;%s;%s".printf (name, priority, screen_on, timeout,
73                                                       dump_pattern (engine_r), dump_pattern (engine_g), dump_pattern (engine_b));
74         }
75
76         private string dump_pattern (List<LedCommandRX44> list) {
77                 string result = "";
78                 foreach (LedCommandRX44 command in list) {
79                         result += "%04x".printf (command.code);
80                 }
81                 return result;
82         }
83
84         public LedPatternRX44 copy () {
85                 var pattern = new LedPatternRX44 ();
86
87                 pattern.name = name.dup ();
88                 pattern.priority = priority;
89                 pattern.screen_on = screen_on;
90                 pattern.timeout = timeout;
91
92                 pattern.duration = duration;
93
94                 pattern.engine_r = deep_copy (pattern, engine_r);
95                 pattern.engine_g = deep_copy (pattern, engine_g);
96                 pattern.engine_b = deep_copy (pattern, engine_b);
97
98                 return pattern;
99         }
100
101         public List<LedCommandRX44> deep_copy (LedPatternRX44 pattern, List<LedCommandRX44> list) {
102                 var list2 = new List<LedCommandRX44> ();
103
104                 foreach (LedCommandRX44 command in list) {
105                         var command2 = command.copy ();
106                         command2.changed.connect (pattern.on_changed);
107                         list2.append (command2);
108                 }
109
110                 return list2;
111         }
112
113         public void replace_with (LedPatternRX44 pattern) {
114                 name = pattern.name;
115                 priority = pattern.priority;
116                 screen_on = pattern.screen_on;
117                 timeout = pattern.timeout;
118
119                 duration = pattern.duration;
120
121                 engine_r = deep_copy (this, pattern.engine_r);
122                 engine_g = deep_copy (this, pattern.engine_g);
123                 engine_b = deep_copy (this, pattern.engine_b);
124
125                 changed ();
126         }
127
128         public void on_changed () {
129                 bool unresolved = calculate_timing ();
130                 if (unresolved)
131                         unresolved = calculate_timing ();
132                 if (unresolved)
133                         Hildon.Banner.show_information (null, null, "Timing unresolved");
134                 changed ();
135         }
136
137         private bool calculate_timing () {
138                 bool unresolved = false;
139                 // Calculate timing and level info for red engine
140                 double time = 0;
141                 int level = 0;
142                 foreach (LedCommandRX44 command in engine_r) {
143                         command.time = time;
144                         if (command.type == CommandType.SET_PWM) {
145                                 level = command.level;
146                         } else {
147                                 command.level = level;
148                                 level += command.steps;
149                         }
150                         if (command.type == CommandType.TRIGGER &&
151                             (command.code & 0x0180) != 0) {
152                                 command.duration = wait_for_trigger (time, engine_g);
153                                 if (command.duration == 0)
154                                         unresolved = true;
155                                 command.duration += 16 * LedCommandRX44.CYCLE_TIME_MS;
156                         }
157                         time += command.duration;
158                         if (level < 0)
159                                 level = 0;
160                         if (level > 255)
161                                 level = 255;
162                 }
163                 duration = time;
164                 // Calculate timing and level info for green engine
165                 time = 0;
166                 level = 0;
167                 foreach (LedCommandRX44 command in engine_g) {
168                         command.time = time;
169                         if (command.type == CommandType.SET_PWM) {
170                                 level = command.level;
171                         } else {
172                                 command.level = level;
173                                 level += command.steps;
174                         }
175                         if (command.type == CommandType.TRIGGER &&
176                             (command.code & 0x0180) != 0) {
177                                 command.duration = wait_for_trigger (time, engine_r);
178                                 if (command.duration == 0)
179                                         unresolved = true;
180                                 command.duration += 16 * LedCommandRX44.CYCLE_TIME_MS;
181                         }
182                         time += command.duration;
183                         if (level < 0)
184                                 level = 0;
185                         if (level > 255)
186                                 level = 255;
187                 }
188                 if (time > duration)
189                         duration = time;
190                 return unresolved;
191         }
192
193         double wait_for_trigger (double time, List<LedCommandRX44> engine) {
194                 double duration = 0;
195                 bool repeat = false;
196                 foreach (LedCommandRX44 command in engine) {
197                         duration = command.time + command.duration;
198                         if (command.type == CommandType.TRIGGER &&
199                             (command.code & 0x0006) != 0 && command.time > time) {
200                                 return command.time - time;
201                         }
202                         if (command.type == CommandType.GO_TO_START) {
203                                 repeat = true;
204                                 break;
205                         }
206                 }
207                 if (repeat) foreach (LedCommandRX44 command in engine) {
208                         if (command.type == CommandType.TRIGGER &&
209                             (command.code & 0x0006) != 0 && (duration + command.time) > time) {
210                                 return duration + command.time - time;
211                         }
212                 }
213                 return 0;
214         }
215 }
216
217 class LedCommandRX44 : LedCommand {
218         internal const double CYCLE_TIME_MS = 1000.0 / 32768.0;
219
220         public uint16 code;
221
222         public LedCommandRX44 () {
223         }
224
225         public LedCommandRX44.with_code (uint16 _code) {
226                 code = _code;
227                 duration = 16 * CYCLE_TIME_MS;
228                 if ((code & 0x8000) == 0) {
229                         if (code == 0x0000) {
230                                 type = CommandType.GO_TO_START;
231                         } else if ((code & 0x3e00) != 0) {
232                                 type = CommandType.RAMP_WAIT;
233                                 steps = code & 0x7f;
234                                 step_time = code >> 8;
235                                 if ((code & 0x4000) == 0)
236                                         step_time = (code >> 9) * 16 * CYCLE_TIME_MS;
237                                 else {
238                                         step_time = ((code & 0x3e00) >> 9) * 512 * CYCLE_TIME_MS;
239                                 }
240                                 duration = step_time * (steps + 1);
241                                 if ((code & 0x80) != 0)
242                                         steps = -steps;
243                         } else {
244                                 type = CommandType.SET_PWM;
245                                 level = code & 0xff;
246                         }
247                 } else {
248                         if ((code & ~0x1f8f) == 0xa000) {
249                                 type = CommandType.BRANCH;
250                                 // 0x1f80: (loop count - 1) << 7
251                                 // 0x000f: step number
252                         } else if ((code & ~0x1800) == 0xc000) {
253                                 type = CommandType.END;
254                                 // 0x1000: interrupt
255                                 if ((code & 0x0800) != 0) // Reset
256                                         steps = -255;
257                         } else if ((code & ~ 0x13fe) == 0xe000) {
258                                 type = CommandType.TRIGGER;
259                                 // 0x1000: wait ext
260                                 // 0x0380: wait B G R
261                                 // 0x0040: set ext
262                                 // 0x000e: set B G R
263                         }
264                 }
265         }
266
267         public override void set_pwm (int _level) {
268                 code = 0x4000 | _level;
269                 base.set_pwm (_level);
270         }
271
272         public override void ramp_wait (double _step_time, int _steps) requires (_step_time >= (16 * CYCLE_TIME_MS)) {
273                 int step_time;
274                 if (_step_time < 32 * (16 * CYCLE_TIME_MS)) {
275                         step_time = (int) ((_step_time + 0.001) / (16 * CYCLE_TIME_MS));
276                         code = (uint16) step_time << 8;
277                         _step_time = step_time * (16 * CYCLE_TIME_MS);
278                 } else if (_step_time < 32*(512 * CYCLE_TIME_MS)) {
279                         step_time = (int) ((_step_time + 0.001) / (512 * CYCLE_TIME_MS));
280                         code = 0x4000 | (step_time << 8);
281                         _step_time = step_time * (512 * CYCLE_TIME_MS);
282                 } else {
283                         return;
284                 }
285                 if (_steps < 0) {
286                         code |= 0x80 | (-_steps);
287                 } else {
288                         code |= _steps;
289                 }
290                 base.ramp_wait (_step_time, _steps);
291         }
292
293         public override void go_to_start () {
294                 code = 0x0000;
295                 base.go_to_start ();
296         }
297
298         public override void end (bool reset) {
299                 code = 0xc000;
300                 if (reset)
301                         code |= 0x0800;
302                 base.end (reset);
303         }
304
305         public LedCommandRX44 copy () {
306                 var command = new LedCommandRX44 ();
307
308                 command.type = type;
309                 command.time = time;
310                 command.step_time = step_time;
311                 command.duration = duration;
312                 command.level = level;
313                 command.steps = steps;
314
315                 command.code = code;
316
317                 return command;
318         }
319 }