Add Ota and mdns (#257)
* Add Sketch Update Library * Add MDNS Library * Add Arduino OTA Library * add missing library file * Add library files for Update * Add missing headers * fix ota command * Add espota binary * remove bad example * PlatformIO does not auto forward declare methods like Arduino Builder
This commit is contained in:
parent
2f5efed220
commit
fa1716e73e
65
libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino
Normal file
65
libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#include <WiFi.h>
|
||||||
|
#include <ESPmDNS.h>
|
||||||
|
#include <WiFiUdp.h>
|
||||||
|
#include <ArduinoOTA.h>
|
||||||
|
|
||||||
|
const char* ssid = "..........";
|
||||||
|
const char* password = "..........";
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.println("Booting");
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
||||||
|
Serial.println("Connection Failed! Rebooting...");
|
||||||
|
delay(5000);
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Port defaults to 3232
|
||||||
|
// ArduinoOTA.setPort(3232);
|
||||||
|
|
||||||
|
// Hostname defaults to esp3232-[MAC]
|
||||||
|
// ArduinoOTA.setHostname("myesp32");
|
||||||
|
|
||||||
|
// No authentication by default
|
||||||
|
// ArduinoOTA.setPassword("admin");
|
||||||
|
|
||||||
|
// Password can be set with it's md5 value as well
|
||||||
|
// MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
|
||||||
|
// ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");
|
||||||
|
|
||||||
|
ArduinoOTA.onStart([]() {
|
||||||
|
String type;
|
||||||
|
if (ArduinoOTA.getCommand() == U_FLASH)
|
||||||
|
type = "sketch";
|
||||||
|
else // U_SPIFFS
|
||||||
|
type = "filesystem";
|
||||||
|
|
||||||
|
// NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
|
||||||
|
Serial.println("Start updating " + type);
|
||||||
|
});
|
||||||
|
ArduinoOTA.onEnd([]() {
|
||||||
|
Serial.println("\nEnd");
|
||||||
|
});
|
||||||
|
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
|
||||||
|
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
|
||||||
|
});
|
||||||
|
ArduinoOTA.onError([](ota_error_t error) {
|
||||||
|
Serial.printf("Error[%u]: ", error);
|
||||||
|
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
|
||||||
|
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
|
||||||
|
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
|
||||||
|
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
|
||||||
|
else if (error == OTA_END_ERROR) Serial.println("End Failed");
|
||||||
|
});
|
||||||
|
ArduinoOTA.begin();
|
||||||
|
Serial.println("Ready");
|
||||||
|
Serial.print("IP address: ");
|
||||||
|
Serial.println(WiFi.localIP());
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
ArduinoOTA.handle();
|
||||||
|
}
|
26
libraries/ArduinoOTA/keywords.txt
Normal file
26
libraries/ArduinoOTA/keywords.txt
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#######################################
|
||||||
|
# Syntax Coloring Map For Ultrasound
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Datatypes (KEYWORD1)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
ArduinoOTA KEYWORD1
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Methods and Functions (KEYWORD2)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
begin KEYWORD2
|
||||||
|
setup KEYWORD2
|
||||||
|
handle KEYWORD2
|
||||||
|
onStart KEYWORD2
|
||||||
|
onEnd KEYWORD2
|
||||||
|
onError KEYWORD2
|
||||||
|
onProgress KEYWORD2
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Constants (LITERAL1)
|
||||||
|
#######################################
|
||||||
|
|
9
libraries/ArduinoOTA/library.properties
Normal file
9
libraries/ArduinoOTA/library.properties
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
name=ArduinoOTA
|
||||||
|
version=1.0
|
||||||
|
author=Ivan Grokhotkov and Hristo Gochkov
|
||||||
|
maintainer=Hristo Gochkov <hristo@espressif.com>
|
||||||
|
sentence=Enables Over The Air upgrades, via wifi and espota.py UDP request/TCP download.
|
||||||
|
paragraph=With this library you can enable your sketch to be upgraded over network. Includes mdns anounces to get discovered by the arduino IDE.
|
||||||
|
category=Communication
|
||||||
|
url=
|
||||||
|
architectures=esp32
|
367
libraries/ArduinoOTA/src/ArduinoOTA.cpp
Normal file
367
libraries/ArduinoOTA/src/ArduinoOTA.cpp
Normal file
@ -0,0 +1,367 @@
|
|||||||
|
#ifndef LWIP_OPEN_SRC
|
||||||
|
#define LWIP_OPEN_SRC
|
||||||
|
#endif
|
||||||
|
#include <functional>
|
||||||
|
#include <WiFiUdp.h>
|
||||||
|
#include "ArduinoOTA.h"
|
||||||
|
#include "ESPmDNS.h"
|
||||||
|
#include "MD5Builder.h"
|
||||||
|
#include "Update.h"
|
||||||
|
|
||||||
|
|
||||||
|
//#define OTA_DEBUG Serial
|
||||||
|
|
||||||
|
ArduinoOTAClass::ArduinoOTAClass()
|
||||||
|
: _port(0)
|
||||||
|
, _initialized(false)
|
||||||
|
, _rebootOnSuccess(true)
|
||||||
|
, _mdnsEnabled(true)
|
||||||
|
, _state(OTA_IDLE)
|
||||||
|
, _size(0)
|
||||||
|
, _cmd(0)
|
||||||
|
, _ota_port(0)
|
||||||
|
, _start_callback(NULL)
|
||||||
|
, _end_callback(NULL)
|
||||||
|
, _error_callback(NULL)
|
||||||
|
, _progress_callback(NULL)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ArduinoOTAClass::~ArduinoOTAClass(){
|
||||||
|
_udp_ota.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArduinoOTAClass::onStart(THandlerFunction fn) {
|
||||||
|
_start_callback = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArduinoOTAClass::onEnd(THandlerFunction fn) {
|
||||||
|
_end_callback = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArduinoOTAClass::onProgress(THandlerFunction_Progress fn) {
|
||||||
|
_progress_callback = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArduinoOTAClass::onError(THandlerFunction_Error fn) {
|
||||||
|
_error_callback = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArduinoOTAClass::setPort(uint16_t port) {
|
||||||
|
if (!_initialized && !_port && port) {
|
||||||
|
_port = port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArduinoOTAClass::setHostname(const char * hostname) {
|
||||||
|
if (!_initialized && !_hostname.length() && hostname) {
|
||||||
|
_hostname = hostname;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String ArduinoOTAClass::getHostname() {
|
||||||
|
return _hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArduinoOTAClass::setPassword(const char * password) {
|
||||||
|
if (!_initialized && !_password.length() && password) {
|
||||||
|
MD5Builder passmd5;
|
||||||
|
passmd5.begin();
|
||||||
|
passmd5.add(password);
|
||||||
|
passmd5.calculate();
|
||||||
|
_password = passmd5.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArduinoOTAClass::setPasswordHash(const char * password) {
|
||||||
|
if (!_initialized && !_password.length() && password) {
|
||||||
|
_password = password;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArduinoOTAClass::setRebootOnSuccess(bool reboot){
|
||||||
|
_rebootOnSuccess = reboot;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArduinoOTAClass::setMdnsEnabled(bool enabled){
|
||||||
|
_mdnsEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArduinoOTAClass::begin() {
|
||||||
|
if (_initialized){
|
||||||
|
log_w("already initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_port) {
|
||||||
|
_port = 3232;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!_udp_ota.begin(_port)){
|
||||||
|
log_e("udp bind failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!_hostname.length()) {
|
||||||
|
char tmp[20];
|
||||||
|
uint8_t mac[6];
|
||||||
|
WiFi.macAddress(mac);
|
||||||
|
sprintf(tmp, "esp32-%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||||
|
_hostname = tmp;
|
||||||
|
}
|
||||||
|
if(_mdnsEnabled){
|
||||||
|
MDNS.begin(_hostname.c_str());
|
||||||
|
MDNS.enableArduino(_port, (_password.length() > 0));
|
||||||
|
}
|
||||||
|
_initialized = true;
|
||||||
|
_state = OTA_IDLE;
|
||||||
|
#ifdef OTA_DEBUG
|
||||||
|
OTA_DEBUG.printf("OTA server at: %s.local:%u\n", _hostname.c_str(), _port);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int ArduinoOTAClass::parseInt(){
|
||||||
|
char data[16];
|
||||||
|
uint8_t index = 0;
|
||||||
|
char value;
|
||||||
|
while(_udp_ota.peek() == ' ') _udp_ota.read();
|
||||||
|
while(true){
|
||||||
|
value = _udp_ota.peek();
|
||||||
|
if(value < '0' || value > '9'){
|
||||||
|
data[index++] = '\0';
|
||||||
|
return atoi(data);
|
||||||
|
}
|
||||||
|
data[index++] = _udp_ota.read();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
String ArduinoOTAClass::readStringUntil(char end){
|
||||||
|
String res = "";
|
||||||
|
char value;
|
||||||
|
while(true){
|
||||||
|
value = _udp_ota.read();
|
||||||
|
if(value == '\0' || value == end){
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
res += value;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArduinoOTAClass::_onRx(){
|
||||||
|
if (_state == OTA_IDLE) {
|
||||||
|
int cmd = parseInt();
|
||||||
|
if (cmd != U_FLASH && cmd != U_SPIFFS)
|
||||||
|
return;
|
||||||
|
_cmd = cmd;
|
||||||
|
_ota_port = parseInt();
|
||||||
|
_size = parseInt();
|
||||||
|
_udp_ota.read();
|
||||||
|
_md5 = readStringUntil('\n');
|
||||||
|
_md5.trim();
|
||||||
|
if(_md5.length() != 32){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_password.length()){
|
||||||
|
MD5Builder nonce_md5;
|
||||||
|
nonce_md5.begin();
|
||||||
|
nonce_md5.add(String(micros()));
|
||||||
|
nonce_md5.calculate();
|
||||||
|
_nonce = nonce_md5.toString();
|
||||||
|
|
||||||
|
_udp_ota.beginPacket(_udp_ota.remoteIP(), _udp_ota.remotePort());
|
||||||
|
_udp_ota.printf("AUTH %s", _nonce.c_str());
|
||||||
|
_udp_ota.endPacket();
|
||||||
|
_state = OTA_WAITAUTH;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
_udp_ota.beginPacket(_udp_ota.remoteIP(), _udp_ota.remotePort());
|
||||||
|
_udp_ota.print("OK");
|
||||||
|
_udp_ota.endPacket();
|
||||||
|
_ota_ip = _udp_ota.remoteIP();
|
||||||
|
_state = OTA_RUNUPDATE;
|
||||||
|
}
|
||||||
|
} else if (_state == OTA_WAITAUTH) {
|
||||||
|
int cmd = parseInt();
|
||||||
|
if (cmd != U_AUTH) {
|
||||||
|
_state = OTA_IDLE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_udp_ota.read();
|
||||||
|
String cnonce = readStringUntil(' ');
|
||||||
|
String response = readStringUntil('\n');
|
||||||
|
if (cnonce.length() != 32 || response.length() != 32) {
|
||||||
|
_state = OTA_IDLE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String challenge = _password + ":" + String(_nonce) + ":" + cnonce;
|
||||||
|
MD5Builder _challengemd5;
|
||||||
|
_challengemd5.begin();
|
||||||
|
_challengemd5.add(challenge);
|
||||||
|
_challengemd5.calculate();
|
||||||
|
String result = _challengemd5.toString();
|
||||||
|
|
||||||
|
if(result.equals(response)){
|
||||||
|
_udp_ota.beginPacket(_udp_ota.remoteIP(), _udp_ota.remotePort());
|
||||||
|
_udp_ota.print("OK");
|
||||||
|
_udp_ota.endPacket();
|
||||||
|
_ota_ip = _udp_ota.remoteIP();
|
||||||
|
_state = OTA_RUNUPDATE;
|
||||||
|
} else {
|
||||||
|
_udp_ota.beginPacket(_udp_ota.remoteIP(), _udp_ota.remotePort());
|
||||||
|
_udp_ota.print("Authentication Failed");
|
||||||
|
_udp_ota.endPacket();
|
||||||
|
if (_error_callback) _error_callback(OTA_AUTH_ERROR);
|
||||||
|
_state = OTA_IDLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArduinoOTAClass::_runUpdate() {
|
||||||
|
if (!Update.begin(_size, _cmd)) {
|
||||||
|
#ifdef OTA_DEBUG
|
||||||
|
Update.printError(OTA_DEBUG);
|
||||||
|
#endif
|
||||||
|
if (_error_callback) {
|
||||||
|
_error_callback(OTA_BEGIN_ERROR);
|
||||||
|
}
|
||||||
|
_state = OTA_IDLE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Update.setMD5(_md5.c_str());
|
||||||
|
|
||||||
|
if (_start_callback) {
|
||||||
|
_start_callback();
|
||||||
|
}
|
||||||
|
if (_progress_callback) {
|
||||||
|
_progress_callback(0, _size);
|
||||||
|
}
|
||||||
|
|
||||||
|
WiFiClient client;
|
||||||
|
if (!client.connect(_ota_ip, _ota_port)) {
|
||||||
|
if (_error_callback) {
|
||||||
|
_error_callback(OTA_CONNECT_ERROR);
|
||||||
|
}
|
||||||
|
_state = OTA_IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t written = 0, total = 0, tried = 0;
|
||||||
|
while (!Update.isFinished() && client.connected()) {
|
||||||
|
size_t waited = 1000;
|
||||||
|
size_t available = client.available();
|
||||||
|
while (!available && waited){
|
||||||
|
delay(1);
|
||||||
|
waited -=1 ;
|
||||||
|
available = client.available();
|
||||||
|
}
|
||||||
|
if (!waited){
|
||||||
|
if(written && tried++ < 3){
|
||||||
|
#ifdef OTA_DEBUG
|
||||||
|
OTA_DEBUG.printf("Try[%u]: %u\n", tried, written);
|
||||||
|
#endif
|
||||||
|
if(!client.printf("%u", written)){
|
||||||
|
#ifdef OTA_DEBUG
|
||||||
|
OTA_DEBUG.printf("failed to respond\n");
|
||||||
|
#endif
|
||||||
|
_state = OTA_IDLE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#ifdef OTA_DEBUG
|
||||||
|
OTA_DEBUG.printf("Receive Failed\n");
|
||||||
|
#endif
|
||||||
|
if (_error_callback) {
|
||||||
|
_error_callback(OTA_RECEIVE_ERROR);
|
||||||
|
}
|
||||||
|
_state = OTA_IDLE;
|
||||||
|
Update.abort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!available){
|
||||||
|
#ifdef OTA_DEBUG
|
||||||
|
OTA_DEBUG.printf("No Data: %u\n", waited);
|
||||||
|
#endif
|
||||||
|
_state = OTA_IDLE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tried = 0;
|
||||||
|
static uint8_t buf[1460];
|
||||||
|
if(available > 1460){
|
||||||
|
available = 1460;
|
||||||
|
}
|
||||||
|
size_t r = client.read(buf, available);
|
||||||
|
if(r != available){
|
||||||
|
log_w("didn't read enough! %u != %u", r, available);
|
||||||
|
}
|
||||||
|
|
||||||
|
written = Update.write(buf, r);
|
||||||
|
if (written > 0) {
|
||||||
|
if(written != r){
|
||||||
|
log_w("didn't write enough! %u != %u", written, r);
|
||||||
|
}
|
||||||
|
if(!client.printf("%u", written)){
|
||||||
|
#ifdef OTA_DEBUG
|
||||||
|
OTA_DEBUG.printf("failed to respond\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
total += written;
|
||||||
|
if(_progress_callback) {
|
||||||
|
_progress_callback(total, _size);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
#ifdef OTA_DEBUG
|
||||||
|
Update.printError(OTA_DEBUG);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Update.end()) {
|
||||||
|
client.print("OK");
|
||||||
|
client.stop();
|
||||||
|
delay(10);
|
||||||
|
if (_end_callback) {
|
||||||
|
_end_callback();
|
||||||
|
}
|
||||||
|
if(_rebootOnSuccess){
|
||||||
|
//let serial/network finish tasks that might be given in _end_callback
|
||||||
|
delay(100);
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_error_callback) {
|
||||||
|
_error_callback(OTA_END_ERROR);
|
||||||
|
}
|
||||||
|
Update.printError(client);
|
||||||
|
client.stop();
|
||||||
|
delay(10);
|
||||||
|
#ifdef OTA_DEBUG
|
||||||
|
OTA_DEBUG.print("Update ERROR: ");
|
||||||
|
Update.printError(OTA_DEBUG);
|
||||||
|
#endif
|
||||||
|
_state = OTA_IDLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArduinoOTAClass::handle() {
|
||||||
|
if (_state == OTA_RUNUPDATE) {
|
||||||
|
_runUpdate();
|
||||||
|
_state = OTA_IDLE;
|
||||||
|
}
|
||||||
|
if(_udp_ota.parsePacket()){
|
||||||
|
_onRx();
|
||||||
|
_udp_ota.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int ArduinoOTAClass::getCommand() {
|
||||||
|
return _cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_ARDUINOOTA)
|
||||||
|
ArduinoOTAClass ArduinoOTA;
|
||||||
|
#endif
|
103
libraries/ArduinoOTA/src/ArduinoOTA.h
Normal file
103
libraries/ArduinoOTA/src/ArduinoOTA.h
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
#ifndef __ARDUINO_OTA_H
|
||||||
|
#define __ARDUINO_OTA_H
|
||||||
|
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <functional>
|
||||||
|
#include "Update.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
OTA_IDLE,
|
||||||
|
OTA_WAITAUTH,
|
||||||
|
OTA_RUNUPDATE
|
||||||
|
} ota_state_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
OTA_AUTH_ERROR,
|
||||||
|
OTA_BEGIN_ERROR,
|
||||||
|
OTA_CONNECT_ERROR,
|
||||||
|
OTA_RECEIVE_ERROR,
|
||||||
|
OTA_END_ERROR
|
||||||
|
} ota_error_t;
|
||||||
|
|
||||||
|
class ArduinoOTAClass
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef std::function<void(void)> THandlerFunction;
|
||||||
|
typedef std::function<void(ota_error_t)> THandlerFunction_Error;
|
||||||
|
typedef std::function<void(unsigned int, unsigned int)> THandlerFunction_Progress;
|
||||||
|
|
||||||
|
ArduinoOTAClass();
|
||||||
|
~ArduinoOTAClass();
|
||||||
|
|
||||||
|
//Sets the service port. Default 8266
|
||||||
|
void setPort(uint16_t port);
|
||||||
|
|
||||||
|
//Sets the device hostname. Default esp8266-xxxxxx
|
||||||
|
void setHostname(const char *hostname);
|
||||||
|
String getHostname();
|
||||||
|
|
||||||
|
//Sets the password that will be required for OTA. Default NULL
|
||||||
|
void setPassword(const char *password);
|
||||||
|
|
||||||
|
//Sets the password as above but in the form MD5(password). Default NULL
|
||||||
|
void setPasswordHash(const char *password);
|
||||||
|
|
||||||
|
//Sets if the device should be rebooted after successful update. Default true
|
||||||
|
void setRebootOnSuccess(bool reboot);
|
||||||
|
|
||||||
|
//Sets if the device should advertise itself to Arduino IDE. Default true
|
||||||
|
void setMdnsEnabled(bool enabled);
|
||||||
|
|
||||||
|
//This callback will be called when OTA connection has begun
|
||||||
|
void onStart(THandlerFunction fn);
|
||||||
|
|
||||||
|
//This callback will be called when OTA has finished
|
||||||
|
void onEnd(THandlerFunction fn);
|
||||||
|
|
||||||
|
//This callback will be called when OTA encountered Error
|
||||||
|
void onError(THandlerFunction_Error fn);
|
||||||
|
|
||||||
|
//This callback will be called when OTA is receiving data
|
||||||
|
void onProgress(THandlerFunction_Progress fn);
|
||||||
|
|
||||||
|
//Starts the ArduinoOTA service
|
||||||
|
void begin();
|
||||||
|
|
||||||
|
//Call this in loop() to run the service
|
||||||
|
void handle();
|
||||||
|
|
||||||
|
//Gets update command type after OTA has started. Either U_FLASH or U_SPIFFS
|
||||||
|
int getCommand();
|
||||||
|
|
||||||
|
private:
|
||||||
|
int _port;
|
||||||
|
String _password;
|
||||||
|
String _hostname;
|
||||||
|
String _nonce;
|
||||||
|
WiFiUDP _udp_ota;
|
||||||
|
bool _initialized;
|
||||||
|
bool _rebootOnSuccess;
|
||||||
|
bool _mdnsEnabled;
|
||||||
|
ota_state_t _state;
|
||||||
|
int _size;
|
||||||
|
int _cmd;
|
||||||
|
int _ota_port;
|
||||||
|
IPAddress _ota_ip;
|
||||||
|
String _md5;
|
||||||
|
|
||||||
|
THandlerFunction _start_callback;
|
||||||
|
THandlerFunction _end_callback;
|
||||||
|
THandlerFunction_Error _error_callback;
|
||||||
|
THandlerFunction_Progress _progress_callback;
|
||||||
|
|
||||||
|
void _runUpdate(void);
|
||||||
|
void _onRx(void);
|
||||||
|
int parseInt(void);
|
||||||
|
String readStringUntil(char end);
|
||||||
|
};
|
||||||
|
|
||||||
|
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_ARDUINOOTA)
|
||||||
|
extern ArduinoOTAClass ArduinoOTA;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __ARDUINO_OTA_H */
|
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
ESP8266 mDNS-SD responder and query sample
|
||||||
|
|
||||||
|
This is an example of announcing and finding services.
|
||||||
|
|
||||||
|
Instructions:
|
||||||
|
- Update WiFi SSID and password as necessary.
|
||||||
|
- Flash the sketch to two ESP8266 boards
|
||||||
|
- The last one powered on should now find the other.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <ESPmDNS.h>
|
||||||
|
|
||||||
|
const char* ssid = "...";
|
||||||
|
const char* password = "...";
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
while (WiFi.status() != WL_CONNECTED) {
|
||||||
|
delay(250);
|
||||||
|
Serial.print(".");
|
||||||
|
}
|
||||||
|
Serial.println("");
|
||||||
|
Serial.print("Connected to ");
|
||||||
|
Serial.println(ssid);
|
||||||
|
Serial.print("IP address: ");
|
||||||
|
Serial.println(WiFi.localIP());
|
||||||
|
|
||||||
|
if (!MDNS.begin("ESP32_Browser")) {
|
||||||
|
Serial.println("Error setting up MDNS responder!");
|
||||||
|
while(1){
|
||||||
|
delay(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
browseService("http", "tcp");
|
||||||
|
delay(1000);
|
||||||
|
browseService("arduino", "tcp");
|
||||||
|
delay(1000);
|
||||||
|
browseService("workstation", "tcp");
|
||||||
|
delay(1000);
|
||||||
|
browseService("smb", "tcp");
|
||||||
|
delay(1000);
|
||||||
|
browseService("afpovertcp", "tcp");
|
||||||
|
delay(1000);
|
||||||
|
browseService("ftp", "tcp");
|
||||||
|
delay(1000);
|
||||||
|
browseService("ipp", "tcp");
|
||||||
|
delay(1000);
|
||||||
|
browseService("printer", "tcp");
|
||||||
|
delay(10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void browseService(const char * service, const char * proto){
|
||||||
|
Serial.printf("Browsing for service _%s._%s.local. ... ", service, proto);
|
||||||
|
int n = MDNS.queryService(service, proto);
|
||||||
|
if (n == 0) {
|
||||||
|
Serial.println("no services found");
|
||||||
|
} else {
|
||||||
|
Serial.print(n);
|
||||||
|
Serial.println(" service(s) found");
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
// Print details for each service found
|
||||||
|
Serial.print(" ");
|
||||||
|
Serial.print(i + 1);
|
||||||
|
Serial.print(": ");
|
||||||
|
Serial.print(MDNS.hostname(i));
|
||||||
|
Serial.print(" (");
|
||||||
|
Serial.print(MDNS.IP(i));
|
||||||
|
Serial.print(":");
|
||||||
|
Serial.print(MDNS.port(i));
|
||||||
|
Serial.println(")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Serial.println();
|
||||||
|
}
|
120
libraries/ESPmDNS/examples/mDNS_Web_Server/mDNS_Web_Server.ino
Normal file
120
libraries/ESPmDNS/examples/mDNS_Web_Server/mDNS_Web_Server.ino
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
ESP32 mDNS responder sample
|
||||||
|
|
||||||
|
This is an example of an HTTP server that is accessible
|
||||||
|
via http://esp32.local URL thanks to mDNS responder.
|
||||||
|
|
||||||
|
Instructions:
|
||||||
|
- Update WiFi SSID and password as necessary.
|
||||||
|
- Flash the sketch to the ESP32 board
|
||||||
|
- Install host software:
|
||||||
|
- For Linux, install Avahi (http://avahi.org/).
|
||||||
|
- For Windows, install Bonjour (http://www.apple.com/support/bonjour/).
|
||||||
|
- For Mac OSX and iOS support is built in through Bonjour already.
|
||||||
|
- Point your browser to http://esp32.local, you should see a response.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <ESPmDNS.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
|
||||||
|
const char* ssid = "............";
|
||||||
|
const char* password = "..............";
|
||||||
|
|
||||||
|
// TCP server at port 80 will respond to HTTP requests
|
||||||
|
WiFiServer server(80);
|
||||||
|
|
||||||
|
void setup(void)
|
||||||
|
{
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
// Connect to WiFi network
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
Serial.println("");
|
||||||
|
|
||||||
|
// Wait for connection
|
||||||
|
while (WiFi.status() != WL_CONNECTED) {
|
||||||
|
delay(500);
|
||||||
|
Serial.print(".");
|
||||||
|
}
|
||||||
|
Serial.println("");
|
||||||
|
Serial.print("Connected to ");
|
||||||
|
Serial.println(ssid);
|
||||||
|
Serial.print("IP address: ");
|
||||||
|
Serial.println(WiFi.localIP());
|
||||||
|
|
||||||
|
// Set up mDNS responder:
|
||||||
|
// - first argument is the domain name, in this example
|
||||||
|
// the fully-qualified domain name is "esp8266.local"
|
||||||
|
// - second argument is the IP address to advertise
|
||||||
|
// we send our IP address on the WiFi network
|
||||||
|
if (!MDNS.begin("esp32")) {
|
||||||
|
Serial.println("Error setting up MDNS responder!");
|
||||||
|
while(1) {
|
||||||
|
delay(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Serial.println("mDNS responder started");
|
||||||
|
|
||||||
|
// Start TCP (HTTP) server
|
||||||
|
server.begin();
|
||||||
|
Serial.println("TCP server started");
|
||||||
|
|
||||||
|
// Add service to MDNS-SD
|
||||||
|
MDNS.addService("http", "tcp", 80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop(void)
|
||||||
|
{
|
||||||
|
// Check if a client has connected
|
||||||
|
WiFiClient client = server.available();
|
||||||
|
if (!client) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Serial.println("");
|
||||||
|
Serial.println("New client");
|
||||||
|
|
||||||
|
// Wait for data from client to become available
|
||||||
|
while(client.connected() && !client.available()){
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the first line of HTTP request
|
||||||
|
String req = client.readStringUntil('\r');
|
||||||
|
|
||||||
|
// First line of HTTP request looks like "GET /path HTTP/1.1"
|
||||||
|
// Retrieve the "/path" part by finding the spaces
|
||||||
|
int addr_start = req.indexOf(' ');
|
||||||
|
int addr_end = req.indexOf(' ', addr_start + 1);
|
||||||
|
if (addr_start == -1 || addr_end == -1) {
|
||||||
|
Serial.print("Invalid request: ");
|
||||||
|
Serial.println(req);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
req = req.substring(addr_start + 1, addr_end);
|
||||||
|
Serial.print("Request: ");
|
||||||
|
Serial.println(req);
|
||||||
|
client.flush();
|
||||||
|
|
||||||
|
String s;
|
||||||
|
if (req == "/")
|
||||||
|
{
|
||||||
|
IPAddress ip = WiFi.localIP();
|
||||||
|
String ipStr = String(ip[0]) + '.' + String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]);
|
||||||
|
s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>Hello from ESP32 at ";
|
||||||
|
s += ipStr;
|
||||||
|
s += "</html>\r\n\r\n";
|
||||||
|
Serial.println("Sending 200");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s = "HTTP/1.1 404 Not Found\r\n\r\n";
|
||||||
|
Serial.println("Sending 404");
|
||||||
|
}
|
||||||
|
client.print(s);
|
||||||
|
|
||||||
|
Serial.println("Done with client");
|
||||||
|
}
|
||||||
|
|
25
libraries/ESPmDNS/keywords.txt
Normal file
25
libraries/ESPmDNS/keywords.txt
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#######################################
|
||||||
|
# Syntax Coloring Map For Ultrasound
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Datatypes (KEYWORD1)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
ESPmDNS KEYWORD1
|
||||||
|
MDNS KEYWORD1
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Methods and Functions (KEYWORD2)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
begin KEYWORD2
|
||||||
|
end KEYWORD2
|
||||||
|
addService KEYWORD2
|
||||||
|
enableArduino KEYWORD2
|
||||||
|
disableArduino KEYWORD2
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Constants (LITERAL1)
|
||||||
|
#######################################
|
||||||
|
|
9
libraries/ESPmDNS/library.properties
Normal file
9
libraries/ESPmDNS/library.properties
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
name=ESPmDNS
|
||||||
|
version=1.0
|
||||||
|
author=Hristo Gochkov, Ivan Grokhtkov
|
||||||
|
maintainer=Hristo Gochkov <hristo@espressif.com>
|
||||||
|
sentence=ESP32 mDNS Library
|
||||||
|
paragraph=
|
||||||
|
category=Communication
|
||||||
|
url=
|
||||||
|
architectures=esp32
|
187
libraries/ESPmDNS/src/ESPmDNS.cpp
Normal file
187
libraries/ESPmDNS/src/ESPmDNS.cpp
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
ESP8266 Multicast DNS (port of CC3000 Multicast DNS library)
|
||||||
|
Version 1.1
|
||||||
|
Copyright (c) 2013 Tony DiCola (tony@tonydicola.com)
|
||||||
|
ESP8266 port (c) 2015 Ivan Grokhotkov (ivan@esp8266.com)
|
||||||
|
MDNS-SD Suport 2015 Hristo Gochkov (hristo@espressif.com)
|
||||||
|
Extended MDNS-SD support 2016 Lars Englund (lars.englund@gmail.com)
|
||||||
|
|
||||||
|
|
||||||
|
License (MIT license):
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Important RFC's for reference:
|
||||||
|
// - DNS request and response: http://www.ietf.org/rfc/rfc1035.txt
|
||||||
|
// - Multicast DNS: http://www.ietf.org/rfc/rfc6762.txt
|
||||||
|
// - MDNS-SD: https://tools.ietf.org/html/rfc6763
|
||||||
|
|
||||||
|
#ifndef LWIP_OPEN_SRC
|
||||||
|
#define LWIP_OPEN_SRC
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "ESPmDNS.h"
|
||||||
|
#include <functional>
|
||||||
|
#include "esp_wifi.h"
|
||||||
|
|
||||||
|
MDNSResponder::MDNSResponder() : mdns(NULL), _if(TCPIP_ADAPTER_IF_STA) {}
|
||||||
|
MDNSResponder::~MDNSResponder() {
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MDNSResponder::begin(const char* hostName, tcpip_adapter_if_t tcpip_if, uint32_t ttl){
|
||||||
|
_if = tcpip_if;
|
||||||
|
if(!mdns && mdns_init(_if, &mdns)){
|
||||||
|
log_e("Failed starting MDNS");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_hostname = hostName;
|
||||||
|
if(mdns_set_hostname(mdns, hostName)) {
|
||||||
|
log_e("Failed setting MDNS hostname");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MDNSResponder::end() {
|
||||||
|
if(!mdns){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mdns_free(mdns);
|
||||||
|
mdns = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MDNSResponder::setInstanceName(String name) {
|
||||||
|
if (name.length() > 63) return;
|
||||||
|
if(mdns_set_instance(mdns, name.c_str())){
|
||||||
|
log_e("Failed setting MDNS instance");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MDNSResponder::enableArduino(uint16_t port, bool auth){
|
||||||
|
const char * arduTxtData[4] = {
|
||||||
|
"board=" ARDUINO_BOARD,
|
||||||
|
"tcp_check=no",
|
||||||
|
"ssh_upload=no",
|
||||||
|
"auth_upload=no"
|
||||||
|
};
|
||||||
|
if(auth){
|
||||||
|
arduTxtData[3] = "auth_upload=yes";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mdns_service_add(mdns, "_arduino", "_tcp", port)) {
|
||||||
|
log_e("Failed adding Arduino service");
|
||||||
|
} else if(mdns_service_txt_set(mdns, "_arduino", "_tcp", 4, arduTxtData)) {
|
||||||
|
log_e("Failed setting Arduino service TXT");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MDNSResponder::disableArduino(){
|
||||||
|
if(mdns_service_remove(mdns, "_arduino", "_tcp")) {
|
||||||
|
log_w("Failed removing Arduino service");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MDNSResponder::enableWorkstation(){
|
||||||
|
char winstance[21+_hostname.length()];
|
||||||
|
uint8_t mac[6];
|
||||||
|
esp_wifi_get_mac((wifi_interface_t)_if, mac);
|
||||||
|
sprintf(winstance, "%s [%02x:%02x:%02x:%02x:%02x:%02x]", _hostname.c_str(), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||||
|
|
||||||
|
if(mdns_service_add(mdns, "_workstation", "_tcp", 9)) {
|
||||||
|
log_e("Failed adding Workstation service");
|
||||||
|
} else if(mdns_service_instance_set(mdns, "_workstation", "_tcp", winstance)) {
|
||||||
|
log_e("Failed setting Workstation service instance name");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MDNSResponder::disableWorkstation(){
|
||||||
|
if(mdns_service_remove(mdns, "_workstation", "_tcp")) {
|
||||||
|
log_w("Failed removing Workstation service");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MDNSResponder::addService(char *name, char *proto, uint16_t port){
|
||||||
|
if(mdns_service_add(mdns, name, proto, port)) {
|
||||||
|
log_e("Failed adding service %s.%s.\n", name, proto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MDNSResponder::addServiceTxt(char *name, char *proto, char *key, char *value){
|
||||||
|
//ToDo: implement it in IDF. This will set the TXT to one record currently
|
||||||
|
String txt = String(key) + "=" + String(value);
|
||||||
|
const char * txt_chr[1] = {txt.c_str()};
|
||||||
|
if(mdns_service_txt_set(mdns, name, proto, 1, txt_chr)) {
|
||||||
|
log_e("Failed setting service TXT");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MDNSResponder::queryService(char *service, char *proto) {
|
||||||
|
mdns_result_free(mdns);
|
||||||
|
if(proto){
|
||||||
|
char srv[strlen(service)+2];
|
||||||
|
char prt[strlen(proto)+2];
|
||||||
|
sprintf(srv, "_%s", service);
|
||||||
|
sprintf(prt, "_%s", proto);
|
||||||
|
return mdns_query(mdns, srv, prt, 2000);
|
||||||
|
}
|
||||||
|
return mdns_query(mdns, service, NULL, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress MDNSResponder::queryHost(char *host){
|
||||||
|
mdns_result_free(mdns);
|
||||||
|
if(!mdns_query(mdns, host, NULL, 2000)){
|
||||||
|
return IPAddress();
|
||||||
|
}
|
||||||
|
return IP(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
String MDNSResponder::hostname(int idx) {
|
||||||
|
const mdns_result_t * result = mdns_result_get(mdns, idx);
|
||||||
|
if(!result){
|
||||||
|
log_e("Result %d not found", idx);
|
||||||
|
return String();
|
||||||
|
}
|
||||||
|
return String(result->host);
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress MDNSResponder::IP(int idx) {
|
||||||
|
const mdns_result_t * result = mdns_result_get(mdns, idx);
|
||||||
|
if(!result){
|
||||||
|
log_e("Result %d not found", idx);
|
||||||
|
return IPAddress();
|
||||||
|
}
|
||||||
|
return IPAddress(result->addr.addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t MDNSResponder::port(int idx) {
|
||||||
|
const mdns_result_t * result = mdns_result_get(mdns, idx);
|
||||||
|
if(!result){
|
||||||
|
log_e("Result %d not found", idx);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return result->port;
|
||||||
|
}
|
||||||
|
|
||||||
|
MDNSResponder MDNS;
|
117
libraries/ESPmDNS/src/ESPmDNS.h
Normal file
117
libraries/ESPmDNS/src/ESPmDNS.h
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
ESP8266 Multicast DNS (port of CC3000 Multicast DNS library)
|
||||||
|
Version 1.1
|
||||||
|
Copyright (c) 2013 Tony DiCola (tony@tonydicola.com)
|
||||||
|
ESP8266 port (c) 2015 Ivan Grokhotkov (ivan@esp8266.com)
|
||||||
|
MDNS-SD Suport 2015 Hristo Gochkov (hristo@espressif.com)
|
||||||
|
Extended MDNS-SD support 2016 Lars Englund (lars.englund@gmail.com)
|
||||||
|
Rewritten for ESP32 by Hristo Gochkov (hristo@espressif.com)
|
||||||
|
|
||||||
|
This is a simple implementation of multicast DNS query support for an Arduino
|
||||||
|
running on ESP32 chip.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
- Include the ESP32 Multicast DNS library in the sketch.
|
||||||
|
- Call the begin method in the sketch's setup and provide a domain name (without
|
||||||
|
the '.local' suffix, i.e. just provide 'foo' to resolve 'foo.local'), and the
|
||||||
|
Adafruit CC3000 class instance. Optionally provide a time to live (in seconds)
|
||||||
|
for the DNS record--the default is 1 hour.
|
||||||
|
- Call the update method in each iteration of the sketch's loop function.
|
||||||
|
|
||||||
|
License (MIT license):
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
*/
|
||||||
|
#ifndef ESP32MDNS_H
|
||||||
|
#define ESP32MDNS_H
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "mdns.h"
|
||||||
|
|
||||||
|
//this should be defined at build time
|
||||||
|
#ifndef ARDUINO_BOARD
|
||||||
|
#define ARDUINO_BOARD "esp32"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class MDNSResponder {
|
||||||
|
public:
|
||||||
|
MDNSResponder();
|
||||||
|
~MDNSResponder();
|
||||||
|
bool begin(const char* hostName, tcpip_adapter_if_t tcpip_if=TCPIP_ADAPTER_IF_STA, uint32_t ttl=120);
|
||||||
|
void end();
|
||||||
|
|
||||||
|
void setInstanceName(String name);
|
||||||
|
void setInstanceName(const char * name){
|
||||||
|
setInstanceName(String(name));
|
||||||
|
}
|
||||||
|
void setInstanceName(char * name){
|
||||||
|
setInstanceName(String(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
void addService(char *service, char *proto, uint16_t port);
|
||||||
|
void addService(const char *service, const char *proto, uint16_t port){
|
||||||
|
addService((char *)service, (char *)proto, port);
|
||||||
|
}
|
||||||
|
void addService(String service, String proto, uint16_t port){
|
||||||
|
addService(service.c_str(), proto.c_str(), port);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool addServiceTxt(char *name, char *proto, char * key, char * value);
|
||||||
|
void addServiceTxt(const char *name, const char *proto, const char *key,const char * value){
|
||||||
|
addServiceTxt((char *)name, (char *)proto, (char *)key, (char *)value);
|
||||||
|
}
|
||||||
|
void addServiceTxt(String name, String proto, String key, String value){
|
||||||
|
addServiceTxt(name.c_str(), proto.c_str(), key.c_str(), value.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void enableArduino(uint16_t port=3232, bool auth=false);
|
||||||
|
void disableArduino();
|
||||||
|
|
||||||
|
void enableWorkstation();
|
||||||
|
void disableWorkstation();
|
||||||
|
|
||||||
|
IPAddress queryHost(char *host);
|
||||||
|
IPAddress queryHost(const char *host){
|
||||||
|
return queryHost((char *)host);
|
||||||
|
}
|
||||||
|
IPAddress queryHost(String host){
|
||||||
|
return queryHost(host.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
int queryService(char *service, char *proto);
|
||||||
|
int queryService(const char *service, const char *proto){
|
||||||
|
return queryService((char *)service, (char *)proto);
|
||||||
|
}
|
||||||
|
int queryService(String service, String proto){
|
||||||
|
return queryService(service.c_str(), proto.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
String hostname(int idx);
|
||||||
|
IPAddress IP(int idx);
|
||||||
|
uint16_t port(int idx);
|
||||||
|
|
||||||
|
private:
|
||||||
|
mdns_server_t * mdns;
|
||||||
|
tcpip_adapter_if_t _if;
|
||||||
|
String _hostname;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern MDNSResponder MDNS;
|
||||||
|
|
||||||
|
#endif //ESP32MDNS_H
|
@ -16,51 +16,6 @@
|
|||||||
#include "SD.h"
|
#include "SD.h"
|
||||||
#include "SPI.h"
|
#include "SPI.h"
|
||||||
|
|
||||||
void setup(){
|
|
||||||
Serial.begin(115200);
|
|
||||||
if(!SD.begin()){
|
|
||||||
Serial.println("Card Mount Failed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint8_t cardType = SD.cardType();
|
|
||||||
|
|
||||||
if(cardType == CARD_NONE){
|
|
||||||
Serial.println("No SD card attached");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.print("SD Card Type: ");
|
|
||||||
if(cardType == CARD_MMC){
|
|
||||||
Serial.println("MMC");
|
|
||||||
} else if(cardType == CARD_SD){
|
|
||||||
Serial.println("SDSC");
|
|
||||||
} else if(cardType == CARD_SDHC){
|
|
||||||
Serial.println("SDHC");
|
|
||||||
} else {
|
|
||||||
Serial.println("UNKNOWN");
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
|
|
||||||
Serial.printf("SD Card Size: %lluMB\n", cardSize);
|
|
||||||
|
|
||||||
listDir(SD, "/", 0);
|
|
||||||
createDir(SD, "/mydir");
|
|
||||||
listDir(SD, "/", 0);
|
|
||||||
removeDir(SD, "/mydir");
|
|
||||||
listDir(SD, "/", 2);
|
|
||||||
writeFile(SD, "/hello.txt", "Hello ");
|
|
||||||
appendFile(SD, "/hello.txt", "World!\n");
|
|
||||||
readFile(SD, "/hello.txt");
|
|
||||||
deleteFile(SD, "/foo.txt");
|
|
||||||
renameFile(SD, "/hello.txt", "/foo.txt");
|
|
||||||
readFile(SD, "/foo.txt");
|
|
||||||
testFileIO(SD, "/test.txt");
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop(){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
|
void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
|
||||||
Serial.printf("Listing directory: %s\n", dirname);
|
Serial.printf("Listing directory: %s\n", dirname);
|
||||||
|
|
||||||
@ -214,3 +169,48 @@ void testFileIO(fs::FS &fs, const char * path){
|
|||||||
Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end);
|
Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end);
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setup(){
|
||||||
|
Serial.begin(115200);
|
||||||
|
if(!SD.begin()){
|
||||||
|
Serial.println("Card Mount Failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint8_t cardType = SD.cardType();
|
||||||
|
|
||||||
|
if(cardType == CARD_NONE){
|
||||||
|
Serial.println("No SD card attached");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.print("SD Card Type: ");
|
||||||
|
if(cardType == CARD_MMC){
|
||||||
|
Serial.println("MMC");
|
||||||
|
} else if(cardType == CARD_SD){
|
||||||
|
Serial.println("SDSC");
|
||||||
|
} else if(cardType == CARD_SDHC){
|
||||||
|
Serial.println("SDHC");
|
||||||
|
} else {
|
||||||
|
Serial.println("UNKNOWN");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
|
||||||
|
Serial.printf("SD Card Size: %lluMB\n", cardSize);
|
||||||
|
|
||||||
|
listDir(SD, "/", 0);
|
||||||
|
createDir(SD, "/mydir");
|
||||||
|
listDir(SD, "/", 0);
|
||||||
|
removeDir(SD, "/mydir");
|
||||||
|
listDir(SD, "/", 2);
|
||||||
|
writeFile(SD, "/hello.txt", "Hello ");
|
||||||
|
appendFile(SD, "/hello.txt", "World!\n");
|
||||||
|
readFile(SD, "/hello.txt");
|
||||||
|
deleteFile(SD, "/foo.txt");
|
||||||
|
renameFile(SD, "/hello.txt", "/foo.txt");
|
||||||
|
readFile(SD, "/foo.txt");
|
||||||
|
testFileIO(SD, "/test.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop(){
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -16,51 +16,6 @@
|
|||||||
#include "FS.h"
|
#include "FS.h"
|
||||||
#include "SD_MMC.h"
|
#include "SD_MMC.h"
|
||||||
|
|
||||||
void setup(){
|
|
||||||
Serial.begin(115200);
|
|
||||||
if(!SD_MMC.begin()){
|
|
||||||
Serial.println("Card Mount Failed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint8_t cardType = SD_MMC.cardType();
|
|
||||||
|
|
||||||
if(cardType == CARD_NONE){
|
|
||||||
Serial.println("No SD_MMC card attached");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.print("SD_MMC Card Type: ");
|
|
||||||
if(cardType == CARD_MMC){
|
|
||||||
Serial.println("MMC");
|
|
||||||
} else if(cardType == CARD_SD){
|
|
||||||
Serial.println("SDSC");
|
|
||||||
} else if(cardType == CARD_SDHC){
|
|
||||||
Serial.println("SDHC");
|
|
||||||
} else {
|
|
||||||
Serial.println("UNKNOWN");
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024);
|
|
||||||
Serial.printf("SD_MMC Card Size: %lluMB\n", cardSize);
|
|
||||||
|
|
||||||
listDir(SD_MMC, "/", 0);
|
|
||||||
createDir(SD_MMC, "/mydir");
|
|
||||||
listDir(SD_MMC, "/", 0);
|
|
||||||
removeDir(SD_MMC, "/mydir");
|
|
||||||
listDir(SD_MMC, "/", 2);
|
|
||||||
writeFile(SD_MMC, "/hello.txt", "Hello ");
|
|
||||||
appendFile(SD_MMC, "/hello.txt", "World!\n");
|
|
||||||
readFile(SD_MMC, "/hello.txt");
|
|
||||||
deleteFile(SD_MMC, "/foo.txt");
|
|
||||||
renameFile(SD_MMC, "/hello.txt", "/foo.txt");
|
|
||||||
readFile(SD_MMC, "/foo.txt");
|
|
||||||
testFileIO(SD_MMC, "/test.txt");
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop(){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
|
void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
|
||||||
Serial.printf("Listing directory: %s\n", dirname);
|
Serial.printf("Listing directory: %s\n", dirname);
|
||||||
|
|
||||||
@ -214,3 +169,48 @@ void testFileIO(fs::FS &fs, const char * path){
|
|||||||
Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end);
|
Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end);
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setup(){
|
||||||
|
Serial.begin(115200);
|
||||||
|
if(!SD_MMC.begin()){
|
||||||
|
Serial.println("Card Mount Failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint8_t cardType = SD_MMC.cardType();
|
||||||
|
|
||||||
|
if(cardType == CARD_NONE){
|
||||||
|
Serial.println("No SD_MMC card attached");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.print("SD_MMC Card Type: ");
|
||||||
|
if(cardType == CARD_MMC){
|
||||||
|
Serial.println("MMC");
|
||||||
|
} else if(cardType == CARD_SD){
|
||||||
|
Serial.println("SDSC");
|
||||||
|
} else if(cardType == CARD_SDHC){
|
||||||
|
Serial.println("SDHC");
|
||||||
|
} else {
|
||||||
|
Serial.println("UNKNOWN");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024);
|
||||||
|
Serial.printf("SD_MMC Card Size: %lluMB\n", cardSize);
|
||||||
|
|
||||||
|
listDir(SD_MMC, "/", 0);
|
||||||
|
createDir(SD_MMC, "/mydir");
|
||||||
|
listDir(SD_MMC, "/", 0);
|
||||||
|
removeDir(SD_MMC, "/mydir");
|
||||||
|
listDir(SD_MMC, "/", 2);
|
||||||
|
writeFile(SD_MMC, "/hello.txt", "Hello ");
|
||||||
|
appendFile(SD_MMC, "/hello.txt", "World!\n");
|
||||||
|
readFile(SD_MMC, "/hello.txt");
|
||||||
|
deleteFile(SD_MMC, "/foo.txt");
|
||||||
|
renameFile(SD_MMC, "/hello.txt", "/foo.txt");
|
||||||
|
readFile(SD_MMC, "/foo.txt");
|
||||||
|
testFileIO(SD_MMC, "/test.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop(){
|
||||||
|
|
||||||
|
}
|
||||||
|
24
libraries/Update/keywords.txt
Normal file
24
libraries/Update/keywords.txt
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#######################################
|
||||||
|
# Syntax Coloring Map For Ultrasound
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Datatypes (KEYWORD1)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
Update KEYWORD1
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Methods and Functions (KEYWORD2)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
begin KEYWORD2
|
||||||
|
end KEYWORD2
|
||||||
|
write KEYWORD2
|
||||||
|
writeStream KEYWORD2
|
||||||
|
printError KEYWORD2
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Constants (LITERAL1)
|
||||||
|
#######################################
|
||||||
|
|
9
libraries/Update/library.properties
Normal file
9
libraries/Update/library.properties
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
name=Update
|
||||||
|
version=1.0
|
||||||
|
author=Hristo Gochkov
|
||||||
|
maintainer=Hristo Gochkov <hristo@espressif.com>
|
||||||
|
sentence=ESP32 Sketch Update Library
|
||||||
|
paragraph=
|
||||||
|
category=Other
|
||||||
|
url=
|
||||||
|
architectures=esp32
|
161
libraries/Update/src/Update.h
Normal file
161
libraries/Update/src/Update.h
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
#ifndef ESP8266UPDATER_H
|
||||||
|
#define ESP8266UPDATER_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <MD5Builder.h>
|
||||||
|
#include "esp_partition.h"
|
||||||
|
|
||||||
|
#define UPDATE_ERROR_OK (0)
|
||||||
|
#define UPDATE_ERROR_WRITE (1)
|
||||||
|
#define UPDATE_ERROR_ERASE (2)
|
||||||
|
#define UPDATE_ERROR_READ (3)
|
||||||
|
#define UPDATE_ERROR_SPACE (4)
|
||||||
|
#define UPDATE_ERROR_SIZE (5)
|
||||||
|
#define UPDATE_ERROR_STREAM (6)
|
||||||
|
#define UPDATE_ERROR_MD5 (7)
|
||||||
|
#define UPDATE_ERROR_MAGIC_BYTE (8)
|
||||||
|
#define UPDATE_ERROR_ACTIVATE (9)
|
||||||
|
#define UPDATE_ERROR_NO_PARTITION (10)
|
||||||
|
#define UPDATE_ERROR_BAD_ARGUMENT (11)
|
||||||
|
#define UPDATE_ERROR_ABORT (12)
|
||||||
|
|
||||||
|
#define UPDATE_SIZE_UNKNOWN 0xFFFFFFFF
|
||||||
|
|
||||||
|
#define U_FLASH 0
|
||||||
|
#define U_SPIFFS 100
|
||||||
|
#define U_AUTH 200
|
||||||
|
|
||||||
|
class UpdateClass {
|
||||||
|
public:
|
||||||
|
UpdateClass();
|
||||||
|
/*
|
||||||
|
Call this to check the space needed for the update
|
||||||
|
Will return false if there is not enough space
|
||||||
|
*/
|
||||||
|
bool begin(size_t size=UPDATE_SIZE_UNKNOWN, int command = U_FLASH);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Writes a buffer to the flash and increments the address
|
||||||
|
Returns the amount written
|
||||||
|
*/
|
||||||
|
size_t write(uint8_t *data, size_t len);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Writes the remaining bytes from the Stream to the flash
|
||||||
|
Uses readBytes() and sets UPDATE_ERROR_STREAM on timeout
|
||||||
|
Returns the bytes written
|
||||||
|
Should be equal to the remaining bytes when called
|
||||||
|
Usable for slow streams like Serial
|
||||||
|
*/
|
||||||
|
size_t writeStream(Stream &data);
|
||||||
|
|
||||||
|
/*
|
||||||
|
If all bytes are written
|
||||||
|
this call will write the config to eboot
|
||||||
|
and return true
|
||||||
|
If there is already an update running but is not finished and !evenIfRemainanig
|
||||||
|
or there is an error
|
||||||
|
this will clear everything and return false
|
||||||
|
the last error is available through getError()
|
||||||
|
evenIfRemaining is helpfull when you update without knowing the final size first
|
||||||
|
*/
|
||||||
|
bool end(bool evenIfRemaining = false);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Aborts the running update
|
||||||
|
*/
|
||||||
|
void abort();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Prints the last error to an output stream
|
||||||
|
*/
|
||||||
|
void printError(Stream &out);
|
||||||
|
|
||||||
|
/*
|
||||||
|
sets the expected MD5 for the firmware (hexString)
|
||||||
|
*/
|
||||||
|
bool setMD5(const char * expected_md5);
|
||||||
|
|
||||||
|
/*
|
||||||
|
returns the MD5 String of the sucessfully ended firmware
|
||||||
|
*/
|
||||||
|
String md5String(void){ return _md5.toString(); }
|
||||||
|
|
||||||
|
/*
|
||||||
|
populated the result with the md5 bytes of the sucessfully ended firmware
|
||||||
|
*/
|
||||||
|
void md5(uint8_t * result){ return _md5.getBytes(result); }
|
||||||
|
|
||||||
|
//Helpers
|
||||||
|
uint8_t getError(){ return _error; }
|
||||||
|
void clearError(){ _error = UPDATE_ERROR_OK; }
|
||||||
|
bool hasError(){ return _error != UPDATE_ERROR_OK; }
|
||||||
|
bool isRunning(){ return _size > 0; }
|
||||||
|
bool isFinished(){ return _progress == _size; }
|
||||||
|
size_t size(){ return _size; }
|
||||||
|
size_t progress(){ return _progress; }
|
||||||
|
size_t remaining(){ return _size - _progress; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
Template to write from objects that expose
|
||||||
|
available() and read(uint8_t*, size_t) methods
|
||||||
|
faster than the writeStream method
|
||||||
|
writes only what is available
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
size_t write(T &data){
|
||||||
|
size_t written = 0;
|
||||||
|
if (hasError() || !isRunning())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
size_t available = data.available();
|
||||||
|
while(available) {
|
||||||
|
if(_bufferLen + available > remaining()){
|
||||||
|
available = remaining() - _bufferLen;
|
||||||
|
}
|
||||||
|
if(_bufferLen + available > 4096) {
|
||||||
|
size_t toBuff = 4096 - _bufferLen;
|
||||||
|
data.read(_buffer + _bufferLen, toBuff);
|
||||||
|
_bufferLen += toBuff;
|
||||||
|
if(!_writeBuffer())
|
||||||
|
return written;
|
||||||
|
written += toBuff;
|
||||||
|
} else {
|
||||||
|
data.read(_buffer + _bufferLen, available);
|
||||||
|
_bufferLen += available;
|
||||||
|
written += available;
|
||||||
|
if(_bufferLen == remaining()) {
|
||||||
|
if(!_writeBuffer()) {
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(remaining() == 0)
|
||||||
|
return written;
|
||||||
|
available = data.available();
|
||||||
|
}
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void _reset();
|
||||||
|
void _abort(uint8_t err);
|
||||||
|
bool _writeBuffer();
|
||||||
|
bool _verifyHeader(uint8_t data);
|
||||||
|
bool _verifyEnd();
|
||||||
|
|
||||||
|
uint8_t _error;
|
||||||
|
uint8_t *_buffer;
|
||||||
|
size_t _bufferLen;
|
||||||
|
size_t _size;
|
||||||
|
uint32_t _progress;
|
||||||
|
uint32_t _command;
|
||||||
|
const esp_partition_t* _partition;
|
||||||
|
|
||||||
|
String _target_md5;
|
||||||
|
MD5Builder _md5;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern UpdateClass Update;
|
||||||
|
|
||||||
|
#endif
|
278
libraries/Update/src/Updater.cpp
Normal file
278
libraries/Update/src/Updater.cpp
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
#include "Update.h"
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "esp_spi_flash.h"
|
||||||
|
#include "esp_ota_ops.h"
|
||||||
|
#include "esp_image_format.h"
|
||||||
|
|
||||||
|
static const char * _err2str(uint8_t _error){
|
||||||
|
if(_error == UPDATE_ERROR_OK){
|
||||||
|
return ("No Error");
|
||||||
|
} else if(_error == UPDATE_ERROR_WRITE){
|
||||||
|
return ("Flash Write Failed");
|
||||||
|
} else if(_error == UPDATE_ERROR_ERASE){
|
||||||
|
return ("Flash Erase Failed");
|
||||||
|
} else if(_error == UPDATE_ERROR_READ){
|
||||||
|
return ("Flash Read Failed");
|
||||||
|
} else if(_error == UPDATE_ERROR_SPACE){
|
||||||
|
return ("Not Enough Space");
|
||||||
|
} else if(_error == UPDATE_ERROR_SIZE){
|
||||||
|
return ("Bad Size Given");
|
||||||
|
} else if(_error == UPDATE_ERROR_STREAM){
|
||||||
|
return ("Stream Read Timeout");
|
||||||
|
} else if(_error == UPDATE_ERROR_MD5){
|
||||||
|
return ("MD5 Check Failed");
|
||||||
|
} else if(_error == UPDATE_ERROR_MAGIC_BYTE){
|
||||||
|
return ("Wrong Magic Byte");
|
||||||
|
} else if(_error == UPDATE_ERROR_ACTIVATE){
|
||||||
|
return ("Could Not Activate The Firmware");
|
||||||
|
} else if(_error == UPDATE_ERROR_NO_PARTITION){
|
||||||
|
return ("Partition Could Not be Found");
|
||||||
|
} else if(_error == UPDATE_ERROR_BAD_ARGUMENT){
|
||||||
|
return ("Bad Argument");
|
||||||
|
} else if(_error == UPDATE_ERROR_ABORT){
|
||||||
|
return ("Aborted");
|
||||||
|
}
|
||||||
|
return ("UNKNOWN");
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateClass::UpdateClass()
|
||||||
|
: _error(0)
|
||||||
|
, _buffer(0)
|
||||||
|
, _bufferLen(0)
|
||||||
|
, _size(0)
|
||||||
|
, _progress(0)
|
||||||
|
, _command(U_FLASH)
|
||||||
|
, _partition(NULL)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateClass::_reset() {
|
||||||
|
if (_buffer)
|
||||||
|
delete[] _buffer;
|
||||||
|
_buffer = 0;
|
||||||
|
_bufferLen = 0;
|
||||||
|
_progress = 0;
|
||||||
|
_size = 0;
|
||||||
|
_command = U_FLASH;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UpdateClass::begin(size_t size, int command) {
|
||||||
|
if(_size > 0){
|
||||||
|
log_w("already running");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_reset();
|
||||||
|
_error = 0;
|
||||||
|
|
||||||
|
if(size == 0) {
|
||||||
|
_error = UPDATE_ERROR_SIZE;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command == U_FLASH) {
|
||||||
|
_partition = esp_ota_get_next_update_partition(NULL);
|
||||||
|
if(!_partition){
|
||||||
|
_error = UPDATE_ERROR_NO_PARTITION;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
log_d("OTA Partition: %s", _partition->label);
|
||||||
|
}
|
||||||
|
else if (command == U_SPIFFS) {
|
||||||
|
_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, NULL);
|
||||||
|
if(!_partition){
|
||||||
|
_error = UPDATE_ERROR_NO_PARTITION;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_error = UPDATE_ERROR_BAD_ARGUMENT;
|
||||||
|
log_e("bad command %u", command);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(size == UPDATE_SIZE_UNKNOWN){
|
||||||
|
size = _partition->size;
|
||||||
|
} else if(size > _partition->size){
|
||||||
|
_error = UPDATE_ERROR_SIZE;
|
||||||
|
log_e("too large %u > %u", size, _partition->size);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//initialize
|
||||||
|
_buffer = (uint8_t*)malloc(SPI_FLASH_SEC_SIZE);
|
||||||
|
if(!_buffer){
|
||||||
|
log_e("malloc failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_size = size;
|
||||||
|
_command = command;
|
||||||
|
_md5.begin();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateClass::_abort(uint8_t err){
|
||||||
|
_reset();
|
||||||
|
_error = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateClass::abort(){
|
||||||
|
_abort(UPDATE_ERROR_ABORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UpdateClass::_writeBuffer(){
|
||||||
|
if(!ESP.flashEraseSector((_partition->address + _progress)/SPI_FLASH_SEC_SIZE)){
|
||||||
|
_abort(UPDATE_ERROR_ERASE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ESP.flashWrite(_partition->address + _progress, (uint32_t*)_buffer, _bufferLen)) {
|
||||||
|
_abort(UPDATE_ERROR_WRITE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_md5.add(_buffer, _bufferLen);
|
||||||
|
_progress += _bufferLen;
|
||||||
|
_bufferLen = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UpdateClass::_verifyHeader(uint8_t data) {
|
||||||
|
if(_command == U_FLASH) {
|
||||||
|
if(data != ESP_IMAGE_HEADER_MAGIC) {
|
||||||
|
_abort(UPDATE_ERROR_MAGIC_BYTE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else if(_command == U_SPIFFS) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UpdateClass::_verifyEnd() {
|
||||||
|
if(_command == U_FLASH) {
|
||||||
|
uint8_t buf[4];
|
||||||
|
if(!ESP.flashRead(_partition->address, (uint32_t*)buf, 4)) {
|
||||||
|
_abort(UPDATE_ERROR_READ);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(buf[0] != ESP_IMAGE_HEADER_MAGIC) {
|
||||||
|
_abort(UPDATE_ERROR_MAGIC_BYTE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(esp_ota_set_boot_partition(_partition)){
|
||||||
|
_abort(UPDATE_ERROR_ACTIVATE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_reset();
|
||||||
|
return true;
|
||||||
|
} else if(_command == U_SPIFFS) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UpdateClass::setMD5(const char * expected_md5){
|
||||||
|
if(strlen(expected_md5) != 32)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_target_md5 = expected_md5;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UpdateClass::end(bool evenIfRemaining){
|
||||||
|
if(hasError() || _size == 0){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!isFinished() && !evenIfRemaining){
|
||||||
|
log_e("premature end: res:%u, pos:%u/%u\n", getError(), progress(), _size);
|
||||||
|
_abort(UPDATE_ERROR_ABORT);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(evenIfRemaining) {
|
||||||
|
if(_bufferLen > 0) {
|
||||||
|
_writeBuffer();
|
||||||
|
}
|
||||||
|
_size = progress();
|
||||||
|
}
|
||||||
|
|
||||||
|
_md5.calculate();
|
||||||
|
if(_target_md5.length()) {
|
||||||
|
if(_target_md5 != _md5.toString()){
|
||||||
|
_abort(UPDATE_ERROR_MD5);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _verifyEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t UpdateClass::write(uint8_t *data, size_t len) {
|
||||||
|
if(hasError() || !isRunning()){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(len > remaining()){
|
||||||
|
_abort(UPDATE_ERROR_SPACE);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t left = len;
|
||||||
|
|
||||||
|
while((_bufferLen + left) > SPI_FLASH_SEC_SIZE) {
|
||||||
|
size_t toBuff = SPI_FLASH_SEC_SIZE - _bufferLen;
|
||||||
|
memcpy(_buffer + _bufferLen, data + (len - left), toBuff);
|
||||||
|
_bufferLen += toBuff;
|
||||||
|
if(!_writeBuffer()){
|
||||||
|
return len - left;
|
||||||
|
}
|
||||||
|
left -= toBuff;
|
||||||
|
}
|
||||||
|
memcpy(_buffer + _bufferLen, data + (len - left), left);
|
||||||
|
_bufferLen += left;
|
||||||
|
if(_bufferLen == remaining()){
|
||||||
|
if(!_writeBuffer()){
|
||||||
|
return len - left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t UpdateClass::writeStream(Stream &data) {
|
||||||
|
size_t written = 0;
|
||||||
|
size_t toRead = 0;
|
||||||
|
if(hasError() || !isRunning())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if(!_verifyHeader(data.peek())) {
|
||||||
|
_reset();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(remaining()) {
|
||||||
|
toRead = data.readBytes(_buffer + _bufferLen, (SPI_FLASH_SEC_SIZE - _bufferLen));
|
||||||
|
if(toRead == 0) { //Timeout
|
||||||
|
delay(100);
|
||||||
|
toRead = data.readBytes(_buffer + _bufferLen, (SPI_FLASH_SEC_SIZE - _bufferLen));
|
||||||
|
if(toRead == 0) { //Timeout
|
||||||
|
_abort(UPDATE_ERROR_STREAM);
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_bufferLen += toRead;
|
||||||
|
if((_bufferLen == remaining() || _bufferLen == SPI_FLASH_SEC_SIZE) && !_writeBuffer())
|
||||||
|
return written;
|
||||||
|
written += toRead;
|
||||||
|
}
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateClass::printError(Stream &out){
|
||||||
|
out.println(_err2str(_error));
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateClass Update;
|
@ -6,6 +6,9 @@ runtime.tools.xtensa-esp32-elf-gcc.path={runtime.platform.path}/tools/xtensa-esp
|
|||||||
tools.esptool.cmd="{runtime.platform.path}/tools/esptool"
|
tools.esptool.cmd="{runtime.platform.path}/tools/esptool"
|
||||||
tools.esptool.cmd.windows="{runtime.platform.path}/tools/esptool.exe"
|
tools.esptool.cmd.windows="{runtime.platform.path}/tools/esptool.exe"
|
||||||
|
|
||||||
|
tools.esptool.network_cmd=python "{runtime.platform.path}/tools/espota.py"
|
||||||
|
tools.esptool.network_cmd.windows="{runtime.platform.path}/tools/espota.exe"
|
||||||
|
|
||||||
tools.gen_esp32part.cmd=python "{runtime.platform.path}/tools/gen_esp32part.py"
|
tools.gen_esp32part.cmd=python "{runtime.platform.path}/tools/gen_esp32part.py"
|
||||||
tools.gen_esp32part.cmd.windows="{runtime.platform.path}/tools/gen_esp32part.exe"
|
tools.gen_esp32part.cmd.windows="{runtime.platform.path}/tools/gen_esp32part.exe"
|
||||||
|
|
||||||
@ -17,7 +20,7 @@ compiler.warning_flags.all=-Wall -Werror=all -Wextra
|
|||||||
|
|
||||||
compiler.path={runtime.tools.xtensa-esp32-elf-gcc.path}/bin/
|
compiler.path={runtime.tools.xtensa-esp32-elf-gcc.path}/bin/
|
||||||
compiler.sdk.path={runtime.platform.path}/tools/sdk
|
compiler.sdk.path={runtime.platform.path}/tools/sdk
|
||||||
compiler.cpreprocessor.flags=-DESP_PLATFORM -DMBEDTLS_CONFIG_FILE="mbedtls/esp_config.h" -DHAVE_CONFIG_H "-I{compiler.sdk.path}/include/config" "-I{compiler.sdk.path}/include/bluedroid" "-I{compiler.sdk.path}/include/bt" "-I{compiler.sdk.path}/include/driver" "-I{compiler.sdk.path}/include/esp32" "-I{compiler.sdk.path}/include/ethernet" "-I{compiler.sdk.path}/include/fatfs" "-I{compiler.sdk.path}/include/freertos" "-I{compiler.sdk.path}/include/log" "-I{compiler.sdk.path}/include/mdns" "-I{compiler.sdk.path}/include/mbedtls" "-I{compiler.sdk.path}/include/mbedtls_port" "-I{compiler.sdk.path}/include/vfs" "-I{compiler.sdk.path}/include/ulp" "-I{compiler.sdk.path}/include/newlib" "-I{compiler.sdk.path}/include/nvs_flash" "-I{compiler.sdk.path}/include/spi_flash" "-I{compiler.sdk.path}/include/sdmmc" "-I{compiler.sdk.path}/include/openssl" "-I{compiler.sdk.path}/include/app_update" "-I{compiler.sdk.path}/include/tcpip_adapter" "-I{compiler.sdk.path}/include/xtensa-debug-module" "-I{compiler.sdk.path}/include/newlib" "-I{compiler.sdk.path}/include/coap" "-I{compiler.sdk.path}/include/wpa_supplicant" "-I{compiler.sdk.path}/include/expat" "-I{compiler.sdk.path}/include/json" "-I{compiler.sdk.path}/include/nghttp" "-I{compiler.sdk.path}/include/lwip"
|
compiler.cpreprocessor.flags=-DESP_PLATFORM -DMBEDTLS_CONFIG_FILE="mbedtls/esp_config.h" -DHAVE_CONFIG_H "-I{compiler.sdk.path}/include/config" "-I{compiler.sdk.path}/include/bluedroid" "-I{compiler.sdk.path}/include/bootloader_support" "-I{compiler.sdk.path}/include/bt" "-I{compiler.sdk.path}/include/driver" "-I{compiler.sdk.path}/include/esp32" "-I{compiler.sdk.path}/include/ethernet" "-I{compiler.sdk.path}/include/fatfs" "-I{compiler.sdk.path}/include/freertos" "-I{compiler.sdk.path}/include/log" "-I{compiler.sdk.path}/include/mdns" "-I{compiler.sdk.path}/include/mbedtls" "-I{compiler.sdk.path}/include/mbedtls_port" "-I{compiler.sdk.path}/include/vfs" "-I{compiler.sdk.path}/include/ulp" "-I{compiler.sdk.path}/include/newlib" "-I{compiler.sdk.path}/include/nvs_flash" "-I{compiler.sdk.path}/include/spi_flash" "-I{compiler.sdk.path}/include/sdmmc" "-I{compiler.sdk.path}/include/openssl" "-I{compiler.sdk.path}/include/app_update" "-I{compiler.sdk.path}/include/tcpip_adapter" "-I{compiler.sdk.path}/include/xtensa-debug-module" "-I{compiler.sdk.path}/include/newlib" "-I{compiler.sdk.path}/include/coap" "-I{compiler.sdk.path}/include/wpa_supplicant" "-I{compiler.sdk.path}/include/expat" "-I{compiler.sdk.path}/include/json" "-I{compiler.sdk.path}/include/nghttp" "-I{compiler.sdk.path}/include/lwip"
|
||||||
|
|
||||||
compiler.c.cmd=xtensa-esp32-elf-gcc
|
compiler.c.cmd=xtensa-esp32-elf-gcc
|
||||||
compiler.c.flags=-std=gnu99 -Os -g3 -ffunction-sections -fdata-sections -fstrict-volatile-bitfields -mlongcalls -nostdlib -Wpointer-arith {compiler.warning_flags} -Wno-error=unused-function -Wno-error=unused-but-set-variable -Wno-error=unused-variable -Wno-error=deprecated-declarations -Wno-unused-parameter -Wno-sign-compare -Wno-old-style-declaration -MMD -c
|
compiler.c.flags=-std=gnu99 -Os -g3 -ffunction-sections -fdata-sections -fstrict-volatile-bitfields -mlongcalls -nostdlib -Wpointer-arith {compiler.warning_flags} -Wno-error=unused-function -Wno-error=unused-but-set-variable -Wno-error=unused-variable -Wno-error=deprecated-declarations -Wno-unused-parameter -Wno-sign-compare -Wno-old-style-declaration -MMD -c
|
||||||
@ -88,3 +91,4 @@ tools.esptool.upload.protocol=esp32
|
|||||||
tools.esptool.upload.params.verbose=
|
tools.esptool.upload.params.verbose=
|
||||||
tools.esptool.upload.params.quiet=
|
tools.esptool.upload.params.quiet=
|
||||||
tools.esptool.upload.pattern={cmd} --chip esp32 --port "{serial.port}" --baud {upload.speed} --before default_reset --after hard_reset write_flash -z --flash_freq {build.flash_freq} --flash_mode {build.flash_mode} --flash_size {build.flash_size} 0x1000 "{runtime.platform.path}/tools/sdk/bin/bootloader.bin" 0x8000 "{build.path}/{build.project_name}.partitions.bin" 0xe000 "{runtime.platform.path}/tools/partitions/boot_app0.bin" 0x10000 "{build.path}/{build.project_name}.bin"
|
tools.esptool.upload.pattern={cmd} --chip esp32 --port "{serial.port}" --baud {upload.speed} --before default_reset --after hard_reset write_flash -z --flash_freq {build.flash_freq} --flash_mode {build.flash_mode} --flash_size {build.flash_size} 0x1000 "{runtime.platform.path}/tools/sdk/bin/bootloader.bin" 0x8000 "{build.path}/{build.project_name}.partitions.bin" 0xe000 "{runtime.platform.path}/tools/partitions/boot_app0.bin" 0x10000 "{build.path}/{build.project_name}.bin"
|
||||||
|
tools.esptool.upload.network_pattern={network_cmd} -i "{serial.port}" -p "{network.port}" "--auth={network.password}" -f "{build.path}/{build.project_name}.bin"
|
||||||
|
BIN
tools/espota.exe
Normal file
BIN
tools/espota.exe
Normal file
Binary file not shown.
321
tools/espota.py
Executable file
321
tools/espota.py
Executable file
@ -0,0 +1,321 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Original espota.py by Ivan Grokhotkov:
|
||||||
|
# https://gist.github.com/igrr/d35ab8446922179dc58c
|
||||||
|
#
|
||||||
|
# Modified since 2015-09-18 from Pascal Gollor (https://github.com/pgollor)
|
||||||
|
# Modified since 2015-11-09 from Hristo Gochkov (https://github.com/me-no-dev)
|
||||||
|
# Modified since 2016-01-03 from Matthew O'Gorman (https://githumb.com/mogorman)
|
||||||
|
#
|
||||||
|
# This script will push an OTA update to the ESP
|
||||||
|
# use it like: python espota.py -i <ESP_IP_address> -I <Host_IP_address> -p <ESP_port> -P <Host_port> [-a password] -f <sketch.bin>
|
||||||
|
# Or to upload SPIFFS image:
|
||||||
|
# python espota.py -i <ESP_IP_address> -I <Host_IP_address> -p <ESP_port> -P <HOST_port> [-a password] -s -f <spiffs.bin>
|
||||||
|
#
|
||||||
|
# Changes
|
||||||
|
# 2015-09-18:
|
||||||
|
# - Add option parser.
|
||||||
|
# - Add logging.
|
||||||
|
# - Send command to controller to differ between flashing and transmitting SPIFFS image.
|
||||||
|
#
|
||||||
|
# Changes
|
||||||
|
# 2015-11-09:
|
||||||
|
# - Added digest authentication
|
||||||
|
# - Enhanced error tracking and reporting
|
||||||
|
#
|
||||||
|
# Changes
|
||||||
|
# 2016-01-03:
|
||||||
|
# - Added more options to parser.
|
||||||
|
#
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import optparse
|
||||||
|
import logging
|
||||||
|
import hashlib
|
||||||
|
import random
|
||||||
|
|
||||||
|
# Commands
|
||||||
|
FLASH = 0
|
||||||
|
SPIFFS = 100
|
||||||
|
AUTH = 200
|
||||||
|
PROGRESS = False
|
||||||
|
# update_progress() : Displays or updates a console progress bar
|
||||||
|
## Accepts a float between 0 and 1. Any int will be converted to a float.
|
||||||
|
## A value under 0 represents a 'halt'.
|
||||||
|
## A value at 1 or bigger represents 100%
|
||||||
|
def update_progress(progress):
|
||||||
|
if (PROGRESS):
|
||||||
|
barLength = 60 # Modify this to change the length of the progress bar
|
||||||
|
status = ""
|
||||||
|
if isinstance(progress, int):
|
||||||
|
progress = float(progress)
|
||||||
|
if not isinstance(progress, float):
|
||||||
|
progress = 0
|
||||||
|
status = "error: progress var must be float\r\n"
|
||||||
|
if progress < 0:
|
||||||
|
progress = 0
|
||||||
|
status = "Halt...\r\n"
|
||||||
|
if progress >= 1:
|
||||||
|
progress = 1
|
||||||
|
status = "Done...\r\n"
|
||||||
|
block = int(round(barLength*progress))
|
||||||
|
text = "\rUploading: [{0}] {1}% {2}".format( "="*block + " "*(barLength-block), int(progress*100), status)
|
||||||
|
sys.stderr.write(text)
|
||||||
|
sys.stderr.flush()
|
||||||
|
else:
|
||||||
|
sys.stderr.write('.')
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
def serve(remoteAddr, localAddr, remotePort, localPort, password, filename, command = FLASH):
|
||||||
|
# Create a TCP/IP socket
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
server_address = (localAddr, localPort)
|
||||||
|
logging.info('Starting on %s:%s', str(server_address[0]), str(server_address[1]))
|
||||||
|
try:
|
||||||
|
sock.bind(server_address)
|
||||||
|
sock.listen(1)
|
||||||
|
except:
|
||||||
|
logging.error("Listen Failed")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
content_size = os.path.getsize(filename)
|
||||||
|
f = open(filename,'rb')
|
||||||
|
file_md5 = hashlib.md5(f.read()).hexdigest()
|
||||||
|
f.close()
|
||||||
|
logging.info('Upload size: %d', content_size)
|
||||||
|
message = '%d %d %d %s\n' % (command, localPort, content_size, file_md5)
|
||||||
|
|
||||||
|
# Wait for a connection
|
||||||
|
inv_trys = 0
|
||||||
|
data = ''
|
||||||
|
msg = 'Sending invitation to %s ' % (remoteAddr)
|
||||||
|
sys.stderr.write(msg)
|
||||||
|
sys.stderr.flush()
|
||||||
|
while (inv_trys < 10):
|
||||||
|
inv_trys += 1
|
||||||
|
sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
remote_address = (remoteAddr, int(remotePort))
|
||||||
|
sent = sock2.sendto(message.encode(), remote_address)
|
||||||
|
sock2.settimeout(1)
|
||||||
|
try:
|
||||||
|
data = sock2.recv(37).decode()
|
||||||
|
break;
|
||||||
|
except:
|
||||||
|
sys.stderr.write('.')
|
||||||
|
sys.stderr.flush()
|
||||||
|
sock2.close()
|
||||||
|
sys.stderr.write('\n')
|
||||||
|
sys.stderr.flush()
|
||||||
|
if (inv_trys == 10):
|
||||||
|
logging.error('No response from the ESP')
|
||||||
|
return 1
|
||||||
|
if (data != "OK"):
|
||||||
|
if(data.startswith('AUTH')):
|
||||||
|
nonce = data.split()[1]
|
||||||
|
cnonce_text = '%s%u%s%s' % (filename, content_size, file_md5, remoteAddr)
|
||||||
|
cnonce = hashlib.md5(cnonce_text.encode()).hexdigest()
|
||||||
|
passmd5 = hashlib.md5(password.encode()).hexdigest()
|
||||||
|
result_text = '%s:%s:%s' % (passmd5 ,nonce, cnonce)
|
||||||
|
result = hashlib.md5(result_text.encode()).hexdigest()
|
||||||
|
sys.stderr.write('Authenticating...')
|
||||||
|
sys.stderr.flush()
|
||||||
|
message = '%d %s %s\n' % (AUTH, cnonce, result)
|
||||||
|
sock2.sendto(message.encode(), remote_address)
|
||||||
|
sock2.settimeout(10)
|
||||||
|
try:
|
||||||
|
data = sock2.recv(32).decode()
|
||||||
|
except:
|
||||||
|
sys.stderr.write('FAIL\n')
|
||||||
|
logging.error('No Answer to our Authentication')
|
||||||
|
sock2.close()
|
||||||
|
return 1
|
||||||
|
if (data != "OK"):
|
||||||
|
sys.stderr.write('FAIL\n')
|
||||||
|
logging.error('%s', data)
|
||||||
|
sock2.close()
|
||||||
|
sys.exit(1);
|
||||||
|
return 1
|
||||||
|
sys.stderr.write('OK\n')
|
||||||
|
else:
|
||||||
|
logging.error('Bad Answer: %s', data)
|
||||||
|
sock2.close()
|
||||||
|
return 1
|
||||||
|
sock2.close()
|
||||||
|
|
||||||
|
logging.info('Waiting for device...')
|
||||||
|
try:
|
||||||
|
sock.settimeout(10)
|
||||||
|
connection, client_address = sock.accept()
|
||||||
|
sock.settimeout(None)
|
||||||
|
connection.settimeout(None)
|
||||||
|
except:
|
||||||
|
logging.error('No response from device')
|
||||||
|
sock.close()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
f = open(filename, "rb")
|
||||||
|
if (PROGRESS):
|
||||||
|
update_progress(0)
|
||||||
|
else:
|
||||||
|
sys.stderr.write('Uploading')
|
||||||
|
sys.stderr.flush()
|
||||||
|
offset = 0
|
||||||
|
while True:
|
||||||
|
chunk = f.read(1460)
|
||||||
|
if not chunk: break
|
||||||
|
offset += len(chunk)
|
||||||
|
update_progress(offset/float(content_size))
|
||||||
|
connection.settimeout(10)
|
||||||
|
try:
|
||||||
|
connection.sendall(chunk)
|
||||||
|
res = connection.recv(5)
|
||||||
|
except:
|
||||||
|
sys.stderr.write('\n')
|
||||||
|
logging.error('Error Uploading')
|
||||||
|
connection.close()
|
||||||
|
f.close()
|
||||||
|
sock.close()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
sys.stderr.write('\n')
|
||||||
|
logging.info('Waiting for result...')
|
||||||
|
try:
|
||||||
|
connection.settimeout(60)
|
||||||
|
data = connection.recv(32).decode()
|
||||||
|
logging.info('Result: %s' ,data)
|
||||||
|
connection.close()
|
||||||
|
f.close()
|
||||||
|
sock.close()
|
||||||
|
if (data != "OK"):
|
||||||
|
sys.stderr.write('\n')
|
||||||
|
logging.error('%s', data)
|
||||||
|
return 1;
|
||||||
|
return 0
|
||||||
|
except:
|
||||||
|
logging.error('No Result!')
|
||||||
|
connection.close()
|
||||||
|
f.close()
|
||||||
|
sock.close()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
finally:
|
||||||
|
connection.close()
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
sock.close()
|
||||||
|
return 1
|
||||||
|
# end serve
|
||||||
|
|
||||||
|
|
||||||
|
def parser(unparsed_args):
|
||||||
|
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("-I", "--host_ip",
|
||||||
|
dest = "host_ip",
|
||||||
|
action = "store",
|
||||||
|
help = "Host IP Address.",
|
||||||
|
default = "0.0.0.0"
|
||||||
|
)
|
||||||
|
group.add_option("-p", "--port",
|
||||||
|
dest = "esp_port",
|
||||||
|
type = "int",
|
||||||
|
help = "ESP8266 ota Port. Default 8266",
|
||||||
|
default = 8266
|
||||||
|
)
|
||||||
|
group.add_option("-P", "--host_port",
|
||||||
|
dest = "host_port",
|
||||||
|
type = "int",
|
||||||
|
help = "Host server ota Port. Default random 10000-60000",
|
||||||
|
default = random.randint(10000,60000)
|
||||||
|
)
|
||||||
|
parser.add_option_group(group)
|
||||||
|
|
||||||
|
# auth
|
||||||
|
group = optparse.OptionGroup(parser, "Authentication")
|
||||||
|
group.add_option("-a", "--auth",
|
||||||
|
dest = "auth",
|
||||||
|
help = "Set authentication password.",
|
||||||
|
action = "store",
|
||||||
|
default = ""
|
||||||
|
)
|
||||||
|
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
|
||||||
|
)
|
||||||
|
group.add_option("-r", "--progress",
|
||||||
|
dest = "progress",
|
||||||
|
help = "Show progress output. Does not work for ArduinoIDE",
|
||||||
|
action = "store_true",
|
||||||
|
default = False
|
||||||
|
)
|
||||||
|
parser.add_option_group(group)
|
||||||
|
|
||||||
|
(options, args) = parser.parse_args(unparsed_args)
|
||||||
|
|
||||||
|
return options
|
||||||
|
# end parser
|
||||||
|
|
||||||
|
|
||||||
|
def main(args):
|
||||||
|
options = parser(args)
|
||||||
|
loglevel = logging.WARNING
|
||||||
|
if (options.debug):
|
||||||
|
loglevel = logging.DEBUG
|
||||||
|
|
||||||
|
logging.basicConfig(level = loglevel, format = '%(asctime)-8s [%(levelname)s]: %(message)s', datefmt = '%H:%M:%S')
|
||||||
|
logging.debug("Options: %s", str(options))
|
||||||
|
|
||||||
|
# check options
|
||||||
|
global PROGRESS
|
||||||
|
PROGRESS = options.progress
|
||||||
|
if (not options.esp_ip or not options.image):
|
||||||
|
logging.critical("Not enough arguments.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
command = FLASH
|
||||||
|
if (options.spiffs):
|
||||||
|
command = SPIFFS
|
||||||
|
|
||||||
|
return serve(options.esp_ip, options.host_ip, options.esp_port, options.host_port, options.auth, options.image, command)
|
||||||
|
# end main
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main(sys.argv))
|
Binary file not shown.
@ -51,6 +51,7 @@ env.Prepend(
|
|||||||
CPPPATH=[
|
CPPPATH=[
|
||||||
join(FRAMEWORK_DIR, "tools", "sdk", "include", "config"),
|
join(FRAMEWORK_DIR, "tools", "sdk", "include", "config"),
|
||||||
join(FRAMEWORK_DIR, "tools", "sdk", "include", "bluedroid"),
|
join(FRAMEWORK_DIR, "tools", "sdk", "include", "bluedroid"),
|
||||||
|
join(FRAMEWORK_DIR, "tools", "sdk", "include", "bootloader_support"),
|
||||||
join(FRAMEWORK_DIR, "tools", "sdk", "include", "bt"),
|
join(FRAMEWORK_DIR, "tools", "sdk", "include", "bt"),
|
||||||
join(FRAMEWORK_DIR, "tools", "sdk", "include", "driver"),
|
join(FRAMEWORK_DIR, "tools", "sdk", "include", "driver"),
|
||||||
join(FRAMEWORK_DIR, "tools", "sdk", "include", "esp32"),
|
join(FRAMEWORK_DIR, "tools", "sdk", "include", "esp32"),
|
||||||
|
56
tools/sdk/include/bootloader_support/esp_efuse.h
Normal file
56
tools/sdk/include/bootloader_support/esp_efuse.h
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
#ifndef _ESP_EFUSE_H
|
||||||
|
#define _ESP_EFUSE_H
|
||||||
|
|
||||||
|
#include "soc/efuse_reg.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* @brief Permanently update values written to the efuse write registers
|
||||||
|
*
|
||||||
|
* After updating EFUSE_BLKx_WDATAx_REG registers with new values to
|
||||||
|
* write, call this function to permanently write them to efuse.
|
||||||
|
*
|
||||||
|
* @note Setting bits in efuse is permanent, they cannot be unset.
|
||||||
|
*
|
||||||
|
* @note Due to this restriction you don't need to copy values to
|
||||||
|
* Efuse write registers from the matching read registers, bits which
|
||||||
|
* are set in the read register but unset in the matching write
|
||||||
|
* register will be unchanged when new values are burned.
|
||||||
|
*
|
||||||
|
* @note This function is not threadsafe, if calling code updates
|
||||||
|
* efuse values from multiple tasks then this is caller's
|
||||||
|
* responsibility to serialise.
|
||||||
|
*
|
||||||
|
* After burning new efuses, the read registers are updated to match
|
||||||
|
* the new efuse values.
|
||||||
|
*/
|
||||||
|
void esp_efuse_burn_new_values(void);
|
||||||
|
|
||||||
|
/* @brief Reset efuse write registers
|
||||||
|
*
|
||||||
|
* Efuse write registers are written to zero, to negate
|
||||||
|
* any changes that have been staged here.
|
||||||
|
*/
|
||||||
|
void esp_efuse_reset(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __ESP_EFUSE_H */
|
||||||
|
|
102
tools/sdk/include/bootloader_support/esp_flash_encrypt.h
Normal file
102
tools/sdk/include/bootloader_support/esp_flash_encrypt.h
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
#ifndef __ESP32_FLASH_ENCRYPT_H
|
||||||
|
#define __ESP32_FLASH_ENCRYPT_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "esp_attr.h"
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "esp_spi_flash.h"
|
||||||
|
#include "soc/efuse_reg.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file esp_partition.h
|
||||||
|
* @brief Support functions for flash encryption features
|
||||||
|
*
|
||||||
|
* Can be compiled as part of app or bootloader code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @brief Is flash encryption currently enabled in hardware?
|
||||||
|
*
|
||||||
|
* Flash encryption is enabled if the FLASH_CRYPT_CNT efuse has an odd number of bits set.
|
||||||
|
*
|
||||||
|
* @return true if flash encryption is enabled.
|
||||||
|
*/
|
||||||
|
static inline /** @cond */ IRAM_ATTR /** @endcond */ bool esp_flash_encryption_enabled(void) {
|
||||||
|
uint32_t flash_crypt_cnt = REG_GET_FIELD(EFUSE_BLK0_RDATA0_REG, EFUSE_RD_FLASH_CRYPT_CNT);
|
||||||
|
/* __builtin_parity is in flash, so we calculate parity inline */
|
||||||
|
bool enabled = false;
|
||||||
|
while(flash_crypt_cnt) {
|
||||||
|
if (flash_crypt_cnt & 1) {
|
||||||
|
enabled = !enabled;
|
||||||
|
}
|
||||||
|
flash_crypt_cnt >>= 1;
|
||||||
|
}
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @brief Update on-device flash encryption
|
||||||
|
*
|
||||||
|
* Intended to be called as part of the bootloader process if flash
|
||||||
|
* encryption is enabled in device menuconfig.
|
||||||
|
*
|
||||||
|
* If FLASH_CRYPT_CNT efuse parity is 1 (ie odd number of bits set),
|
||||||
|
* then return ESP_OK immediately (indicating flash encryption is enabled
|
||||||
|
* and functional).
|
||||||
|
*
|
||||||
|
* If FLASH_CRYPT_CNT efuse parity is 0 (ie even number of bits set),
|
||||||
|
* assume the flash has just been written with plaintext that needs encrypting.
|
||||||
|
*
|
||||||
|
* The following regions of flash are encrypted in place:
|
||||||
|
*
|
||||||
|
* - The bootloader image, if a valid plaintext image is found.[*]
|
||||||
|
* - The partition table, if a valid plaintext table is found.
|
||||||
|
* - Any app partition that contains a valid plaintext app image.
|
||||||
|
* - Any other partitions with the "encrypt" flag set. [**]
|
||||||
|
*
|
||||||
|
* After the re-encryption process completes, a '1' bit is added to the
|
||||||
|
* FLASH_CRYPT_CNT value (setting the parity to 1) and the EFUSE is re-burned.
|
||||||
|
*
|
||||||
|
* [*] If reflashing bootloader with secure boot enabled, pre-encrypt
|
||||||
|
* the bootloader before writing it to flash or secure boot will fail.
|
||||||
|
*
|
||||||
|
* [**] For this reason, if serial re-flashing a previous flashed
|
||||||
|
* device with secure boot enabled and using FLASH_CRYPT_CNT to
|
||||||
|
* trigger re-encryption, you must simultaneously re-flash plaintext
|
||||||
|
* content to all partitions with the "encrypt" flag set or this
|
||||||
|
* data will be corrupted (encrypted twice).
|
||||||
|
*
|
||||||
|
* @note The post-condition of this function is that all
|
||||||
|
* partitions that should be encrypted are encrypted.
|
||||||
|
*
|
||||||
|
* @note Take care not to power off the device while this function
|
||||||
|
* is running, or the partition currently being encrypted will be lost.
|
||||||
|
*
|
||||||
|
* @return ESP_OK if all operations succeeded, ESP_ERR_INVALID_STATE
|
||||||
|
* if a fatal error occured during encryption of all partitions.
|
||||||
|
*/
|
||||||
|
esp_err_t esp_flash_encrypt_check_and_update(void);
|
||||||
|
|
||||||
|
|
||||||
|
/** @brief Encrypt-in-place a block of flash sectors
|
||||||
|
*
|
||||||
|
* @param src_addr Source offset in flash. Should be multiple of 4096 bytes.
|
||||||
|
* @param data_length Length of data to encrypt in bytes. Will be rounded up to next multiple of 4096 bytes.
|
||||||
|
*
|
||||||
|
* @return ESP_OK if all operations succeeded, ESP_ERR_FLASH_OP_FAIL
|
||||||
|
* if SPI flash fails, ESP_ERR_FLASH_OP_TIMEOUT if flash times out.
|
||||||
|
*/
|
||||||
|
esp_err_t esp_flash_encrypt_region(uint32_t src_addr, size_t data_length);
|
||||||
|
|
||||||
|
#endif
|
39
tools/sdk/include/bootloader_support/esp_flash_partitions.h
Normal file
39
tools/sdk/include/bootloader_support/esp_flash_partitions.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
#ifndef __ESP_FLASH_PARTITIONS_H
|
||||||
|
#define __ESP_FLASH_PARTITIONS_H
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "esp_flash_data_types.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/* Pre-partition table fixed flash offsets */
|
||||||
|
#define ESP_BOOTLOADER_DIGEST_OFFSET 0x0
|
||||||
|
#define ESP_BOOTLOADER_OFFSET 0x1000 /* Offset of bootloader image. Has matching value in bootloader KConfig.projbuild file. */
|
||||||
|
#define ESP_PARTITION_TABLE_OFFSET 0x8000 /* Offset of partition table. Has matching value in partition_table Kconfig.projbuild file. */
|
||||||
|
|
||||||
|
#define ESP_PARTITION_TABLE_MAX_LEN 0xC00 /* Maximum length of partition table data */
|
||||||
|
#define ESP_PARTITION_TABLE_MAX_ENTRIES (ESP_PARTITION_TABLE_MAX_LEN / sizeof(esp_partition_info_t)) /* Maximum length of partition table data, including terminating entry */
|
||||||
|
|
||||||
|
/* @brief Verify the partition table (does not include verifying secure boot cryptographic signature)
|
||||||
|
*
|
||||||
|
* @param partition_table Pointer to at least ESP_PARTITION_TABLE_MAX_ENTRIES of potential partition table data. (ESP_PARTITION_TABLE_MAX_LEN bytes.)
|
||||||
|
* @param log_errors Log errors if the partition table is invalid.
|
||||||
|
* @param num_partitions If result is ESP_OK, num_partitions is updated with total number of partitions (not including terminating entry).
|
||||||
|
*
|
||||||
|
* @return ESP_OK on success, ESP_ERR_INVALID_STATE if partition table is not valid.
|
||||||
|
*/
|
||||||
|
esp_err_t esp_partition_table_basic_verify(const esp_partition_info_t *partition_table, bool log_errors, int *num_partitions);
|
||||||
|
|
||||||
|
#endif
|
143
tools/sdk/include/bootloader_support/esp_image_format.h
Normal file
143
tools/sdk/include/bootloader_support/esp_image_format.h
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
#ifndef __ESP32_IMAGE_FORMAT_H
|
||||||
|
#define __ESP32_IMAGE_FORMAT_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <esp_err.h>
|
||||||
|
|
||||||
|
#define ESP_ERR_IMAGE_BASE 0x2000
|
||||||
|
#define ESP_ERR_IMAGE_FLASH_FAIL (ESP_ERR_IMAGE_BASE + 1)
|
||||||
|
#define ESP_ERR_IMAGE_INVALID (ESP_ERR_IMAGE_BASE + 2)
|
||||||
|
|
||||||
|
/* Support for app/bootloader image parsing
|
||||||
|
Can be compiled as part of app or bootloader code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* SPI flash mode, used in esp_image_header_t */
|
||||||
|
typedef enum {
|
||||||
|
ESP_IMAGE_SPI_MODE_QIO,
|
||||||
|
ESP_IMAGE_SPI_MODE_QOUT,
|
||||||
|
ESP_IMAGE_SPI_MODE_DIO,
|
||||||
|
ESP_IMAGE_SPI_MODE_DOUT,
|
||||||
|
ESP_IMAGE_SPI_MODE_FAST_READ,
|
||||||
|
ESP_IMAGE_SPI_MODE_SLOW_READ
|
||||||
|
} esp_image_spi_mode_t;
|
||||||
|
|
||||||
|
/* SPI flash clock frequency */
|
||||||
|
enum {
|
||||||
|
ESP_IMAGE_SPI_SPEED_40M,
|
||||||
|
ESP_IMAGE_SPI_SPEED_26M,
|
||||||
|
ESP_IMAGE_SPI_SPEED_20M,
|
||||||
|
ESP_IMAGE_SPI_SPEED_80M = 0xF
|
||||||
|
} esp_image_spi_freq_t;
|
||||||
|
|
||||||
|
/* Supported SPI flash sizes */
|
||||||
|
typedef enum {
|
||||||
|
ESP_IMAGE_FLASH_SIZE_1MB = 0,
|
||||||
|
ESP_IMAGE_FLASH_SIZE_2MB,
|
||||||
|
ESP_IMAGE_FLASH_SIZE_4MB,
|
||||||
|
ESP_IMAGE_FLASH_SIZE_8MB,
|
||||||
|
ESP_IMAGE_FLASH_SIZE_16MB,
|
||||||
|
ESP_IMAGE_FLASH_SIZE_MAX
|
||||||
|
} esp_image_flash_size_t;
|
||||||
|
|
||||||
|
#define ESP_IMAGE_HEADER_MAGIC 0xE9
|
||||||
|
|
||||||
|
/* Main header of binary image */
|
||||||
|
typedef struct {
|
||||||
|
uint8_t magic;
|
||||||
|
uint8_t segment_count;
|
||||||
|
uint8_t spi_mode; /* flash read mode (esp_image_spi_mode_t as uint8_t) */
|
||||||
|
uint8_t spi_speed: 4; /* flash frequency (esp_image_spi_freq_t as uint8_t) */
|
||||||
|
uint8_t spi_size: 4; /* flash chip size (esp_image_flash_size_t as uint8_t) */
|
||||||
|
uint32_t entry_addr;
|
||||||
|
uint8_t encrypt_flag; /* encrypt flag */
|
||||||
|
uint8_t extra_header[15]; /* ESP32 additional header, unused by second bootloader */
|
||||||
|
} esp_image_header_t;
|
||||||
|
|
||||||
|
/* Header of binary image segment */
|
||||||
|
typedef struct {
|
||||||
|
uint32_t load_addr;
|
||||||
|
uint32_t data_len;
|
||||||
|
} esp_image_segment_header_t;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read an ESP image header from flash.
|
||||||
|
*
|
||||||
|
* If encryption is enabled, data will be transparently decrypted.
|
||||||
|
*
|
||||||
|
* @param src_addr Address in flash to load image header. Must be 4 byte aligned.
|
||||||
|
* @param log_errors Log error output if image header appears invalid.
|
||||||
|
* @param[out] image_header Pointer to an esp_image_header_t struture to be filled with data. If the function fails, contents are undefined.
|
||||||
|
*
|
||||||
|
* @return ESP_OK if image header was loaded, ESP_ERR_IMAGE_FLASH_FAIL
|
||||||
|
* if a SPI flash error occurs, ESP_ERR_IMAGE_INVALID if the image header
|
||||||
|
* appears invalid.
|
||||||
|
*/
|
||||||
|
esp_err_t esp_image_load_header(uint32_t src_addr, bool log_errors, esp_image_header_t *image_header);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read the segment header and data offset of a segment in the image.
|
||||||
|
*
|
||||||
|
* If encryption is enabled, data will be transparently decrypted.
|
||||||
|
*
|
||||||
|
* @param index Index of the segment to load information for.
|
||||||
|
* @param src_addr Base address in flash of the image.
|
||||||
|
* @param[in] image_header Pointer to the flash image header, already loaded by @ref esp_image_load_header().
|
||||||
|
* @param log_errors Log errors reading the segment header.
|
||||||
|
* @param[out] segment_header Pointer to a segment header structure to be filled with data. If the function fails, contents are undefined.
|
||||||
|
* @param[out] segment_data_offset Pointer to the data offset of the segment.
|
||||||
|
*
|
||||||
|
* @return ESP_OK if segment_header & segment_data_offset were loaded successfully, ESP_ERR_IMAGE_FLASH_FAIL if a SPI flash error occurs, ESP_ERR_IMAGE_INVALID if the image header appears invalid, ESP_ERR_INVALID_ARG if the index is invalid.
|
||||||
|
*/
|
||||||
|
esp_err_t esp_image_load_segment_header(uint8_t index, uint32_t src_addr, const esp_image_header_t *image_header, bool log_errors, esp_image_segment_header_t *segment_header, uint32_t *segment_data_offset);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Non-cryptographically validate app image integrity. On success, length of image is provided to caller.
|
||||||
|
*
|
||||||
|
* If the image has a secure boot signature appended, the signature is not checked and this length is not included in the
|
||||||
|
* output value.
|
||||||
|
*
|
||||||
|
* Image validation checks:
|
||||||
|
* - Magic byte
|
||||||
|
* - No single segment longer than 16MB
|
||||||
|
* - Total image no longer than 16MB
|
||||||
|
* - 8 bit image checksum is valid
|
||||||
|
*
|
||||||
|
* If flash encryption is enabled, the image will be tranpsarently decrypted.
|
||||||
|
*
|
||||||
|
* @param src_addr Offset of the start of the image in flash. Must be 4 byte aligned.
|
||||||
|
* @param allow_decrypt If true and flash encryption is enabled, the image will be transparently decrypted.
|
||||||
|
* @param log_errors Log errors verifying the image.
|
||||||
|
* @param[out] length Length of the image, set to a value if the image is valid. Can be null.
|
||||||
|
*
|
||||||
|
* @return ESP_OK if image is valid, ESP_FAIL or ESP_ERR_IMAGE_INVALID on errors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
esp_err_t esp_image_basic_verify(uint32_t src_addr, bool log_errors, uint32_t *length);
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t drom_addr;
|
||||||
|
uint32_t drom_load_addr;
|
||||||
|
uint32_t drom_size;
|
||||||
|
uint32_t irom_addr;
|
||||||
|
uint32_t irom_load_addr;
|
||||||
|
uint32_t irom_size;
|
||||||
|
} esp_image_flash_mapping_t;
|
||||||
|
|
||||||
|
#endif
|
91
tools/sdk/include/bootloader_support/esp_secure_boot.h
Normal file
91
tools/sdk/include/bootloader_support/esp_secure_boot.h
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
#ifndef __ESP32_SECUREBOOT_H
|
||||||
|
#define __ESP32_SECUREBOOT_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <esp_err.h>
|
||||||
|
#include "soc/efuse_reg.h"
|
||||||
|
|
||||||
|
/* Support functions for secure boot features.
|
||||||
|
|
||||||
|
Can be compiled as part of app or bootloader code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @brief Is secure boot currently enabled in hardware?
|
||||||
|
*
|
||||||
|
* Secure boot is enabled if the ABS_DONE_0 efuse is blown. This means
|
||||||
|
* that the ROM bootloader code will only boot a verified secure
|
||||||
|
* bootloader digest from now on.
|
||||||
|
*
|
||||||
|
* @return true if secure boot is enabled.
|
||||||
|
*/
|
||||||
|
static inline bool esp_secure_boot_enabled(void) {
|
||||||
|
return REG_READ(EFUSE_BLK0_RDATA6_REG) & EFUSE_RD_ABS_DONE_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** @brief Enable secure boot if it is not already enabled.
|
||||||
|
*
|
||||||
|
* @important If this function succeeds, secure boot is permanently
|
||||||
|
* enabled on the chip via efuse.
|
||||||
|
*
|
||||||
|
* @important This function is intended to be called from bootloader code only.
|
||||||
|
*
|
||||||
|
* If secure boot is not yet enabled for bootloader, this will
|
||||||
|
* generate the secure boot digest and enable secure boot by blowing
|
||||||
|
* the EFUSE_RD_ABS_DONE_0 efuse.
|
||||||
|
*
|
||||||
|
* This function does not verify secure boot of the bootloader (the
|
||||||
|
* ROM bootloader does this.)
|
||||||
|
*
|
||||||
|
* Will fail if efuses have been part-burned in a way that indicates
|
||||||
|
* secure boot should not or could not be correctly enabled.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return ESP_ERR_INVALID_STATE if efuse state doesn't allow
|
||||||
|
* secure boot to be enabled cleanly. ESP_OK if secure boot
|
||||||
|
* is enabled on this chip from now on.
|
||||||
|
*/
|
||||||
|
esp_err_t esp_secure_boot_permanently_enable(void);
|
||||||
|
|
||||||
|
/** @brief Verify the secure boot signature (determinstic ECDSA w/ SHA256) appended to some binary data in flash.
|
||||||
|
*
|
||||||
|
* Public key is compiled into the calling program. See docs/security/secure-boot.rst for details.
|
||||||
|
*
|
||||||
|
* @param src_addr Starting offset of the data in flash.
|
||||||
|
* @param length Length of data in bytes. Signature is appended -after- length bytes.
|
||||||
|
*
|
||||||
|
* If flash encryption is enabled, the image will be transparently decrypted while being verified.
|
||||||
|
*
|
||||||
|
* @return ESP_OK if signature is valid, ESP_ERR_INVALID_STATE if
|
||||||
|
* signature fails, ESP_FAIL for other failures (ie can't read flash).
|
||||||
|
*/
|
||||||
|
esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length);
|
||||||
|
|
||||||
|
/** @brief Secure boot verification block, on-flash data format. */
|
||||||
|
typedef struct {
|
||||||
|
uint32_t version;
|
||||||
|
uint8_t signature[64];
|
||||||
|
} esp_secure_boot_sig_block_t;
|
||||||
|
|
||||||
|
#define FLASH_OFFS_SECURE_BOOT_IV_DIGEST 0
|
||||||
|
|
||||||
|
/** @brief Secure boot IV+digest header */
|
||||||
|
typedef struct {
|
||||||
|
uint8_t iv[128];
|
||||||
|
uint8_t digest[64];
|
||||||
|
} esp_secure_boot_iv_digest_t;
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue
Block a user