From aff284f57093626d8a1d0fa262fa6cfe414db386 Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Tue, 30 Aug 2016 21:24:34 +0200 Subject: [PATCH] Initial OTA implementation --- common/common_config.h | 2 + master/docker-compose.yml | 7 ++ master/ota.py | 30 ++++++ spejsiot/SpejsNode.cpp | 114 ++++++++++++++++++-- spejsiot/SpejsNode.h | 81 ++------------ switch/Makefile | 5 + switch/Makefile-user.mk | 4 + switch/rom0.ld | 219 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 383 insertions(+), 79 deletions(-) create mode 100644 master/ota.py create mode 100644 switch/rom0.ld diff --git a/common/common_config.h b/common/common_config.h index 26b4f1a..d603f30 100644 --- a/common/common_config.h +++ b/common/common_config.h @@ -13,6 +13,8 @@ #define TOPIC_PREFIX "iot/" +#define OTA_URL "http://" MQTT_BROKER "/" + #define BTN_PIN 0 #define LED_PIN 2 diff --git a/master/docker-compose.yml b/master/docker-compose.yml index 175025f..3722e05 100644 --- a/master/docker-compose.yml +++ b/master/docker-compose.yml @@ -2,3 +2,10 @@ mosquitto: image: toke/mosquitto ports: - "1883:1883" + +nginx: + image: nginx + volumes: + - ota:/usr/share/nginx/html + ports: + - "80:80" diff --git a/master/ota.py b/master/ota.py new file mode 100644 index 0000000..cea70e7 --- /dev/null +++ b/master/ota.py @@ -0,0 +1,30 @@ +import paho.mqtt.client as mqtt +import time +import random +import sys + +# The callback for when the client receives a CONNACK response from the server. +def on_connect(client, userdata, rc): + print("Connected with result code "+str(rc)) + # Subscribing in on_connect() means that if we lose the connection and + # reconnect then subscriptions will be renewed. + client.subscribe("light/status") + client.subscribe("#") + client.publish("iot/" + sys.argv[1] + "/" + sys.argv[2], sys.argv[3]) + +# The callback for when a PUBLISH message is received from the server. +def on_message(client, userdata, msg): + print(str(time.time())+" "+msg.topic+" "+str(msg.payload)) + exit() + +client = mqtt.Client("test-client-%d" % random.randint(100, 999)) +client.on_connect = on_connect +client.on_message = on_message + +client.connect("localhost", 1883, 60) + +# Blocking call that processes network traffic, dispatches callbacks and +# handles reconnecting. +# Other loop*() functions are available that give a threaded interface and a +# manual interface. +client.loop_forever() diff --git a/spejsiot/SpejsNode.cpp b/spejsiot/SpejsNode.cpp index 54eeeab..b72308b 100644 --- a/spejsiot/SpejsNode.cpp +++ b/spejsiot/SpejsNode.cpp @@ -3,10 +3,16 @@ void SpejsNode::init() { deviceID = WifiStation.getMAC().substring(6, 12); + currentSlot = 0; + if(!rboot_get_last_boot_rom(¤tSlot)) { + currentSlot = rboot_get_current_rom(); + } + Serial.begin(115200); Serial.systemDebugOutput(false); // Debug output to serial - Serial.print("*** SpejsNode init, running on: "); - Serial.println(deviceID); + Serial.print("*** SpejsNode init, running on:"); + Serial.print(deviceID); + Serial.printf(", current rom: %d\r\n", currentSlot); WifiStation.config(WIFI_SSID, WIFI_PWD); WifiStation.enable(true); @@ -18,12 +24,26 @@ void SpejsNode::init() { 20, *[] { Serial.println("Connection failed"); }); + + inputs["control"] = MqttStringSubscriptionCallback(&SpejsNode::controlHandler, this); } void SpejsNode::keepAliveHandler() { if(mqtt.getConnectionState() != eTCS_Connected) { Serial.println("Reconnecting"); onConnected(); + } else { + uint8_t mode; + if(rboot_get_last_boot_mode(&mode)) { + if(mode == MODE_TEMP_ROM) { + rboot_set_current_rom(currentSlot); + Serial.println("Successfuly connected, accepting temp rom"); + } else { + Serial.printf("Not a TEMP ROM boot: %d\r\n", mode); + } + } else { + Serial.println("No boot mode info"); + } } } @@ -54,26 +74,104 @@ bool SpejsNode::notify(String key, String value) { return false; } -void SpejsNode::registerInput(String key, InputCallback callback) { +void SpejsNode::registerInput(String key, MqttStringSubscriptionCallback callback) { inputs[key] = callback; } -void SpejsNode::mqttCallback(String topic, String value) { +void SpejsNode::mqttCallback(String origtopic, String value) { String devicePrefix = TOPIC_PREFIX + deviceID; - if(!topic.startsWith(devicePrefix)) { + if(!origtopic.startsWith(devicePrefix)) { Serial.println("ignoring"); return; } - topic = topic.substring(devicePrefix.length() + 1); + String topic = origtopic.substring(devicePrefix.length() + 1); Serial.println(topic); Serial.println(value); if(inputs.contains(topic)) { Serial.println("dupa"); - inputs[topic](value); + inputs[topic](origtopic, value); } else { - Serial.println("default"); + Serial.println("unknown topic?"); + } +} + +void SpejsNode::controlHandler(String key, String value) { + Serial.println("Control command: " + value); + if(value == "ota") { + startOTA(); + } else if(value == "restart") { + //System.restart(); + keepaliveTimer.initializeMs(500, *[] { + System.restart(); + }).start(); + } else { + Serial.println("Invalid command"); + } +} + +void SpejsNode::startOTA() { + uint8_t slot; + rboot_config bootconf; + String romURL = OTA_URL + deviceID + "/rom0.bin"; + String spiffsURL = OTA_URL + deviceID + "/spiff_rom.bin"; + + Serial.println("Updating..."); + + // need a clean object, otherwise if run before and failed will not run again + if (otaUpdater) delete otaUpdater; + otaUpdater = new rBootHttpUpdate(); + + bootconf = rboot_get_config(); + + if (currentSlot == 0) + slot = 1; + else + slot = 0; + + Serial.printf("Updating to rom %d.\r\n", slot); + + // flash rom to position indicated in the rBoot config rom table + otaUpdater->addItem(bootconf.roms[slot], romURL); + +#ifndef DISABLE_SPIFFS + // use user supplied values (defaults for 4mb flash in makefile) + if (slot == 0) { + otaUpdater->addItem(RBOOT_SPIFFS_0, spiffsURL); + } else { + otaUpdater->addItem(RBOOT_SPIFFS_1, spiffsURL); + } +#endif + + otaUpdater->setCallback(otaUpdateDelegate(&SpejsNode::otaUpdateCallback, this)); + otaUpdater->start(); + notify("ota", "started"); +} + +void SpejsNode::otaUpdateCallback(bool result) { + Serial.println("In callback..."); + if(result == true) { + // success + notify("ota", "finished"); + + uint8 slot; + + if (currentSlot == 0) + slot = 1; + else + slot = 0; + + // set to boot new rom and then reboot + Serial.printf("Firmware updated, rebooting to rom %d...\r\n", slot); + + rboot_set_temp_rom(slot); + + keepaliveTimer.initializeMs(500, *[] { + System.restart(); + }).start(); + } else { + notify("ota", "failed"); } } diff --git a/spejsiot/SpejsNode.h b/spejsiot/SpejsNode.h index 434bd3e..c089868 100644 --- a/spejsiot/SpejsNode.h +++ b/spejsiot/SpejsNode.h @@ -2,96 +2,35 @@ #include #include -typedef void (*InputCallback)(String); - class SpejsNode { protected: String deviceID; String deviceType; - MqttClient mqtt; // (MQTT_BROKER, MQTT_PORT); + + MqttClient mqtt; + Timer keepaliveTimer; - HashMap inputs; + rBootHttpUpdate* otaUpdater = 0; + + HashMap inputs; void onConnected(); void startOTA(); void keepAliveHandler(); + uint8_t currentSlot; public: SpejsNode(String _deviceType) : mqtt(MQTT_BROKER, MQTT_PORT, MqttStringSubscriptionCallback(&SpejsNode::mqttCallback, this)), - //*[](String topic, String message) { - // Serial.printf("*** message received @ %s:\n\t%s\n***\n", topic.c_str(), message.c_str()); - //}), deviceType(_deviceType) {}; void init(); - //void registerInput(uint32_t gpio); - //void registerOutput(uint32_t gpio); - bool notify(String key, String value); - void registerInput(String key, InputCallback cb); + void registerInput(String key, MqttStringSubscriptionCallback cb); void mqttCallback(String, String); + void controlHandler(String, String); + void otaUpdateCallback(bool result); }; - -/* -MqttClient mqtt(MQTT_BROKER, MQTT_PORT, *[](String topic, String message) { - Serial.printf("*** message received @ %s:\n\t%s\n***\n", topic.c_str(), message.c_str()); -}); - -Timer keepaliveTimer; -String deviceName; - -void startMqttClient() -{ - Serial.println("*** Connecting to MQTT as " + deviceName); - - mqtt.setWill("main/status/" + deviceName, "offline", 1, true); - mqtt.connect(deviceName); - mqtt.publish("main/status/" + deviceName, "online"); - - keepaliveTimer.initializeMs(5000, *[] { - mqtt.publish("main/status/" + deviceName, "alive " + String(millis())); - }).start(); -} - -void init() -{ - deviceName = "switch-" + WifiStation.getMAC().substring(6, 12); - - Serial.begin(SERIAL_BAUD_RATE); // 115200 by default - Serial.systemDebugOutput(false); // Debug output to serial - - Serial.println("*** Starting " + deviceName + " ..."); - - WifiStation.config(WIFI_SSID, WIFI_PWD); - WifiStation.setIP(IPAddress(10, 5, 0, 39), IPAddress(255, 255, 255, 0), IPAddress(10, 5, 0, 1)); - WifiStation.enable(true); - - WifiAccessPoint.enable(false); - - - WifiStation.waitConnection(*[] { - Serial.println("*** Connection succeeded"); - startMqttClient(); - }, 20, *[] { - Serial.println("*** Connection failed"); - }); - - attachInterrupt(BTN_PIN, *[] { - static int lastSwitch = 0; - - // Debouncing - if(lastSwitch + 150 > millis()) { - Serial.println("--- debouncing"); - return; - } - lastSwitch = millis(); - - Serial.println("*** Button pressed"); - mqtt.publish("light/status", "toggle"); - }, FALLING); -} -*/ diff --git a/switch/Makefile b/switch/Makefile index 16d76cd..e5af1e5 100644 --- a/switch/Makefile +++ b/switch/Makefile @@ -22,3 +22,8 @@ include $(SMING_HOME)/Makefile-rboot.mk else include $(SMING_HOME)/Makefile-project.mk endif + +ota: all + -mkdir ../master/ota/$(DEVICE) >/dev/null + cp -r out/firmware/* ../master/ota/$(DEVICE) + python ../master/ota.py $(DEVICE) control ota diff --git a/switch/Makefile-user.mk b/switch/Makefile-user.mk index e6daf21..1367727 100644 --- a/switch/Makefile-user.mk +++ b/switch/Makefile-user.mk @@ -1,3 +1,7 @@ MODULES = app ../spejsiot DISABLE_SPIFFS = 1 USER_CFLAGS = -I../common + +RBOOT_ENABLED ?= 1 +RBOOT_BIG_FLASH ?= 1 +SPI_SIZE ?= 4M diff --git a/switch/rom0.ld b/switch/rom0.ld new file mode 100644 index 0000000..7b2a9f7 --- /dev/null +++ b/switch/rom0.ld @@ -0,0 +1,219 @@ +/* This linker script generated from xt-genldscripts.tpp for LSP . */ +/* Linker Script for ld -N */ +MEMORY +{ + dport0_0_seg : org = 0x3FF00000, len = 0x10 + dram0_0_seg : org = 0x3FFE8000, len = 0x14000 + iram1_0_seg : org = 0x40100000, len = 0x8000 + irom0_0_seg : org = 0x40202010, len = 0x42000 +} + +PHDRS +{ + dport0_0_phdr PT_LOAD; + dram0_0_phdr PT_LOAD; + dram0_0_bss_phdr PT_LOAD; + iram1_0_phdr PT_LOAD; + irom0_0_phdr PT_LOAD; +} + + +/* Default entry point: */ +ENTRY(call_user_start) +EXTERN(_DebugExceptionVector) +EXTERN(_DoubleExceptionVector) +EXTERN(_KernelExceptionVector) +EXTERN(_NMIExceptionVector) +EXTERN(_UserExceptionVector) +PROVIDE(_memmap_vecbase_reset = 0x40000000); +/* Various memory-map dependent cache attribute settings: */ +_memmap_cacheattr_wb_base = 0x00000110; +_memmap_cacheattr_wt_base = 0x00000110; +_memmap_cacheattr_bp_base = 0x00000220; +_memmap_cacheattr_unused_mask = 0xFFFFF00F; +_memmap_cacheattr_wb_trapnull = 0x2222211F; +_memmap_cacheattr_wba_trapnull = 0x2222211F; +_memmap_cacheattr_wbna_trapnull = 0x2222211F; +_memmap_cacheattr_wt_trapnull = 0x2222211F; +_memmap_cacheattr_bp_trapnull = 0x2222222F; +_memmap_cacheattr_wb_strict = 0xFFFFF11F; +_memmap_cacheattr_wt_strict = 0xFFFFF11F; +_memmap_cacheattr_bp_strict = 0xFFFFF22F; +_memmap_cacheattr_wb_allvalid = 0x22222112; +_memmap_cacheattr_wt_allvalid = 0x22222112; +_memmap_cacheattr_bp_allvalid = 0x22222222; +PROVIDE(_memmap_cacheattr_reset = _memmap_cacheattr_wb_trapnull); + +SECTIONS +{ + + .dport0.rodata : ALIGN(4) + { + _dport0_rodata_start = ABSOLUTE(.); + *(.dport0.rodata) + *(.dport.rodata) + _dport0_rodata_end = ABSOLUTE(.); + } >dport0_0_seg :dport0_0_phdr + + .dport0.literal : ALIGN(4) + { + _dport0_literal_start = ABSOLUTE(.); + *(.dport0.literal) + *(.dport.literal) + _dport0_literal_end = ABSOLUTE(.); + } >dport0_0_seg :dport0_0_phdr + + .dport0.data : ALIGN(4) + { + _dport0_data_start = ABSOLUTE(.); + *(.dport0.data) + *(.dport.data) + _dport0_data_end = ABSOLUTE(.); + } >dport0_0_seg :dport0_0_phdr + + .data : ALIGN(4) + { + _data_start = ABSOLUTE(.); + *(.data) + *(.data.*) + *(.gnu.linkonce.d.*) + *(.data1) + *(.sdata) + *(.sdata.*) + *(.gnu.linkonce.s.*) + *(.sdata2) + *(.sdata2.*) + *(.gnu.linkonce.s2.*) + *(.jcr) + _data_end = ABSOLUTE(.); + } >dram0_0_seg :dram0_0_phdr + + .rodata : ALIGN(4) + { + _rodata_start = ABSOLUTE(.); + *(.sdk.version) + *(.rodata) + *(.rodata.*) + *(.gnu.linkonce.r.*) + *(.rodata1) + __XT_EXCEPTION_TABLE__ = ABSOLUTE(.); + *(.xt_except_table) + *(.gcc_except_table) + *(.gnu.linkonce.e.*) + *(.gnu.version_r) + *(.eh_frame) + . = (. + 3) & ~ 3; + /* C++ constructor and destructor tables, properly ordered: */ + __dso_handle = ABSOLUTE(.); + __init_array_start = ABSOLUTE(.); + KEEP (*crtbegin.o(.ctors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + __init_array_end = ABSOLUTE(.); + KEEP (*crtbegin.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + /* C++ exception handlers table: */ + __XT_EXCEPTION_DESCS__ = ABSOLUTE(.); + *(.xt_except_desc) + *(.gnu.linkonce.h.*) + __XT_EXCEPTION_DESCS_END__ = ABSOLUTE(.); + *(.xt_except_desc_end) + *(.dynamic) + *(.gnu.version_d) + . = ALIGN(4); /* this table MUST be 4-byte aligned */ + _bss_table_start = ABSOLUTE(.); + LONG(_bss_start) + LONG(_bss_end) + _bss_table_end = ABSOLUTE(.); + _rodata_end = ABSOLUTE(.); + } >dram0_0_seg :dram0_0_phdr + + .bss ALIGN(8) (NOLOAD) : ALIGN(4) + { + . = ALIGN (8); + _bss_start = ABSOLUTE(.); + *(.dynsbss) + *(.sbss) + *(.sbss.*) + *(.gnu.linkonce.sb.*) + *(.scommon) + *(.sbss2) + *(.sbss2.*) + *(.gnu.linkonce.sb2.*) + *(.dynbss) + *(.bss) + *(.bss.*) + *(.gnu.linkonce.b.*) + *(COMMON) + . = ALIGN (8); + _bss_end = ABSOLUTE(.); + _heap_start = ABSOLUTE(.); +/* _stack_sentry = ALIGN(0x8); */ + } >dram0_0_seg :dram0_0_bss_phdr +/* __stack = 0x3ffc8000; */ + + .irom0.text : ALIGN(4) + { + _irom0_text_start = ABSOLUTE(.); + *(.irom0.literal .irom.literal .irom.text.literal .irom0.text .irom.text) + out/build/app_app.a:*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*) + *libsming.a:*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*) + _irom0_text_end = ABSOLUTE(.); + _flash_code_end = ABSOLUTE(.); + } >irom0_0_seg :irom0_0_phdr + + .text : ALIGN(4) + { + _stext = .; + _text_start = ABSOLUTE(.); + *(.UserEnter.text) + . = ALIGN(16); + *(.DebugExceptionVector.text) + . = ALIGN(16); + *(.NMIExceptionVector.text) + . = ALIGN(16); + *(.KernelExceptionVector.text) + LONG(0) + LONG(0) + LONG(0) + LONG(0) + . = ALIGN(16); + *(.UserExceptionVector.text) + LONG(0) + LONG(0) + LONG(0) + LONG(0) + . = ALIGN(16); + *(.DoubleExceptionVector.text) + LONG(0) + LONG(0) + LONG(0) + LONG(0) + . = ALIGN (16); + *(.entry.text) + *(.init.literal) + *(.init) + *(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*) + *(.iram.literal .iram.text.literal .iram.text) + *(.fini.literal) + *(.fini) + *(.gnu.version) + _text_end = ABSOLUTE(.); + _etext = .; + } >iram1_0_seg :iram1_0_phdr + + .lit4 : ALIGN(4) + { + _lit4_start = ABSOLUTE(.); + *(*.lit4) + *(.lit4.*) + *(.gnu.linkonce.lit4.*) + _lit4_end = ABSOLUTE(.); + } >iram1_0_seg :iram1_0_phdr +} + +/* get ROM code address */ +INCLUDE "../ld/eagle.rom.addr.v6.ld"