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 "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){
|
||||
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);
|
||||
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 "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){
|
||||
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);
|
||||
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.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.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.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.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.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.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=[
|
||||
join(FRAMEWORK_DIR, "tools", "sdk", "include", "config"),
|
||||
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", "driver"),
|
||||
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