forked from espressif/arduino-esp32
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:
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;
|
Reference in New Issue
Block a user