From da7114cac9f2e8dce5ef8d07456717b12d756f00 Mon Sep 17 00:00:00 2001 From: Pascal Gollor Date: Fri, 18 Sep 2015 16:03:14 +0200 Subject: [PATCH] add SPIFFS flashing over the air --- .../OTA-mDNS-SPIFFS/OTA-mDNS-SPIFFS.ino | 294 ++++++++++++++++++ .../OTA-mDNS-SPIFFS/data/cl_conf.txt | 2 + tools/espota.py | 142 +++++++-- 3 files changed, 417 insertions(+), 21 deletions(-) create mode 100644 libraries/ESP8266mDNS/OTA-mDNS-SPIFFS/OTA-mDNS-SPIFFS.ino create mode 100644 libraries/ESP8266mDNS/OTA-mDNS-SPIFFS/data/cl_conf.txt diff --git a/libraries/ESP8266mDNS/OTA-mDNS-SPIFFS/OTA-mDNS-SPIFFS.ino b/libraries/ESP8266mDNS/OTA-mDNS-SPIFFS/OTA-mDNS-SPIFFS.ino new file mode 100644 index 000000000..c56e4cbc3 --- /dev/null +++ b/libraries/ESP8266mDNS/OTA-mDNS-SPIFFS/OTA-mDNS-SPIFFS.ino @@ -0,0 +1,294 @@ +/** + * @file OTA-mDNS-SPIFFS.ino + * + * @author Pascal Gollor (http://www.pgollor.de/cms/) + * @data 2015-09-18 + * + */ + + +#include +#include +#include +#include + + +/** + * @brief mDNS and OTA Constants + * @{ + */ +#define HOSTNAME "ESP8266-ota" ///< Hostename +#define APORT 8266 ///< Port for OTA update +/// @} + +/** + * @brief Default WiFi connection information. + * @{ + */ +const char* ap_default_ssid = "esp8266"; ///< Default SSID. +const char* ap_default_psk = "esp8266esp8266"; ///< Default PSK. +/// @} + +/// OTA Update UDP server handle. +WiFiUDP OTA; + +/// Global WiFi SSID. +String g_ssid = ""; + +/// Global WiFi PSK. +String g_pass = ""; + + +/** + * @brief Read WiFi connection information from file system. + * @param ssid String pointer for storing SSID. + * @param pass String pointer for storing PSK. + * @return True or False. + * + * The config file have to containt the WiFi SSID in the first line + * and the WiFi PSK in the second line. + * Line seperator have to be \r\n (CR LF). + */ +bool loadConfig(String *ssid, String *pass) +{ + // open file for reading. + File configFile = SPIFFS.open("/cl_conf.txt", "r"); + if (!configFile) + { + Serial.println("Failed to open cl_conf.txt."); + + return false; + } + + // Read content from config file. + String content = configFile.readString(); + configFile.close(); + + content.trim(); + + // Check if ther is a second line available. + uint8_t pos = content.indexOf("\r\n"); + if (pos == 0) + { + Serial.println("Infvalid content."); + Serial.println(content); + + return false; + } + + // Store SSID and PSK into string vars. + *ssid = content.substring(0, pos); + *pass = content.substring(pos + 2); + + // Print SSID. + Serial.print("ssid: "); + Serial.println(*ssid); + + return true; +} // loadConfig + + +/** + * @brief Save WiFi SSID and PSK to configuration file. + * @param ssid SSID as string pointer. + * @param pass PSK as string pointer, + * @return True or False. + */ +bool saveConfig(String *ssid, String *pass) +{ + // Open config file for writing. + File configFile = SPIFFS.open("/cl_conf.txt", "w"); + if (!configFile) + { + Serial.println("Failed to open cl_conf.txt for writing"); + + return false; + } + + // Save SSID and PSK. + configFile.println(*ssid); + configFile.println(*pass); + + configFile.close(); + + return true; +} // saveConfig + + +/** + * @brief Handle OTA update stuff. + * + * This function comes from ESP8266 Arduino example: + * https://github.com/esp8266/Arduino/blob/esp8266/hardware/esp8266com/esp8266/libraries/ESP8266mDNS/examples/DNS_SD_Arduino_OTA/DNS_SD_Arduino_OTA.ino + * + * Modification for uploading SPIFFS images from Pascal Gollor. + * + */ +static inline void ota_handle(void) +{ + bool spiffs = false; + + if (! OTA.parsePacket()) + { + return; + } + + // Get remote IP + IPAddress remote = OTA.remoteIP(); + + // Get command + int cmd = OTA.parseInt(); + Serial.print("command: "); + Serial.println(cmd); + if (cmd == U_SPIFFS) + { + spiffs = true; + Serial.println("Get SPIFFS image."); + } + + // Get remote port + int port = OTA.parseInt(); + + // Get sketch size. + int sketch_size = OTA.parseInt(); + + // Output stuff + Serial.print("Update Start: ip:"); + Serial.print(remote); + Serial.printf(", port:%d, size:%d\r\n", port, sketch_size); + + // Stop all UDP connections. + WiFiUDP::stopAll(); + + // OTA start Time + uint32_t startTime = millis(); + + // Start Updateing. + if(!Update.begin(sketch_size, cmd)) + { + Serial.println("Update Begin Error"); + return; + } + + WiFiClient client; + if (client.connect(remote, port)) + { + uint32_t written; + while(!Update.isFinished()) + { + written = Update.write(client); + if(written > 0) client.print(written, DEC); + } + Serial.setDebugOutput(false); + + if(Update.end()) + { + client.println("OK"); + Serial.printf("Update Success: %u\nRebooting...\n", (unsigned int)(millis() - startTime)); + ESP.restart(); + } + else + { + Update.printError(client); + Update.printError(Serial); + } + } + else + { + Serial.printf("Connect Failed: %u\n", (unsigned int)(millis() - startTime)); + } +} // ota_handle + + +/** + * @brief Arduino setup function. + */ +void setup() +{ + g_ssid = ""; + g_pass = ""; + + Serial.begin(115200); + + delay(100); + + Serial.println("\r\n"); + Serial.print("Chip ID: "); + Serial.println(ESP.getChipId(), HEX); + + // Initialize file system. + if (!SPIFFS.begin()) + { + Serial.println("Failed to mount file system"); + return; + } + + // Load wifi connection information. + if (! loadConfig(&g_ssid, &g_pass)) + { + g_ssid = ""; + g_pass = ""; + + Serial.println("No WiFi connection information available."); + } + + // Set Hostname. + WiFi.hostname(HOSTNAME); + + Serial.println("Wait for WiFi connection."); + + // Try to connect to WiFi AP. + WiFi.mode(WIFI_STA); + delay(10); + WiFi.begin(g_ssid.c_str(), g_pass.c_str()); + + // Give ESP 10 seconds to connect to ap. + unsigned long startTime = millis(); + while (WiFi.status() != WL_CONNECTED && millis() - startTime < 10000) + { + Serial.write('.'); + delay(500); + } + Serial.println(); + + // check connection + if(WiFi.status() == WL_CONNECTED) + { + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + } + else + { + Serial.println("Can not connect. Go into AP mode."); + + // Go into AP mode. + WiFi.mode(WIFI_AP); + + delay(10); + + WiFi.softAP(ap_default_ssid, ap_default_psk); + + Serial.print("IP address: "); + Serial.println(WiFi.softAPIP()); + } + + // Initialize mDNS service. + MDNS.begin(HOSTNAME); + + // ... Add OTA service. + MDNS.addService("arduino", "tcp", APORT); + + // Open OTA Server. + OTA.begin(APORT); +} + + +/** + * @brief Arduino loop function. + */ +void loop() +{ + // Handle OTA update. + ota_handle(); +} + diff --git a/libraries/ESP8266mDNS/OTA-mDNS-SPIFFS/data/cl_conf.txt b/libraries/ESP8266mDNS/OTA-mDNS-SPIFFS/data/cl_conf.txt new file mode 100644 index 000000000..d2f1ff4ae --- /dev/null +++ b/libraries/ESP8266mDNS/OTA-mDNS-SPIFFS/data/cl_conf.txt @@ -0,0 +1,2 @@ +YOUR_SSID +YOUR_PSK diff --git a/tools/espota.py b/tools/espota.py index 01d79d558..7e26b5cfb 100755 --- a/tools/espota.py +++ b/tools/espota.py @@ -1,45 +1,63 @@ #!/usr/bin/env python # -# this script will push an OTA update to the ESP -# use it like: python espota.py +# Original espoty.py comes from ...? +# +# Modified since 2015-09-18 from Pascal Gollor (https://github.com/pgollor) +# +# This script will push an OTA update to the ESP +# use it like: python espota.py -i -p -f +# +# Changes +# 2015-09-18: +# - Add option parser. +# - Add logging. +# - Send command to controller to differ between flashing and transmitting SPIFFS image. +# from __future__ import print_function import socket import sys import os +import optparse +import logging + +# Commands +FLASH = 0 +SPIFFS = 100 + -def serve(remoteAddr, remotePort, filename): +def serve(remoteAddr, remotePort, filename, command = FLASH): # Create a TCP/IP socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serverPort = 48266 server_address = ('0.0.0.0', serverPort) - print('Starting on %s:%s' % server_address, file=sys.stderr) + logging.info('Starting on %s:%s', str(server_address[0]), str(server_address[1])) try: sock.bind(server_address) sock.listen(1) except: - print('Listen Failed', file=sys.stderr) + logging.error("Listen Failed") return 1 content_size = os.path.getsize(filename) - print('Upload size: %d' % content_size, file=sys.stderr) - message = '%d %d %d\n' % (0, serverPort, content_size) + logging.info('Upload size: %d', content_size) + message = '%d %d %d\n' % (command, serverPort, content_size) # Wait for a connection - print('Sending invitation to:', remoteAddr, file=sys.stderr) + logging.info('Sending invitation to: %s', remoteAddr) sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) remote_address = (remoteAddr, int(remotePort)) sent = sock2.sendto(message, remote_address) sock2.close() - print('Waiting for device...\n', file=sys.stderr) + logging.info('Waiting for device...\n') try: sock.settimeout(10) connection, client_address = sock.accept() sock.settimeout(None) connection.settimeout(None) except: - print('No response from device', file=sys.stderr) + logging.error('No response from device') sock.close() return 1 @@ -57,23 +75,23 @@ def serve(remoteAddr, remotePort, filename): connection.sendall(chunk) res = connection.recv(4) except: - print('\nError Uploading', file=sys.stderr) + logging.error('\nError Uploading') connection.close() f.close() sock.close() return 1 - print('\nWaiting for result...\n', file=sys.stderr) + logging.info('\nWaiting for result...\n') try: connection.settimeout(60) data = connection.recv(32) - print('Result: %s' % data, file=sys.stderr) + logging.info('Result: %s' ,data) connection.close() f.close() sock.close() return 0 except: - print('Result: No Answer!', file=sys.stderr) + logging.error('Result: No Answer!') connection.close() f.close() sock.close() @@ -85,12 +103,94 @@ def serve(remoteAddr, remotePort, filename): sock.close() return 1 - +# end serve + + +def parser(): + parser = optparse.OptionParser( + usage = "%prog [options]", + description = "Transmit image over the air to the esp8266 module with OTA support." + ) + + # destination ip and port + group = optparse.OptionGroup(parser, "Destination") + group.add_option("-i", "--ip", + dest = "esp_ip", + action = "store", + help = "ESP8266 IP Address.", + default = False + ) + group.add_option("-p", "--port", + dest = "esp_port", + type = "int", + help = "ESP8266 ota Port.", + default = 8266 + ) + parser.add_option_group(group) + + # image + group = optparse.OptionGroup(parser, "Image") + group.add_option("-f", "--file", + dest = "image", + help = "Image file.", + metavar="FILE", + default = None + ) + group.add_option("-s", "--spiffs", + dest = "spiffs", + action = "store_true", + help = "Use this option to transmit a SPIFFS image and do not flash the module.", + default = False + ) + parser.add_option_group(group) + + # output group + group = optparse.OptionGroup(parser, "Output") + group.add_option("-d", "--debug", + dest = "debug", + help = "Show debug output. And override loglevel with debug.", + action = "store_true", + default = False + ) + parser.add_option_group(group) + + (options, args) = parser.parse_args() + + return options +# end parser + + def main(args): - return serve(args[1], args[2], args[3]) - - - + # get options + options = parser() + + # adapt log level + loglevel = logging.WARNING + if (options.debug): + loglevel = logging.DEBUG + # end if + + # logging + logging.basicConfig(level = loglevel, format = '%(asctime)-8s [%(levelname)s]: %(message)s', datefmt = '%H:%M:%S') + + logging.debug("Options: %s", str(options)) + + # check options + if (not options.esp_ip or not options.image): + logging.critical("Not enough arguments.") + + return 1 + # end if + + command = FLASH + if (options.spiffs): + command = SPIFFS + # end if + + return serve(options.esp_ip, options.esp_port, options.image, command) +# end main + + if __name__ == '__main__': - sys.exit(main(sys.argv)) - + sys.exit(main(sys.argv)) +# end if