reboot before flashing and retry a bit more often
[hmcfgusb] / flash-ota.c
CommitLineData
25870f58
MG
1/* flasher for HomeMatic-devices supporting OTA updates
2 *
3 * Copyright (c) 2014 Michael Gernoth <michael@gernoth.net>
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to
7 * deal in the Software without restriction, including without limitation the
8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 * sell copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 */
23
24#include <stdio.h>
25#include <stdlib.h>
26#include <unistd.h>
27#include <stdint.h>
28#include <string.h>
29#include <strings.h>
30#include <poll.h>
31#include <errno.h>
32#include <sys/types.h>
33#include <sys/stat.h>
34#include <fcntl.h>
35#include <sys/time.h>
36#include <libusb-1.0/libusb.h>
37
38#include "hexdump.h"
39#include "firmware.h"
40#include "hm.h"
41#include "version.h"
42#include "hmcfgusb.h"
43
2d1f08ac
MG
44#define MAX_RETRIES 5
45
25870f58 46uint32_t hmid = 0;
558a94bb 47uint32_t my_hmid = 0;
25870f58
MG
48
49enum message_type {
50 MESSAGE_TYPE_E,
51 MESSAGE_TYPE_R,
52};
53
54struct recv_data {
55 uint8_t message[64];
56 enum message_type message_type;
57 uint16_t status;
58 int speed;
865d5b4c 59 uint16_t hmcfgusb_version;
25870f58
MG
60};
61
62static int parse_hmcfgusb(uint8_t *buf, int buf_len, void *data)
63{
64 struct recv_data *rdata = data;
65
66 if (buf_len < 1)
67 return 1;
68
69 switch (buf[0]) {
70 case 'E':
71 if ((!hmid) ||
72 ((buf[0x11] == ((hmid >> 16) & 0xff)) &&
73 (buf[0x12] == ((hmid >> 8) & 0xff)) &&
74 (buf[0x13] == (hmid & 0xff)))) {
75 memset(rdata->message, 0, sizeof(rdata->message));
76 memcpy(rdata->message, buf + 0x0d, buf[0x0d] + 1);
77 rdata->message_type = MESSAGE_TYPE_E;
78 }
79 break;
80 case 'R':
81 memset(rdata->message, 0, sizeof(rdata->message));
82 memcpy(rdata->message, buf + 0x0e, buf[0x0e] + 1);
83 rdata->status = (buf[5] << 8) | buf[6];
84 rdata->message_type = MESSAGE_TYPE_R;
85 break;
86 case 'G':
87 rdata->speed = buf[1];
88 break;
865d5b4c
MG
89 case 'H':
90 rdata->hmcfgusb_version = (buf[11] << 8) | buf[12];
558a94bb 91 my_hmid = (buf[0x1b] << 16) | (buf[0x1c] << 8) | buf[0x1d];
865d5b4c 92 break;
25870f58
MG
93 default:
94 break;
95 }
96
97 if (buf_len != 1)
98 return 1;
99
100 return 1;
101}
102
103int send_hm_message(struct hmcfgusb_dev *dev, struct recv_data *rdata, uint8_t *msg)
104{
105 static uint32_t id = 1;
106 struct timeval tv;
107 uint8_t out[0x40];
108 int pfd;
109
110 if (gettimeofday(&tv, NULL) == -1) {
111 perror("gettimeofay");
112 return 0;
113 }
114
115 memset(out, 0, sizeof(out));
116
117 out[0] = 'S';
118 out[1] = (id >> 24) & 0xff;
119 out[2] = (id >> 16) & 0xff;
120 out[3] = (id >> 8) & 0xff;
121 out[4] = id & 0xff;
122 out[10] = 0x01;
123 out[11] = (tv.tv_usec >> 24) & 0xff;
124 out[12] = (tv.tv_usec >> 16) & 0xff;
125 out[13] = (tv.tv_usec >> 8) & 0xff;
126 out[14] = tv.tv_usec & 0xff;
127
128
129 memcpy(&out[0x0f], msg, msg[0] + 1);
130
131 memset(rdata, 0, sizeof(struct recv_data));
268d2cc6 132 hmcfgusb_send(dev, out, sizeof(out), 1);
25870f58
MG
133
134 while (1) {
135 if (rdata->message_type == MESSAGE_TYPE_R) {
136 if (((rdata->status & 0xff) == 0x01) ||
137 ((rdata->status & 0xff) == 0x02)) {
138 break;
139 } else {
2d1f08ac
MG
140 if ((rdata->status & 0xff00) == 0x0400) {
141 fprintf(stderr, "\nOut of credits!\n");
142 } else if ((rdata->status & 0xff) == 0x08) {
143 fprintf(stderr, "\nMissing ACK!\n");
144 } else {
145 fprintf(stderr, "\nInvalid status: %04x\n", rdata->status);
146 }
25870f58
MG
147 return 0;
148 }
149 }
150 errno = 0;
151 pfd = hmcfgusb_poll(dev, 1);
152 if ((pfd < 0) && errno) {
153 if (errno != ETIMEDOUT) {
154 perror("\n\nhmcfgusb_poll");
155 exit(EXIT_FAILURE);
156 }
157 }
158 }
159
160 id++;
161 return 1;
162}
163
da4ab971
MG
164static int switch_speed(struct hmcfgusb_dev *dev, struct recv_data *rdata, uint8_t speed)
165{
166 uint8_t out[0x40];
167 int pfd;
168
169 printf("Entering %uk-mode\n", speed);
170
171 memset(out, 0, sizeof(out));
172 out[0] = 'G';
173 out[1] = speed;
174
268d2cc6 175 hmcfgusb_send(dev, out, sizeof(out), 1);
da4ab971
MG
176
177 while (1) {
178 errno = 0;
179 pfd = hmcfgusb_poll(dev, 1);
180 if ((pfd < 0) && errno) {
181 if (errno != ETIMEDOUT) {
182 perror("\n\nhmcfgusb_poll");
183 exit(EXIT_FAILURE);
184 }
185 }
186 if (rdata->speed == speed)
187 break;
188 }
189
190 return 1;
191}
192
25870f58
MG
193int main(int argc, char **argv)
194{
195 const char twiddlie[] = { '-', '\\', '|', '/' };
196 const uint8_t switch_msg[] = { 0x10, 0x5B, 0x11, 0xF8, 0x15, 0x47 };
197 struct hmcfgusb_dev *dev;
198 struct recv_data rdata;
199 uint8_t out[0x40];
200 uint8_t *pos;
201 uint8_t msgid = 0x1;
202 uint16_t len;
203 struct firmware *fw;
204 int block;
205 int pfd;
206 int debug = 0;
207 int cnt;
da4ab971 208 int switchcnt = 0;
25870f58
MG
209 int msgnum = 0;
210 int switched = 0;
211
212 printf("HomeMatic OTA flasher version " VERSION "\n\n");
213
214 if (argc != 3) {
215 if (argc == 1)
216 fprintf(stderr, "Missing firmware filename!\n\n");
217
218 if (argc == 2)
219 fprintf(stderr, "Missing serial!\n\n");
220
221 fprintf(stderr, "Syntax: %s firmware.eq3 SERIALNUMBER\n\n", argv[0]);
222 exit(EXIT_FAILURE);
223 }
224
225 fw = firmware_read_firmware(argv[1], debug);
226 if (!fw)
227 exit(EXIT_FAILURE);
228
229 hmcfgusb_set_debug(debug);
230
231 memset(&rdata, 0, sizeof(rdata));
232
233 dev = hmcfgusb_init(parse_hmcfgusb, &rdata);
234 if (!dev) {
235 fprintf(stderr, "Can't initialize HM-CFG-USB\n");
236 exit(EXIT_FAILURE);
237 }
238
2d1f08ac
MG
239 printf("\nRebooting HM-CFG-USB to avoid running out of credits\n\n");
240
241 if (!dev->bootloader) {
242 printf("HM-CFG-USB not in bootloader mode, entering bootloader.\n");
243 hmcfgusb_enter_bootloader(dev);
244 printf("Waiting for device to reappear...\n");
245
246 do {
247 if (dev) {
248 hmcfgusb_close(dev);
249 }
250 sleep(1);
251 } while (((dev = hmcfgusb_init(parse_hmcfgusb, &rdata)) == NULL) || (!dev->bootloader));
252 }
253
25870f58 254 if (dev->bootloader) {
2d1f08ac
MG
255 printf("HM-CFG-USB in bootloader mode, rebooting\n");
256 hmcfgusb_leave_bootloader(dev);
257
258 do {
259 if (dev) {
260 hmcfgusb_close(dev);
261 }
262 sleep(1);
263 } while (((dev = hmcfgusb_init(parse_hmcfgusb, &rdata)) == NULL) || (dev->bootloader));
25870f58
MG
264 }
265
2d1f08ac 266 printf("\n\nHM-CFG-USB opened\n\n");
25870f58 267
865d5b4c
MG
268 memset(out, 0, sizeof(out));
269 out[0] = 'K';
270 hmcfgusb_send(dev, out, sizeof(out), 1);
271
272 while (1) {
273 errno = 0;
274 pfd = hmcfgusb_poll(dev, 1);
275 if ((pfd < 0) && errno) {
276 if (errno != ETIMEDOUT) {
277 perror("\n\nhmcfgusb_poll");
278 exit(EXIT_FAILURE);
279 }
280 }
281 if (rdata.hmcfgusb_version)
282 break;
283 }
284
285 if (rdata.hmcfgusb_version < 0x3c7) {
286 fprintf(stderr, "HM-CFG-USB firmware too low: %u < 967\n", rdata.hmcfgusb_version);
287 exit(EXIT_FAILURE);
288 }
289
290 printf("HM-CFG-USB firmware version: %u\n", rdata.hmcfgusb_version);
291
da4ab971
MG
292 if (!switch_speed(dev, &rdata, 10)) {
293 fprintf(stderr, "Can't switch speed!\n");
294 exit(EXIT_FAILURE);
25870f58
MG
295 }
296
297 printf("Waiting for device with serial %s\n", argv[2]);
298
299 while (1) {
300 errno = 0;
301 pfd = hmcfgusb_poll(dev, 1);
302 if ((pfd < 0) && errno) {
303 if (errno != ETIMEDOUT) {
304 perror("\n\nhmcfgusb_poll");
305 exit(EXIT_FAILURE);
306 }
307 }
308
309 if ((rdata.message[LEN] == 0x14) && /* Length */
310 (rdata.message[MSGID] == 0x00) && /* Message ID */
311 (rdata.message[CTL] == 0x00) && /* Control Byte */
312 (rdata.message[TYPE] == 0x10) && /* Messagte type: Information */
313 (DST(rdata.message) == 0x000000) && /* Broadcast */
314 (rdata.message[PAYLOAD] == 0x00) && /* FUP? */
315 (rdata.message[PAYLOAD+2] == 'E') &&
316 (rdata.message[PAYLOAD+3] == 'Q')) {
317 if (!strncmp((char*)&(rdata.message[0x0b]), argv[2], 10)) {
318 hmid = SRC(rdata.message);
319 break;
320 }
321 }
322 }
323
324 printf("Device with serial %s (hmid: %06x) entered firmware-update-mode\n", argv[2], hmid);
325
326 printf("Adding HMID\n");
327
328 memset(out, 0, sizeof(out));
329 out[0] = '+';
330 out[1] = (hmid >> 16) & 0xff;
331 out[2] = (hmid >> 8) & 0xff;
332 out[3] = hmid & 0xff;
333
268d2cc6 334 hmcfgusb_send(dev, out, sizeof(out), 1);
25870f58 335
da4ab971 336 switchcnt = 3;
25870f58
MG
337 do {
338 printf("Initiating remote switch to 100k\n");
339
340 memset(out, 0, sizeof(out));
341
342 out[MSGID] = msgid++;
343 out[CTL] = 0x00;
344 out[TYPE] = 0xCB;
558a94bb 345 SET_SRC(out, my_hmid);
25870f58
MG
346 SET_DST(out, hmid);
347
348 memcpy(&out[PAYLOAD], switch_msg, sizeof(switch_msg));
349 SET_LEN_FROM_PAYLOADLEN(out, sizeof(switch_msg));
350
351 if (!send_hm_message(dev, &rdata, out)) {
352 exit(EXIT_FAILURE);
353 }
354
da4ab971
MG
355 if (!switch_speed(dev, &rdata, 100)) {
356 fprintf(stderr, "Can't switch speed!\n");
357 exit(EXIT_FAILURE);
25870f58
MG
358 }
359
360 printf("Has the device switched?\n");
361
362 memset(out, 0, sizeof(out));
363
364 out[MSGID] = msgid++;
365 out[CTL] = 0x20;
366 out[TYPE] = 0xCB;
558a94bb 367 SET_SRC(out, my_hmid);
25870f58
MG
368 SET_DST(out, hmid);
369
370 memcpy(&out[PAYLOAD], switch_msg, sizeof(switch_msg));
371 SET_LEN_FROM_PAYLOADLEN(out, sizeof(switch_msg));
372
373 cnt = 3;
374 do {
375 if (send_hm_message(dev, &rdata, out)) {
376 /* A0A02000221B9AD00000000 */
377 switched = 1;
378 break;
379
380 }
381 } while (cnt--);
382
383 if (!switched) {
da4ab971 384 printf("No!\n");
25870f58 385
da4ab971
MG
386 if (!switch_speed(dev, &rdata, 10)) {
387 fprintf(stderr, "Can't switch speed!\n");
388 exit(EXIT_FAILURE);
25870f58
MG
389 }
390 }
da4ab971 391 } while ((!switched) && (switchcnt--));
25870f58 392
268d2cc6
MG
393 if (!switched) {
394 fprintf(stderr, "Too many errors, giving up!\n");
395 exit(EXIT_FAILURE);
396 }
25870f58 397
da4ab971 398 printf("Yes!\n");
25870f58
MG
399
400 printf("Flashing %d blocks", fw->fw_blocks);
401 if (debug) {
402 printf("\n");
403 } else {
404 printf(": %04u/%04u %c", 0, fw->fw_blocks, twiddlie[0]);
405 fflush(stdout);
406 }
407
408 for (block = 0; block < fw->fw_blocks; block++) {
409 int first;
410
411 len = fw->fw[block][2] << 8;
412 len |= fw->fw[block][3];
413
414 pos = &(fw->fw[block][2]);
415
416 len += 2; /* length */
417
418 if (debug)
419 hexdump(pos, len, "F> ");
420
421 first = 1;
422 cnt = 0;
423 do {
424 int payloadlen = 35;
425 int ack = 0;
426
427 if (first) {
428 payloadlen = 37;
429 first = 0;
430 }
431
432 if ((len - (pos - &(fw->fw[block][2]))) < payloadlen)
433 payloadlen = (len - (pos - &(fw->fw[block][2])));
434
435 if (((pos + payloadlen) - &(fw->fw[block][2])) == len)
436 ack = 1;
437
438 memset(&rdata, 0, sizeof(rdata));
439
440 memset(out, 0, sizeof(out));
441
da4ab971 442 out[MSGID] = msgid;
25870f58
MG
443 if (ack)
444 out[CTL] = 0x20;
445 out[TYPE] = 0xCA;
558a94bb 446 SET_SRC(out, my_hmid);
25870f58
MG
447 SET_DST(out, hmid);
448
449 memcpy(&out[PAYLOAD], pos, payloadlen);
450 SET_LEN_FROM_PAYLOADLEN(out, payloadlen);
451
452 if (send_hm_message(dev, &rdata, out)) {
453 pos += payloadlen;
454 } else {
455 pos = &(fw->fw[block][2]);
456 cnt++;
2d1f08ac 457 if (cnt == MAX_RETRIES) {
25870f58
MG
458 fprintf(stderr, "\nToo many errors, giving up!\n");
459 exit(EXIT_FAILURE);
460 } else {
461 printf("Flashing %d blocks: %04u/%04u %c", fw->fw_blocks, block + 1, fw->fw_blocks, twiddlie[msgnum % sizeof(twiddlie)]);
462 }
463 }
464
465 msgnum++;
466
467 if (!debug) {
468 printf("\b\b\b\b\b\b\b\b\b\b\b%04u/%04u %c",
469 block + 1, fw->fw_blocks, twiddlie[msgnum % sizeof(twiddlie)]);
470 fflush(stdout);
471 }
472 } while((pos - &(fw->fw[block][2])) < len);
da4ab971 473 msgid++;
25870f58
MG
474 }
475
476 firmware_free(fw);
477
da4ab971 478 printf("\n");
25870f58 479
da4ab971
MG
480 if (!switch_speed(dev, &rdata, 10)) {
481 fprintf(stderr, "Can't switch speed!\n");
482 exit(EXIT_FAILURE);
25870f58
MG
483 }
484
485 printf("Waiting for device to reboot\n");
486
487 cnt = 10;
488 do {
489 errno = 0;
490 pfd = hmcfgusb_poll(dev, 1);
491 if ((pfd < 0) && errno) {
492 if (errno != ETIMEDOUT) {
493 perror("\n\nhmcfgusb_poll");
494 exit(EXIT_FAILURE);
495 }
496 }
497 if (rdata.message_type == MESSAGE_TYPE_E) {
498 break;
499 }
500 } while(cnt--);
501
502 if (rdata.message_type == MESSAGE_TYPE_E) {
503 printf("Device rebooted\n");
504 }
505
506 hmcfgusb_close(dev);
507
508 return EXIT_SUCCESS;
509}
Impressum, Datenschutz