From 2012eaccf4c28aaacb85a62175334fae2b72ab6f Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Mon, 27 Jul 2020 15:43:24 +0200 Subject: [PATCH] POC3 Zigbee EFR32 xmodem upload POC3 Zigbee EFR32 xmodem upload (#8583) - Extend ACK timeout after EOT --- tasmota/xdrv_23_zigbee_9a_upload.ino | 143 ++++++++++++++++----------- 1 file changed, 87 insertions(+), 56 deletions(-) diff --git a/tasmota/xdrv_23_zigbee_9a_upload.ino b/tasmota/xdrv_23_zigbee_9a_upload.ino index 560445279..5280ea472 100644 --- a/tasmota/xdrv_23_zigbee_9a_upload.ino +++ b/tasmota/xdrv_23_zigbee_9a_upload.ino @@ -1,5 +1,5 @@ /* - xdrv_23_zigbee_9_serial.ino - zigbee: serial communication with MCU + xdrv_23_zigbee_9a_upload.ino - zigbee: serial xmodem upload to MCU Copyright (C) 2020 Theo Arends and Stephan Hadinger @@ -40,9 +40,9 @@ #define XM_XOFF 0x13 #define XM_NAK 0x15 #define XM_CAN 0x18 -#define XM_EOF 0x1a +#define XM_SUB 0x1a -enum ZbUploadSteps { ZBU_IDLE, ZBU_INIT, ZBU_PROMPT, ZBU_SYNC, ZBU_UPLOAD, ZBU_DONE, ZBU_COMPLETE }; +enum ZbUploadSteps { ZBU_IDLE, ZBU_INIT, ZBU_PROMPT, ZBU_SYNC, ZBU_UPLOAD, ZBU_EOT, ZBU_COMPLETE, ZBU_DONE, ZBU_FINISH }; const uint8_t PIN_ZIGBEE_BOOTLOADER = 5; @@ -87,7 +87,9 @@ char ZigbeeUploadFlashRead(void) { ZbUpload.byte_counter++; if (ZbUpload.byte_counter > ZbUpload.ota_size) { - data = XM_EOF; + // When the source device reaches the last XModem data block, it should be padded to 128 bytes + // of data using SUB (ASCII 0x1A) characters. + data = XM_SUB; // if (ZbUpload.buffer) { free(ZbUpload.buffer); } // Don't in case of retries } return data; @@ -143,7 +145,7 @@ char XModemWaitACK(void) if (i > 200) { return -1; } } in_char = ZigbeeSerial->read(); - if (in_char == XM_CAN) { return XM_CAN; } + if (XM_CAN == in_char) { return XM_CAN; } } while ((in_char != XM_NAK) && (in_char != XM_ACK) && (in_char != 'C')); return in_char; } @@ -187,20 +189,6 @@ bool XModemSendPacket(uint32_t packet_no) { return true; } -bool XModemSendEOT(void) { - // Send EOT and wait for ACK - uint32_t retries = 0; - char in_char; - do { - ZigbeeSerial->write(XM_EOT); - in_char = XModemWaitACK(); - retries++; - // When timed out, leave immediately - if (retries == XMODEM_SYNC_TIMEOUT) { return false; } - } while (in_char != XM_ACK); - return true; -} - /*********************************************************************************************\ * Step 2 - Upload MCU firmware from ESP8266 flash to MCU EFR32 using XMODEM protocol * @@ -215,15 +203,6 @@ void ZigbeeUploadSetBootloader(uint8_t state) { digitalWrite(Pin(GPIO_ZIGBEE_RST), 1); // Reboot MCU EFR32 } -void ZigbeeUploadBootloaderDone(void) { - ZbUpload.ota_step = ZBU_COMPLETE; // Never return to zero without a restart to get a sane Zigbee environment - ZigbeeUploadSetBootloader(1); // Disable bootloader and reset MCU - should happen or restart - if (1 == ssleep) { - ssleep = Settings.sleep; // Restore loop sleep - } - restart_flag = 2; // Restart to disable bootloader and use new firmware -} - bool ZigbeeUploadBootloaderPrompt(void) { // Scripts that interact with the bootloader should use only the “BL >” prompt to determine // when the bootloader is ready for input. While current menu options should remain functionally @@ -246,26 +225,28 @@ bool ZigbeeUploadBootloaderPrompt(void) { } bool ZigbeeUploadXmodem(void) { - if (!ZbUpload.ota_step) { return false; } - switch (ZbUpload.ota_step) { - case ZBU_INIT: { // Init ESF32 bootloader + case ZBU_IDLE: { // *** Upload disabled + return false; + } + case ZBU_INIT: { // *** Init ESF32 bootloader AddLog_P2(LOG_LEVEL_DEBUG, PSTR("XMD: Init bootloader")); - ZigbeeUploadSetBootloader(0); // Reboot MCU EFR32 which returns below text + ZigbeeUploadSetBootloader(0); // Reboot MCU EFR32 which returns below text XModem.timeout = millis() + (30 * 1000); // Allow 30 seconds to receive EBL prompt XModem.delay = millis() + 500; + ZbUpload.byte_counter = 0; ZbUpload.ota_step = ZBU_PROMPT; break; } - case ZBU_PROMPT: { // Wait for prompt and select option upload ebl + case ZBU_PROMPT: { // *** Wait for prompt and select option upload ebl if (millis() > XModem.timeout) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR("XMD: Bootloader timeout")); - ZigbeeUploadBootloaderDone(); + ZbUpload.ota_step = ZBU_DONE; return true; } else if (!ZigbeeSerial->available()) { - // The target device’s bootloader sends output over its serial port after it receives a carriage return - // from the source device + // The target device’s bootloader sends output over its serial port after it receives a + // carriage return from the source device if (millis() > XModem.delay) { ZigbeeSerial->write(XM_CR); XModem.delay = millis() + 500; @@ -280,9 +261,9 @@ bool ZigbeeUploadXmodem(void) { if (ZigbeeUploadBootloaderPrompt()) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR("XMD: Init sync")); ZigbeeSerial->flush(); - ZigbeeSerial->write('1'); // upload ebl + ZigbeeSerial->write('1'); // upload ebl if (ssleep > 0) { - ssleep = 1; // Speed up loop used for xmodem upload + ssleep = 1; // Speed up loop used for xmodem upload } XModem.timeout = millis() + (XMODEM_SYNC_TIMEOUT * 1000); ZbUpload.ota_step = ZBU_SYNC; @@ -290,17 +271,16 @@ bool ZigbeeUploadXmodem(void) { } break; } - case ZBU_SYNC: { // Handle file upload using XModem - sync + case ZBU_SYNC: { // *** Handle file upload using XModem - sync if (millis() > XModem.timeout) { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("XMD: Sync timeout")); - ZigbeeUploadBootloaderDone(); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("XMD: SYNC timeout")); + ZbUpload.ota_step = ZBU_DONE; return true; } - // Wait for either C or NACK as a sync packet. - // Determines protocol details, checksum algorithm. + // Wait for either C or NACK as a sync packet. Determines protocol details, checksum algorithm. if (ZigbeeSerial->available()) { char xmodem_sync = ZigbeeSerial->read(); - if ((xmodem_sync == 'C') || (xmodem_sync == XM_NAK)) { + if (('C' == xmodem_sync) || (XM_NAK == xmodem_sync)) { // Determine which checksum algorithm to use XModem.oldChecksum = (xmodem_sync == XM_NAK); XModem.packetNo = 1; @@ -311,27 +291,78 @@ bool ZigbeeUploadXmodem(void) { } break; } - case ZBU_UPLOAD: { // Handle file upload using XModem - upload + case ZBU_UPLOAD: { // *** Handle file upload using XModem - upload if (ZigbeeUploadAvailable()) { if (!XModemSendPacket(XModem.packetNo)) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR("XMD: Packet send failed")); - ZigbeeUploadBootloaderDone(); + ZbUpload.ota_step = ZBU_DONE; return true; } XModem.packetNo++; } else { - if (!XModemSendEOT()) { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("XMD: EOT failed")); - ZigbeeUploadBootloaderDone(); - return true; - } - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("XMD: Complete")); - ZbUpload.ota_step = ZBU_DONE; + // Once the last block is ACKed by the target, the transfer should be finalized by an + // EOT (ASCII 0x04) packet from the source. Once this packet is confirmed via XModem ACK + // from the target, the device will reboot, causing the new firmware to be launched. + ZigbeeSerial->write(XM_EOT); + XModem.timeout = millis() + (30 * 1000); // Allow 30 seconds to receive EOT ACK + ZbUpload.ota_step = ZBU_EOT; } break; } - case ZBU_DONE: { // Clean up and restart to disable bootloader and use new firmware - ZigbeeUploadBootloaderDone(); + case ZBU_EOT: { // *** Send EOT and wait for ACK + // The ACK for the last XModem data packet may take much longer (1-3 seconds) than prior + // data packets to be received. This is due to the CRC32 checksum being performed across + // the received EBL file data prior to sending the ACK. The source device must ensure that + // its XModem state machine waits a sufficient amount of time to allow this checksum process + // to occur without timing out on the response just before the EOT is sent. + if (millis() > XModem.timeout) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("XMD: EOT ACK timeout")); + ZbUpload.ota_step = ZBU_DONE; + return true; + } + if (ZigbeeSerial->available()) { + char xmodem_ack = XModemWaitACK(); + if (XM_ACK == xmodem_ack) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("XMD: " D_SUCCESSFUL)); + XModem.timeout = millis() + (30 * 1000); // Allow 30 seconds to receive EBL prompt + ZbUpload.byte_counter = 0; +// ZbUpload.ota_step = ZBU_COMPLETE; + ZbUpload.ota_step = ZBU_DONE; // Skip prompt for now + } + } + break; + } +/* + case ZBU_COMPLETE: { // *** Wait for Serial upload complete EBL prompt + if (millis() > XModem.timeout) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("XMD: Bootloader timeout")); + ZbUpload.ota_step = ZBU_DONE; + return true; + } else { + // After an image successfully uploads, the XModem transaction completes and the bootloader displays + // ‘Serial upload complete’ before redisplaying the menu + // Serial upload complete + // BL > + if (ZigbeeUploadBootloaderPrompt()) { + ZbUpload.ota_step = ZBU_DONE; + } + } + break; + } +*/ + case ZBU_DONE: { // *** Clean up and restart to disable bootloader and use new firmware + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("XMD: " D_RESTARTING)); + ZigbeeUploadSetBootloader(1); // Disable bootloader and reset MCU - should happen at restart + if (1 == ssleep) { + ssleep = Settings.sleep; // Restore loop sleep + } + restart_flag = 2; // Restart to disable bootloader and use new firmware + ZbUpload.ota_step = ZBU_FINISH; // Never return to zero without a restart to get a sane Zigbee environment + break; + } + case ZBU_FINISH: { // *** Wait for restart making sure not to start Zigbee serial again + // Wait for restart + break; } } return true; @@ -367,7 +398,7 @@ bool ZigbeeUploadWriteBuffer(uint8_t *buf, size_t size) { if (2 == ZbUpload.sector_cursor) { // The web upload sends 2048 bytes at a time so keep track of the cursor position to reset it for the next flash sector erase ZbUpload.sector_cursor = 0; ZbUpload.sector_counter++; - if (ZbUpload.sector_counter > (SPIFFS_END + 12)) { + if (ZbUpload.sector_counter > (SPIFFS_END -2)) { return false; // File too large - Not enough free space } }