Add error handling to OTA process
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
8f052ecb37
commit
24f5e4398d
6 changed files with 119 additions and 51 deletions
|
@ -4,40 +4,68 @@ import time
|
||||||
import websocket
|
import websocket
|
||||||
import argparse
|
import argparse
|
||||||
import zlib
|
import zlib
|
||||||
|
from websocket import WebSocketTimeoutException
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Update fiatlux firmware via websocket.')
|
parser = argparse.ArgumentParser(description='Update fiatlux firmware via websocket.')
|
||||||
parser.add_argument("binfile")
|
parser.add_argument("binfile")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
bs = 1024
|
||||||
|
|
||||||
|
|
||||||
|
def parse_reply(bytes):
|
||||||
|
cmd = bytes[0:1].decode("utf-8")
|
||||||
|
ret = int.from_bytes(bytes[1:2], "big")
|
||||||
|
val = int.from_bytes(bytes[2:4], "big")
|
||||||
|
return {'cmd': cmd, 'ret': ret, 'val': val}
|
||||||
|
|
||||||
|
|
||||||
with open(args.binfile, "rb") as f:
|
with open(args.binfile, "rb") as f:
|
||||||
try:
|
try:
|
||||||
ws = websocket.WebSocket()
|
ws = websocket.WebSocket()
|
||||||
|
print("send {}".format(args.binfile))
|
||||||
ws.connect("ws://172.16.0.1")
|
ws.connect("ws://172.16.0.1")
|
||||||
i = 0
|
i = 0
|
||||||
rolling = 0
|
bytes = f.read()
|
||||||
total = 0
|
rolling = zlib.crc32(bytes)
|
||||||
|
total = len(bytes)
|
||||||
while True:
|
while True:
|
||||||
bytes = f.read(512)
|
chunk = bytes[bs * i:bs * i + bs]
|
||||||
rolling = zlib.crc32(bytes, rolling)
|
|
||||||
total += len(bytes)
|
|
||||||
msg = b'F\x00\x00\x00'
|
msg = b'F\x00\x00\x00'
|
||||||
msg += i.to_bytes(2, 'big')
|
msg += i.to_bytes(2, 'big')
|
||||||
msg += len(bytes).to_bytes(2, 'big')
|
msg += len(chunk).to_bytes(2, 'big')
|
||||||
msg += (zlib.crc32(bytes) & 0xffffffff).to_bytes(4, 'big')
|
msg += (zlib.crc32(chunk) & 0xffffffff).to_bytes(4, 'big')
|
||||||
msg += bytes
|
msg += chunk
|
||||||
ws.send(msg)
|
ws.send(msg)
|
||||||
reply = ws.recv()
|
print("\r{:6.2f}%".format(100 * i * bs / total), end='')
|
||||||
time.sleep(0.05)
|
reply = parse_reply(ws.recv())
|
||||||
|
if reply['cmd'] == 'F' and reply['ret'] == 0:
|
||||||
i += 1
|
i += 1
|
||||||
if len(bytes) != 512:
|
elif reply['cmd'] == 'F' and reply['ret'] == 1:
|
||||||
|
print("Error: SEQUENCE_OUT_OF_ORDER")
|
||||||
|
i = reply['val']
|
||||||
|
elif reply['cmd'] == 'F' and reply['ret'] == 2:
|
||||||
|
print("Error: CHECKSUM_MISMATCH")
|
||||||
|
i = reply['val']
|
||||||
|
else:
|
||||||
|
print(reply)
|
||||||
|
|
||||||
|
time.sleep(0.05)
|
||||||
|
if len(chunk) != bs:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
print("\rdone ")
|
||||||
msg = b'C\x00\x00\x00'
|
msg = b'C\x00\x00\x00'
|
||||||
msg += total.to_bytes(4, 'big')
|
msg += total.to_bytes(4, 'big')
|
||||||
msg += rolling.to_bytes(4, 'big')
|
msg += (rolling).to_bytes(4, 'big')
|
||||||
|
ws.settimeout(5)
|
||||||
ws.send(msg)
|
ws.send(msg)
|
||||||
print(ws.recv())
|
reply = parse_reply(ws.recv())
|
||||||
|
print(reply)
|
||||||
ws.close()
|
ws.close()
|
||||||
|
except WebSocketTimeoutException:
|
||||||
|
pass
|
||||||
except ConnectionResetError:
|
except ConnectionResetError:
|
||||||
pass
|
pass
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
|
|
@ -68,7 +68,7 @@ void system_otaflash_init() {
|
||||||
otaflash_context.seq = 0;
|
otaflash_context.seq = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int system_otaflash_chunk(uint8_t *data, uint16_t len, uint16_t seq, uint32_t hash) {
|
enum return_code system_otaflash_chunk(uint8_t *data, uint16_t len, uint16_t seq, uint32_t hash, uint16_t *ack) {
|
||||||
uint32_t local_hash = crc32(data, len);
|
uint32_t local_hash = crc32(data, len);
|
||||||
if(hash == local_hash && otaflash_context.seq == seq) {
|
if(hash == local_hash && otaflash_context.seq == seq) {
|
||||||
if(otaflash_context.head % SECTOR_SIZE == 0) {
|
if(otaflash_context.head % SECTOR_SIZE == 0) {
|
||||||
|
@ -83,9 +83,13 @@ int system_otaflash_chunk(uint8_t *data, uint16_t len, uint16_t seq, uint32_t ha
|
||||||
}
|
}
|
||||||
otaflash_context.head += len;
|
otaflash_context.head += len;
|
||||||
otaflash_context.seq++;
|
otaflash_context.seq++;
|
||||||
return 0x88;
|
return OK;
|
||||||
|
} else if(hash != local_hash) {
|
||||||
|
return CHECKSUM_MISMATCH;
|
||||||
} else {
|
} else {
|
||||||
return 0xff;
|
if(ack)
|
||||||
|
*ack = otaflash_context.seq;
|
||||||
|
return SEQUENCE_OUT_OF_ORDER;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -96,21 +100,22 @@ void system_otaflash_verify_chunk(void *ctx, void *data, size_t len) {
|
||||||
*(uint32_t *) ctx = digest;
|
*(uint32_t *) ctx = digest;
|
||||||
}
|
}
|
||||||
|
|
||||||
int system_otaflash_verify_and_switch(uint32_t len, uint32_t hash) {
|
enum return_code system_otaflash_verify_and_switch(uint32_t len, uint32_t hash) {
|
||||||
|
|
||||||
uint32_t digest = 0;
|
uint32_t digest = 0;
|
||||||
rboot_digest_image(otaflash_context.base, min(len, MAX_IMAGE_SIZE), system_otaflash_verify_chunk, &digest);
|
rboot_digest_image(otaflash_context.base, min(len, MAX_IMAGE_SIZE), system_otaflash_verify_chunk, &digest);
|
||||||
|
|
||||||
if(hash != digest) {
|
if(hash != digest) {
|
||||||
printf("OTA failed to verify firmware\r\n");
|
printf("OTA failed to verify firmware\r\n");
|
||||||
return 0x99;
|
return CHECKSUM_MISMATCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
vPortEnterCritical();
|
vPortEnterCritical();
|
||||||
if(!rboot_set_current_rom(otaflash_context.slot)) {
|
if(!rboot_set_current_rom(otaflash_context.slot)) {
|
||||||
printf("OTA failed to set new rboot slot\r\n");
|
printf("OTA Update failed to set new rboot slot\r\n");
|
||||||
|
vPortExitCritical();
|
||||||
|
return RBOOT_SWITCH_FAILED;
|
||||||
}
|
}
|
||||||
sdk_system_restart();
|
vPortExitCritical();
|
||||||
vPortExitCritical(); // | should not be reached
|
return OK;
|
||||||
return 0x77; // |
|
|
||||||
}
|
}
|
|
@ -6,6 +6,7 @@
|
||||||
#define FIRMWARE_SYSTEM_H
|
#define FIRMWARE_SYSTEM_H
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -17,9 +18,9 @@ void system_init_config();
|
||||||
|
|
||||||
void system_otaflash_init();
|
void system_otaflash_init();
|
||||||
|
|
||||||
int system_otaflash_chunk(uint8_t *data, uint16_t len, uint16_t seq, uint32_t hash);
|
enum return_code system_otaflash_chunk(uint8_t *data, uint16_t len, uint16_t seq, uint32_t hash, uint16_t *ack);
|
||||||
|
|
||||||
int system_otaflash_verify_and_switch(uint32_t len, uint32_t hash);
|
enum return_code system_otaflash_verify_and_switch(uint32_t len, uint32_t hash);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
20
firmware/types.h
Normal file
20
firmware/types.h
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
//
|
||||||
|
// Created by jedi on 09.09.21.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef FIRMWARE_TYPES_H
|
||||||
|
#define FIRMWARE_TYPES_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum return_code {
|
||||||
|
OK = 0, SEQUENCE_OUT_OF_ORDER, CHECKSUM_MISMATCH, RBOOT_SWITCH_FAILED, ERROR = 0xFF
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif //FIRMWARE_TYPES_H
|
|
@ -215,38 +215,48 @@ struct fw_check {
|
||||||
void websocket_cb(struct tcp_pcb *pcb, char *data, u16_t data_len,
|
void websocket_cb(struct tcp_pcb *pcb, char *data, u16_t data_len,
|
||||||
uint8_t /*mode*/) { //mode should be WS_BIN_MODE or WS_TEXT_MODE
|
uint8_t /*mode*/) { //mode should be WS_BIN_MODE or WS_TEXT_MODE
|
||||||
|
|
||||||
uint8_t response[3];
|
uint8_t response[4];
|
||||||
uint16_t val = 0;
|
auto &cmd = (char &) response[0];
|
||||||
char cmd = '0';
|
auto &ret = response[1];
|
||||||
|
auto &val = (uint16_t &) response[2];
|
||||||
|
cmd = '0';
|
||||||
|
ret = ERROR;
|
||||||
|
val = 0;
|
||||||
|
|
||||||
bool togl = 0;
|
bool togl = false;
|
||||||
|
|
||||||
switch (data[0]) {
|
switch (data[0]) {
|
||||||
case 'R': // Restart
|
case 'R': // Restart
|
||||||
cmd = 'R';
|
cmd = 'R';
|
||||||
|
ret = OK;
|
||||||
break;
|
break;
|
||||||
case 'X': // Clear Config
|
case 'X': // Clear Config
|
||||||
cmd = 'X';
|
cmd = 'X';
|
||||||
|
ret = OK;
|
||||||
break;
|
break;
|
||||||
case 'D': // Disable LED
|
case 'D': // Disable LED
|
||||||
signal_led(false);
|
signal_led(false);
|
||||||
val = 1;
|
|
||||||
cmd = 'G';
|
cmd = 'G';
|
||||||
|
ret = OK;
|
||||||
|
val = 1;
|
||||||
break;
|
break;
|
||||||
case 'E': // Enable LED
|
case 'E': // Enable LED
|
||||||
signal_led(true);
|
signal_led(true);
|
||||||
val = 0;
|
|
||||||
cmd = 'G';
|
cmd = 'G';
|
||||||
|
ret = OK;
|
||||||
|
val = 0;
|
||||||
break;
|
break;
|
||||||
case 'F':
|
case 'F':
|
||||||
togl = ~togl;
|
togl = !togl;
|
||||||
signal_led(togl);
|
signal_led(togl);
|
||||||
{
|
{
|
||||||
auto *f = (fw_frame *) data;
|
auto *f = (fw_frame *) data;
|
||||||
if(f->seq == 0) {
|
if(f->seq == 0) {
|
||||||
system_otaflash_init();
|
system_otaflash_init();
|
||||||
}
|
}
|
||||||
val = system_otaflash_chunk(f->data, ntohs(f->len), ntohs(f->seq), ntohl(f->hash));
|
uint16_t ack = 0;
|
||||||
|
ret = system_otaflash_chunk(f->data, ntohs(f->len), ntohs(f->seq), ntohl(f->hash), &ack);
|
||||||
|
val = htons(ack);
|
||||||
}
|
}
|
||||||
cmd = 'F';
|
cmd = 'F';
|
||||||
break;
|
break;
|
||||||
|
@ -254,34 +264,33 @@ void websocket_cb(struct tcp_pcb *pcb, char *data, u16_t data_len,
|
||||||
signal_led(false);
|
signal_led(false);
|
||||||
{
|
{
|
||||||
auto *f = (fw_check *) data;
|
auto *f = (fw_check *) data;
|
||||||
val = system_otaflash_verify_and_switch(ntohl(f->len), ntohl(f->hash));
|
ret = system_otaflash_verify_and_switch(ntohl(f->len), ntohl(f->hash));
|
||||||
}
|
}
|
||||||
cmd = 'C';
|
cmd = 'C';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
printf("[websocket_callback]:\n%.*s\n", (int) data_len, (char *) data);
|
printf("[websocket_callback]:\n%.*s\n", (int) data_len, (char *) data);
|
||||||
printf("Unknown command %c\n", data[0]);
|
printf("Unknown command %c\n", data[0]);
|
||||||
val = 0;
|
ret = ERROR;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
response[2] = (uint8_t) val;
|
|
||||||
response[1] = val >> 8;
|
|
||||||
response[0] = cmd;
|
|
||||||
|
|
||||||
LOCK_TCPIP_CORE();
|
LOCK_TCPIP_CORE();
|
||||||
websocket_write(pcb, response, 3, WS_BIN_MODE);
|
websocket_write(pcb, response, 4, WS_BIN_MODE);
|
||||||
UNLOCK_TCPIP_CORE();
|
UNLOCK_TCPIP_CORE();
|
||||||
|
|
||||||
if(data[0] == 'R') { // Restart
|
if(ret == OK) {
|
||||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
if(cmd == 'R' || cmd == 'C') { // Restart
|
||||||
|
printf("rebooting now");
|
||||||
|
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||||
vPortEnterCritical();
|
vPortEnterCritical();
|
||||||
sdk_system_restart();
|
sdk_system_restart();
|
||||||
} else if(data[0] == 'X') { // Clear Config
|
} else if(cmd == 'X') { // Clear Config
|
||||||
vTaskDelay(500 / portTICK_PERIOD_MS);
|
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||||
system_clear_config();
|
system_clear_config();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is called when new websocket is open and
|
* This function is called when new websocket is open and
|
||||||
|
|
|
@ -189,6 +189,10 @@
|
||||||
return pos;
|
return pos;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DataView.prototype.getChar = function (pos) {
|
||||||
|
return String.fromCharCode(this.getInt8(pos));
|
||||||
|
};
|
||||||
|
|
||||||
DataView.prototype.setString = function (pos, str) {
|
DataView.prototype.setString = function (pos, str) {
|
||||||
for (var i = 0; i < str.length; i++) {
|
for (var i = 0; i < str.length; i++) {
|
||||||
this.setInt8(pos++, str.charCodeAt(i));
|
this.setInt8(pos++, str.charCodeAt(i));
|
||||||
|
@ -366,11 +370,11 @@
|
||||||
frame.set(new Uint8Array(slice), 12);
|
frame.set(new Uint8Array(slice), 12);
|
||||||
receive_chunk_confirmation = (dv) => {
|
receive_chunk_confirmation = (dv) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
resolve(i);
|
resolve({cmd: dv.getChar(0), ret: dv.getUint8(1), val: dv.getUint16(2)});
|
||||||
}, 50);
|
}, 50);
|
||||||
}
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
reject(i);
|
reject({frame_error: i});
|
||||||
}, 2000);
|
}, 2000);
|
||||||
wsWrite(frame.buffer);
|
wsWrite(frame.buffer);
|
||||||
});
|
});
|
||||||
|
@ -384,10 +388,10 @@
|
||||||
headerview.setInt32(4, buf.byteLength);
|
headerview.setInt32(4, buf.byteLength);
|
||||||
headerview.setInt32(8, hash);
|
headerview.setInt32(8, hash);
|
||||||
receive_chunk_confirmation = (dv) => {
|
receive_chunk_confirmation = (dv) => {
|
||||||
resolve(true);
|
resolve({cmd: dv.getChar(0), ret: dv.getUint8(1), val: dv.getUint16(2)});
|
||||||
}
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
reject(false);
|
reject({final_error: 0});
|
||||||
}, 500);
|
}, 500);
|
||||||
wsWrite(frame);
|
wsWrite(frame);
|
||||||
});
|
});
|
||||||
|
@ -400,7 +404,8 @@
|
||||||
const ash = crc32(firmware_file);
|
const ash = crc32(firmware_file);
|
||||||
for (var i = 0; i * chunk_size < firmware_file.byteLength; i++) {
|
for (var i = 0; i * chunk_size < firmware_file.byteLength; i++) {
|
||||||
update_progress("ota", i * chunk_size / firmware_file.byteLength * 100);
|
update_progress("ota", i * chunk_size / firmware_file.byteLength * 100);
|
||||||
await transmit_firmware_chunk(firmware_file, i);
|
const reply = await transmit_firmware_chunk(firmware_file, i);
|
||||||
|
console.log("reply", reply)
|
||||||
}
|
}
|
||||||
await transmit_firmware_final(firmware_file, crc32(firmware_file));
|
await transmit_firmware_final(firmware_file, crc32(firmware_file));
|
||||||
update_progress("ota", 100);
|
update_progress("ota", 100);
|
||||||
|
|
Loading…
Reference in a new issue