1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-04-21 10:26:06 +03:00

Merge pull request #802 from pgollor/SPIFFS-OTA

Flash SPIFFS over the air (OTA)
This commit is contained in:
Ivan Grokhotkov 2015-09-28 18:11:09 +03:00
commit d137fa9c18
9 changed files with 525 additions and 46 deletions

View File

@ -19,6 +19,7 @@ UpdaterClass::UpdaterClass()
, _size(0) , _size(0)
, _startAddress(0) , _startAddress(0)
, _currentAddress(0) , _currentAddress(0)
, _command(U_FLASH)
{ {
} }
@ -30,9 +31,10 @@ void UpdaterClass::_reset() {
_startAddress = 0; _startAddress = 0;
_currentAddress = 0; _currentAddress = 0;
_size = 0; _size = 0;
_command = U_FLASH;
} }
bool UpdaterClass::begin(size_t size){ bool UpdaterClass::begin(size_t size, int command) {
if(_size > 0){ if(_size > 0){
#ifdef DEBUG_UPDATER #ifdef DEBUG_UPDATER
DEBUG_UPDATER.println("already running"); DEBUG_UPDATER.println("already running");
@ -40,7 +42,13 @@ bool UpdaterClass::begin(size_t size){
return false; return false;
} }
if(size == 0){ #ifdef DEBUG_UPDATER
if (command == U_SPIFFS) {
DEBUG_UPDATER.println("Update SPIFFS.");
}
#endif
if(size == 0) {
_error = UPDATE_ERROR_SIZE; _error = UPDATE_ERROR_SIZE;
#ifdef DEBUG_UPDATER #ifdef DEBUG_UPDATER
printError(DEBUG_UPDATER); printError(DEBUG_UPDATER);
@ -51,6 +59,8 @@ bool UpdaterClass::begin(size_t size){
_reset(); _reset();
_error = 0; _error = 0;
uint32_t updateStartAddress = 0;
if (command == U_FLASH) {
//size of current sketch rounded to a sector //size of current sketch rounded to a sector
uint32_t currentSketchSize = (ESP.getSketchSize() + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); uint32_t currentSketchSize = (ESP.getSketchSize() + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
//address of the end of the space available for sketch and update //address of the end of the space available for sketch and update
@ -58,13 +68,24 @@ bool UpdaterClass::begin(size_t size){
//size of the update rounded to a sector //size of the update rounded to a sector
uint32_t roundedSize = (size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); uint32_t roundedSize = (size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
//address where we will start writing the update //address where we will start writing the update
uint32_t updateStartAddress = updateEndAddress - roundedSize; updateStartAddress = updateEndAddress - roundedSize;
//make sure that the size of both sketches is less than the total space (updateEndAddress) //make sure that the size of both sketches is less than the total space (updateEndAddress)
if(updateStartAddress < currentSketchSize){ if(updateStartAddress < currentSketchSize) {
_error = UPDATE_ERROR_SPACE; _error = UPDATE_ERROR_SPACE;
#ifdef DEBUG_UPDATER #ifdef DEBUG_UPDATER
printError(DEBUG_UPDATER); printError(DEBUG_UPDATER);
#endif
return false;
}
}
else if (command == U_SPIFFS) {
updateStartAddress = (uint32_t)&_SPIFFS_start - 0x40200000;
}
else {
// unknown command
#ifdef DEBUG_UPDATER
DEBUG_UPDATER.println("Unknown update command.");
#endif #endif
return false; return false;
} }
@ -74,6 +95,7 @@ bool UpdaterClass::begin(size_t size){
_currentAddress = _startAddress; _currentAddress = _startAddress;
_size = size; _size = size;
_buffer = new uint8_t[FLASH_SECTOR_SIZE]; _buffer = new uint8_t[FLASH_SECTOR_SIZE];
_command = command;
return true; return true;
} }
@ -95,13 +117,14 @@ bool UpdaterClass::end(bool evenIfRemaining){
return false; return false;
} }
if(evenIfRemaining){ if(evenIfRemaining) {
if(_bufferLen > 0){ if(_bufferLen > 0) {
_writeBuffer(); _writeBuffer();
} }
_size = progress(); _size = progress();
} }
if (_command == U_FLASH) {
eboot_command ebcmd; eboot_command ebcmd;
ebcmd.action = ACTION_COPY_RAW; ebcmd.action = ACTION_COPY_RAW;
ebcmd.args[0] = _startAddress; ebcmd.args[0] = _startAddress;
@ -111,7 +134,11 @@ bool UpdaterClass::end(bool evenIfRemaining){
#ifdef DEBUG_UPDATER #ifdef DEBUG_UPDATER
DEBUG_UPDATER.printf("Staged: address:0x%08X, size:0x%08X\n", _startAddress, _size); DEBUG_UPDATER.printf("Staged: address:0x%08X, size:0x%08X\n", _startAddress, _size);
}
else if (_command == U_SPIFFS) {
DEBUG_UPDATER.printf("SPIFFS: address:0x%08X, size:0x%08X\n", _startAddress, _size);
#endif #endif
}
_reset(); _reset();
return true; return true;

View File

@ -11,6 +11,9 @@
#define UPDATE_ERROR_SIZE 4 #define UPDATE_ERROR_SIZE 4
#define UPDATE_ERROR_STREAM 5 #define UPDATE_ERROR_STREAM 5
#define U_FLASH 0
#define U_SPIFFS 100
//#define DEBUG_UPDATER Serial1 //#define DEBUG_UPDATER Serial1
class UpdaterClass { class UpdaterClass {
@ -20,7 +23,7 @@ class UpdaterClass {
Call this to check the space needed for the update Call this to check the space needed for the update
Will return false if there is not enough space Will return false if there is not enough space
*/ */
bool begin(size_t size); bool begin(size_t size, int = U_FLASH);
/* /*
Writes a buffer to the flash and increments the address Writes a buffer to the flash and increments the address
@ -116,6 +119,7 @@ class UpdaterClass {
size_t _size; size_t _size;
uint32_t _startAddress; uint32_t _startAddress;
uint32_t _currentAddress; uint32_t _currentAddress;
uint32_t _command;
}; };
extern UpdaterClass Update; extern UpdaterClass Update;

View File

@ -41,6 +41,7 @@ localIP KEYWORD2
subnetMask KEYWORD2 subnetMask KEYWORD2
gatewayIP KEYWORD2 gatewayIP KEYWORD2
SSID KEYWORD2 SSID KEYWORD2
psk KEYWORD2
BSSID KEYWORD2 BSSID KEYWORD2
RSSI KEYWORD2 RSSI KEYWORD2
encryptionType KEYWORD2 encryptionType KEYWORD2

View File

@ -145,6 +145,17 @@ int ESP8266WiFiClass::begin(const char* ssid, const char *passphrase, int32_t ch
return status(); return status();
} }
int ESP8266WiFiClass::begin()
{
ETS_UART_INTR_DISABLE();
wifi_station_connect();
ETS_UART_INTR_ENABLE();
if(!_useStaticIp)
wifi_station_dhcpc_start();
return status();
}
uint8_t ESP8266WiFiClass::waitForConnectResult(){ uint8_t ESP8266WiFiClass::waitForConnectResult(){
if ((wifi_get_opmode() & 1) == 0)//1 and 3 have STA enabled if ((wifi_get_opmode() & 1) == 0)//1 and 3 have STA enabled
return WL_DISCONNECTED; return WL_DISCONNECTED;
@ -366,6 +377,13 @@ char* ESP8266WiFiClass::SSID()
return reinterpret_cast<char*>(conf.ssid); return reinterpret_cast<char*>(conf.ssid);
} }
const char* ESP8266WiFiClass::psk()
{
static struct station_config conf;
wifi_station_get_config(&conf);
return reinterpret_cast<const char*>(conf.password);
}
uint8_t* ESP8266WiFiClass::BSSID(void) uint8_t* ESP8266WiFiClass::BSSID(void)
{ {
static struct station_config conf; static struct station_config conf;

View File

@ -58,6 +58,9 @@ public:
int begin(const char* ssid, const char *passphrase = NULL, int32_t channel = 0, uint8_t bssid[6] = NULL); int begin(const char* ssid, const char *passphrase = NULL, int32_t channel = 0, uint8_t bssid[6] = NULL);
int begin(char* ssid, char *passphrase = NULL, int32_t channel = 0, uint8_t bssid[6] = NULL); int begin(char* ssid, char *passphrase = NULL, int32_t channel = 0, uint8_t bssid[6] = NULL);
// Use sdk config to connect.
int begin();
/* Wait for Wifi connection to reach a result /* Wait for Wifi connection to reach a result
* returns the status reached or disconnect if STA is off * returns the status reached or disconnect if STA is off
@ -172,6 +175,13 @@ public:
*/ */
char* SSID(); char* SSID();
/*
* Return the current pre shared key associated with the network
*
* return: psk string
*/
const char* psk();
/* /*
* Return the current bssid / mac associated with the network if configured * Return the current bssid / mac associated with the network if configured
* *

View File

@ -0,0 +1,317 @@
/**
* @file OTA-mDNS-SPIFFS.ino
*
* @author Pascal Gollor (http://www.pgollor.de/cms/)
* @data 2015-09-18
*
*/
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <FS.h>
/**
* @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;
/**
* @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);
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()
{
String station_ssid = "";
String station_psk = "";
Serial.begin(115200);
delay(100);
Serial.println("\r\n");
Serial.print("Chip ID: 0x");
Serial.println(ESP.getChipId(), HEX);
// Set Hostname.
WiFi.hostname(HOSTNAME);
Serial.print("hostname: ");
Serial.println(WiFi.hostname());
// Initialize file system.
if (!SPIFFS.begin())
{
Serial.println("Failed to mount file system");
return;
}
// Load wifi connection information.
if (! loadConfig(&station_ssid, &station_psk))
{
station_ssid = "";
station_psk = "";
Serial.println("No WiFi connection information available.");
}
// Check WiFi connection
// ... check mode
if (WiFi.getMode() != WIFI_STA)
{
WiFi.mode(WIFI_STA);
delay(10);
}
// ... Load sdk config.
String ssid(WiFi.SSID());
String psk(WiFi.psk());
// ... Compare fiel config with sdk config.
if (ssid != station_ssid || psk != station_psk)
{
Serial.println("WiFi config changed.");
// ... Try to connect to WiFi station.
WiFi.begin(station_ssid.c_str(), station_psk.c_str());
// ... Pritn new SSID
Serial.print("new SSID: ");
Serial.println(WiFi.SSID());
// ... Uncomment this for debugging output.
//WiFi.printDiag(Serial);
}
else
{
// ... Begin with sdk config.
WiFi.begin();
}
Serial.println("Wait for WiFi connection.");
// ... Give ESP 10 seconds to connect to station.
unsigned long startTime = millis();
while (WiFi.status() != WL_CONNECTED && millis() - startTime < 10000)
{
Serial.write('.');
//Serial.print(WiFi.status());
delay(500);
}
Serial.println();
// Check connection
if(WiFi.status() == WL_CONNECTED)
{
// ... print IP Address
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
else
{
Serial.println("Can not connect to WiFi station. Go into AP mode.");
// Go into software 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();
}

View File

@ -0,0 +1,2 @@
YOUR_SSID
YOUR_PSK

View File

@ -93,7 +93,7 @@ tools.esptool.upload.protocol=esp
tools.esptool.upload.params.verbose=-vv tools.esptool.upload.params.verbose=-vv
tools.esptool.upload.params.quiet= tools.esptool.upload.params.quiet=
tools.esptool.upload.pattern="{path}/{cmd}" {upload.verbose} -cd {upload.resetmethod} -cb {upload.speed} -cp "{serial.port}" -ca 0x00000 -cf "{build.path}/{build.project_name}.bin" tools.esptool.upload.pattern="{path}/{cmd}" {upload.verbose} -cd {upload.resetmethod} -cb {upload.speed} -cp "{serial.port}" -ca 0x00000 -cf "{build.path}/{build.project_name}.bin"
tools.esptool.network.pattern=python "{path}/espota.py" "{serial.port}" "{network.port}" "{build.path}/{build.project_name}.bin" tools.esptool.network.pattern=python "{path}/espota.py" -i "{serial.port}" -p "{network.port}" -f "{build.path}/{build.project_name}.bin"
tools.espota.cmd=python tools.espota.cmd=python
tools.espota.cmd.windows=python.exe tools.espota.cmd.windows=python.exe
@ -102,4 +102,4 @@ tools.espota.path={runtime.platform.path}/tools
tools.espota.upload.protocol=espota tools.espota.upload.protocol=espota
tools.espota.upload.params.verbose= tools.espota.upload.params.verbose=
tools.espota.upload.params.quiet= tools.espota.upload.params.quiet=
tools.espota.upload.pattern="{cmd}" "{path}/espota.py" "{serial.port}" 8266 "{build.path}/{build.project_name}.bin" tools.espota.upload.pattern="{cmd}" "{path}/espota.py" -i "{serial.port}" -p 8266 -f "{build.path}/{build.project_name}.bin"

View File

@ -1,45 +1,63 @@
#!/usr/bin/env python #!/usr/bin/env python
# #
# this script will push an OTA update to the ESP # Original espoty.py comes from ...?
# use it like: python espota.py <ESP_IP_address> <sketch.bin> #
# 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 <ESP_IP_address> -p <ESP_port> -f <sketch.bin>
#
# 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 from __future__ import print_function
import socket import socket
import sys import sys
import os import os
import optparse
import logging
def serve(remoteAddr, remotePort, filename): # Commands
FLASH = 0
SPIFFS = 100
def serve(remoteAddr, remotePort, filename, command = FLASH):
# Create a TCP/IP socket # Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serverPort = 48266 serverPort = 48266
server_address = ('0.0.0.0', serverPort) 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: try:
sock.bind(server_address) sock.bind(server_address)
sock.listen(1) sock.listen(1)
except: except:
print('Listen Failed', file=sys.stderr) logging.error("Listen Failed")
return 1 return 1
content_size = os.path.getsize(filename) content_size = os.path.getsize(filename)
print('Upload size: %d' % content_size, file=sys.stderr) logging.info('Upload size: %d', content_size)
message = '%d %d %d\n' % (0, serverPort, content_size) message = '%d %d %d\n' % (command, serverPort, content_size)
# Wait for a connection # 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) sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
remote_address = (remoteAddr, int(remotePort)) remote_address = (remoteAddr, int(remotePort))
sent = sock2.sendto(message, remote_address) sent = sock2.sendto(message, remote_address)
sock2.close() sock2.close()
print('Waiting for device...\n', file=sys.stderr) logging.info('Waiting for device...\n')
try: try:
sock.settimeout(10) sock.settimeout(10)
connection, client_address = sock.accept() connection, client_address = sock.accept()
sock.settimeout(None) sock.settimeout(None)
connection.settimeout(None) connection.settimeout(None)
except: except:
print('No response from device', file=sys.stderr) logging.error('No response from device')
sock.close() sock.close()
return 1 return 1
@ -57,23 +75,23 @@ def serve(remoteAddr, remotePort, filename):
connection.sendall(chunk) connection.sendall(chunk)
res = connection.recv(4) res = connection.recv(4)
except: except:
print('\nError Uploading', file=sys.stderr) logging.error('\nError Uploading')
connection.close() connection.close()
f.close() f.close()
sock.close() sock.close()
return 1 return 1
print('\nWaiting for result...\n', file=sys.stderr) logging.info('\nWaiting for result...\n')
try: try:
connection.settimeout(60) connection.settimeout(60)
data = connection.recv(32) data = connection.recv(32)
print('Result: %s' % data, file=sys.stderr) logging.info('Result: %s' ,data)
connection.close() connection.close()
f.close() f.close()
sock.close() sock.close()
return 0 return 0
except: except:
print('Result: No Answer!', file=sys.stderr) logging.error('Result: No Answer!')
connection.close() connection.close()
f.close() f.close()
sock.close() sock.close()
@ -85,12 +103,94 @@ def serve(remoteAddr, remotePort, filename):
sock.close() sock.close()
return 1 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): 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__': if __name__ == '__main__':
sys.exit(main(sys.argv)) sys.exit(main(sys.argv))
# end if