1 /* HM-MOD-UART/HM-LGW-O-TW-W-EU driver
3 * Copyright (c) 2016-17 Michael Gernoth <michael@gernoth.net>
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:
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
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
32 #include <sys/types.h>
38 #include "hmuartlgw.h"
40 #define HMUARTLGW_INIT_TIMEOUT 10000
42 #define HMUARTLGW_SETTLE_TIME 1
46 enum hmuartlgw_state
{
47 HMUARTLGW_QUERY_APPSTATE
,
48 HMUARTLGW_ENTER_BOOTLOADER
,
49 HMUARTLGW_ENTER_BOOTLOADER_ACK
,
51 HMUARTLGW_HMIP_BOOTLOADER
,
52 HMUARTLGW_ENTER_APPLICATION
,
53 HMUARTLGW_ENTER_APPLICATION_ACK
,
54 HMUARTLGW_APPLICATION
,
55 HMUARTLGW_DUAL_APPLICATION
,
56 HMUARTLGW_HMIP_APPLICATION
,
60 enum hmuartlgw_state state
;
61 struct hmuartlgw_dev
*dev
;
65 #define CRC16_POLY 0x8005
67 static uint16_t crc16(uint8_t* buf
, int length
)
69 uint16_t crc
= 0xd77f;
74 for (i
= 0; i
< 8; i
++) {
87 static int hmuartlgw_init_parse(enum hmuartlgw_dst dst
, uint8_t *buf
, int buf_len
, void *data
)
89 struct recv_data
*rdata
= data
;
93 printf("Length: %d\n", buf_len
);
94 hexdump(buf
, buf_len
, "INIT > ");
98 /* Minimally handle DualCopro-Firmware */
99 if (dst
== HMUARTLGW_DUAL
) {
100 if ((buf_len
== 14) && (buf
[0] == 0x00) && !strncmp(((char*)buf
)+1, "DualCoPro_App", 13)) {
101 rdata
->state
= HMUARTLGW_DUAL_APPLICATION
;
105 switch (rdata
->state
) {
106 case HMUARTLGW_QUERY_APPSTATE
:
107 if ((buf
[0] == 0x05) && (buf
[1] == 0x01)) {
108 if (!strncmp(((char*)buf
)+2, "DualCoPro_App", 13)) {
109 rdata
->state
= HMUARTLGW_DUAL_APPLICATION
;
111 } else if (!strncmp(((char*)buf
)+2, "HMIP_TRX_App", 12)) {
112 rdata
->state
= HMUARTLGW_HMIP_APPLICATION
;
114 } else if (!strncmp(((char*)buf
)+2, "HMIP_TRX_Bl", 11)) {
115 rdata
->state
= HMUARTLGW_HMIP_BOOTLOADER
;
120 case HMUARTLGW_ENTER_BOOTLOADER
:
121 if ((buf_len
== 2) &&
124 rdata
->state
= HMUARTLGW_ENTER_BOOTLOADER_ACK
;
129 fprintf(stderr
, "Don't know how to handle this state (%d) for unsupported firmware, giving up!\n", rdata
->state
);
137 /* Re-send commands for DualCopro Firmware */
138 if (dst
== HMUARTLGW_DUAL_ERR
) {
139 uint8_t buf
[128] = { 0 };
141 switch(rdata
->state
) {
142 case HMUARTLGW_QUERY_APPSTATE
:
144 printf("Re-sending appstate-query for new firmare\n");
147 buf
[0] = HMUARTLGW_DUAL_GET_APP
;
148 hmuartlgw_send(rdata
->dev
, buf
, 1, HMUARTLGW_DUAL
);
150 case HMUARTLGW_ENTER_BOOTLOADER
:
152 printf("Re-sending switch to bootloader for new firmare\n");
155 buf
[0] = HMUARTLGW_DUAL_CHANGE_APP
;
156 hmuartlgw_send(rdata
->dev
, buf
, 1, HMUARTLGW_DUAL
);
159 fprintf(stderr
, "Don't know how to handle this error-state (%d) for unsupported firmware, giving up!\n", rdata
->state
);
167 if (dst
!= HMUARTLGW_OS
)
170 if ((buf_len
== 10) && (buf
[0] == 0x00) && !strncmp(((char*)buf
)+1, "Co_CPU_BL", 9)) {
171 rdata
->state
= HMUARTLGW_BOOTLOADER
;
175 if ((buf_len
== 11) && (buf
[0] == 0x00) && !strncmp(((char*)buf
)+1, "Co_CPU_App", 10)) {
176 rdata
->state
= HMUARTLGW_APPLICATION
;
180 switch(rdata
->state
) {
181 case HMUARTLGW_QUERY_APPSTATE
:
182 if ((buf
[0] == HMUARTLGW_OS_ACK
) && (buf
[1] == 0x02)) {
183 if (!strncmp(((char*)buf
)+2, "Co_CPU_BL", 9)) {
184 rdata
->state
= HMUARTLGW_BOOTLOADER
;
185 } else if (!strncmp(((char*)buf
)+2, "Co_CPU_App", 10)) {
186 rdata
->state
= HMUARTLGW_APPLICATION
;
190 case HMUARTLGW_ENTER_BOOTLOADER
:
191 if ((buf_len
== 2) &&
192 (buf
[0] == HMUARTLGW_OS_ACK
) &&
194 rdata
->state
= HMUARTLGW_ENTER_BOOTLOADER_ACK
;
197 case HMUARTLGW_ENTER_BOOTLOADER_ACK
:
198 rdata
->state
= HMUARTLGW_ENTER_BOOTLOADER
;
200 case HMUARTLGW_ENTER_APPLICATION
:
201 if ((buf_len
== 2) &&
202 (buf
[0] == HMUARTLGW_OS_ACK
) &&
204 rdata
->state
= HMUARTLGW_ENTER_APPLICATION_ACK
;
207 case HMUARTLGW_ENTER_APPLICATION_ACK
:
208 rdata
->state
= HMUARTLGW_ENTER_APPLICATION
;
215 /* Try to query current app in case we might be in the DUAL/HMIP-Bootloader */
216 if ((buf
[0] == HMUARTLGW_OS_ACK
) && (buf
[1] == 0x03)) {
217 buf
[0] = HMUARTLGW_DUAL_GET_APP
;
218 hmuartlgw_send(rdata
->dev
, buf
, 1, HMUARTLGW_DUAL
);
224 struct hmuartlgw_dev
*hmuart_init(char *device
, hmuartlgw_cb_fn cb
, void *data
, int app
)
226 struct hmuartlgw_dev
*dev
= NULL
;
227 struct termios oldtio
, tio
;
229 dev
= malloc(sizeof(struct hmuartlgw_dev
));
231 perror("malloc(struct hmuartlgw_dev)");
235 memset(dev
, 0, sizeof(struct hmuartlgw_dev
));
237 dev
->fd
= open(device
, O_RDWR
| O_NOCTTY
);
239 perror("open(hmuartlgw)");
244 fprintf(stderr
, "%s opened\n", device
);
247 if (tcgetattr(dev
->fd
, &oldtio
) == -1) {
252 memset(&tio
, 0, sizeof(tio
));
254 tio
.c_cflag
= B115200
| CS8
| CLOCAL
| CREAD
;
255 tio
.c_iflag
= IGNPAR
;
261 tcflush(dev
->fd
, TCIFLUSH
);
262 if (tcsetattr(dev
->fd
, TCSANOW
, &tio
) == -1) {
268 fprintf(stderr
, "serial parameters set\n");
271 hmuartlgw_flush(dev
);
274 hmuartlgw_enter_app(dev
);
276 hmuartlgw_enter_bootloader(dev
);
291 struct hmuartlgw_dev
*hmlgw_init(char *device
, hmuartlgw_cb_fn cb
, void *data
)
293 struct hmuartlgw_dev
*dev
= NULL
;
298 void hmuartlgw_enter_bootloader(struct hmuartlgw_dev
*dev
)
300 hmuartlgw_cb_fn cb_old
= dev
->cb
;
301 void *cb_data_old
= dev
->cb_data
;
302 struct recv_data rdata
= { 0 };
303 uint8_t buf
[128] = { 0 };
307 fprintf(stderr
, "Entering bootloader\n");
310 dev
->cb
= hmuartlgw_init_parse
;
311 dev
->cb_data
= &rdata
;
314 rdata
.state
= HMUARTLGW_QUERY_APPSTATE
;
315 buf
[0] = HMUARTLGW_OS_GET_APP
;
316 hmuartlgw_send(dev
, buf
, 1, HMUARTLGW_OS
);
319 ret
= hmuartlgw_poll(dev
, HMUARTLGW_INIT_TIMEOUT
);
320 if (ret
== -1 && errno
== ETIMEDOUT
) {
321 fprintf(stderr
, "Communication with the module timed out, is the serial port configured correctly?\n");
324 } while (rdata
.state
== HMUARTLGW_QUERY_APPSTATE
);
326 if ((rdata
.state
!= HMUARTLGW_BOOTLOADER
) &&
327 (rdata
.state
!= HMUARTLGW_HMIP_BOOTLOADER
)) {
329 rdata
.state
= HMUARTLGW_ENTER_BOOTLOADER
;
330 buf
[0] = HMUARTLGW_OS_CHANGE_APP
;
331 hmuartlgw_send(dev
, buf
, 1, HMUARTLGW_OS
);
334 ret
= hmuartlgw_poll(dev
, HMUARTLGW_INIT_TIMEOUT
);
335 if (ret
== -1 && errno
== ETIMEDOUT
) {
336 fprintf(stderr
, "Communication with the module timed out, is the serial port configured correctly?\n");
339 } while ((rdata
.state
!= HMUARTLGW_BOOTLOADER
) &&
340 (rdata
.state
!= HMUARTLGW_HMIP_BOOTLOADER
));
342 printf("Waiting for bootloader to settle...\n");
343 sleep(HMUARTLGW_SETTLE_TIME
);
347 dev
->cb_data
= cb_data_old
;
350 void hmuartlgw_enter_app(struct hmuartlgw_dev
*dev
)
352 hmuartlgw_cb_fn cb_old
= dev
->cb
;
353 void *cb_data_old
= dev
->cb_data
;
354 struct recv_data rdata
= { 0 };
355 uint8_t buf
[128] = { 0 };
359 fprintf(stderr
, "Entering application\n");
362 dev
->cb
= hmuartlgw_init_parse
;
363 dev
->cb_data
= &rdata
;
366 rdata
.state
= HMUARTLGW_QUERY_APPSTATE
;
367 buf
[0] = HMUARTLGW_OS_GET_APP
;
368 hmuartlgw_send(dev
, buf
, 1, HMUARTLGW_OS
);
371 ret
= hmuartlgw_poll(dev
, HMUARTLGW_INIT_TIMEOUT
);
372 if (ret
== -1 && errno
== ETIMEDOUT
) {
373 fprintf(stderr
, "Communication with the module timed out, is the serial port configured correctly?\n");
376 } while (rdata
.state
== HMUARTLGW_QUERY_APPSTATE
);
378 if ((rdata
.state
!= HMUARTLGW_APPLICATION
) &&
379 (rdata
.state
!= HMUARTLGW_DUAL_APPLICATION
)) {
381 rdata
.state
= HMUARTLGW_ENTER_APPLICATION
;
382 buf
[0] = HMUARTLGW_OS_CHANGE_APP
;
383 hmuartlgw_send(dev
, buf
, 1, HMUARTLGW_OS
);
386 ret
= hmuartlgw_poll(dev
, HMUARTLGW_INIT_TIMEOUT
);
387 if (ret
== -1 && errno
== ETIMEDOUT
) {
388 fprintf(stderr
, "Communication with the module timed out, is the serial port configured correctly?\n");
391 } while ((rdata
.state
!= HMUARTLGW_APPLICATION
) &&
392 (rdata
.state
!= HMUARTLGW_DUAL_APPLICATION
));
394 if (rdata
.state
== HMUARTLGW_APPLICATION
) {
395 printf("Waiting for application to settle...\n");
396 sleep(HMUARTLGW_SETTLE_TIME
);
400 if (rdata
.state
== HMUARTLGW_DUAL_APPLICATION
) {
401 fprintf(stderr
, "Unsupported firmware, please install HM-only firmware!\n");
407 dev
->cb_data
= cb_data_old
;
410 static int hmuartlgw_escape(uint8_t *frame
, int framelen
)
414 for (i
= 1; i
< framelen
; i
++) {
415 if (frame
[i
] == 0xfc || frame
[i
] == 0xfd) {
416 memmove(frame
+ i
+ 1, frame
+ i
, framelen
- i
);
425 int hmuartlgw_send_raw(struct hmuartlgw_dev
*dev
, uint8_t *frame
, int framelen
)
431 hexdump(frame
, framelen
, "UARTLGW < ");
434 framelen
= hmuartlgw_escape(frame
, framelen
);
437 ret
= write(dev
->fd
, frame
+ w
, framelen
- w
);
443 } while (w
< framelen
);
448 int hmuartlgw_send(struct hmuartlgw_dev
*dev
, uint8_t *cmd
, int cmdlen
, enum hmuartlgw_dst dst
)
450 static uint8_t cnt
= 0;
451 uint8_t frame
[4096] = { 0 };
455 frame
[1] = ((cmdlen
+ 2) >> 8) & 0xff;
456 frame
[2] = (cmdlen
+ 2) & 0xff;
458 dev
->last_send_cnt
= cnt
;
460 memcpy(&(frame
[5]), cmd
, cmdlen
);
461 crc
= crc16(frame
, cmdlen
+ 5);
462 frame
[cmdlen
+ 5] = (crc
>> 8) & 0xff;
463 frame
[cmdlen
+ 6] = crc
& 0xff;
465 return hmuartlgw_send_raw(dev
, frame
, cmdlen
+ 7);
468 int hmuartlgw_poll(struct hmuartlgw_dev
*dev
, int timeout
)
470 struct pollfd pfds
[1];
477 memset(pfds
, 0, sizeof(struct pollfd
) * 1);
479 pfds
[0].fd
= dev
->fd
;
480 pfds
[0].events
= POLLIN
;
482 ret
= poll(pfds
, 1, timeout
);
492 if (!(pfds
[0].revents
& POLLIN
)) {
497 r
= read(dev
->fd
, dev
->buf
+dev
->pos
, 1);
508 if (dev
->buf
[0] != 0xfd) {
509 memset(dev
->buf
, 0, sizeof(dev
->buf
));
511 dev
->unescape_next
= 0;
515 if (dev
->unescape_next
) {
516 dev
->buf
[dev
->pos
-1] |= 0x80;
517 dev
->unescape_next
= 0;
518 } else if (dev
->buf
[dev
->pos
-1] == 0xfc) {
519 dev
->unescape_next
= 1;
527 len
= ((dev
->buf
[1] << 8) & 0xff00) | (dev
->buf
[2] & 0xff);
529 if (dev
->pos
< len
+ 5)
535 crc
= crc16(dev
->buf
, dev
->pos
);
538 hexdump(dev
->buf
, dev
->pos
, "UARTLGW > ");
540 dev
->cb(dev
->buf
[3], dev
->buf
+ 5 , dev
->pos
- 7, dev
->cb_data
);
542 memset(dev
->buf
, 0, sizeof(dev
->buf
));
544 dev
->unescape_next
= 0;
546 fprintf(stderr
, "Invalid checksum received!\n");
547 hexdump(dev
->buf
, dev
->pos
, "ERR> ");
548 printf("calculated: %04x\n", crc
);
550 memset(dev
->buf
, 0, sizeof(dev
->buf
));
552 dev
->unescape_next
= 0;
559 void hmuartlgw_close(struct hmuartlgw_dev
*dev
)
564 void hmuartlgw_flush(struct hmuartlgw_dev
*dev
)
566 struct pollfd pfds
[1];
571 tcflush(dev
->fd
, TCIOFLUSH
);
574 memset(pfds
, 0, sizeof(struct pollfd
) * 1);
576 pfds
[0].fd
= dev
->fd
;
577 pfds
[0].events
= POLLIN
;
579 ret
= poll(pfds
, 1, 100);
583 if (!(pfds
[0].revents
& POLLIN
))
586 memset(buf
, 0, sizeof(buf
));
587 r
= read(dev
->fd
, buf
, sizeof(buf
));
595 void hmuartlgw_set_debug(int d
)