Compare commits
@@ -20,7 +20,7 @@ jobs:
|
||||
- name: Run codespell
|
||||
uses: codespell-project/actions-codespell@master
|
||||
with:
|
||||
skip: ./ArduinoCore-API,./libraries/ESP8266SdFat,./libraries/Adafruit_TinyUSB_Arduino,./libraries/LittleFS/lib,./tools/pyserial,./pico-sdk,./.github,./docs/i2s.rst,./cores/rp2040/api,./libraries/FreeRTOS,./tools/libbearssl/bearssl,./include,./libraries/WiFi/examples/BearSSL_Server
|
||||
skip: ./ArduinoCore-API,./libraries/ESP8266SdFat,./libraries/Adafruit_TinyUSB_Arduino,./libraries/LittleFS/lib,./tools/pyserial,./pico-sdk,./.github,./docs/i2s.rst,./cores/rp2040/api,./libraries/FreeRTOS,./tools/libbearssl/bearssl,./include,./libraries/WiFi/examples/BearSSL_Server,./ota/uzlib
|
||||
ignore_words_list: ser,dout
|
||||
|
||||
# Consistent style
|
||||
@@ -185,7 +185,14 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
submodules: 'true'
|
||||
- name: Initialize needed submodules
|
||||
run: |
|
||||
cd pico-sdk
|
||||
git submodule update --init
|
||||
cd ../libraries/Adafruit_TinyUSB_Arduino
|
||||
git submodule update --init
|
||||
cd ../..
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
@@ -212,3 +219,7 @@ jobs:
|
||||
run: pio ci --board=rpipico --board=adafruit_feather -O "platform_packages=framework-arduinopico@symlink:///home/runner/work/arduino-pico/arduino-pico" libraries/rp2040/examples/Fade/Fade.ino
|
||||
- name: Build TinyUSB Example
|
||||
run: pio ci --board=rpipico --board=adafruit_feather -O "platform_packages=framework-arduinopico@symlink:///home/runner/work/arduino-pico/arduino-pico" -O "build_flags=-DUSE_TINYUSB" libraries/Adafruit_TinyUSB_Arduino/examples/CDC/cdc_multi/cdc_multi.ino
|
||||
- name: Build WiFi Example
|
||||
run: pio ci --board=rpipicow -O "platform_packages=framework-arduinopico@symlink:///home/runner/work/arduino-pico/arduino-pico" libraries/WiFi/examples/ScanNetworks/ScanNetworks.ino
|
||||
- name: Build Signed OTA Example
|
||||
run: pio ci --board=rpipicow -O "platform_packages=framework-arduinopico@symlink:///home/runner/work/arduino-pico/arduino-pico" libraries/ArduinoOTA/examples/SignedOTA/SignedOTA.ino
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
.DS_Store
|
||||
system
|
||||
tools/dist
|
||||
docs/_build
|
||||
ota/build
|
||||
tools/libpico/build
|
||||
+7
-1
@@ -3,7 +3,7 @@
|
||||
url = https://github.com/earlephilhower/ArduinoCore-API.git
|
||||
[submodule "pico-sdk"]
|
||||
path = pico-sdk
|
||||
url = https://github.com/raspberrypi/pico-sdk.git
|
||||
url = https://github.com/earlephilhower/pico-sdk.git
|
||||
[submodule "system/pyserial"]
|
||||
path = tools/pyserial
|
||||
url = https://github.com/pyserial/pyserial.git
|
||||
@@ -19,6 +19,9 @@
|
||||
[submodule "libraries/Mouse"]
|
||||
path = libraries/Mouse
|
||||
url = https://github.com/earlephilhower/Mouse
|
||||
[submodule "libraries/Joystick"]
|
||||
path = libraries/Joystick
|
||||
url = https://github.com/benjaminaigner/Joystick
|
||||
[submodule "libraries/Adafruit_TinyUSB_Arduino"]
|
||||
path = libraries/Adafruit_TinyUSB_Arduino
|
||||
url = https://github.com/adafruit/Adafruit_TinyUSB_Arduino.git
|
||||
@@ -28,3 +31,6 @@
|
||||
[submodule "tools/libbearssl/bearssl"]
|
||||
path = tools/libbearssl/bearssl
|
||||
url = https://github.com/earlephilhower/bearssl-esp8266.git
|
||||
[submodule "ota/uzlib"]
|
||||
path = ota/uzlib
|
||||
url = https://github.com/pfalcon/uzlib.git
|
||||
|
||||
@@ -24,6 +24,7 @@ See https://arduino-pico.readthedocs.io/en/latest/ along with the examples for m
|
||||
* Cytron Maker Nano RP2040
|
||||
* DeRuiLab FlyBoard2040 Core
|
||||
* DFRobot Beetle RP2040
|
||||
* ElectronicCats Hunter Cat NFC
|
||||
* Invector Labs Challenger RP2040 WiFi
|
||||
* Invector Labs Challenger RP2040 WiFi/BLE
|
||||
* Invector Labs Challenger NB RP2040 WiFi
|
||||
@@ -145,6 +146,8 @@ The installed tools include a version of OpenOCD (in the pqt-openocd directory)
|
||||
# Features
|
||||
* Adafruit TinyUSB Arduino (USB mouse, keyboard, flash drive, generic HID, CDC Serial, MIDI, WebUSB, others)
|
||||
* Generic Arduino USB Serial, Keyboard, and Mouse emulation
|
||||
* WiFi (Pico W)
|
||||
* Over-the-Air (OTA) upgrades
|
||||
* Filesystems (LittleFS and SD/SDFS)
|
||||
* Multicore support (setup1() and loop1())
|
||||
* FreeRTOS SMP support
|
||||
@@ -182,6 +185,9 @@ If you want to contribute or have bugfixes, drop me a note at <earlephilhower@ya
|
||||
* DHCP server for AP host mode from the [Micropython Project](https://micropython.org), distributed under the MIT License.
|
||||
* [FreeRTOS](https://freertos.org) is Copyright Amazon.com, Inc. or its affiliates, and distributed under the MIT license.
|
||||
* [lwIP](https://savannah.nongnu.org/projects/lwip/) is (c) the Swedish Institute of Computer Science and licenced under the BSD license.
|
||||
* [BearSSL](https://bearssl.org) library written by Thomas Pornin, is distributed under the [MIT License](https://bearssl.org/#legal-details).
|
||||
* [UZLib](https://github.com/pfalcon/uzlib) is copyright (c) 2003 Joergen Ibsen and distributed under the zlib license.
|
||||
* [LEAmDMS](https://github.com/LaborEtArs/ESP8266mDNS) is copyright multiple authors and distributed under the MIT license.
|
||||
|
||||
-Earle F. Philhower, III
|
||||
-Earle F. Philhower, III
|
||||
earlephilhower@yahoo.com
|
||||
|
||||
+2622
-1597
File diff suppressed because it is too large
Load Diff
+1
-2
@@ -90,9 +90,8 @@ public:
|
||||
uint8_t obuf[256];
|
||||
size_t doneLen = 0;
|
||||
size_t sentLen;
|
||||
int i;
|
||||
|
||||
while (src.available() > sizeof(obuf)) {
|
||||
while ((size_t)src.available() > sizeof(obuf)) {
|
||||
src.read(obuf, sizeof(obuf));
|
||||
sentLen = write(obuf, sizeof(obuf));
|
||||
doneLen = doneLen + sentLen;
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
#include <hardware/clocks.h>
|
||||
#include <hardware/irq.h>
|
||||
#include <hardware/pio.h>
|
||||
#include <pico/unique_id.h>
|
||||
#include <hardware/exception.h>
|
||||
#include <hardware/watchdog.h>
|
||||
#include <hardware/structs/rosc.h>
|
||||
#include <hardware/structs/systick.h>
|
||||
#include <pico/multicore.h>
|
||||
@@ -32,6 +34,18 @@
|
||||
|
||||
extern "C" volatile bool __otherCoreIdled;
|
||||
|
||||
// Halt the FreeRTOS PendSV task switching magic
|
||||
extern "C" int __holdUpPendSV;
|
||||
|
||||
// FreeRTOS weak functions, to be overridden when we really are running FreeRTOS
|
||||
extern "C" {
|
||||
extern void vTaskSuspendAll() __attribute__((weak));
|
||||
extern int32_t xTaskResumeAll() __attribute__((weak));
|
||||
typedef struct tskTaskControlBlock * TaskHandle_t;
|
||||
extern void vTaskPreemptionDisable(TaskHandle_t p) __attribute__((weak));
|
||||
extern void vTaskPreemptionEnable(TaskHandle_t p) __attribute__((weak));
|
||||
}
|
||||
|
||||
class _MFIFO {
|
||||
public:
|
||||
_MFIFO() { /* noop */ };
|
||||
@@ -86,6 +100,11 @@ public:
|
||||
if (!_multicore) {
|
||||
return;
|
||||
}
|
||||
__holdUpPendSV = 1;
|
||||
if (__isFreeRTOS) {
|
||||
vTaskPreemptionDisable(nullptr);
|
||||
vTaskSuspendAll();
|
||||
}
|
||||
mutex_enter_blocking(&_idleMutex);
|
||||
__otherCoreIdled = false;
|
||||
multicore_fifo_push_blocking(_GOTOSLEEP);
|
||||
@@ -98,6 +117,12 @@ public:
|
||||
}
|
||||
mutex_exit(&_idleMutex);
|
||||
__otherCoreIdled = false;
|
||||
if (__isFreeRTOS) {
|
||||
xTaskResumeAll();
|
||||
vTaskPreemptionEnable(nullptr);
|
||||
}
|
||||
__holdUpPendSV = 0;
|
||||
|
||||
// Other core will exit busy-loop and return to operation
|
||||
// once __otherCoreIdled == false.
|
||||
}
|
||||
@@ -288,6 +313,22 @@ public:
|
||||
multicore_launch_core1(main1);
|
||||
}
|
||||
|
||||
void reboot() {
|
||||
watchdog_reboot(0, 0, 100);
|
||||
}
|
||||
|
||||
inline void restart() {
|
||||
reboot();
|
||||
}
|
||||
|
||||
const char *getChipID() {
|
||||
static char id[PICO_UNIQUE_BOARD_ID_SIZE_BYTES + 1] = { 0 };
|
||||
if (!id[0]) {
|
||||
pico_get_unique_board_id_string(id, sizeof(id));
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
// Multicore comms FIFO
|
||||
_MFIFO fifo;
|
||||
|
||||
|
||||
+76
-31
@@ -91,7 +91,7 @@ const uint8_t *tud_descriptor_device_cb(void) {
|
||||
.iSerialNumber = USBD_STR_SERIAL,
|
||||
.bNumConfigurations = 1
|
||||
};
|
||||
if (__USBInstallSerial && !__USBInstallKeyboard && !__USBInstallMouse && !__USBInstallMIDI) {
|
||||
if (__USBInstallSerial && !__USBInstallKeyboard && !__USBInstallMouse && !__USBInstallJoystick && !__USBInstallMIDI) {
|
||||
// Can use as-is, this is the default USB case
|
||||
return (const uint8_t *)&usbd_desc_device;
|
||||
}
|
||||
@@ -102,6 +102,9 @@ const uint8_t *tud_descriptor_device_cb(void) {
|
||||
if (__USBInstallMouse) {
|
||||
usbd_desc_device.idProduct |= 0x4000;
|
||||
}
|
||||
if (__USBInstallJoystick) {
|
||||
usbd_desc_device.idProduct |= 0x0100;
|
||||
}
|
||||
if (__USBInstallMIDI) {
|
||||
usbd_desc_device.idProduct |= 0x2000;
|
||||
}
|
||||
@@ -120,6 +123,17 @@ int __USBGetMouseReportID() {
|
||||
return __USBInstallKeyboard ? 2 : 1;
|
||||
}
|
||||
|
||||
int __USBGetJoystickReportID() {
|
||||
int i = 1;
|
||||
if (__USBInstallKeyboard) {
|
||||
i++;
|
||||
}
|
||||
if (__USBInstallMouse) {
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static int __hid_report_len = 0;
|
||||
static uint8_t *__hid_report = nullptr;
|
||||
|
||||
@@ -131,37 +145,68 @@ static uint8_t *GetDescHIDReport(int *len) {
|
||||
}
|
||||
|
||||
void __SetupDescHIDReport() {
|
||||
if (__USBInstallKeyboard && __USBInstallMouse) {
|
||||
uint8_t desc_hid_report[] = {
|
||||
TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(1)),
|
||||
TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(2))
|
||||
};
|
||||
__hid_report = (uint8_t *)malloc(sizeof(desc_hid_report));
|
||||
if (__hid_report) {
|
||||
__hid_report_len = sizeof(desc_hid_report);
|
||||
memcpy(__hid_report, desc_hid_report, __hid_report_len);
|
||||
}
|
||||
} else if (__USBInstallKeyboard && ! __USBInstallMouse) {
|
||||
uint8_t desc_hid_report[] = {
|
||||
TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(1))
|
||||
};
|
||||
__hid_report = (uint8_t *)malloc(sizeof(desc_hid_report));
|
||||
if (__hid_report) {
|
||||
__hid_report_len = sizeof(desc_hid_report);
|
||||
memcpy(__hid_report, desc_hid_report, __hid_report_len);
|
||||
}
|
||||
} else if (! __USBInstallKeyboard && __USBInstallMouse) {
|
||||
uint8_t desc_hid_report[] = {
|
||||
TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(1))
|
||||
};
|
||||
__hid_report = (uint8_t *)malloc(sizeof(desc_hid_report));
|
||||
if (__hid_report) {
|
||||
__hid_report_len = sizeof(desc_hid_report);
|
||||
memcpy(__hid_report, desc_hid_report, __hid_report_len);
|
||||
}
|
||||
} else {
|
||||
//allocate memory for the HID report descriptors. We don't use them, but need the size here.
|
||||
uint8_t desc_hid_report_mouse[] = { TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(1)) };
|
||||
uint8_t desc_hid_report_joystick[] = { TUD_HID_REPORT_DESC_GAMEPAD(HID_REPORT_ID(1)) };
|
||||
uint8_t desc_hid_report_keyboard[] = { TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(1)) };
|
||||
int size = 0;
|
||||
|
||||
//accumulate the size of all used HID report descriptors
|
||||
if (__USBInstallKeyboard) {
|
||||
size += sizeof(desc_hid_report_keyboard);
|
||||
}
|
||||
if (__USBInstallMouse) {
|
||||
size += sizeof(desc_hid_report_mouse);
|
||||
}
|
||||
if (__USBInstallJoystick) {
|
||||
size += sizeof(desc_hid_report_joystick);
|
||||
}
|
||||
|
||||
//no HID used at all
|
||||
if (size == 0) {
|
||||
__hid_report = nullptr;
|
||||
__hid_report_len = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
//allocate the "real" HID report descriptor
|
||||
__hid_report = (uint8_t *)malloc(size);
|
||||
if (__hid_report) {
|
||||
__hid_report_len = size;
|
||||
|
||||
//now copy the descriptors
|
||||
|
||||
//1.) keyboard descriptor, if requested
|
||||
if (__USBInstallKeyboard) {
|
||||
memcpy(__hid_report, desc_hid_report_keyboard, sizeof(desc_hid_report_keyboard));
|
||||
}
|
||||
|
||||
//2.) mouse descriptor, if necessary. Additional offset & new array is necessary if there is a keyboard.
|
||||
if (__USBInstallMouse) {
|
||||
//determine if we need an offset (USB keyboard is installed)
|
||||
if (__USBInstallKeyboard) {
|
||||
uint8_t desc_local[] = { TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(2)) };
|
||||
memcpy(__hid_report + sizeof(desc_hid_report_keyboard), desc_local, sizeof(desc_local));
|
||||
} else {
|
||||
memcpy(__hid_report, desc_hid_report_mouse, sizeof(desc_hid_report_mouse));
|
||||
}
|
||||
}
|
||||
|
||||
//3.) joystick descriptor. 2 additional checks are necessary for mouse and/or keyboard
|
||||
if (__USBInstallJoystick) {
|
||||
uint8_t reportid = 1;
|
||||
int offset = 0;
|
||||
if (__USBInstallKeyboard) {
|
||||
reportid++;
|
||||
offset += sizeof(desc_hid_report_keyboard);
|
||||
}
|
||||
if (__USBInstallMouse) {
|
||||
reportid++;
|
||||
offset += sizeof(desc_hid_report_mouse);
|
||||
}
|
||||
uint8_t desc_local[] = { TUD_HID_REPORT_DESC_GAMEPAD(HID_REPORT_ID(reportid)) };
|
||||
memcpy(__hid_report + offset, desc_local, sizeof(desc_local));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +226,7 @@ const uint8_t *tud_descriptor_configuration_cb(uint8_t index) {
|
||||
|
||||
void __SetupUSBDescriptor() {
|
||||
if (!usbd_desc_cfg) {
|
||||
bool hasHID = __USBInstallKeyboard || __USBInstallMouse;
|
||||
bool hasHID = __USBInstallKeyboard || __USBInstallMouse || __USBInstallJoystick;
|
||||
|
||||
uint8_t interface_count = (__USBInstallSerial ? 2 : 0) + (hasHID ? 1 : 0) + (__USBInstallMIDI ? 2 : 0);
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
// Weak function definitions for each type of endpoint
|
||||
extern void __USBInstallSerial() __attribute__((weak));
|
||||
extern void __USBInstallKeyboard() __attribute__((weak));
|
||||
extern void __USBInstallJoystick() __attribute__((weak));
|
||||
extern void __USBInstallMouse() __attribute__((weak));
|
||||
extern void __USBInstallMIDI() __attribute__((weak));
|
||||
|
||||
@@ -34,6 +35,7 @@ extern mutex_t __usb_mutex;
|
||||
// HID report ID inquiry (report ID will vary depending on the number/type of other HID)
|
||||
int __USBGetKeyboardReportID();
|
||||
int __USBGetMouseReportID();
|
||||
int __USBGetJoystickReportID();
|
||||
|
||||
// Called by main() to init the USB HW/SW.
|
||||
void __USBStart();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#pragma once
|
||||
#define ARDUINO_PICO_MAJOR 2
|
||||
#define ARDUINO_PICO_MINOR 3
|
||||
#define ARDUINO_PICO_REVISION 2
|
||||
#define ARDUINO_PICO_VERSION_STR "2.3.2"
|
||||
#define ARDUINO_PICO_MINOR 4
|
||||
#define ARDUINO_PICO_REVISION 0
|
||||
#define ARDUINO_PICO_VERSION_STR "2.4.0"
|
||||
|
||||
@@ -292,7 +292,7 @@ int SerialUART::available() {
|
||||
} else {
|
||||
_pumpFIFO();
|
||||
}
|
||||
return (_writer - _reader) % _fifoSize;
|
||||
return (_fifoSize + _writer - _reader) % _fifoSize;
|
||||
}
|
||||
|
||||
int SerialUART::availableForWrite() {
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
StreamString.h
|
||||
|
||||
Copyright (c) 2020 D. Gauchard. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
*/
|
||||
|
||||
#ifndef __STREAMSTRING_H
|
||||
#define __STREAMSTRING_H
|
||||
|
||||
#include <limits>
|
||||
#include <algorithm>
|
||||
#include "Stream.h"
|
||||
#include "api/String.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// S2Stream points to a String and makes it a Stream
|
||||
// (it is also the helper for StreamString)
|
||||
|
||||
class S2Stream: public Stream {
|
||||
public:
|
||||
S2Stream(String& string, int peekPointer = -1) : string(&string), peekPointer(peekPointer) { }
|
||||
|
||||
S2Stream(String* string, int peekPointer = -1) : string(string), peekPointer(peekPointer) { }
|
||||
|
||||
virtual int available() override {
|
||||
return string->length();
|
||||
}
|
||||
|
||||
virtual int availableForWrite() override {
|
||||
return std::numeric_limits<int16_t>::max();
|
||||
}
|
||||
|
||||
virtual int read() override {
|
||||
if (peekPointer < 0) {
|
||||
// consume chars
|
||||
if (string->length()) {
|
||||
char c = string->charAt(0);
|
||||
string->remove(0, 1);
|
||||
return c;
|
||||
}
|
||||
} else if (peekPointer < (int)string->length()) {
|
||||
// return pointed and move pointer
|
||||
return string->charAt(peekPointer++);
|
||||
}
|
||||
|
||||
// everything is read
|
||||
return -1;
|
||||
}
|
||||
|
||||
virtual size_t write(uint8_t data) override {
|
||||
return string->concat((char)data);
|
||||
}
|
||||
|
||||
// virtual int read(uint8_t* buffer, size_t len) override
|
||||
// {
|
||||
// if (peekPointer < 0)
|
||||
// {
|
||||
// // string will be consumed
|
||||
// size_t l = std::min(len, (size_t)string->length());
|
||||
// memcpy(buffer, string->c_str(), l);
|
||||
// string->remove(0, l);
|
||||
// return l;
|
||||
// }
|
||||
//
|
||||
// if (peekPointer >= (int)string->length())
|
||||
// {
|
||||
// return 0;
|
||||
// }
|
||||
//
|
||||
// // only the pointer is moved
|
||||
// size_t l = std::min(len, (size_t)(string->length() - peekPointer));
|
||||
// memcpy(buffer, string->c_str() + peekPointer, l);
|
||||
// peekPointer += l;
|
||||
// return l;
|
||||
// }
|
||||
|
||||
virtual size_t write(const uint8_t* buffer, size_t len) override {
|
||||
return string->concat((const char*)buffer, len) ? len : 0;
|
||||
}
|
||||
|
||||
virtual int peek() override {
|
||||
if (peekPointer < 0) {
|
||||
if (string->length()) {
|
||||
return string->charAt(0);
|
||||
}
|
||||
} else if (peekPointer < (int)string->length()) {
|
||||
return string->charAt(peekPointer);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
virtual void flush() override {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
#if 0
|
||||
virtual bool inputCanTimeout() override {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool outputCanTimeout() override {
|
||||
return false;
|
||||
}
|
||||
|
||||
//// Stream's peekBufferAPI
|
||||
|
||||
virtual bool hasPeekBufferAPI() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual size_t peekAvailable() {
|
||||
if (peekPointer < 0) {
|
||||
return string->length();
|
||||
}
|
||||
return string->length() - peekPointer;
|
||||
}
|
||||
|
||||
virtual const char* peekBuffer() override {
|
||||
if (peekPointer < 0) {
|
||||
return string->c_str();
|
||||
}
|
||||
if (peekPointer < (int)string->length()) {
|
||||
return string->c_str() + peekPointer;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual void peekConsume(size_t consume) override {
|
||||
if (peekPointer < 0) {
|
||||
// string is really consumed
|
||||
string->remove(0, consume);
|
||||
} else {
|
||||
// only the pointer is moved
|
||||
peekPointer = std::min((size_t)string->length(), peekPointer + consume);
|
||||
}
|
||||
}
|
||||
|
||||
virtual ssize_t streamRemaining() override {
|
||||
return peekPointer < 0 ? string->length() : string->length() - peekPointer;
|
||||
}
|
||||
|
||||
// calling setConsume() will consume bytes as the stream is read
|
||||
// (enabled by default)
|
||||
void setConsume() {
|
||||
peekPointer = -1;
|
||||
}
|
||||
#endif
|
||||
// Reading this stream will mark the string as read without consuming
|
||||
// (not enabled by default)
|
||||
// Calling resetPointer() resets the read state and allows rereading.
|
||||
void resetPointer(int pointer = 0) {
|
||||
peekPointer = pointer;
|
||||
}
|
||||
|
||||
protected:
|
||||
String* string;
|
||||
int peekPointer; // -1:String is consumed / >=0:resettable pointer
|
||||
};
|
||||
|
||||
// StreamString is a S2Stream holding the String
|
||||
|
||||
class StreamString: public String, public S2Stream {
|
||||
protected:
|
||||
void resetpp() {
|
||||
if (peekPointer > 0) {
|
||||
peekPointer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
StreamString(StreamString&& bro) : String(bro), S2Stream(this) { }
|
||||
StreamString(const StreamString& bro) : String(bro), S2Stream(this) { }
|
||||
|
||||
// duplicate String constructors and operator=:
|
||||
|
||||
StreamString(const char* text = nullptr) : String(text), S2Stream(this) { }
|
||||
StreamString(const String& string) : String(string), S2Stream(this) { }
|
||||
StreamString(const __FlashStringHelper* str) : String(str), S2Stream(this) { }
|
||||
StreamString(String&& string) : String(string), S2Stream(this) { }
|
||||
|
||||
explicit StreamString(char c) : String(c), S2Stream(this) { }
|
||||
explicit StreamString(unsigned char c, unsigned char base = 10) :
|
||||
String(c, base), S2Stream(this) {
|
||||
}
|
||||
explicit StreamString(int i, unsigned char base = 10) : String(i, base), S2Stream(this) { }
|
||||
explicit StreamString(unsigned int i, unsigned char base = 10) : String(i, base), S2Stream(this) {
|
||||
}
|
||||
explicit StreamString(long l, unsigned char base = 10) : String(l, base), S2Stream(this) { }
|
||||
explicit StreamString(unsigned long l, unsigned char base = 10) :
|
||||
String(l, base), S2Stream(this) {
|
||||
}
|
||||
explicit StreamString(float f, unsigned char decimalPlaces = 2) :
|
||||
String(f, decimalPlaces), S2Stream(this) {
|
||||
}
|
||||
explicit StreamString(double d, unsigned char decimalPlaces = 2) :
|
||||
String(d, decimalPlaces), S2Stream(this) {
|
||||
}
|
||||
|
||||
StreamString& operator=(const StreamString& rhs) {
|
||||
String::operator=(rhs);
|
||||
resetpp();
|
||||
return *this;
|
||||
}
|
||||
|
||||
StreamString& operator=(const String& rhs) {
|
||||
String::operator=(rhs);
|
||||
resetpp();
|
||||
return *this;
|
||||
}
|
||||
|
||||
StreamString& operator=(const char* cstr) {
|
||||
String::operator=(cstr);
|
||||
resetpp();
|
||||
return *this;
|
||||
}
|
||||
|
||||
StreamString& operator=(const __FlashStringHelper* str) {
|
||||
String::operator=(str);
|
||||
resetpp();
|
||||
return *this;
|
||||
}
|
||||
|
||||
StreamString& operator=(String&& rval) {
|
||||
String::operator=(rval);
|
||||
resetpp();
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // __STREAMSTRING_H
|
||||
@@ -179,8 +179,10 @@ bool IPAddress::isValid(const char* arg) {
|
||||
return IPAddress().fromString(arg);
|
||||
}
|
||||
|
||||
namespace arduino {
|
||||
const IPAddress INADDR_ANY; // generic "0.0.0.0" for IPv4 & IPv6
|
||||
const IPAddress INADDR_NONE(255,255,255,255);
|
||||
};
|
||||
|
||||
void IPAddress::clear() {
|
||||
(*this) = INADDR_ANY;
|
||||
|
||||
@@ -27,11 +27,14 @@
|
||||
#include <lwip/ip_addr.h>
|
||||
#include <lwip/ip4_addr.h>
|
||||
|
||||
namespace arduino {
|
||||
|
||||
#if !LWIP_IPV6
|
||||
struct ip_addr: ipv4_addr { };
|
||||
#endif // !LWIP_IPV6
|
||||
// forward declarations of global name space friend classes
|
||||
class EthernetClass;
|
||||
class DhcpClass;
|
||||
class DNSClient;
|
||||
|
||||
|
||||
namespace arduino {
|
||||
|
||||
// to display a netif id with printf:
|
||||
#define NETIFID_STR "%c%c%u"
|
||||
@@ -48,8 +51,19 @@ struct ip_addr: ipv4_addr { };
|
||||
|
||||
class IPAddress: public Printable {
|
||||
private:
|
||||
|
||||
#if !LWIP_IPV6
|
||||
// Ugly hack to allow Arduino Ethernet library to twiddle internal bits.
|
||||
// This can only work in IPv4-only mode, of course.
|
||||
union {
|
||||
ip_addr_t _ip;
|
||||
struct {
|
||||
uint8_t bytes[4];
|
||||
} _address;
|
||||
};
|
||||
static_assert(sizeof(_ip) == sizeof(_address), "IP_ADDR_T size != _ADDRESS size");
|
||||
#else
|
||||
ip_addr_t _ip;
|
||||
#endif
|
||||
|
||||
// Access the raw byte array containing the address. Because this returns a pointer
|
||||
// to the internal structure rather than a copy of the address this function should only
|
||||
@@ -149,6 +163,10 @@ class IPAddress: public Printable {
|
||||
friend class DhcpClass;
|
||||
friend class DNSClient;
|
||||
|
||||
friend ::EthernetClass;
|
||||
friend ::DhcpClass;
|
||||
friend ::DNSClient;
|
||||
|
||||
/*
|
||||
lwIP address compatibility
|
||||
*/
|
||||
@@ -167,7 +185,6 @@ class IPAddress: public Printable {
|
||||
|
||||
bool isLocal () const { return ip_addr_islinklocal(&_ip); }
|
||||
|
||||
#if LWIP_IPV6
|
||||
|
||||
IPAddress(const ip_addr_t& lwip_addr) { ip_addr_copy(_ip, lwip_addr); }
|
||||
IPAddress(const ip_addr_t* lwip_addr) { ip_addr_copy(_ip, *lwip_addr); }
|
||||
@@ -175,6 +192,7 @@ class IPAddress: public Printable {
|
||||
IPAddress& operator=(const ip_addr_t& lwip_addr) { ip_addr_copy(_ip, lwip_addr); return *this; }
|
||||
IPAddress& operator=(const ip_addr_t* lwip_addr) { ip_addr_copy(_ip, *lwip_addr); return *this; }
|
||||
|
||||
#if LWIP_IPV6
|
||||
uint16_t* raw6()
|
||||
{
|
||||
setV6();
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
RP2040 rp2040;
|
||||
extern "C" {
|
||||
volatile bool __otherCoreIdled = false;
|
||||
int __holdUpPendSV = 0;
|
||||
};
|
||||
|
||||
mutex_t _pioMutex;
|
||||
|
||||
@@ -89,7 +89,7 @@ semaphore_t cyw43_irq_sem;
|
||||
|
||||
// Called in low priority pendsv interrupt only to do lwip processing and check cyw43 sleep
|
||||
static void periodic_worker(void) {
|
||||
#if CYW43_USE_STATS
|
||||
#if CYW43_USE_STATS && LWIP_SYS_CHECK_MS
|
||||
static uint32_t counter;
|
||||
if (counter++ % (30000 / LWIP_SYS_CHECK_MS) == 0) {
|
||||
cyw43_dump_stats();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
:orphan:
|
||||
|
||||
WiFiClientSecure Class
|
||||
----------------------
|
||||
======================
|
||||
|
||||
`BearSSL::WiFiClientSecure` is the object which actually handles TLS encrypted WiFi connections to a remote server or client. It extends `WiFiClient` and so can be used with minimal changes to code that does unsecured communications.
|
||||
|
||||
@@ -117,3 +117,30 @@ setSSLVersion(uint32_t min, uint32_t max)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Valid values for min and max are `BR_TLS10`, `BR_TLS11`, `BR_TLS12`. Min and max may be set to the same value if only a single TLS version is desired.
|
||||
|
||||
|
||||
ESP32 Compatibility
|
||||
===================
|
||||
Simple ESP32 ``WiFiClientSecure`` compatibility is built-in, allow for some sketches to run without any modification.
|
||||
The following methods are implemented:
|
||||
|
||||
.. code :: cpp
|
||||
|
||||
void setCACert(const char *rootCA);
|
||||
void setCertificate(const char *client_ca);
|
||||
void setPrivateKey(const char *private_key);
|
||||
bool loadCACert(Stream& stream, size_t size);
|
||||
bool loadCertificate(Stream& stream, size_t size);
|
||||
bool loadPrivateKey(Stream& stream, size_t size);
|
||||
int connect(IPAddress ip, uint16_t port, int32_t timeout);
|
||||
int connect(const char *host, uint16_t port, int32_t timeout);
|
||||
int connect(IPAddress ip, uint16_t port, const char *rootCABuff, const char *cli_cert, const char *cli_key);
|
||||
int connect(const char *host, uint16_t port, const char *rootCABuff, const char *cli_cert, const char *cli_key);
|
||||
|
||||
Note that the SSL backend is very different between Arduino-Pico and ESP32-Arduino (BearSSL vs. mbedTLS). This means
|
||||
that, for instance, the SSL connection will check valid dates of certificates (and hence require system time to be
|
||||
set on the Pico, which is automatically done in this case).
|
||||
|
||||
TLS-Pre Shared Keys (PSK) is not supported by BearSSL, and hence not implemented here. Neither is ALPN.
|
||||
|
||||
For more advanced control, it is recommended to port to the native Pico calls which allows much more flexibility and control.
|
||||
|
||||
+2
-2
@@ -54,9 +54,9 @@ author = u'Earle F. Philhower, III'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = u'2.3.2'
|
||||
version = u'2.4.0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = u'2.3.2'
|
||||
release = u'2.4.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
||||
@@ -50,6 +50,8 @@ For the latest version, always check https://github.com/earlephilhower/arduino-p
|
||||
WiFiClientSecure (TLS/SSL/HTTPS) <bearssl-client-secure-class>
|
||||
WiFiServerSecure (TLS/SSL/HTTPS) <bearssl-server-secure-class>
|
||||
|
||||
Over-the-Air (OTA) Updates <ota>
|
||||
|
||||
Ported/Optimized Libraries <libraries>
|
||||
Using Pico-SDK <sdk>
|
||||
|
||||
|
||||
@@ -65,6 +65,26 @@ Them hit the upload button and your sketch should upload and run.
|
||||
In some cases the Pico will encounter a hard hang and its USB port will not respond to the auto-reset request. Should this happen, just
|
||||
follow the initial procedure of holding the BOOTSEL button down while plugging in the Pico to enter the ROM bootloader.
|
||||
|
||||
Unable to Upload First Sketch
|
||||
-----------------------------
|
||||
If the Arduino IDE has never seen a serial port from a working device (Pico, AVR, or any other serial port), you
|
||||
may not be able to install using the prior directions because the ``Tools->Port`` menu will be grayed out and
|
||||
empty. In this case, a special workaround identified by @fjansson in `this issue report <https://github.com/earlephilhower/arduino-pico/issues/688>`_ .
|
||||
|
||||
To allow subsequent uploads to automatically work, you will need to manually install a UF2 binary onto the Raspberry Pi Pico one time to
|
||||
allow it to present a Serial port for the Arduino IDE to detect and save.
|
||||
|
||||
Perform the following steps to program a dummy sketch:
|
||||
|
||||
1. Open a new, empty sketch (doesn't work with a read-only example sketch).
|
||||
2. Compile it by pressing the "Verify" checkmark button.
|
||||
3. Select the ``Sketch -> Export Compiled Binary`` menu. Select a location e.g. the desktop. A folder is created there, containing a file ending in .uf2
|
||||
4. Copy this file to the Pico's drive, by drag and drop in the Explorer.
|
||||
|
||||
The Pico restarts and now now has a serial port. Now the Port menu in Arduino is not gray anymore, select the port there.
|
||||
After this, normal uploading from the Arduino editor should work.
|
||||
|
||||
|
||||
Windows 7 Driver Notes
|
||||
----------------------
|
||||
|
||||
|
||||
Executable
+239
@@ -0,0 +1,239 @@
|
||||
OTA Updates
|
||||
===========
|
||||
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
OTA (Over the Air) update is the process of uploading firmware to a Pico using a Wi-Fi, Ethernet, or other connection rather than a serial port. This is especially useful for WiFi enabled Picos, like the Pico W, because it lets systems be updated remotely, without needing physical access.
|
||||
|
||||
OTA may be done using:
|
||||
|
||||
- `Arduino IDE <#arduino-ide>`__
|
||||
- `Web Browser <#web-browser>`__ - Coming soon
|
||||
- `HTTP Server <#http-server>`__ - Coming soon
|
||||
|
||||
The Arduino IDE option is intended primarily for the software development phase. The other two options would be more useful after deployment, to provide the module with application updates either manually with a web browser, or automatically using an HTTP server.
|
||||
|
||||
In any case, the first firmware upload has to be done over a serial port. If the OTA routines are correctly implemented in the sketch, then all subsequent uploads may be done over the air.
|
||||
|
||||
By default, there is no imposed security for the OTA process. It is up to the developer to ensure that updates are allowed only from legitimate / trusted sources. Once the update is complete, the module restarts, and the new code is executed. The developer should ensure that the application running on the module is shut down and restarted in a safe manner. Chapters below provide additional information regarding security and safety of OTA updates.
|
||||
|
||||
OTA Requirements
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
OTA requires a LittleFS partition to store firmware upgrade files. Make sure that you configure the sketch with a filesystem large enough to handle whatever size firmware binary you expect. Updates may be compressed, minimizing the total space needed.
|
||||
|
||||
Power Fail Safety
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The update commands are all stored in flash, so a power cycle during update (except if the OTA bootloader is being changed) should not brick the device because when power is restored the OTA bootloader will begin the process from scratch once again.
|
||||
|
||||
|
||||
Security Disclaimer
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
No guarantees as to the level of security provided for your application by the following methods is implied. Please refer to the GNU LGPL license associated for this project for full disclaimers. If you do find security weaknesses, please don't hesitate to contact the maintainers or supply pull requests with fixes. The MD5 verification and password protection schemes are already known to supply a very weak level of security.
|
||||
|
||||
Basic Security
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
The module has to be exposed wirelessly to get it updated with a new sketch. That poses a risk of the module being violently hacked and programmed with some other code. To reduce the likelihood of being hacked, consider protecting your uploads with a password, selecting certain OTA port, etc.
|
||||
|
||||
Check functionality provided with the `ArduinoOTA <https://github.com/earlephilhower/arduino-pico/tree/master/libraries/ArduinoOTA>`__ library that may improve security:
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
void setPort(uint16_t port);
|
||||
void setHostname(const char* hostname);
|
||||
void setPassword(const char* password);
|
||||
|
||||
Certain basic protection is already built in and does not require any additional coding by the developer. `ArduinoOTA <https://github.com/earlephilhower/arduino-pico/tree/master/libraries/ArduinoOTA>`__ and espota.py use `Digest-MD5 <https://en.wikipedia.org/wiki/Digest_access_authentication>`__ to authenticate uploads. Integrity of transferred data is verified on the ESP side using `MD5 <https://en.wikipedia.org/wiki/MD5>`__ checksum.
|
||||
|
||||
Make your own risk analysis and, depending on the application, decide what library functions to implement. If required, consider implementation of other means of protection from being hacked, like exposing modules for uploads only according to a specific schedule, triggering OTA only when the user presses a dedicated “Update” button wired to the ESP, etc.
|
||||
|
||||
Advanced Security - Signed Updates
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
While the above password-based security will dissuade casual hacking attempts, it is not highly secure. For applications where a higher level of security is needed, cryptographically signed OTA updates can be required. This uses SHA256 hashing in place of MD5 (which is known to be cryptographically broken) and RSA-2048 bit level public-key encryption to guarantee that only the holder of a cryptographic private key can produce signed updates accepted by the OTA update mechanisms.
|
||||
|
||||
Signed updates are updates whose compiled binaries are signed with a private key (held by the developer) and verified with a public key (stored in the application and available for all to see). The signing process computes a hash of the binary code, encrypts the hash with the developer's private key, and appends this encrypted hash (also called a signature) to the binary that is uploaded (via OTA, web, or HTTP server). If the code is modified or replaced in any way by anyone except the holder of the developer's private key, the signature will not match and the ESP8266 will reject the upload.
|
||||
|
||||
Cryptographic signing only protects against tampering with binaries delivered via OTA. If someone has physical access, they will always be able to flash the device over the serial port. Signing also does not encrypt anything but the hash (so that it can't be modified), so this does not protect code inside the device: if a user has physical access they can read out your program.
|
||||
|
||||
**Securing your private key is paramount. The same private/public key pair that was used with the original upload must also be used to sign later binaries. Loss of the private key associated with a binary means that you will not be able to OTA-update any of your devices in the field. Alternatively, if someone else copies the private key, then they will be able to use it to sign binaries which will be accepted by the Pico.**
|
||||
|
||||
Signed Binary Format
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The format of a signed binary is compatible with the standard binary format, and can be uploaded to a non-signed Pico via serial or OTA without any conditions. Note, however, that once an unsigned OTA app is overwritten by this signed version, further updates will require signing.
|
||||
|
||||
As shown below, the signed hash is appended to the unsigned binary, followed by the total length of the signed hash (i.e., if the signed hash was 64 bytes, then this uint32 data segment will contain 64). This format allows for extensibility (such as adding a CA-based validation scheme allowing multiple signing keys all based on a trust anchor). Pull requests are always welcome. (currently it uses SHA256 with RSASSA-PKCS1-V1_5-SIGN signature scheme from RSA PKCS #1 v1.5)
|
||||
|
||||
.. code:: bash
|
||||
|
||||
NORMAL-BINARY <SIGNATURE> <uint32 LENGTH-OF-SIGNATURE>
|
||||
|
||||
Signed Binary Prerequisites
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
OpenSSL is required to run the standard signing steps, and should be available on any UNIX-like or Windows system. As usual, the latest stable version of OpenSSL is recommended.
|
||||
|
||||
Signing requires the generation of an RSA-2048 key (other bit lengths are supported as well, but 2048 is a good selection today) using any appropriate tool. The following shell commands will generate a new public/private key pair. Run them in the sketch directory:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
openssl genrsa -out private.key 2048
|
||||
openssl rsa -in private.key -outform PEM -pubout -out public.key
|
||||
|
||||
Automatic Signing
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
The simplest way of implementing signing is to use the automatic mode, which presently is only possible on Linux and Mac due to some of the tools not being available for Windows. This mode uses the IDE to configure the source code to enable sigining verification with a given public key, and signs binaries as part of the standard build process using a given public key.
|
||||
|
||||
To enable this mode, just include `private.key` and `public.key` in the sketch `.ino` directory. The IDE will call a helper script (`tools/signing.py`) before the build begins to create a header to enable key validation using the given public key, and to actually do the signing after the build process, generating a `sketch.bin.signed` file. When OTA is enabled (ArduinoOTA, Web, or HTTP), the binary will automatically only accept signed updates.
|
||||
|
||||
When the signing process starts, the message:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
Enabling binary signing
|
||||
|
||||
will appear in the IDE window before a compile is launched. At the completion of the build, the signed binary file well be displayed in the IDE build window as:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
Signed binary: /full/path/to/sketch.bin.signed
|
||||
|
||||
If you receive either of the following messages in the IDE window, the signing was not completed and you will need to verify the `public.key` and `private.key`:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
Not enabling binary signing
|
||||
... or ...
|
||||
Not signing the generated binary
|
||||
|
||||
Manual Signing of Binaries
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Users may also manually sign executables and require the OTA process to verify their signature. In the main code, before enabling any update methods, add the following declarations and function call:
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
<in globals>
|
||||
BearSSL::PublicKey signPubKey( ... key contents ... );
|
||||
BearSSL::HashSHA256 hash;
|
||||
BearSSL::SigningVerifier sign( &signPubKey );
|
||||
...
|
||||
<in setup()>
|
||||
Update.installSignature( &hash, &sign );
|
||||
|
||||
The above snippet creates a BearSSL public key and a SHA256 hash verifier, and tells the Update object to use them to validate any updates it receives from any method.
|
||||
|
||||
Compile the sketch normally and, once a `.bin` file is available, sign it using the signer script:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
<ESP8266ArduinoPath>/tools/signing.py --mode sign --privatekey <path-to-private.key> --bin <path-to-unsigned-bin> --out <path-to-signed-binary>
|
||||
|
||||
Compression
|
||||
-----------
|
||||
|
||||
The eboot bootloader incorporates a GZIP decompressor, built for very low code requirements. For applications, this optional decompression is completely transparent. For uploading compressed filesystems, the application must be built with `ATOMIC_FS_UPDATE` defined because, otherwise, eboot will not be involved in writing the filesystem.
|
||||
|
||||
No changes to the application are required. The `Updater` class and `eboot` bootloader (which performs actual application overwriting on update) automatically search for the `gzip` header in the uploaded binary, and if found, handle it.
|
||||
|
||||
Compress an application `.bin` file or filesystem package using any `gzip` available, at any desired compression level (`gzip -9` is recommended because it provides the maximum compression and uncompresses as fast as any other compressino level). For example:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
gzip -9 sketch.bin # Maximum compression, output sketch.bin.gz
|
||||
<Upload the resultant sketch.bin.gz>
|
||||
|
||||
If signing is desired, sign the gzip compressed file *after* compression.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
gzip -9 sketch.bin
|
||||
<ESP8266ArduinoPath>/tools/signing.py --mode sign --privatekey <path-to-private.key> --bin sketch.bin.gz --out sketch.bin.gz.signed
|
||||
|
||||
Safety
|
||||
~~~~~~
|
||||
|
||||
The OTA process consumes some of the ESP’s resources and bandwidth during upload. Then, the module is restarted and a new sketch executed. Analyse and test how this affects the functionality of the existing and new sketches.
|
||||
|
||||
If the ESP is in a remote location and controlling some equipment, you should devote additional attention to what happens if operation of this equipment is suddenly interrupted by the update process. Therefore, decide how to put this equipment into a safe state before starting the update. For instance, your module may be controlling a garden watering system in a sequence. If this sequence is not properly shut down and a water valve is left open, the garden may be flooded.
|
||||
|
||||
The following functions are provided with the `ArduinoOTA <https://github.com/earlephilhower/arduino-pico/tree/master/libraries/ArduinoOTA>`__ library and intended to handle functionality of your application during specific stages of OTA, or on an OTA error:
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
void onStart(OTA_CALLBACK(fn));
|
||||
void onEnd(OTA_CALLBACK(fn));
|
||||
void onProgress(OTA_CALLBACK_PROGRESS(fn));
|
||||
void onError(OTA_CALLBACK_ERROR (fn));
|
||||
|
||||
Uploading from the Arduino IDE
|
||||
------------------------------
|
||||
|
||||
Uploading modules wirelessly from Arduino IDE is intended for the following typical scenarios:
|
||||
|
||||
- During firmware development as a quicker alternative to loading over a serial port,
|
||||
|
||||
- For updating a small number of modules,
|
||||
|
||||
- Only if modules are accessible on the same network as the computer with the Arduino IDE.
|
||||
|
||||
- For all IDE uploads,m the Pico W and the computer must be connected to the same network.
|
||||
|
||||
To upload wirelessly from the IDE:
|
||||
|
||||
1. Build a sketch starts ``WiFi`` and includes the appropriare calls to ``ArduinoOTA`` (see the examples for reference). These include the ``ArduinoOTA.begin()`` call in ``setup()`` and periodically calling ``ArduinoOTA.handle();`` from the ``loop()``
|
||||
|
||||
2. Upload using standard USB connection the first time.
|
||||
|
||||
3. The ``Tools->Port`` should now list ``pico-######`` under the ``Network Ports``. Select it (you won't be able to use the serial monitor, of course).
|
||||
|
||||
4. Try another upload. It should display the OTA process in place of the serial port upload.
|
||||
|
||||
Password Protection
|
||||
-------------------
|
||||
|
||||
Protecting your OTA uploads with password is really straightforward. All you need to do, is to include the following statement in your code:
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
ArduinoOTA.setPassword((const char *)"123");
|
||||
|
||||
Where ``123`` is a sample password that you should replace with your own.
|
||||
|
||||
Before implementing it in your sketch, it is a good idea to check how it works using *BasicOTA.ino* sketch available under *File > Examples > ArduinoOTA*. Go ahead, open *BasicOTA.ino*, uncomment the above statement that is already there, and upload the sketch. To make troubleshooting easier, do not modify example sketch besides what is absolutely required. This is including original simple ``123`` OTA password. Then attempt to upload sketch again (using OTA). After compilation is complete, once upload is about to begin, you should see prompt for password.
|
||||
|
||||
Enter the password and upload should be initiated as usual with the only difference being ``Authenticating...OK`` message visible in upload log.
|
||||
|
||||
You will not be prompted for a reentering the same password next time. Arduino IDE will remember it for you. You will see prompt for password only after reopening IDE, or if you change it in your sketch, upload the sketch and then try to upload it again.
|
||||
|
||||
Please note, it is possible to reveal password entered previously in Arduino IDE, if IDE has not been closed since last upload. This can be done by enabling *Show verbose output during: upload* in *File > Preferences* and attempting to upload the module.
|
||||
|
||||
Stream Interface
|
||||
----------------
|
||||
|
||||
The Stream Interface is the base for all other update modes like OTA, HTTP Server / client. Given a Stream-class variable `streamVar` providing `byteCount` bytes of firmware, it can store the firmware as follows:
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
Update.begin(firmwareLengthInBytes);
|
||||
Update.writeStream(streamVar);
|
||||
Update.end();
|
||||
|
||||
OTA Bootloader and Memory Map
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A firmware file is uploaded via any method (Ethernet, WiFi, serial ZModem, etc.) and stored on the LittleFS filesystem as a normal file. The Updater class (or the underlying PicoOTA) will make a special "OTA command" file on the filesystem, which will be read by the OTA bootloader. On a reboot, this OTA bootloader will check for an upgrade file, verify its contents, and then perform the requested update and reboot. If no upgrade file is present, the OTA bootloader simply jumps to the main sketch.
|
||||
|
||||
The ROM layout consists of:
|
||||
|
||||
... code:
|
||||
|
||||
[boot2.S] [OTA Bootloader] [0-pad] [OTA partition table] [Main sketch] [LittleFS filesystem] [EEPROM]
|
||||
|
||||
@@ -222,6 +222,18 @@ default Pico SDK USB stack. To change it, add
|
||||
Note that the special "No USB" setting is also supported, through the
|
||||
shortcut-define ``PIO_FRAMEWORK_ARDUINO_NO_USB``.
|
||||
|
||||
IP Stack
|
||||
---------
|
||||
|
||||
The lwIP stack can be configured to support only IPv4 (default) or additionally IPv6. To activate IPv6 support, add
|
||||
|
||||
.. code:: ini
|
||||
|
||||
; IPv6
|
||||
build_flags = -DPIO_FRAMEWORK_ARDUINO_ENABLE_IPV6
|
||||
|
||||
to the ``platformio.ini``.
|
||||
|
||||
|
||||
Selecting a different core version
|
||||
----------------------------------
|
||||
|
||||
@@ -33,6 +33,10 @@ values returned may not meet the most stringent random tests. **If your
|
||||
application needs absolute bulletproof random numbers, consider using
|
||||
dedicated external hardware.**
|
||||
|
||||
void reboot()
|
||||
~~~~~~~~~~~~~
|
||||
Forces a hardware reboot of the Pico.
|
||||
|
||||
Memory Information
|
||||
------------------
|
||||
|
||||
|
||||
+3
-2
@@ -12,8 +12,9 @@ serial port, ``Serial`` as well as supporting automatic reset-to-upload
|
||||
from the IDE.
|
||||
|
||||
The Arduino-Pico core includes ported versions of the basic Arduino
|
||||
``Keyboard`` and ``Mouse`` libraries. These libraries allow you to
|
||||
emulate a keyboard or mouse with the Pico in your sketches.
|
||||
``Keyboard``, ``Mouse`` and ``Joystick`` libraries. These libraries
|
||||
allow you to emulate a keyboard, a gamepad or mouse (or all together)
|
||||
with the Pico in your sketches.
|
||||
|
||||
See the examples and Arduino Reference at
|
||||
https://www.arduino.cc/reference/en/language/functions/usb/keyboard/
|
||||
|
||||
@@ -39,3 +39,38 @@ returns a sane value before continuing a sketch:
|
||||
Serial.print("Current time: ");
|
||||
Serial.print(asctime(&timeinfo));
|
||||
}
|
||||
|
||||
bool NTP.waitSet(uint32_t timeout)
|
||||
----------------------------------
|
||||
This call will wait up to timeout milliseconds for the time to be set, and returns
|
||||
success or failure. It will also begin NTP with a default "pool.ntp.org" server if
|
||||
it is not already running. Using this method, the above code becomes:
|
||||
|
||||
.. code :: cpp
|
||||
|
||||
void setClock() {
|
||||
NTP.begin("pool.ntp.org", "time.nist.gov");
|
||||
NTP.waitSet();
|
||||
time_t now = time(nullptr);
|
||||
struct tm timeinfo;
|
||||
gmtime_r(&now, &timeinfo);
|
||||
Serial.print("Current time: ");
|
||||
Serial.print(asctime(&timeinfo));
|
||||
}
|
||||
|
||||
bool NTP.waitSet(void (\*cb)(), uint32_t timeout)
|
||||
-------------------------------------------------
|
||||
Allows for a callback that will be called every 1/10th of a second while waiting for
|
||||
NTP sync. For example, using lambdas you can simply print "."s:"
|
||||
|
||||
.. code :: cpp
|
||||
|
||||
void setClock() {
|
||||
NTP.begin("pool.ntp.org", "time.nist.gov");
|
||||
NTP.waitSet([]() { Serial.print("."); });
|
||||
time_t now = time(nullptr);
|
||||
struct tm timeinfo;
|
||||
gmtime_r(&now, &timeinfo);
|
||||
Serial.print("Current time: ");
|
||||
Serial.print(asctime(&timeinfo));
|
||||
}
|
||||
|
||||
+5
-1
@@ -21,7 +21,7 @@ extern void interrupts();
|
||||
#define MEM_LIBC_MALLOC 0
|
||||
|
||||
#define MEM_ALIGNMENT 4
|
||||
#define MEM_SIZE 4000
|
||||
#define MEM_SIZE 16384
|
||||
#define MEMP_NUM_TCP_SEG 32
|
||||
#define MEMP_NUM_ARP_QUEUE 10
|
||||
#define PBUF_POOL_SIZE 24
|
||||
@@ -53,6 +53,10 @@ extern void interrupts();
|
||||
#define DHCP_DOES_ARP_CHECK 0
|
||||
#define LWIP_DHCP_DOES_ACD_CHECK 0
|
||||
|
||||
#if LWIP_IPV6
|
||||
#define LWIP_IPV6_DHCP6 1
|
||||
#endif
|
||||
|
||||
// NTP
|
||||
extern void __setSystemTime(unsigned long long sec, unsigned long us);
|
||||
#define SNTP_SET_SYSTEM_TIME_US(sec, us) __setSystemTime(sec, us)
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
ota_command.h - OTA stub that copies from LittleFS to flash
|
||||
Copyright (c) 2022 Earle F. Philhower, III. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define _OTA_WRITE 1
|
||||
#define _OTA_VERIFY 1
|
||||
|
||||
typedef struct {
|
||||
uint32_t command;
|
||||
union {
|
||||
struct {
|
||||
char filename[64];
|
||||
uint32_t fileOffset;
|
||||
uint32_t fileLength;
|
||||
uint32_t flashAddress; // Normally XIP_BASE
|
||||
} write;
|
||||
};
|
||||
} commandEntry;
|
||||
|
||||
// Must fit within 4K page
|
||||
typedef struct {
|
||||
uint8_t sign[8]; // "Pico OTA"
|
||||
|
||||
// List of operations
|
||||
uint32_t count;
|
||||
commandEntry cmd[8];
|
||||
|
||||
uint32_t crc32; // CRC32 over just the contents of this struct, up until just before this value
|
||||
} OTACmdPage;
|
||||
|
||||
#define _OTA_COMMAND_FILE "otacommand.bin"
|
||||
@@ -23,6 +23,8 @@ pop KEYWORD2
|
||||
pop_nb KEYWORD2
|
||||
|
||||
rp2040 KEYWORD2
|
||||
reboot KEYWORD2
|
||||
restart KEYWORD2
|
||||
RP2040 KEYWORD2
|
||||
usToPIOCycles KEYWORD2
|
||||
f_cpu KEYWORD2
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -55,6 +55,28 @@ SECTIONS
|
||||
ASSERT(__boot2_end__ - __boot2_start__ == 256,
|
||||
"ERROR: Pico second stage bootloader must be 256 bytes in size")
|
||||
|
||||
.ota : {
|
||||
/* Start image with OTA */
|
||||
KEEP (*(.OTA))
|
||||
/* Align to the last 16-bytes of the OTA region */
|
||||
/* If anyone has a better way of doing this, please submit a PR! */
|
||||
/* . = __flash_binary_start + 0x2ff0;
|
||||
LONG(__FS_START__)
|
||||
LONG(__FS_END__)
|
||||
LONG(__EEPROM_START__)
|
||||
LONG(__FLASH_LENGTH__)*/
|
||||
} > FLASH
|
||||
|
||||
.partition : {
|
||||
/* Align to the last 16-bytes of the OTA region */
|
||||
/* If anyone has a better way of doing this, please submit a PR! */
|
||||
. = __flash_binary_start + 0x2ff0;
|
||||
LONG(__FS_START__)
|
||||
LONG(__FS_END__)
|
||||
LONG(__EEPROM_START__)
|
||||
LONG(__FLASH_LENGTH__)
|
||||
} > FLASH
|
||||
|
||||
/* The second stage will always enter the image at the start of .text.
|
||||
The debugger will use the ELF entry point, which is the _entry_point
|
||||
symbol if present, otherwise defaults to start of .text.
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266mDNS.h>
|
||||
#include <WiFiUdp.h>
|
||||
#include <ArduinoOTA.h>
|
||||
|
||||
#ifndef STASSID
|
||||
#define STASSID "your-ssid"
|
||||
#define STAPSK "your-password"
|
||||
#endif
|
||||
|
||||
const char* ssid = STASSID;
|
||||
const char* password = STAPSK;
|
||||
|
||||
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);
|
||||
rp2040.restart();
|
||||
}
|
||||
|
||||
// Port defaults to 8266
|
||||
// ArduinoOTA.setPort(8266);
|
||||
|
||||
// Hostname defaults to esp8266-[ChipID]
|
||||
// ArduinoOTA.setHostname("myesp8266");
|
||||
|
||||
// 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_FS
|
||||
type = "filesystem";
|
||||
}
|
||||
|
||||
// NOTE: if updating FS this would be the place to unmount FS using FS.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();
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266mDNS.h>
|
||||
#include <WiFiUdp.h>
|
||||
#include <ArduinoOTA.h>
|
||||
|
||||
#ifndef STASSID
|
||||
#define STASSID "your-ssid"
|
||||
#define STAPSK "your-password"
|
||||
#endif
|
||||
|
||||
const char* ssid = STASSID;
|
||||
const char* password = STAPSK;
|
||||
const char* host = "OTA-LEDS";
|
||||
|
||||
int led_pin = 13;
|
||||
#define N_DIMMERS 3
|
||||
int dimmer_pin[] = { 14, 5, 15 };
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
/* switch on led */
|
||||
pinMode(led_pin, OUTPUT);
|
||||
digitalWrite(led_pin, LOW);
|
||||
|
||||
Serial.println("Booting");
|
||||
WiFi.mode(WIFI_STA);
|
||||
|
||||
WiFi.begin(ssid, password);
|
||||
|
||||
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
||||
WiFi.begin(ssid, password);
|
||||
Serial.println("Retrying connection...");
|
||||
}
|
||||
/* switch off led */
|
||||
digitalWrite(led_pin, HIGH);
|
||||
|
||||
/* configure dimmers, and OTA server events */
|
||||
analogWriteRange(1000);
|
||||
analogWrite(led_pin, 990);
|
||||
|
||||
for (int i = 0; i < N_DIMMERS; i++) {
|
||||
pinMode(dimmer_pin[i], OUTPUT);
|
||||
analogWrite(dimmer_pin[i], 50);
|
||||
}
|
||||
|
||||
ArduinoOTA.setHostname(host);
|
||||
ArduinoOTA.onStart([]() { // switch off all the PWMs during upgrade
|
||||
for (int i = 0; i < N_DIMMERS; i++) {
|
||||
analogWrite(dimmer_pin[i], 0);
|
||||
}
|
||||
analogWrite(led_pin, 0);
|
||||
});
|
||||
|
||||
ArduinoOTA.onEnd([]() { // do a fancy thing with our board led at end
|
||||
for (int i = 0; i < 30; i++) {
|
||||
analogWrite(led_pin, (i * 100) % 1001);
|
||||
delay(50);
|
||||
}
|
||||
});
|
||||
|
||||
ArduinoOTA.onError([](ota_error_t error) {
|
||||
(void)error;
|
||||
rp2040.restart();
|
||||
});
|
||||
|
||||
/* setup the OTA server */
|
||||
ArduinoOTA.begin();
|
||||
Serial.println("Ready");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
ArduinoOTA.handle();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// To be used with SignedOTA. The IDE will sign the binary
|
||||
// automatically and upload over WiFi
|
||||
|
||||
// Released to the public domain, Earle Philhower, 2022
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
void setup() {
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
srand(123);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
int del = rand() % 100;
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
delay(del * 10);
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
delay(del * 10);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEAu1Pt7yEk/xI+6cozLj5Bu4xV8gXDXcHS0rSJFfl4wBTk4UXp
|
||||
aJRaLfR1k0juEEa5LBRZaoA0iLj2e6kfCibONx0VVoWmeqN2HBc3zkA1eqCksI0Q
|
||||
Uudzto4KhKHp0odiZ2zo6c/2Tn1zqD/m3OLoSjVTbsJmGuwx8RGMBXozpg/uL0hH
|
||||
flihX+HND4Xfw92QXv7SaPBhgvM9xyRxn0/w3J2nNjtuPuVN5vcQkd8ncMexVfy9
|
||||
AWp+HSA5AT5N8CJ/EeIsdDMY1US28bUePzj1WIo75bZHKZNFw/iXe2xoPpm74qri
|
||||
MNSlW2craFP2K3KYnI28vJeUU6t9I6LS9zt2zQIDAQABAoIBAE5GpuDKb8Qp4qIc
|
||||
fMBxAVSWMn+cSuONj0O+bp4BDaTt1ioP5ZVukDQtt0ehLOEePFgf9LEc+1a6Ozy3
|
||||
EaJTTs4W2Ai8djE+xqa8SPRlPjOMluSzPUP3NRHuTpTXd3YiXksrZjP1U02+/Cos
|
||||
8ZIROtFvcPqSPso3MjMyitjrFFPqEtf1P+UiamjDrMSM72YX4W55kOkiCWCnAOmw
|
||||
mGTlXOIqDSTBb1lloKWJfpB3RdnNo2izkU1HMBn7hVi433NUBA22o+RZhDFSZdD4
|
||||
3kbkUqXd4p+vc/sh6muJtWS/COSIPFkLzdEYpBdt3XQ4FhlsRtILJaPWXa4OPjR6
|
||||
ZoOwMB0CgYEA6OHfIofQiu4+HlTDN5YdyTmtYEYcrtbaQUxuQSEa2mshBphHP8uT
|
||||
mYRVl2BzuprFmXZPz+FcjnPnfxqEehljvA3wMjA/PE+nQo9yyOC0N4ulXpkkqHdR
|
||||
f+4KZVR7D+hesGe+57OQmvTqYZSHEt/ubjC9wZ90UFonLjsa4zibbrsCgYEAzexn
|
||||
XDnThb3ffyBgvprP0IJjgMAEY0pXD++PKPQqPu9JMz68t7roYzkKFCFVOsaWpKxC
|
||||
vX9mvYjTBjLpWh+ltIAN+EFz6seIbeSJ0RNybsAXYwT/mFWGHx2tMtlW6DgBu3UD
|
||||
J2Yf76n0JaddBkfNMQI00Dl41+MU+AwwTB9fTBcCgYB2+f6Pm6d1cyYVROS/X1g0
|
||||
V9011FwPDwFOXwftCka31Ad5YQ71jsIHqk44GjTF3xCYyJMZ917cAGcCzr9jydjk
|
||||
WJKgcXm9DEy9ep//9Jzdy+BepgrObrcajriM8E424FaP9VDY+yojoICl/cXMZM9h
|
||||
SFGJvDcmXgiqW9PuxhrSxQKBgAMN2oqXoPd+1W3BQS4ShbqF9IvYTThbxebKmsj0
|
||||
thuw2NkVuR7Qetnd4rRhui3g/CL9GxBMb22oNdkFsEhR59dBfvOLpPh6dR+MIC8l
|
||||
prDV0IL7c/8CZbbYbdUvPAa9rejl12IiNZ8MWj6kuNB7CCQN8FKWR6CMEaeMJrs6
|
||||
S+OJAoGAbehNOUwEzmUKkfxf+279kBkgabcQ3NTaeSx0QOnI9KWHFGLYLQk9cMSu
|
||||
maQJ1TYpbIoP1njzJ4bI2tynhwEuSMEhh4afP6U5H10NJX4PqSd0Rqc1vSJYcszr
|
||||
5mUWil8FfbCBZ8jod2NQ55KYMVY5CphCqaK/s2bw2pvIR3uqJGg=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -0,0 +1,9 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1Pt7yEk/xI+6cozLj5B
|
||||
u4xV8gXDXcHS0rSJFfl4wBTk4UXpaJRaLfR1k0juEEa5LBRZaoA0iLj2e6kfCibO
|
||||
Nx0VVoWmeqN2HBc3zkA1eqCksI0QUudzto4KhKHp0odiZ2zo6c/2Tn1zqD/m3OLo
|
||||
SjVTbsJmGuwx8RGMBXozpg/uL0hHflihX+HND4Xfw92QXv7SaPBhgvM9xyRxn0/w
|
||||
3J2nNjtuPuVN5vcQkd8ncMexVfy9AWp+HSA5AT5N8CJ/EeIsdDMY1US28bUePzj1
|
||||
WIo75bZHKZNFw/iXe2xoPpm74qriMNSlW2craFP2K3KYnI28vJeUU6t9I6LS9zt2
|
||||
zQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -0,0 +1,91 @@
|
||||
// Simple Signed OTA example
|
||||
// Released to the public domain by Earle Philhower, Aug 2022
|
||||
//
|
||||
// Note that the actual code of this is the same as the BasicOTA. No user
|
||||
// code changes are needed, only the presence of public.key and private.key
|
||||
// in the sketch directory. The core will automatically sign any binaries
|
||||
// and include the necessary code to verify signatures. For more info
|
||||
// check the documentation
|
||||
//
|
||||
// After uploading this sketch, try uploading the SignedOTA-Blink sketch
|
||||
// All unsigned binaries, or binaries signed with a different private
|
||||
// key will fail to upload.
|
||||
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <LEAmDNS.h>
|
||||
#include <WiFiUdp.h>
|
||||
#include <ArduinoOTA.h>
|
||||
|
||||
#ifndef STASSID
|
||||
#define STASSID "your-ssid"
|
||||
#define STAPSK "your-password"
|
||||
#endif
|
||||
|
||||
const char* ssid = STASSID;
|
||||
const char* password = STAPSK;
|
||||
|
||||
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);
|
||||
rp2040.restart();
|
||||
}
|
||||
|
||||
// Port defaults to 8266
|
||||
// ArduinoOTA.setPort(8266);
|
||||
|
||||
// Hostname defaults to esp8266-[ChipID]
|
||||
// ArduinoOTA.setHostname("myesp8266");
|
||||
|
||||
// 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_FS
|
||||
type = "filesystem";
|
||||
}
|
||||
|
||||
// NOTE: if updating FS this would be the place to unmount FS using FS.end()
|
||||
Serial.println("Start updating " + type);
|
||||
});
|
||||
ArduinoOTA.onEnd([]() {
|
||||
Serial.println("\nEnd");
|
||||
});
|
||||
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
|
||||
Serial.printf("Progress: %u%%\r\n", (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();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEAu1Pt7yEk/xI+6cozLj5Bu4xV8gXDXcHS0rSJFfl4wBTk4UXp
|
||||
aJRaLfR1k0juEEa5LBRZaoA0iLj2e6kfCibONx0VVoWmeqN2HBc3zkA1eqCksI0Q
|
||||
Uudzto4KhKHp0odiZ2zo6c/2Tn1zqD/m3OLoSjVTbsJmGuwx8RGMBXozpg/uL0hH
|
||||
flihX+HND4Xfw92QXv7SaPBhgvM9xyRxn0/w3J2nNjtuPuVN5vcQkd8ncMexVfy9
|
||||
AWp+HSA5AT5N8CJ/EeIsdDMY1US28bUePzj1WIo75bZHKZNFw/iXe2xoPpm74qri
|
||||
MNSlW2craFP2K3KYnI28vJeUU6t9I6LS9zt2zQIDAQABAoIBAE5GpuDKb8Qp4qIc
|
||||
fMBxAVSWMn+cSuONj0O+bp4BDaTt1ioP5ZVukDQtt0ehLOEePFgf9LEc+1a6Ozy3
|
||||
EaJTTs4W2Ai8djE+xqa8SPRlPjOMluSzPUP3NRHuTpTXd3YiXksrZjP1U02+/Cos
|
||||
8ZIROtFvcPqSPso3MjMyitjrFFPqEtf1P+UiamjDrMSM72YX4W55kOkiCWCnAOmw
|
||||
mGTlXOIqDSTBb1lloKWJfpB3RdnNo2izkU1HMBn7hVi433NUBA22o+RZhDFSZdD4
|
||||
3kbkUqXd4p+vc/sh6muJtWS/COSIPFkLzdEYpBdt3XQ4FhlsRtILJaPWXa4OPjR6
|
||||
ZoOwMB0CgYEA6OHfIofQiu4+HlTDN5YdyTmtYEYcrtbaQUxuQSEa2mshBphHP8uT
|
||||
mYRVl2BzuprFmXZPz+FcjnPnfxqEehljvA3wMjA/PE+nQo9yyOC0N4ulXpkkqHdR
|
||||
f+4KZVR7D+hesGe+57OQmvTqYZSHEt/ubjC9wZ90UFonLjsa4zibbrsCgYEAzexn
|
||||
XDnThb3ffyBgvprP0IJjgMAEY0pXD++PKPQqPu9JMz68t7roYzkKFCFVOsaWpKxC
|
||||
vX9mvYjTBjLpWh+ltIAN+EFz6seIbeSJ0RNybsAXYwT/mFWGHx2tMtlW6DgBu3UD
|
||||
J2Yf76n0JaddBkfNMQI00Dl41+MU+AwwTB9fTBcCgYB2+f6Pm6d1cyYVROS/X1g0
|
||||
V9011FwPDwFOXwftCka31Ad5YQ71jsIHqk44GjTF3xCYyJMZ917cAGcCzr9jydjk
|
||||
WJKgcXm9DEy9ep//9Jzdy+BepgrObrcajriM8E424FaP9VDY+yojoICl/cXMZM9h
|
||||
SFGJvDcmXgiqW9PuxhrSxQKBgAMN2oqXoPd+1W3BQS4ShbqF9IvYTThbxebKmsj0
|
||||
thuw2NkVuR7Qetnd4rRhui3g/CL9GxBMb22oNdkFsEhR59dBfvOLpPh6dR+MIC8l
|
||||
prDV0IL7c/8CZbbYbdUvPAa9rejl12IiNZ8MWj6kuNB7CCQN8FKWR6CMEaeMJrs6
|
||||
S+OJAoGAbehNOUwEzmUKkfxf+279kBkgabcQ3NTaeSx0QOnI9KWHFGLYLQk9cMSu
|
||||
maQJ1TYpbIoP1njzJ4bI2tynhwEuSMEhh4afP6U5H10NJX4PqSd0Rqc1vSJYcszr
|
||||
5mUWil8FfbCBZ8jod2NQ55KYMVY5CphCqaK/s2bw2pvIR3uqJGg=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -0,0 +1,9 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1Pt7yEk/xI+6cozLj5B
|
||||
u4xV8gXDXcHS0rSJFfl4wBTk4UXpaJRaLfR1k0juEEa5LBRZaoA0iLj2e6kfCibO
|
||||
Nx0VVoWmeqN2HBc3zkA1eqCksI0QUudzto4KhKHp0odiZ2zo6c/2Tn1zqD/m3OLo
|
||||
SjVTbsJmGuwx8RGMBXozpg/uL0hHflihX+HND4Xfw92QXv7SaPBhgvM9xyRxn0/w
|
||||
3J2nNjtuPuVN5vcQkd8ncMexVfy9AWp+HSA5AT5N8CJ/EeIsdDMY1US28bUePzj1
|
||||
WIo75bZHKZNFw/iXe2xoPpm74qriMNSlW2craFP2K3KYnI28vJeUU6t9I6LS9zt2
|
||||
zQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -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)
|
||||
#######################################
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
name=ArduinoOTA
|
||||
version=1.0
|
||||
author=Ivan Grokhotkov and Miguel Angel Ajo
|
||||
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.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 announces to get discovered by the arduino IDE.
|
||||
category=Communication
|
||||
url=https://github.com/earlephilhower/arduino-pico
|
||||
architectures=rp2040
|
||||
dot_a_linkage=true
|
||||
@@ -0,0 +1,398 @@
|
||||
#include <functional>
|
||||
#include <WiFiUdp.h>
|
||||
#include "ArduinoOTA.h"
|
||||
#include "MD5Builder.h"
|
||||
#include <PicoOTA.h>
|
||||
#include <StreamString.h>
|
||||
|
||||
#include "lwip/udp.h"
|
||||
#include "include/UdpContext.h"
|
||||
|
||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS)
|
||||
#include <LEAmDNS.h>
|
||||
#endif
|
||||
|
||||
//#ifdef DEBUG_ESP_OTA
|
||||
//#ifdef DEBUG_ESP_PORT
|
||||
//#define OTA_DEBUG DEBUG_ESP_PORT
|
||||
//#endif
|
||||
//#endif
|
||||
#define OTA_DEBUG Serial
|
||||
|
||||
ArduinoOTAClass::ArduinoOTAClass() {
|
||||
}
|
||||
|
||||
ArduinoOTAClass::~ArduinoOTAClass() {
|
||||
if (_udp_ota) {
|
||||
_udp_ota->unref();
|
||||
_udp_ota = 0;
|
||||
}
|
||||
}
|
||||
|
||||
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::begin(bool useMDNS) {
|
||||
if (_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
_useMDNS = useMDNS;
|
||||
|
||||
if (!_hostname.length()) {
|
||||
char tmp[15];
|
||||
sprintf(tmp, "pico-%s", rp2040.getChipID());
|
||||
_hostname = tmp;
|
||||
}
|
||||
if (!_port) {
|
||||
_port = 2040;
|
||||
}
|
||||
|
||||
if (_udp_ota) {
|
||||
_udp_ota->unref();
|
||||
_udp_ota = 0;
|
||||
}
|
||||
|
||||
_udp_ota = new UdpContext;
|
||||
_udp_ota->ref();
|
||||
|
||||
if (!_udp_ota->listen(IP_ADDR_ANY, _port)) {
|
||||
return;
|
||||
}
|
||||
_udp_ota->onRx(std::bind(&ArduinoOTAClass::_onRx, this));
|
||||
|
||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS)
|
||||
if (_useMDNS) {
|
||||
MDNS.begin(_hostname.c_str());
|
||||
|
||||
if (_password.length()) {
|
||||
MDNS.enableArduino(_port, true);
|
||||
} else {
|
||||
MDNS.enableArduino(_port);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
_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;
|
||||
char value;
|
||||
while (_udp_ota->peek() == ' ') {
|
||||
_udp_ota->read();
|
||||
}
|
||||
for (index = 0; index < sizeof(data); ++index) {
|
||||
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;
|
||||
int value;
|
||||
while (true) {
|
||||
value = _udp_ota->read();
|
||||
if (value < 0 || value == '\0' || value == end) {
|
||||
return res;
|
||||
}
|
||||
res += static_cast<char>(value);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void ArduinoOTAClass::_onRx() {
|
||||
if (!_udp_ota->next()) {
|
||||
return;
|
||||
}
|
||||
IPAddress ota_ip;
|
||||
|
||||
if (_state == OTA_IDLE) {
|
||||
int cmd = parseInt();
|
||||
if (cmd != U_FLASH && cmd != U_FS) {
|
||||
return;
|
||||
}
|
||||
_ota_ip = _udp_ota->getRemoteAddress();
|
||||
_cmd = cmd;
|
||||
_ota_port = parseInt();
|
||||
_ota_udp_port = _udp_ota->getRemotePort();
|
||||
_size = parseInt();
|
||||
_udp_ota->read();
|
||||
_md5 = readStringUntil('\n');
|
||||
_md5.trim();
|
||||
if (_md5.length() != 32) {
|
||||
return;
|
||||
}
|
||||
|
||||
ota_ip = _ota_ip;
|
||||
|
||||
if (_password.length()) {
|
||||
MD5Builder nonce_md5;
|
||||
nonce_md5.begin();
|
||||
nonce_md5.add(String(micros()));
|
||||
nonce_md5.calculate();
|
||||
_nonce = nonce_md5.toString();
|
||||
|
||||
char auth_req[38];
|
||||
sprintf(auth_req, "AUTH %s", _nonce.c_str());
|
||||
_udp_ota->append((const char *)auth_req, strlen(auth_req));
|
||||
_udp_ota->send(ota_ip, _ota_udp_port);
|
||||
_state = OTA_WAITAUTH;
|
||||
return;
|
||||
} else {
|
||||
_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();
|
||||
|
||||
ota_ip = _ota_ip;
|
||||
// if(result.equalsConstantTime(response)) {
|
||||
if (result.equals(response)) {
|
||||
_state = OTA_RUNUPDATE;
|
||||
} else {
|
||||
_udp_ota->append("Authentication Failed", 21);
|
||||
_udp_ota->send(ota_ip, _ota_udp_port);
|
||||
if (_error_callback) {
|
||||
_error_callback(OTA_AUTH_ERROR);
|
||||
}
|
||||
_state = OTA_IDLE;
|
||||
}
|
||||
}
|
||||
|
||||
while (_udp_ota->next()) {
|
||||
_udp_ota->flush();
|
||||
}
|
||||
}
|
||||
|
||||
void ArduinoOTAClass::_runUpdate() {
|
||||
IPAddress ota_ip = _ota_ip;
|
||||
|
||||
if (!Update.begin(_size, _cmd)) {
|
||||
#ifdef OTA_DEBUG
|
||||
OTA_DEBUG.println("Update Begin Error");
|
||||
#endif
|
||||
if (_error_callback) {
|
||||
_error_callback(OTA_BEGIN_ERROR);
|
||||
}
|
||||
|
||||
StreamString ss;
|
||||
Update.printError(ss);
|
||||
_udp_ota->append("ERR: ", 5);
|
||||
_udp_ota->append(ss.c_str(), ss.length());
|
||||
_udp_ota->send(ota_ip, _ota_udp_port);
|
||||
delay(100);
|
||||
_udp_ota->listen(IP_ADDR_ANY, _port);
|
||||
_state = OTA_IDLE;
|
||||
return;
|
||||
}
|
||||
if (!LittleFS.begin()) {
|
||||
_udp_ota->append("ERR: ", 5);
|
||||
_udp_ota->append("nofilesystem", 6);
|
||||
_udp_ota->send(ota_ip, _ota_udp_port);
|
||||
delay(100);
|
||||
_udp_ota->listen(IP_ADDR_ANY, _port);
|
||||
_state = OTA_IDLE;
|
||||
return;
|
||||
}
|
||||
|
||||
_udp_ota->append("OK", 2);
|
||||
_udp_ota->send(ota_ip, _ota_udp_port);
|
||||
delay(100);
|
||||
|
||||
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)) {
|
||||
#ifdef OTA_DEBUG
|
||||
OTA_DEBUG.printf("Connect Failed\n");
|
||||
#endif
|
||||
_udp_ota->listen(IP_ADDR_ANY, _port);
|
||||
if (_error_callback) {
|
||||
_error_callback(OTA_CONNECT_ERROR);
|
||||
}
|
||||
_state = OTA_IDLE;
|
||||
}
|
||||
// OTA sends little packets
|
||||
client.setNoDelay(true);
|
||||
|
||||
uint32_t written, total = 0;
|
||||
while (!Update.isFinished() && (client.connected() || client.available())) {
|
||||
int waited = 1000;
|
||||
while (!client.available() && waited--) {
|
||||
delay(1);
|
||||
}
|
||||
if (!waited) {
|
||||
#ifdef OTA_DEBUG
|
||||
OTA_DEBUG.printf("Receive Failed\n");
|
||||
#endif
|
||||
_udp_ota->listen(IP_ADDR_ANY, _port);
|
||||
if (_error_callback) {
|
||||
_error_callback(OTA_RECEIVE_ERROR);
|
||||
}
|
||||
_state = OTA_IDLE;
|
||||
}
|
||||
written = Update.write(client);
|
||||
if (written > 0) {
|
||||
client.print(written, DEC);
|
||||
total += written;
|
||||
if (_progress_callback) {
|
||||
_progress_callback(total, _size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (Update.end()) {
|
||||
// Ensure last count packet has been sent out and not combined with the final OK
|
||||
client.flush();
|
||||
delay(1000);
|
||||
client.print("OK");
|
||||
client.flush();
|
||||
delay(1000);
|
||||
client.stop();
|
||||
#ifdef OTA_DEBUG
|
||||
OTA_DEBUG.printf("Update Success\n");
|
||||
#endif
|
||||
if (_end_callback) {
|
||||
_end_callback();
|
||||
}
|
||||
if (_rebootOnSuccess) {
|
||||
#ifdef OTA_DEBUG
|
||||
OTA_DEBUG.printf("Rebooting...\n");
|
||||
#endif
|
||||
LittleFS.end();
|
||||
//let serial/network finish tasks that might be given in _end_callback
|
||||
delay(100);
|
||||
rp2040.reboot();
|
||||
}
|
||||
} else {
|
||||
_udp_ota->listen(IP_ADDR_ANY, _port);
|
||||
if (_error_callback) {
|
||||
_error_callback(OTA_END_ERROR);
|
||||
}
|
||||
Update.printError(client);
|
||||
#ifdef OTA_DEBUG
|
||||
Update.printError(OTA_DEBUG);
|
||||
#endif
|
||||
_state = OTA_IDLE;
|
||||
}
|
||||
}
|
||||
|
||||
void ArduinoOTAClass::end() {
|
||||
_initialized = false;
|
||||
_udp_ota->unref();
|
||||
_udp_ota = 0;
|
||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS)
|
||||
if (_useMDNS) {
|
||||
MDNS.end();
|
||||
}
|
||||
#endif
|
||||
_state = OTA_IDLE;
|
||||
#ifdef OTA_DEBUG
|
||||
OTA_DEBUG.printf("OTA server stopped.\n");
|
||||
#endif
|
||||
}
|
||||
//this needs to be called in the loop()
|
||||
void ArduinoOTAClass::handle() {
|
||||
if (_state == OTA_RUNUPDATE) {
|
||||
_runUpdate();
|
||||
_state = OTA_IDLE;
|
||||
}
|
||||
|
||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS)
|
||||
if (_useMDNS) {
|
||||
MDNS.update(); //handle MDNS update as well, given that ArduinoOTA relies on it anyways
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int ArduinoOTAClass::getCommand() {
|
||||
return _cmd;
|
||||
}
|
||||
|
||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_ARDUINOOTA)
|
||||
ArduinoOTAClass ArduinoOTA;
|
||||
#endif
|
||||
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <functional>
|
||||
#include <LittleFS.h>
|
||||
#include <Updater.h>
|
||||
|
||||
class UdpContext;
|
||||
|
||||
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 2040
|
||||
void setPort(uint16_t port);
|
||||
|
||||
//Sets the device hostname. Default pico-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);
|
||||
|
||||
//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(bool useMDNS = true);
|
||||
|
||||
//Ends the ArduinoOTA service
|
||||
void end();
|
||||
//Call this in loop() to run the service. Also calls MDNS.update() when begin() or begin(true) is used.
|
||||
void handle();
|
||||
|
||||
//Gets update command type after OTA has started. Either U_FLASH or U_FS
|
||||
int getCommand();
|
||||
|
||||
private:
|
||||
void _runUpdate(void);
|
||||
void _onRx(void);
|
||||
int parseInt(void);
|
||||
String readStringUntil(char end);
|
||||
|
||||
int _port = 0;
|
||||
String _password;
|
||||
String _hostname;
|
||||
String _nonce;
|
||||
UdpContext *_udp_ota = nullptr;
|
||||
bool _initialized = false;
|
||||
bool _rebootOnSuccess = true;
|
||||
bool _useMDNS = true;
|
||||
ota_state_t _state = OTA_IDLE;
|
||||
int _size = 0;
|
||||
int _cmd = 0;
|
||||
uint16_t _ota_port = 0;
|
||||
uint16_t _ota_udp_port = 0;
|
||||
IPAddress _ota_ip;
|
||||
String _md5;
|
||||
|
||||
THandlerFunction _start_callback = nullptr;
|
||||
THandlerFunction _end_callback = nullptr;
|
||||
THandlerFunction_Error _error_callback = nullptr;
|
||||
THandlerFunction_Progress _progress_callback = nullptr;
|
||||
};
|
||||
|
||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_ARDUINOOTA)
|
||||
extern ArduinoOTAClass ArduinoOTA;
|
||||
#endif
|
||||
Submodule libraries/FreeRTOS/lib/FreeRTOS-Kernel updated: f5fe79dacd...0b55ee70ad
@@ -26,6 +26,7 @@
|
||||
#define configSUPPORT_DYNAMIC_ALLOCATION 1
|
||||
#define configSUPPORT_STATIC_ALLOCATION 0
|
||||
#define configSTACK_DEPTH_TYPE uint32_t
|
||||
#define configUSE_TASK_PREEMPTION_DISABLE 1
|
||||
|
||||
#define configUSE_NEWLIB_REENTRANT 1
|
||||
#define configNEWLIB_REENTRANT_IS_DYNAMIC 0 /* Note that we have a different config option, portSET_IMPURE_PTR */
|
||||
@@ -34,8 +35,8 @@ extern void __register_impure_ptr(struct _reent *p);
|
||||
#define portSET_IMPURE_PTR(x) __register_impure_ptr(x)
|
||||
|
||||
/* Run time stats related definitions. */
|
||||
void vMainConfigureTimerForRunTimeStats( void );
|
||||
unsigned long ulMainGetRunTimeCounterValue( void );
|
||||
void vMainConfigureTimerForRunTimeStats(void);
|
||||
unsigned long ulMainGetRunTimeCounterValue(void);
|
||||
#define configGENERATE_RUN_TIME_STATS 1
|
||||
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() //vMainConfigureTimerForRunTimeStats()
|
||||
#define portGET_RUN_TIME_COUNTER_VALUE() ulMainGetRunTimeCounterValue()
|
||||
@@ -48,10 +49,10 @@ unsigned long ulMainGetRunTimeCounterValue( void );
|
||||
#define configUSE_TIMERS 1
|
||||
#define configTIMER_TASK_PRIORITY ( 2 )
|
||||
#define configTIMER_QUEUE_LENGTH 5
|
||||
#define configTIMER_TASK_STACK_DEPTH ( 80 )
|
||||
#define configTIMER_TASK_STACK_DEPTH ( 1024 )
|
||||
|
||||
/* Set the following definitions to 1 to include the API function, or zero
|
||||
to exclude the API function. */
|
||||
/* Set the following definitions to 1 to include the API function, or zero
|
||||
to exclude the API function. */
|
||||
#define INCLUDE_xTaskGetCurrentTaskHandle 1
|
||||
#define INCLUDE_vTaskPrioritySet 1
|
||||
#define INCLUDE_uxTaskPriorityGet 1
|
||||
@@ -62,10 +63,10 @@ to exclude the API function. */
|
||||
#define INCLUDE_vTaskDelay 1
|
||||
#define INCLUDE_eTaskGetState 1
|
||||
|
||||
/* This demo makes use of one or more example stats formatting functions. These
|
||||
format the raw data provided by the uxTaskGetSystemState() function in to human
|
||||
readable ASCII form. See the notes in the implementation of vTaskList() within
|
||||
FreeRTOS/Source/tasks.c for limitations. */
|
||||
/* This demo makes use of one or more example stats formatting functions. These
|
||||
format the raw data provided by the uxTaskGetSystemState() function in to human
|
||||
readable ASCII form. See the notes in the implementation of vTaskList() within
|
||||
FreeRTOS/Source/tasks.c for limitations. */
|
||||
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
|
||||
|
||||
#define configUSE_MUTEXES 1
|
||||
@@ -73,50 +74,50 @@ FreeRTOS/Source/tasks.c for limitations. */
|
||||
#define INCLUDE_xTaskGetIdleTaskHandle 1
|
||||
#define configUSE_MALLOC_FAILED_HOOK 1
|
||||
#define configCHECK_FOR_STACK_OVERFLOW 2
|
||||
/* Normal assert() semantics without relying on the provision of an assert.h
|
||||
header file. */
|
||||
/* Normal assert() semantics without relying on the provision of an assert.h
|
||||
header file. */
|
||||
|
||||
/* Cortex-M specific definitions. */
|
||||
#undef __NVIC_PRIO_BITS
|
||||
#ifdef __NVIC_PRIO_BITS
|
||||
/* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
|
||||
#define configPRIO_BITS __NVIC_PRIO_BITS
|
||||
/* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
|
||||
#define configPRIO_BITS __NVIC_PRIO_BITS
|
||||
#else
|
||||
#define configPRIO_BITS 3 /* 8 priority levels */
|
||||
#define configPRIO_BITS 3 /* 8 priority levels */
|
||||
#endif
|
||||
|
||||
/* The lowest interrupt priority that can be used in a call to a "set priority"
|
||||
function. */
|
||||
/* The lowest interrupt priority that can be used in a call to a "set priority"
|
||||
function. */
|
||||
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0x7
|
||||
|
||||
/* The highest interrupt priority that can be used by any interrupt service
|
||||
routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL
|
||||
INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
|
||||
PRIORITY THAN THIS! (higher priorities are lower numeric values. */
|
||||
/* The highest interrupt priority that can be used by any interrupt service
|
||||
routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL
|
||||
INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
|
||||
PRIORITY THAN THIS! (higher priorities are lower numeric values. */
|
||||
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
|
||||
|
||||
/* Interrupt priorities used by the kernel port layer itself. These are generic
|
||||
to all Cortex-M ports, and do not rely on any particular library functions. */
|
||||
/* Interrupt priorities used by the kernel port layer itself. These are generic
|
||||
to all Cortex-M ports, and do not rely on any particular library functions. */
|
||||
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
|
||||
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
|
||||
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
|
||||
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
|
||||
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
|
||||
|
||||
extern void rtosFatalError(void);
|
||||
#define configASSERT( x ) \
|
||||
if( ( x ) == 0 ) { portDISABLE_INTERRUPTS(); rtosFatalError(); }
|
||||
|
||||
/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
|
||||
standard names - or at least those used in the unmodified vector table. */
|
||||
/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
|
||||
standard names - or at least those used in the unmodified vector table. */
|
||||
//#define xPortPendSVHandler PendSV_Handler
|
||||
//#define xPortSysTickHandler SysTick_Handler
|
||||
//#define vPortSVCHandler SVC_Handler
|
||||
|
||||
/* The size of the global output buffer that is available for use when there
|
||||
are multiple command interpreters running at once (for example, one on a UART
|
||||
and one on TCP/IP). This is done to prevent an output buffer being defined by
|
||||
each implementation - which would waste RAM. In this case, there is only one
|
||||
command interpreter running. */
|
||||
/* The size of the global output buffer that is available for use when there
|
||||
are multiple command interpreters running at once (for example, one on a UART
|
||||
and one on TCP/IP). This is done to prevent an output buffer being defined by
|
||||
each implementation - which would waste RAM. In this case, there is only one
|
||||
command interpreter running. */
|
||||
#define configCOMMAND_INT_MAX_OUTPUT_SIZE 2048
|
||||
|
||||
|
||||
|
||||
@@ -1,35 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Phillip Stevens All Rights Reserved.
|
||||
* Modifications by Earle F. Philhower, III, for Arduino-Pico
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* This file is NOT part of the FreeRTOS distribution.
|
||||
*
|
||||
*/
|
||||
Copyright (C) 2021 Phillip Stevens All Rights Reserved.
|
||||
Modifications by Earle F. Philhower, III, for Arduino-Pico
|
||||
|
||||
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.
|
||||
|
||||
|
||||
This file is NOT part of the FreeRTOS distribution.
|
||||
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
|
||||
/* Arduino Core includes */
|
||||
#include <Arduino.h>
|
||||
#include <RP2040USB.h>
|
||||
#include "tusb.h"
|
||||
#define USB_TASK_IRQ 31
|
||||
|
||||
|
||||
/* Raspberry PI Pico includes */
|
||||
@@ -43,8 +42,7 @@
|
||||
|
||||
/*-----------------------------------------------------------*/
|
||||
|
||||
void initFreeRTOS(void)
|
||||
{
|
||||
void initFreeRTOS(void) {
|
||||
}
|
||||
|
||||
extern void setup() __attribute__((weak));
|
||||
@@ -55,8 +53,7 @@ extern void loop1() __attribute__((weak));
|
||||
extern void __loop();
|
||||
volatile bool __usbInitted = false;
|
||||
|
||||
static void __core0(void *params)
|
||||
{
|
||||
static void __core0(void *params) {
|
||||
(void) params;
|
||||
#ifndef NO_USB
|
||||
while (!__usbInitted) {
|
||||
@@ -78,8 +75,7 @@ static void __core0(void *params)
|
||||
}
|
||||
}
|
||||
|
||||
static void __core1(void *params)
|
||||
{
|
||||
static void __core1(void *params) {
|
||||
(void) params;
|
||||
#ifndef NO_USB
|
||||
while (!__usbInitted) {
|
||||
@@ -98,7 +94,7 @@ static void __core1(void *params)
|
||||
vTaskDelay(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void delay(unsigned long ms) {
|
||||
vTaskDelay(ms / portTICK_PERIOD_MS);
|
||||
@@ -112,17 +108,16 @@ extern mutex_t __usb_mutex;
|
||||
static TaskHandle_t __usbTask;
|
||||
static void __usb(void *param);
|
||||
|
||||
void startFreeRTOS(void)
|
||||
{
|
||||
void startFreeRTOS(void) {
|
||||
|
||||
TaskHandle_t c0;
|
||||
xTaskCreate(__core0, "CORE0", 1024, 0, configMAX_PRIORITIES / 2, &c0);
|
||||
vTaskCoreAffinitySet( c0, 1 << 0 );
|
||||
vTaskCoreAffinitySet(c0, 1 << 0);
|
||||
|
||||
if (setup1 || loop1) {
|
||||
TaskHandle_t c1;
|
||||
xTaskCreate(__core1, "CORE1", 1024, 0, configMAX_PRIORITIES / 2, &c1);
|
||||
vTaskCoreAffinitySet( c1, 1 << 1 );
|
||||
vTaskCoreAffinitySet(c1, 1 << 1);
|
||||
}
|
||||
|
||||
// Initialise and run the freeRTOS scheduler. Execution should never return here.
|
||||
@@ -136,33 +131,30 @@ void startFreeRTOS(void)
|
||||
|
||||
/*-----------------------------------------------------------*/
|
||||
|
||||
void prvDisableInterrupts()
|
||||
{
|
||||
void prvDisableInterrupts() {
|
||||
portDISABLE_INTERRUPTS();
|
||||
}
|
||||
|
||||
void prvEnableInterrupts()
|
||||
{
|
||||
void prvEnableInterrupts() {
|
||||
portENABLE_INTERRUPTS();
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------*/
|
||||
#if ( configUSE_IDLE_HOOK == 1 )
|
||||
/*
|
||||
* Call the user defined loop() function from within the idle task.
|
||||
* This allows the application designer to add background functionality
|
||||
* without the overhead of a separate task.
|
||||
*
|
||||
* NOTE: vApplicationIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES, CALL A FUNCTION THAT MIGHT BLOCK.
|
||||
*
|
||||
*/
|
||||
Call the user defined loop() function from within the idle task.
|
||||
This allows the application designer to add background functionality
|
||||
without the overhead of a separate task.
|
||||
|
||||
NOTE: vApplicationIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES, CALL A FUNCTION THAT MIGHT BLOCK.
|
||||
|
||||
*/
|
||||
|
||||
extern "C"
|
||||
void vApplicationIdleHook( void ) __attribute__((weak));
|
||||
void vApplicationIdleHook(void) __attribute__((weak));
|
||||
|
||||
|
||||
void vApplicationIdleHook( void )
|
||||
{
|
||||
void vApplicationIdleHook(void) {
|
||||
__wfe(); // Low power idle if nothing to do...
|
||||
}
|
||||
|
||||
@@ -171,22 +163,21 @@ void vApplicationIdleHook( void )
|
||||
|
||||
#if ( configUSE_MINIMAL_IDLE_HOOK == 1 )
|
||||
/*
|
||||
* Call the user defined minimalIdle() function from within the idle task.
|
||||
* This allows the application designer to add background functionality
|
||||
* without the overhead of a separate task.
|
||||
*
|
||||
* NOTE: vApplicationMinimalIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES, CALL A FUNCTION THAT MIGHT BLOCK.
|
||||
*
|
||||
*/
|
||||
void minimalIdle( void ) __attribute__((weak));
|
||||
Call the user defined minimalIdle() function from within the idle task.
|
||||
This allows the application designer to add background functionality
|
||||
without the overhead of a separate task.
|
||||
|
||||
NOTE: vApplicationMinimalIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES, CALL A FUNCTION THAT MIGHT BLOCK.
|
||||
|
||||
*/
|
||||
void minimalIdle(void) __attribute__((weak));
|
||||
void minimalIdle() {} //Empty minimalIdle function
|
||||
|
||||
extern "C"
|
||||
void vApplicationMinimalIdleHook( void ) __attribute__((weak));
|
||||
void vApplicationMinimalIdleHook(void) __attribute__((weak));
|
||||
|
||||
void vApplicationMinimalIdleHook( void )
|
||||
{
|
||||
minimalIdle();
|
||||
void vApplicationMinimalIdleHook(void) {
|
||||
minimalIdle();
|
||||
}
|
||||
|
||||
#endif /* configUSE_MINIMAL_IDLE_HOOK == 1 */
|
||||
@@ -194,22 +185,21 @@ void vApplicationMinimalIdleHook( void )
|
||||
|
||||
#if ( configUSE_TICK_HOOK == 1 )
|
||||
/*
|
||||
* Call the user defined minimalIdle() function from within the idle task.
|
||||
* This allows the application designer to add background functionality
|
||||
* without the overhead of a separate task.
|
||||
*
|
||||
* NOTE: vApplicationMinimalIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES, CALL A FUNCTION THAT MIGHT BLOCK.
|
||||
*
|
||||
*/
|
||||
void tick( void ) __attribute__((weak));
|
||||
Call the user defined minimalIdle() function from within the idle task.
|
||||
This allows the application designer to add background functionality
|
||||
without the overhead of a separate task.
|
||||
|
||||
NOTE: vApplicationMinimalIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES, CALL A FUNCTION THAT MIGHT BLOCK.
|
||||
|
||||
*/
|
||||
void tick(void) __attribute__((weak));
|
||||
void tick() {} //Empty minimalIdle function
|
||||
|
||||
extern "C"
|
||||
void vApplicationTickHook( void ) __attribute__((weak));
|
||||
void vApplicationTickHook(void) __attribute__((weak));
|
||||
|
||||
void vApplicationTickHook( void )
|
||||
{
|
||||
tick();
|
||||
void vApplicationTickHook(void) {
|
||||
tick();
|
||||
}
|
||||
|
||||
#endif /* configUSE_TICK_HOOK == 1 */
|
||||
@@ -218,37 +208,33 @@ void vApplicationTickHook( void )
|
||||
#if ( configUSE_MALLOC_FAILED_HOOK == 1 || configCHECK_FOR_STACK_OVERFLOW >= 1 || configDEFAULT_ASSERT == 1 )
|
||||
|
||||
/**
|
||||
* Private function to enable board led to use it in application hooks
|
||||
*/
|
||||
void prvSetMainLedOn( void )
|
||||
{
|
||||
gpio_init(LED_BUILTIN);
|
||||
gpio_set_dir(LED_BUILTIN, true);
|
||||
gpio_put(LED_BUILTIN, true);
|
||||
Private function to enable board led to use it in application hooks
|
||||
*/
|
||||
void prvSetMainLedOn(void) {
|
||||
gpio_init(LED_BUILTIN);
|
||||
gpio_set_dir(LED_BUILTIN, true);
|
||||
gpio_put(LED_BUILTIN, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private function to blink board led to use it in application hooks
|
||||
*/
|
||||
void prvBlinkMainLed( void )
|
||||
{
|
||||
gpio_put(LED_BUILTIN, !gpio_get(LED_BUILTIN));
|
||||
Private function to blink board led to use it in application hooks
|
||||
*/
|
||||
void prvBlinkMainLed(void) {
|
||||
gpio_put(LED_BUILTIN, !gpio_get(LED_BUILTIN));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*---------------------------------------------------------------------------*\
|
||||
Usage:
|
||||
/* ---------------------------------------------------------------------------*\
|
||||
Usage:
|
||||
called on fatal error (interrupts disabled already)
|
||||
\*---------------------------------------------------------------------------*/
|
||||
\*---------------------------------------------------------------------------*/
|
||||
extern "C"
|
||||
void rtosFatalError(void)
|
||||
{
|
||||
void rtosFatalError(void) {
|
||||
prvSetMainLedOn(); // Main LED on.
|
||||
|
||||
for(;;)
|
||||
{
|
||||
// Main LED slow flash
|
||||
|
||||
for (;;) {
|
||||
// Main LED slow flash
|
||||
sleep_ms(100);
|
||||
prvBlinkMainLed();
|
||||
sleep_ms(2000);
|
||||
@@ -257,30 +243,28 @@ void rtosFatalError(void)
|
||||
}
|
||||
|
||||
#if ( configUSE_MALLOC_FAILED_HOOK == 1 )
|
||||
/*---------------------------------------------------------------------------*\
|
||||
Usage:
|
||||
/* ---------------------------------------------------------------------------*\
|
||||
Usage:
|
||||
called by task system when a malloc failure is noticed
|
||||
Description:
|
||||
Description:
|
||||
Malloc failure handler -- Shut down all interrupts, send serious complaint
|
||||
to command port. FAST Blink on main LED.
|
||||
Arguments:
|
||||
Arguments:
|
||||
pxTask - pointer to task handle
|
||||
pcTaskName - pointer to task name
|
||||
Results:
|
||||
Results:
|
||||
<none>
|
||||
Notes:
|
||||
Notes:
|
||||
This routine will never return.
|
||||
This routine is referenced in the task.c file of FreeRTOS as an extern.
|
||||
\*---------------------------------------------------------------------------*/
|
||||
\*---------------------------------------------------------------------------*/
|
||||
extern "C"
|
||||
void vApplicationMallocFailedHook( void ) __attribute__((weak));
|
||||
void vApplicationMallocFailedHook(void) __attribute__((weak));
|
||||
|
||||
void vApplicationMallocFailedHook( void )
|
||||
{
|
||||
void vApplicationMallocFailedHook(void) {
|
||||
prvSetMainLedOn(); // Main LED on.
|
||||
|
||||
for(;;)
|
||||
{
|
||||
|
||||
for (;;) {
|
||||
sleep_ms(50);
|
||||
prvBlinkMainLed(); // Main LED fast blink.
|
||||
}
|
||||
@@ -293,16 +277,14 @@ void vApplicationMallocFailedHook( void )
|
||||
#if ( configCHECK_FOR_STACK_OVERFLOW >= 1 )
|
||||
|
||||
extern "C"
|
||||
void vApplicationStackOverflowHook( TaskHandle_t xTask,
|
||||
char * pcTaskName ) __attribute__((weak));
|
||||
void vApplicationStackOverflowHook(TaskHandle_t xTask,
|
||||
char * pcTaskName) __attribute__((weak));
|
||||
|
||||
void vApplicationStackOverflowHook( TaskHandle_t xTask __attribute__((unused)),
|
||||
char * pcTaskName __attribute__((unused)) )
|
||||
{
|
||||
void vApplicationStackOverflowHook(TaskHandle_t xTask __attribute__((unused)),
|
||||
char * pcTaskName __attribute__((unused))) {
|
||||
prvSetMainLedOn(); // Main LED on.
|
||||
|
||||
for(;;)
|
||||
{
|
||||
for (;;) {
|
||||
sleep_ms(2000);
|
||||
prvBlinkMainLed(); // Main LED slow blink.
|
||||
}
|
||||
@@ -314,14 +296,13 @@ void vApplicationStackOverflowHook( TaskHandle_t xTask __attribute__((unused)),
|
||||
#if ( configSUPPORT_STATIC_ALLOCATION >= 1 )
|
||||
|
||||
extern "C"
|
||||
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
|
||||
StackType_t ** ppxIdleTaskStackBuffer,
|
||||
configSTACK_DEPTH_TYPE * pulIdleTaskStackSize ) __attribute__((weak));
|
||||
void vApplicationGetIdleTaskMemory(StaticTask_t ** ppxIdleTaskTCBBuffer,
|
||||
StackType_t ** ppxIdleTaskStackBuffer,
|
||||
configSTACK_DEPTH_TYPE * pulIdleTaskStackSize) __attribute__((weak));
|
||||
|
||||
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
|
||||
StackType_t ** ppxIdleTaskStackBuffer,
|
||||
configSTACK_DEPTH_TYPE * pulIdleTaskStackSize )
|
||||
{
|
||||
void vApplicationGetIdleTaskMemory(StaticTask_t ** ppxIdleTaskTCBBuffer,
|
||||
StackType_t ** ppxIdleTaskStackBuffer,
|
||||
configSTACK_DEPTH_TYPE * pulIdleTaskStackSize) {
|
||||
static StaticTask_t xIdleTaskTCB;
|
||||
static StackType_t uxIdleTaskStack[ configMINIMAL_STACK_SIZE ];
|
||||
|
||||
@@ -333,14 +314,13 @@ void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
|
||||
#if ( configUSE_TIMERS >= 1 )
|
||||
|
||||
extern "C"
|
||||
void vApplicationGetTimerTaskMemory( StaticTask_t ** ppxTimerTaskTCBBuffer,
|
||||
StackType_t ** ppxTimerTaskStackBuffer,
|
||||
configSTACK_DEPTH_TYPE * pulTimerTaskStackSize ) __attribute__((weak));
|
||||
void vApplicationGetTimerTaskMemory(StaticTask_t ** ppxTimerTaskTCBBuffer,
|
||||
StackType_t ** ppxTimerTaskStackBuffer,
|
||||
configSTACK_DEPTH_TYPE * pulTimerTaskStackSize) __attribute__((weak));
|
||||
|
||||
void vApplicationGetTimerTaskMemory( StaticTask_t ** ppxTimerTaskTCBBuffer,
|
||||
StackType_t ** ppxTimerTaskStackBuffer,
|
||||
configSTACK_DEPTH_TYPE * pulTimerTaskStackSize )
|
||||
{
|
||||
void vApplicationGetTimerTaskMemory(StaticTask_t ** ppxTimerTaskTCBBuffer,
|
||||
StackType_t ** ppxTimerTaskStackBuffer,
|
||||
configSTACK_DEPTH_TYPE * pulTimerTaskStackSize) {
|
||||
static StaticTask_t xTimerTaskTCB;
|
||||
static StackType_t uxTimerTaskStack[ configTIMER_TASK_STACK_DEPTH ];
|
||||
|
||||
@@ -354,8 +334,8 @@ void vApplicationGetTimerTaskMemory( StaticTask_t ** ppxTimerTaskTCBBuffer,
|
||||
#endif /* configSUPPORT_STATIC_ALLOCATION >= 1 */
|
||||
|
||||
/**
|
||||
* configASSERT default implementation
|
||||
*/
|
||||
configASSERT default implementation
|
||||
*/
|
||||
#if configDEFAULT_ASSERT == 1
|
||||
|
||||
extern "C"
|
||||
@@ -364,8 +344,7 @@ void vApplicationAssertHook() {
|
||||
taskDISABLE_INTERRUPTS(); // Disable task interrupts
|
||||
|
||||
prvSetMainLedOn(); // Main LED on.
|
||||
for(;;)
|
||||
{
|
||||
for (;;) {
|
||||
sleep_ms(100);
|
||||
prvBlinkMainLed(); // Led off.
|
||||
|
||||
@@ -382,19 +361,10 @@ void vApplicationAssertHook() {
|
||||
#endif
|
||||
|
||||
|
||||
static void __usb_irq() {
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
vTaskNotifyGiveFromISR( __usbTask, &xHigherPriorityTaskWoken );
|
||||
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
|
||||
}
|
||||
|
||||
static void __usb(void *param)
|
||||
{
|
||||
static void __usb(void *param) {
|
||||
(void) param;
|
||||
|
||||
tusb_init();
|
||||
irq_set_exclusive_handler(USB_TASK_IRQ, __usb_irq);
|
||||
irq_set_enabled(USB_TASK_IRQ, true);
|
||||
|
||||
Serial.begin(115200);
|
||||
|
||||
@@ -412,8 +382,7 @@ static void __usb(void *param)
|
||||
extern void __SetupDescHIDReport();
|
||||
extern void __SetupUSBDescriptor();
|
||||
|
||||
void __USBStart()
|
||||
{
|
||||
void __USBStart() {
|
||||
mutex_init(&__usb_mutex);
|
||||
|
||||
__SetupDescHIDReport();
|
||||
@@ -421,5 +390,5 @@ void __USBStart()
|
||||
|
||||
// Make highest prio and locked to core 0
|
||||
xTaskCreate(__usb, "USB", 256, 0, configMAX_PRIORITIES - 1, &__usbTask);
|
||||
vTaskCoreAffinitySet( __usbTask, 1 << 0 );
|
||||
vTaskCoreAffinitySet(__usbTask, 1 << 0);
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ bool I2S::setBuffers(size_t buffers, size_t bufferWords, int32_t silenceSample)
|
||||
bool I2S::setFrequency(int newFreq) {
|
||||
_freq = newFreq;
|
||||
if (_running) {
|
||||
float bitClk = _freq * _bps * 2.0;
|
||||
float bitClk = _freq * _bps * 2.0 /* channels */ * 2.0 /* edges per clock */;
|
||||
pio_sm_set_clkdiv(_pio, _sm, (float)clock_get_hz(clk_sys) / bitClk);
|
||||
}
|
||||
return true;
|
||||
|
||||
Submodule
+1
Submodule libraries/Joystick added at af745f3e62
@@ -0,0 +1,61 @@
|
||||
ESP8266 Multicast DNS
|
||||
=====================
|
||||
|
||||
A port of CC3000 Multicast DNS library (version 1.1)
|
||||
|
||||
This is a simple implementation of multicast DNS query support for an
|
||||
Arduino running on ESP8266 chip. Only support for resolving address
|
||||
queries is currently implemented.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
- ESP8266WiFi library
|
||||
- MDNS support in your operating system/client machines:
|
||||
- For Mac OSX support is built in through Bonjour already.
|
||||
- For Linux, install `Avahi <http://avahi.org/>`__.
|
||||
- For Windows, install
|
||||
`Bonjour <http://www.apple.com/support/bonjour/>`__.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
1. Download this repository as a zip (button on the right) and follow
|
||||
`these instructions to install into
|
||||
Arduino <http://arduino.cc/en/Guide/Libraries>`__.
|
||||
2. Include the ESP8266mDNS library in the sketch.
|
||||
3. Call MDNS.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'). Optionally provide the IP address to advertise and time
|
||||
to live (in seconds) for the DNS record -- the default is 1 hour.
|
||||
4. To advertise DNS-SD services, call MDNS.addService(service, proto,
|
||||
port), where service and proto are strings with service and protocol
|
||||
name (e.g. "http", "tcp"), and port is an integer port number for
|
||||
this service (e.g. 80).
|
||||
|
||||
See the included MDNS + HTTP server sketch for a full example.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
Copyright (c) 2013 Tony DiCola (tony@tonydicola.com) ESP8266 port (c)
|
||||
2015 Ivan Grokhotkov (ivan@esp8266.com)
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,25 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map For Ultrasound
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
LEAmDNS KEYWORD1
|
||||
MDNSResponder KEYWORD1
|
||||
MDNS KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
begin KEYWORD2
|
||||
update KEYWORD2
|
||||
addService KEYWORD2
|
||||
enableArduino KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
name=LEAmDNS
|
||||
version=1.2
|
||||
author=multiple, see files
|
||||
maintainer=LaborEtArs
|
||||
sentence=Creates a mDNS responder.
|
||||
paragraph=Creates a mDNS responder to ensure host domain uniqueness in local networks and to allow for mDNS service discovery and announcement.
|
||||
category=Communication
|
||||
url=https://github.com/LaborEtArs/ESP8266mDNS
|
||||
architectures=rp2040
|
||||
dot_a_linkage=true
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
|
||||
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.
|
||||
|
||||
*/
|
||||
#include <ESP8266mDNS.h>
|
||||
|
||||
/*
|
||||
MDNS responder global instance
|
||||
|
||||
Class type that is instantiated depends on the type mapping in ESP8266mDNS.h
|
||||
*/
|
||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS)
|
||||
MDNSResponder MDNS;
|
||||
#endif
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
ESP8266mDNS.h - mDNSResponder for ESP8266 family
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
mDNS implementation, that supports many mDNS features like:
|
||||
- Presenting a DNS-SD service to interested observers, eg. a http server by presenting
|
||||
_http._tcp service
|
||||
- Support for multi-level compressed names in input; in output only a very simple one-leven
|
||||
full-name compression is implemented
|
||||
- Probing host and service domains for uniqueness in the local network
|
||||
- Tiebreaking while probing is supported in a very minimalistic way (the 'higher' IP address
|
||||
wins the tiebreak)
|
||||
- Announcing available services after successful probing
|
||||
- Using fixed service TXT items or
|
||||
- Using dynamic service TXT items for presented services (via callback)
|
||||
- Remove services (and un-announcing them to the observers by sending goodbye-messages)
|
||||
- Static queries for DNS-SD services (creating a fixed answer set after a certain timeout
|
||||
period)
|
||||
- Dynamic queries for DNS-SD services with cached and updated answers and user notifications
|
||||
- Support for multi-homed client host domains
|
||||
|
||||
See 'src/LEAmDNS.h' for implementation details, configuration and usage information.
|
||||
See 'examples/LEAmDNS/' for examples of the new features.
|
||||
|
||||
LEAmDNS is expected to be compatible with the original ESP8266mDNS implementation, and it can be
|
||||
used as a drop-in replacement in existing projects.
|
||||
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "LEAmDNS.h" // LEA
|
||||
|
||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS)
|
||||
// Maps the implementation to use to the global namespace type
|
||||
using MDNSResponder = esp8266::MDNSImplementation::MDNSResponder; // LEA
|
||||
|
||||
extern MDNSResponder MDNS;
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,671 @@
|
||||
/*
|
||||
LEAmDNS_Helpers.cpp
|
||||
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
#include <lwip/igmp.h>
|
||||
#include <stdlib_noniso.h> // strrstr()
|
||||
|
||||
#include "ESP8266mDNS.h"
|
||||
#include "LEAmDNS_lwIPdefs.h"
|
||||
#include "LEAmDNS_Priv.h"
|
||||
|
||||
namespace esp8266 {
|
||||
|
||||
/*
|
||||
LEAmDNS
|
||||
*/
|
||||
namespace MDNSImplementation {
|
||||
|
||||
/**
|
||||
HELPERS
|
||||
*/
|
||||
|
||||
/*
|
||||
MDNSResponder::indexDomain (static)
|
||||
|
||||
Updates the given domain 'p_rpcHostname' by appending a delimiter and an index number.
|
||||
|
||||
If the given domain already hasa numeric index (after the given delimiter), this index
|
||||
incremented. If not, the delimiter and index '2' is added.
|
||||
|
||||
If 'p_rpcHostname' is empty (==0), the given default name 'p_pcDefaultHostname' is used,
|
||||
if no default is given, 'esp8266' is used.
|
||||
|
||||
*/
|
||||
/*static*/ bool MDNSResponder::indexDomain(char*& p_rpcDomain,
|
||||
const char* p_pcDivider /*= "-"*/,
|
||||
const char* p_pcDefaultDomain /*= 0*/) {
|
||||
bool bResult = false;
|
||||
|
||||
// Ensure a divider exists; use '-' as default
|
||||
const char* pcDivider = (p_pcDivider ? : "-");
|
||||
|
||||
if (p_rpcDomain) {
|
||||
const char* pFoundDivider = strrstr(p_rpcDomain, pcDivider);
|
||||
if (pFoundDivider) { // maybe already extended
|
||||
char* pEnd = 0;
|
||||
unsigned long ulIndex = strtoul((pFoundDivider + strlen(pcDivider)), &pEnd, 10);
|
||||
if ((ulIndex) && ((pEnd - p_rpcDomain) == (ptrdiff_t)strlen(p_rpcDomain))
|
||||
&& (!*pEnd)) { // Valid (old) index found
|
||||
char acIndexBuffer[16];
|
||||
sprintf(acIndexBuffer, "%lu", (++ulIndex));
|
||||
size_t stLength = ((pFoundDivider - p_rpcDomain + strlen(pcDivider))
|
||||
+ strlen(acIndexBuffer) + 1);
|
||||
char* pNewHostname = new char[stLength];
|
||||
if (pNewHostname) {
|
||||
memcpy(pNewHostname, p_rpcDomain,
|
||||
(pFoundDivider - p_rpcDomain + strlen(pcDivider)));
|
||||
pNewHostname[pFoundDivider - p_rpcDomain + strlen(pcDivider)] = 0;
|
||||
strcat(pNewHostname, acIndexBuffer);
|
||||
|
||||
delete[] p_rpcDomain;
|
||||
p_rpcDomain = pNewHostname;
|
||||
|
||||
bResult = true;
|
||||
} else {
|
||||
DEBUG_EX_ERR(DEBUG_OUTPUT.println(
|
||||
F("[MDNSResponder] indexDomain: FAILED to alloc new hostname!")););
|
||||
}
|
||||
} else {
|
||||
pFoundDivider = 0; // Flag the need to (base) extend the hostname
|
||||
}
|
||||
}
|
||||
|
||||
if (!pFoundDivider) // not yet extended (or failed to increment extension) -> start
|
||||
// indexing
|
||||
{
|
||||
size_t stLength = strlen(p_rpcDomain)
|
||||
+ (strlen(pcDivider) + 1 + 1); // Name + Divider + '2' + '\0'
|
||||
char* pNewHostname = new char[stLength];
|
||||
if (pNewHostname) {
|
||||
sprintf(pNewHostname, "%s%s2", p_rpcDomain, pcDivider);
|
||||
|
||||
delete[] p_rpcDomain;
|
||||
p_rpcDomain = pNewHostname;
|
||||
|
||||
bResult = true;
|
||||
} else {
|
||||
DEBUG_EX_ERR(DEBUG_OUTPUT.println(
|
||||
F("[MDNSResponder] indexDomain: FAILED to alloc new hostname!")););
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No given host domain, use base or default
|
||||
const char* cpcDefaultName = (p_pcDefaultDomain ? : "esp8266");
|
||||
|
||||
size_t stLength = strlen(cpcDefaultName) + 1; // '\0'
|
||||
p_rpcDomain = new char[stLength];
|
||||
if (p_rpcDomain) {
|
||||
strncpy(p_rpcDomain, cpcDefaultName, stLength);
|
||||
bResult = true;
|
||||
} else {
|
||||
DEBUG_EX_ERR(DEBUG_OUTPUT.println(
|
||||
F("[MDNSResponder] indexDomain: FAILED to alloc new hostname!")););
|
||||
}
|
||||
}
|
||||
DEBUG_EX_INFO(
|
||||
DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] indexDomain: %s\n"), p_rpcDomain););
|
||||
return bResult;
|
||||
}
|
||||
|
||||
/*
|
||||
UDP CONTEXT
|
||||
*/
|
||||
|
||||
bool MDNSResponder::_callProcess(void) {
|
||||
DEBUG_EX_INFO(
|
||||
DEBUG_OUTPUT.printf("[MDNSResponder] _callProcess (%lu, triggered by: %s)\n", millis(),
|
||||
IPAddress(m_pUDPContext->getRemoteAddress()).toString().c_str()););
|
||||
|
||||
return _process(false);
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::_allocUDPContext
|
||||
|
||||
(Re-)Creates the one-and-only UDP context for the MDNS responder.
|
||||
The context is added to the 'multicast'-group and listens to the MDNS port (5353).
|
||||
The travel-distance for multicast messages is set to 1 (local, via MDNS_MULTICAST_TTL).
|
||||
Messages are received via the MDNSResponder '_update' function. CAUTION: This function
|
||||
is called from the WiFi stack side of the ESP stack system.
|
||||
|
||||
*/
|
||||
bool MDNSResponder::_allocUDPContext(void) {
|
||||
DEBUG_EX_INFO(DEBUG_OUTPUT.println("[MDNSResponder] _allocUDPContext"););
|
||||
|
||||
_releaseUDPContext();
|
||||
_joinMulticastGroups();
|
||||
|
||||
m_pUDPContext = new UdpContext;
|
||||
m_pUDPContext->ref();
|
||||
|
||||
if (m_pUDPContext->listen(IP4_ADDR_ANY, DNS_MQUERY_PORT)) {
|
||||
m_pUDPContext->setMulticastTTL(MDNS_MULTICAST_TTL);
|
||||
m_pUDPContext->onRx(std::bind(&MDNSResponder::_callProcess, this));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::_releaseUDPContext
|
||||
*/
|
||||
bool MDNSResponder::_releaseUDPContext(void) {
|
||||
if (m_pUDPContext) {
|
||||
m_pUDPContext->unref();
|
||||
m_pUDPContext = 0;
|
||||
_leaveMulticastGroups();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
SERVICE QUERY
|
||||
*/
|
||||
|
||||
/*
|
||||
MDNSResponder::_allocServiceQuery
|
||||
*/
|
||||
MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_allocServiceQuery(void) {
|
||||
stcMDNSServiceQuery* pServiceQuery = new stcMDNSServiceQuery;
|
||||
if (pServiceQuery) {
|
||||
// Link to query list
|
||||
pServiceQuery->m_pNext = m_pServiceQueries;
|
||||
m_pServiceQueries = pServiceQuery;
|
||||
}
|
||||
return m_pServiceQueries;
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::_removeServiceQuery
|
||||
*/
|
||||
bool MDNSResponder::_removeServiceQuery(MDNSResponder::stcMDNSServiceQuery* p_pServiceQuery) {
|
||||
bool bResult = false;
|
||||
|
||||
if (p_pServiceQuery) {
|
||||
stcMDNSServiceQuery* pPred = m_pServiceQueries;
|
||||
while ((pPred) && (pPred->m_pNext != p_pServiceQuery)) {
|
||||
pPred = pPred->m_pNext;
|
||||
}
|
||||
if (pPred) {
|
||||
pPred->m_pNext = p_pServiceQuery->m_pNext;
|
||||
delete p_pServiceQuery;
|
||||
bResult = true;
|
||||
} else { // No predecessor
|
||||
if (m_pServiceQueries == p_pServiceQuery) {
|
||||
m_pServiceQueries = p_pServiceQuery->m_pNext;
|
||||
delete p_pServiceQuery;
|
||||
bResult = true;
|
||||
} else {
|
||||
DEBUG_EX_ERR(DEBUG_OUTPUT.println(
|
||||
"[MDNSResponder] _releaseServiceQuery: INVALID service query!"););
|
||||
}
|
||||
}
|
||||
}
|
||||
return bResult;
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::_removeLegacyServiceQuery
|
||||
*/
|
||||
bool MDNSResponder::_removeLegacyServiceQuery(void) {
|
||||
stcMDNSServiceQuery* pLegacyServiceQuery = _findLegacyServiceQuery();
|
||||
return (pLegacyServiceQuery ? _removeServiceQuery(pLegacyServiceQuery) : true);
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::_findServiceQuery
|
||||
|
||||
'Convert' hMDNSServiceQuery to stcMDNSServiceQuery* (ensure existence)
|
||||
|
||||
*/
|
||||
MDNSResponder::stcMDNSServiceQuery*
|
||||
MDNSResponder::_findServiceQuery(MDNSResponder::hMDNSServiceQuery p_hServiceQuery) {
|
||||
stcMDNSServiceQuery* pServiceQuery = m_pServiceQueries;
|
||||
while (pServiceQuery) {
|
||||
if ((hMDNSServiceQuery)pServiceQuery == p_hServiceQuery) {
|
||||
break;
|
||||
}
|
||||
pServiceQuery = pServiceQuery->m_pNext;
|
||||
}
|
||||
return pServiceQuery;
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::_findLegacyServiceQuery
|
||||
*/
|
||||
MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_findLegacyServiceQuery(void) {
|
||||
stcMDNSServiceQuery* pServiceQuery = m_pServiceQueries;
|
||||
while (pServiceQuery) {
|
||||
if (pServiceQuery->m_bLegacyQuery) {
|
||||
break;
|
||||
}
|
||||
pServiceQuery = pServiceQuery->m_pNext;
|
||||
}
|
||||
return pServiceQuery;
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::_releaseServiceQueries
|
||||
*/
|
||||
bool MDNSResponder::_releaseServiceQueries(void) {
|
||||
while (m_pServiceQueries) {
|
||||
stcMDNSServiceQuery* pNext = m_pServiceQueries->m_pNext;
|
||||
delete m_pServiceQueries;
|
||||
m_pServiceQueries = pNext;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::_findNextServiceQueryByServiceType
|
||||
*/
|
||||
MDNSResponder::stcMDNSServiceQuery* MDNSResponder::_findNextServiceQueryByServiceType(
|
||||
const stcMDNS_RRDomain& p_ServiceTypeDomain, const stcMDNSServiceQuery* p_pPrevServiceQuery) {
|
||||
stcMDNSServiceQuery* pMatchingServiceQuery = 0;
|
||||
|
||||
stcMDNSServiceQuery* pServiceQuery
|
||||
= (p_pPrevServiceQuery ? p_pPrevServiceQuery->m_pNext : m_pServiceQueries);
|
||||
while (pServiceQuery) {
|
||||
if (p_ServiceTypeDomain == pServiceQuery->m_ServiceTypeDomain) {
|
||||
pMatchingServiceQuery = pServiceQuery;
|
||||
break;
|
||||
}
|
||||
pServiceQuery = pServiceQuery->m_pNext;
|
||||
}
|
||||
return pMatchingServiceQuery;
|
||||
}
|
||||
|
||||
/*
|
||||
HOSTNAME
|
||||
*/
|
||||
|
||||
/*
|
||||
MDNSResponder::_setHostname
|
||||
*/
|
||||
bool MDNSResponder::_setHostname(const char* p_pcHostname) {
|
||||
// DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] _allocHostname (%s)\n"),
|
||||
// p_pcHostname););
|
||||
|
||||
bool bResult = false;
|
||||
|
||||
_releaseHostname();
|
||||
|
||||
size_t stLength = 0;
|
||||
if ((p_pcHostname)
|
||||
&& (MDNS_DOMAIN_LABEL_MAXLENGTH
|
||||
>= (stLength = strlen(p_pcHostname)))) { // char max size for a single label
|
||||
// Copy in hostname characters as lowercase
|
||||
if ((bResult = (0 != (m_pcHostname = new char[stLength + 1])))) {
|
||||
#ifdef MDNS_FORCE_LOWERCASE_HOSTNAME
|
||||
size_t i = 0;
|
||||
for (; i < stLength; ++i) {
|
||||
m_pcHostname[i]
|
||||
= (isupper(p_pcHostname[i]) ? tolower(p_pcHostname[i]) : p_pcHostname[i]);
|
||||
}
|
||||
m_pcHostname[i] = 0;
|
||||
#else
|
||||
strncpy(m_pcHostname, p_pcHostname, (stLength + 1));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return bResult;
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::_releaseHostname
|
||||
*/
|
||||
bool MDNSResponder::_releaseHostname(void) {
|
||||
if (m_pcHostname) {
|
||||
delete[] m_pcHostname;
|
||||
m_pcHostname = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
SERVICE
|
||||
*/
|
||||
|
||||
/*
|
||||
MDNSResponder::_allocService
|
||||
*/
|
||||
MDNSResponder::stcMDNSService* MDNSResponder::_allocService(const char* p_pcName,
|
||||
const char* p_pcService,
|
||||
const char* p_pcProtocol,
|
||||
uint16_t p_u16Port) {
|
||||
stcMDNSService* pService = 0;
|
||||
if (((!p_pcName) || (MDNS_DOMAIN_LABEL_MAXLENGTH >= strlen(p_pcName))) && (p_pcService)
|
||||
&& (MDNS_SERVICE_NAME_LENGTH >= strlen(p_pcService)) && (p_pcProtocol)
|
||||
&& (MDNS_SERVICE_PROTOCOL_LENGTH >= strlen(p_pcProtocol)) && (p_u16Port)
|
||||
&& (0 != (pService = new stcMDNSService))
|
||||
&& (pService->setName(p_pcName ? : m_pcHostname)) && (pService->setService(p_pcService))
|
||||
&& (pService->setProtocol(p_pcProtocol))) {
|
||||
pService->m_bAutoName = (0 == p_pcName);
|
||||
pService->m_u16Port = p_u16Port;
|
||||
|
||||
// Add to list (or start list)
|
||||
pService->m_pNext = m_pServices;
|
||||
m_pServices = pService;
|
||||
}
|
||||
return pService;
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::_releaseService
|
||||
*/
|
||||
bool MDNSResponder::_releaseService(MDNSResponder::stcMDNSService* p_pService) {
|
||||
bool bResult = false;
|
||||
|
||||
if (p_pService) {
|
||||
stcMDNSService* pPred = m_pServices;
|
||||
while ((pPred) && (pPred->m_pNext != p_pService)) {
|
||||
pPred = pPred->m_pNext;
|
||||
}
|
||||
if (pPred) {
|
||||
pPred->m_pNext = p_pService->m_pNext;
|
||||
delete p_pService;
|
||||
bResult = true;
|
||||
} else { // No predecessor
|
||||
if (m_pServices == p_pService) {
|
||||
m_pServices = p_pService->m_pNext;
|
||||
delete p_pService;
|
||||
bResult = true;
|
||||
} else {
|
||||
DEBUG_EX_ERR(
|
||||
DEBUG_OUTPUT.println("[MDNSResponder] _releaseService: INVALID service!"););
|
||||
}
|
||||
}
|
||||
}
|
||||
return bResult;
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::_releaseServices
|
||||
*/
|
||||
bool MDNSResponder::_releaseServices(void) {
|
||||
stcMDNSService* pService = m_pServices;
|
||||
while (pService) {
|
||||
_releaseService(pService);
|
||||
pService = m_pServices;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::_findService
|
||||
*/
|
||||
MDNSResponder::stcMDNSService* MDNSResponder::_findService(const char* p_pcName,
|
||||
const char* p_pcService,
|
||||
const char* p_pcProtocol) {
|
||||
stcMDNSService* pService = m_pServices;
|
||||
while (pService) {
|
||||
if ((0 == strcmp(pService->m_pcName, p_pcName))
|
||||
&& (0 == strcmp(pService->m_pcService, p_pcService))
|
||||
&& (0 == strcmp(pService->m_pcProtocol, p_pcProtocol))) {
|
||||
break;
|
||||
}
|
||||
pService = pService->m_pNext;
|
||||
}
|
||||
return pService;
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::_findService
|
||||
*/
|
||||
MDNSResponder::stcMDNSService*
|
||||
MDNSResponder::_findService(const MDNSResponder::hMDNSService p_hService) {
|
||||
stcMDNSService* pService = m_pServices;
|
||||
while (pService) {
|
||||
if (p_hService == (hMDNSService)pService) {
|
||||
break;
|
||||
}
|
||||
pService = pService->m_pNext;
|
||||
}
|
||||
return pService;
|
||||
}
|
||||
|
||||
/*
|
||||
SERVICE TXT
|
||||
*/
|
||||
|
||||
/*
|
||||
MDNSResponder::_allocServiceTxt
|
||||
*/
|
||||
MDNSResponder::stcMDNSServiceTxt*
|
||||
MDNSResponder::_allocServiceTxt(MDNSResponder::stcMDNSService* p_pService, const char* p_pcKey,
|
||||
const char* p_pcValue, bool p_bTemp) {
|
||||
stcMDNSServiceTxt* pTxt = 0;
|
||||
|
||||
if ((p_pService) && (p_pcKey)
|
||||
&& (MDNS_SERVICE_TXT_MAXLENGTH > (p_pService->m_Txts.length() + 1 + // Length byte
|
||||
(p_pcKey ? strlen(p_pcKey) : 0) + 1 + // '='
|
||||
(p_pcValue ? strlen(p_pcValue) : 0)))) {
|
||||
pTxt = new stcMDNSServiceTxt;
|
||||
if (pTxt) {
|
||||
size_t stLength = (p_pcKey ? strlen(p_pcKey) : 0);
|
||||
pTxt->m_pcKey = new char[stLength + 1];
|
||||
if (pTxt->m_pcKey) {
|
||||
strncpy(pTxt->m_pcKey, p_pcKey, stLength);
|
||||
pTxt->m_pcKey[stLength] = 0;
|
||||
}
|
||||
|
||||
if (p_pcValue) {
|
||||
stLength = (p_pcValue ? strlen(p_pcValue) : 0);
|
||||
pTxt->m_pcValue = new char[stLength + 1];
|
||||
if (pTxt->m_pcValue) {
|
||||
strncpy(pTxt->m_pcValue, p_pcValue, stLength);
|
||||
pTxt->m_pcValue[stLength] = 0;
|
||||
}
|
||||
}
|
||||
pTxt->m_bTemp = p_bTemp;
|
||||
|
||||
// Add to list (or start list)
|
||||
p_pService->m_Txts.add(pTxt);
|
||||
}
|
||||
}
|
||||
return pTxt;
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::_releaseServiceTxt
|
||||
*/
|
||||
bool MDNSResponder::_releaseServiceTxt(MDNSResponder::stcMDNSService* p_pService,
|
||||
MDNSResponder::stcMDNSServiceTxt* p_pTxt) {
|
||||
return ((p_pService) && (p_pTxt) && (p_pService->m_Txts.remove(p_pTxt)));
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::_updateServiceTxt
|
||||
*/
|
||||
MDNSResponder::stcMDNSServiceTxt*
|
||||
MDNSResponder::_updateServiceTxt(MDNSResponder::stcMDNSService* p_pService,
|
||||
MDNSResponder::stcMDNSServiceTxt* p_pTxt,
|
||||
const char* p_pcValue, bool p_bTemp) {
|
||||
if ((p_pService) && (p_pTxt)
|
||||
&& (MDNS_SERVICE_TXT_MAXLENGTH
|
||||
> (p_pService->m_Txts.length() - (p_pTxt->m_pcValue ? strlen(p_pTxt->m_pcValue) : 0)
|
||||
+ (p_pcValue ? strlen(p_pcValue) : 0)))) {
|
||||
p_pTxt->update(p_pcValue);
|
||||
p_pTxt->m_bTemp = p_bTemp;
|
||||
}
|
||||
return p_pTxt;
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::_findServiceTxt
|
||||
*/
|
||||
MDNSResponder::stcMDNSServiceTxt*
|
||||
MDNSResponder::_findServiceTxt(MDNSResponder::stcMDNSService* p_pService, const char* p_pcKey) {
|
||||
return (p_pService ? p_pService->m_Txts.find(p_pcKey) : 0);
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::_findServiceTxt
|
||||
*/
|
||||
MDNSResponder::stcMDNSServiceTxt*
|
||||
MDNSResponder::_findServiceTxt(MDNSResponder::stcMDNSService* p_pService, const hMDNSTxt p_hTxt) {
|
||||
return (((p_pService) && (p_hTxt)) ? p_pService->m_Txts.find((stcMDNSServiceTxt*)p_hTxt)
|
||||
: 0);
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::_addServiceTxt
|
||||
*/
|
||||
MDNSResponder::stcMDNSServiceTxt*
|
||||
MDNSResponder::_addServiceTxt(MDNSResponder::stcMDNSService* p_pService, const char* p_pcKey,
|
||||
const char* p_pcValue, bool p_bTemp) {
|
||||
stcMDNSServiceTxt* pResult = 0;
|
||||
|
||||
if ((p_pService) && (p_pcKey) && (strlen(p_pcKey))) {
|
||||
stcMDNSServiceTxt* pTxt = p_pService->m_Txts.find(p_pcKey);
|
||||
if (pTxt) {
|
||||
pResult = _updateServiceTxt(p_pService, pTxt, p_pcValue, p_bTemp);
|
||||
} else {
|
||||
pResult = _allocServiceTxt(p_pService, p_pcKey, p_pcValue, p_bTemp);
|
||||
}
|
||||
}
|
||||
return pResult;
|
||||
}
|
||||
|
||||
MDNSResponder::stcMDNSServiceTxt*
|
||||
MDNSResponder::_answerKeyValue(const hMDNSServiceQuery p_hServiceQuery,
|
||||
const uint32_t p_u32AnswerIndex) {
|
||||
stcMDNSServiceQuery* pServiceQuery = _findServiceQuery(p_hServiceQuery);
|
||||
stcMDNSServiceQuery::stcAnswer* pSQAnswer
|
||||
= (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0);
|
||||
// Fill m_pcTxts (if not already done)
|
||||
return (pSQAnswer) ? pSQAnswer->m_Txts.m_pTxts : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::_collectServiceTxts
|
||||
*/
|
||||
bool MDNSResponder::_collectServiceTxts(MDNSResponder::stcMDNSService& p_rService) {
|
||||
// Call Dynamic service callbacks
|
||||
if (m_fnServiceTxtCallback) {
|
||||
m_fnServiceTxtCallback((hMDNSService)&p_rService);
|
||||
}
|
||||
if (p_rService.m_fnTxtCallback) {
|
||||
p_rService.m_fnTxtCallback((hMDNSService)&p_rService);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::_releaseTempServiceTxts
|
||||
*/
|
||||
bool MDNSResponder::_releaseTempServiceTxts(MDNSResponder::stcMDNSService& p_rService) {
|
||||
return (p_rService.m_Txts.removeTempTxts());
|
||||
}
|
||||
|
||||
/*
|
||||
MISC
|
||||
*/
|
||||
|
||||
#ifdef DEBUG_ESP_MDNS_RESPONDER
|
||||
/*
|
||||
MDNSResponder::_printRRDomain
|
||||
*/
|
||||
bool MDNSResponder::_printRRDomain(const MDNSResponder::stcMDNS_RRDomain& p_RRDomain) const {
|
||||
// DEBUG_OUTPUT.printf_P(PSTR("Domain: "));
|
||||
|
||||
const char* pCursor = p_RRDomain.m_acName;
|
||||
uint8_t u8Length = *pCursor++;
|
||||
if (u8Length) {
|
||||
while (u8Length) {
|
||||
for (uint8_t u = 0; u < u8Length; ++u) {
|
||||
DEBUG_OUTPUT.printf_P(PSTR("%c"), *(pCursor++));
|
||||
}
|
||||
u8Length = *pCursor++;
|
||||
if (u8Length) {
|
||||
DEBUG_OUTPUT.printf_P(PSTR("."));
|
||||
}
|
||||
}
|
||||
} else { // empty domain
|
||||
DEBUG_OUTPUT.printf_P(PSTR("-empty-"));
|
||||
}
|
||||
// DEBUG_OUTPUT.printf_P(PSTR("\n"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::_printRRAnswer
|
||||
*/
|
||||
bool MDNSResponder::_printRRAnswer(const MDNSResponder::stcMDNS_RRAnswer& p_RRAnswer) const {
|
||||
DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] RRAnswer: "));
|
||||
_printRRDomain(p_RRAnswer.m_Header.m_Domain);
|
||||
DEBUG_OUTPUT.printf_P(PSTR(" Type:0x%04X Class:0x%04X TTL:%u, "),
|
||||
p_RRAnswer.m_Header.m_Attributes.m_u16Type,
|
||||
p_RRAnswer.m_Header.m_Attributes.m_u16Class, p_RRAnswer.m_u32TTL);
|
||||
switch (p_RRAnswer.m_Header.m_Attributes.m_u16Type
|
||||
& (~0x8000)) { // Topmost bit might carry 'cache flush' flag
|
||||
#ifdef MDNS_IP4_SUPPORT
|
||||
case DNS_RRTYPE_A:
|
||||
DEBUG_OUTPUT.printf_P(
|
||||
PSTR("A IP:%s"),
|
||||
((const stcMDNS_RRAnswerA*)&p_RRAnswer)->m_IPAddress.toString().c_str());
|
||||
break;
|
||||
#endif
|
||||
case DNS_RRTYPE_PTR:
|
||||
DEBUG_OUTPUT.printf_P(PSTR("PTR "));
|
||||
_printRRDomain(((const stcMDNS_RRAnswerPTR*)&p_RRAnswer)->m_PTRDomain);
|
||||
break;
|
||||
case DNS_RRTYPE_TXT: {
|
||||
size_t stTxtLength = ((const stcMDNS_RRAnswerTXT*)&p_RRAnswer)->m_Txts.c_strLength();
|
||||
char* pTxts = new char[stTxtLength];
|
||||
if (pTxts) {
|
||||
((/*const c_str()!!*/ stcMDNS_RRAnswerTXT*)&p_RRAnswer)->m_Txts.c_str(pTxts);
|
||||
DEBUG_OUTPUT.printf_P(PSTR("TXT(%zu) %s"), stTxtLength, pTxts);
|
||||
delete[] pTxts;
|
||||
}
|
||||
break;
|
||||
}
|
||||
#ifdef MDNS_IP6_SUPPORT
|
||||
case DNS_RRTYPE_AAAA:
|
||||
DEBUG_OUTPUT.printf_P(
|
||||
PSTR("AAAA IP:%s"),
|
||||
((stcMDNS_RRAnswerA*&)p_rpRRAnswer)->m_IPAddress.toString().c_str());
|
||||
break;
|
||||
#endif
|
||||
case DNS_RRTYPE_SRV:
|
||||
DEBUG_OUTPUT.printf_P(PSTR("SRV Port:%u "),
|
||||
((const stcMDNS_RRAnswerSRV*)&p_RRAnswer)->m_u16Port);
|
||||
_printRRDomain(((const stcMDNS_RRAnswerSRV*)&p_RRAnswer)->m_SRVDomain);
|
||||
break;
|
||||
default:
|
||||
DEBUG_OUTPUT.printf_P(PSTR("generic "));
|
||||
break;
|
||||
}
|
||||
DEBUG_OUTPUT.printf_P(PSTR("\n"));
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace MDNSImplementation
|
||||
|
||||
} // namespace esp8266
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
LEAmDNS_Priv.h
|
||||
|
||||
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 MDNS_PRIV_H
|
||||
#define MDNS_PRIV_H
|
||||
|
||||
namespace esp8266 {
|
||||
|
||||
/*
|
||||
LEAmDNS
|
||||
*/
|
||||
|
||||
namespace MDNSImplementation {
|
||||
|
||||
// Enable class debug functions
|
||||
#define ESP_8266_MDNS_INCLUDE
|
||||
//#define DEBUG_ESP_MDNS_RESPONDER
|
||||
|
||||
#if !defined(DEBUG_ESP_MDNS_RESPONDER) && defined(DEBUG_ESP_MDNS)
|
||||
#define DEBUG_ESP_MDNS_RESPONDER
|
||||
#endif
|
||||
|
||||
//
|
||||
// If ENABLE_ESP_MDNS_RESPONDER_PASSIV_MODE is defined, the mDNS responder ignores a successful
|
||||
// probing This allows to drive the responder in a environment, where 'update()' isn't called in the
|
||||
// loop
|
||||
//#define ENABLE_ESP_MDNS_RESPONDER_PASSIV_MODE
|
||||
|
||||
// Enable/disable debug trace macros
|
||||
#if defined(DEBUG_ESP_PORT) && defined(DEBUG_ESP_MDNS_RESPONDER)
|
||||
#define DEBUG_ESP_MDNS_INFO
|
||||
#define DEBUG_ESP_MDNS_ERR
|
||||
#define DEBUG_ESP_MDNS_TX
|
||||
#define DEBUG_ESP_MDNS_RX
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG_ESP_MDNS_RESPONDER
|
||||
#ifdef DEBUG_ESP_MDNS_INFO
|
||||
#define DEBUG_EX_INFO(A) A
|
||||
#else
|
||||
#define DEBUG_EX_INFO(A)
|
||||
#endif
|
||||
#ifdef DEBUG_ESP_MDNS_ERR
|
||||
#define DEBUG_EX_ERR(A) A
|
||||
#else
|
||||
#define DEBUG_EX_ERR(A)
|
||||
#endif
|
||||
#ifdef DEBUG_ESP_MDNS_TX
|
||||
#define DEBUG_EX_TX(A) A
|
||||
#else
|
||||
#define DEBUG_EX_TX(A)
|
||||
#endif
|
||||
#ifdef DEBUG_ESP_MDNS_RX
|
||||
#define DEBUG_EX_RX(A) A
|
||||
#else
|
||||
#define DEBUG_EX_RX(A)
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG_ESP_PORT
|
||||
#define DEBUG_OUTPUT DEBUG_ESP_PORT
|
||||
#else
|
||||
#define DEBUG_OUTPUT Serial
|
||||
#endif
|
||||
#else
|
||||
#define DEBUG_EX_INFO(A) \
|
||||
do \
|
||||
{ \
|
||||
(void)0; \
|
||||
} while (0)
|
||||
#define DEBUG_EX_ERR(A) \
|
||||
do \
|
||||
{ \
|
||||
(void)0; \
|
||||
} while (0)
|
||||
#define DEBUG_EX_TX(A) \
|
||||
do \
|
||||
{ \
|
||||
(void)0; \
|
||||
} while (0)
|
||||
#define DEBUG_EX_RX(A) \
|
||||
do \
|
||||
{ \
|
||||
(void)0; \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
/* already defined in lwIP ('lwip/prot/dns.h')
|
||||
#ifdef MDNS_IP4_SUPPORT
|
||||
#define DNS_MQUERY_IPV4_GROUP_INIT (IPAddress(224, 0, 0, 251)) // ip_addr_t
|
||||
v4group = DNS_MQUERY_IPV4_GROUP_INIT #endif #ifdef MDNS_IP6_SUPPORT #define
|
||||
DNS_MQUERY_IPV6_GROUP_INIT IPADDR6_INIT_HOST(0xFF020000,0,0,0xFB) // ip_addr_t v6group =
|
||||
DNS_MQUERY_IPV6_GROUP_INIT #endif*/
|
||||
//#define MDNS_MULTICAST_PORT 5353
|
||||
|
||||
/*
|
||||
This is NOT the TTL (Time-To-Live) for MDNS records, but the
|
||||
subnet level distance MDNS records should travel.
|
||||
1 sets the subnet distance to 'local', which is default for MDNS.
|
||||
(Btw.: 255 would set it to 'as far as possible' -> internet)
|
||||
|
||||
However, RFC 3171 seems to force 255 instead
|
||||
*/
|
||||
#define MDNS_MULTICAST_TTL 255 /*1*/
|
||||
|
||||
/*
|
||||
This is the MDNS record TTL
|
||||
Host level records are set to 2min (120s)
|
||||
service level records are set to 75min (4500s)
|
||||
*/
|
||||
#define MDNS_HOST_TTL 120
|
||||
#define MDNS_SERVICE_TTL 4500
|
||||
|
||||
/*
|
||||
Compressed labels are flagged by the two topmost bits of the length byte being set
|
||||
*/
|
||||
#define MDNS_DOMAIN_COMPRESS_MARK 0xC0
|
||||
/*
|
||||
Avoid endless recursion because of malformed compressed labels
|
||||
*/
|
||||
#define MDNS_DOMAIN_MAX_REDIRCTION 6
|
||||
|
||||
/*
|
||||
Default service priority and weight in SRV answers
|
||||
*/
|
||||
#define MDNS_SRV_PRIORITY 0
|
||||
#define MDNS_SRV_WEIGHT 0
|
||||
|
||||
/*
|
||||
Delay between and number of probes for host and service domains
|
||||
Delay between and number of announces for host and service domains
|
||||
Delay between and number of service queries; the delay is multiplied by the resent number in
|
||||
'_checkServiceQueryCache'
|
||||
*/
|
||||
#define MDNS_PROBE_DELAY 250
|
||||
#define MDNS_PROBE_COUNT 3
|
||||
#define MDNS_ANNOUNCE_DELAY 1000
|
||||
#define MDNS_ANNOUNCE_COUNT 8
|
||||
#define MDNS_DYNAMIC_QUERY_RESEND_COUNT 5
|
||||
#define MDNS_DYNAMIC_QUERY_RESEND_DELAY 5000
|
||||
|
||||
/*
|
||||
Force host domain to use only lowercase letters
|
||||
*/
|
||||
//#define MDNS_FORCE_LOWERCASE_HOSTNAME
|
||||
|
||||
/*
|
||||
Enable/disable the usage of the F() macro in debug trace printf calls.
|
||||
There needs to be an PGM compatible printf function to use this.
|
||||
|
||||
USE_PGM_PRINTF and F
|
||||
*/
|
||||
#define USE_PGM_PRINTF
|
||||
|
||||
#ifdef USE_PGM_PRINTF
|
||||
#else
|
||||
#ifdef F
|
||||
#undef F
|
||||
#endif
|
||||
#define F(A) A
|
||||
#endif
|
||||
|
||||
} // namespace MDNSImplementation
|
||||
|
||||
} // namespace esp8266
|
||||
|
||||
// Include the main header, so the submodlues only need to include this header
|
||||
#include "LEAmDNS.h"
|
||||
|
||||
#endif // MDNS_PRIV_H
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
LEAmDNS_Priv.h
|
||||
|
||||
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 MDNS_LWIPDEFS_H
|
||||
#define MDNS_LWIPDEFS_H
|
||||
|
||||
#include <lwip/prot/dns.h> // DNS_RRTYPE_xxx, DNS_MQUERY_PORT
|
||||
|
||||
#endif // MDNS_LWIPDEFS_H
|
||||
@@ -0,0 +1,20 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map For Ultrasound
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
LittleFS KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
format KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map For Ultrasound
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
MD5Builder KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
begin KEYWORD2
|
||||
add KEYWORD2
|
||||
addHexString KEYWORD2
|
||||
addStream KEYWORD2
|
||||
calculate KEYWORD2
|
||||
getBytes KEYWORD2
|
||||
getChars KEYWORD2
|
||||
toString KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
name=MD5Builder
|
||||
version=1.0.0
|
||||
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
sentence=Allows generating MD5 hashes of streams
|
||||
paragraph=Allows generating MD5 hashes of streams
|
||||
category=Data Processing
|
||||
url=https://github.com/earlephilhower/arduino-pico
|
||||
architectures=rp2040
|
||||
dot_a_linkage=true
|
||||
includes=MD5Builder.h
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
MD5Builder - Simple MD5 hash calculations
|
||||
|
||||
Updated for the Pico by Earle F. Philhower, III
|
||||
|
||||
Modified from the ESP8266 version which is
|
||||
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <MD5Builder.h>
|
||||
#include <memory>
|
||||
|
||||
static uint8_t hex_char_to_byte(uint8_t c) {
|
||||
return (c >= 'a' && c <= 'f') ? (c - ((uint8_t)'a' - 0xa)) :
|
||||
(c >= 'A' && c <= 'F') ? (c - ((uint8_t)'A' - 0xA)) :
|
||||
(c >= '0' && c <= '9') ? (c - (uint8_t)'0') : 0;
|
||||
}
|
||||
|
||||
void MD5Builder::begin(void) {
|
||||
memset(_buf, 0x00, 16);
|
||||
br_md5_init(&_ctx);
|
||||
}
|
||||
|
||||
void MD5Builder::add(const uint8_t * data, const uint16_t len) {
|
||||
br_md5_update(&_ctx, data, len);
|
||||
}
|
||||
|
||||
void MD5Builder::addHexString(const char * data) {
|
||||
uint16_t i, len = strlen(data);
|
||||
auto tmp = std::unique_ptr<uint8_t[]> {new (std::nothrow) uint8_t[len / 2]};
|
||||
|
||||
if (!tmp) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < len; i += 2) {
|
||||
uint8_t high = hex_char_to_byte(data[i]);
|
||||
uint8_t low = hex_char_to_byte(data[i + 1]);
|
||||
tmp[i / 2] = (high & 0x0F) << 4 | (low & 0x0F);
|
||||
}
|
||||
add(tmp.get(), len / 2);
|
||||
}
|
||||
|
||||
bool MD5Builder::addStream(Stream &stream, const size_t maxLen) {
|
||||
const int buf_size = 512;
|
||||
int maxLengthLeft = maxLen;
|
||||
|
||||
auto buf = std::unique_ptr<uint8_t[]> {new (std::nothrow) uint8_t[buf_size]};
|
||||
|
||||
if (!buf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int bytesAvailable = stream.available();
|
||||
while ((bytesAvailable > 0) && (maxLengthLeft > 0)) {
|
||||
|
||||
// determine number of bytes to read
|
||||
int readBytes = bytesAvailable;
|
||||
if (readBytes > maxLengthLeft) {
|
||||
readBytes = maxLengthLeft; // read only until max_len
|
||||
}
|
||||
if (readBytes > buf_size) {
|
||||
readBytes = buf_size; // not read more the buffer can handle
|
||||
}
|
||||
|
||||
// read data and check if we got something
|
||||
int numBytesRead = stream.readBytes(buf.get(), readBytes);
|
||||
if (numBytesRead < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update MD5 with buffer payload
|
||||
br_md5_update(&_ctx, buf.get(), numBytesRead);
|
||||
|
||||
// update available number of bytes
|
||||
maxLengthLeft -= numBytesRead;
|
||||
bytesAvailable = stream.available();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MD5Builder::calculate(void) {
|
||||
br_md5_out(&_ctx, _buf);
|
||||
}
|
||||
|
||||
void MD5Builder::getBytes(uint8_t * output) const {
|
||||
memcpy(output, _buf, 16);
|
||||
}
|
||||
|
||||
void MD5Builder::getChars(char * output) const {
|
||||
for (uint8_t i = 0; i < 16; i++) {
|
||||
sprintf(output + (i * 2), "%02x", _buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
String MD5Builder::toString(void) const {
|
||||
char out[33];
|
||||
getChars(out);
|
||||
return String(out);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
MD5Builder - Simple MD5 hash calculations
|
||||
|
||||
Updated for the Pico by Earle F. Philhower, III
|
||||
|
||||
Modified from the ESP8266 version which is
|
||||
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <api/String.h>
|
||||
#include <Stream.h>
|
||||
#include <bearssl/bearssl_hash.h>
|
||||
|
||||
class MD5Builder {
|
||||
private:
|
||||
br_md5_context _ctx;
|
||||
uint8_t _buf[16];
|
||||
public:
|
||||
void begin(void);
|
||||
void add(const uint8_t * data, const uint16_t len);
|
||||
void add(const char * data) {
|
||||
add((const uint8_t*)data, strlen(data));
|
||||
}
|
||||
void add(char * data) {
|
||||
add((const char*)data);
|
||||
}
|
||||
void add(const String& data) {
|
||||
add(data.c_str());
|
||||
}
|
||||
void addHexString(const char * data);
|
||||
void addHexString(char * data) {
|
||||
addHexString((const char*)data);
|
||||
}
|
||||
void addHexString(const String& data) {
|
||||
addHexString(data.c_str());
|
||||
}
|
||||
bool addStream(Stream & stream, const size_t maxLen);
|
||||
void calculate(void);
|
||||
void getBytes(uint8_t * output) const;
|
||||
void getChars(char * output) const;
|
||||
String toString(void) const;
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
// This example overwrites itself with a serial blinker sketch using OTA
|
||||
// In general, you will get a file from the Internet, over a serial port, etc.
|
||||
// and not include it in a header like we do here for simplicity.
|
||||
//
|
||||
// The blinker.BIN file was compressed with `gzip -9` and will be expanded
|
||||
// during OTA
|
||||
//
|
||||
// You need to have at least 256K of filesystem configured in
|
||||
// Tools->Flash Size
|
||||
//
|
||||
// Released to the public domain August 2022 by Earle F. Philhower, III
|
||||
|
||||
#include <PicoOTA.h>
|
||||
#include <LittleFS.h>
|
||||
#include "blink_100_1000.h"
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(5000);
|
||||
|
||||
Serial.printf("Writing OTA image of blinker...");
|
||||
LittleFS.begin();
|
||||
File f = LittleFS.open("blink.bin.gz", "w");
|
||||
if (sizeof(blink_gz) != f.write(blink_gz, sizeof(blink_gz))) {
|
||||
Serial.printf("Unable to write OTA binary. Is the filesystem size set?\n");
|
||||
return;
|
||||
}
|
||||
f.close();
|
||||
Serial.printf("done\n\n");
|
||||
Serial.printf("Programming OTA commands...");
|
||||
picoOTA.begin();
|
||||
picoOTA.addFile("blink.bin.gz");
|
||||
picoOTA.commit();
|
||||
LittleFS.end();
|
||||
Serial.printf("done\n\n");
|
||||
Serial.printf("Rebooting in 5 seconds, should begin blinker instead of this app...\n");
|
||||
delay(5000);
|
||||
rp2040.reboot();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
||||
// This example overwrites itself with a serial blinker sketch using OTA
|
||||
// In general, you will get a file from the Internet, over a serial port, etc.
|
||||
// and not include it in a header like we do here for simplicity.
|
||||
//
|
||||
// You need to have at least 256K of filesystem configured in
|
||||
// Tools->Flash Size
|
||||
//
|
||||
// Released to the public domain August 2022 by Earle F. Philhower, III
|
||||
|
||||
#include <PicoOTA.h>
|
||||
#include <LittleFS.h>
|
||||
#include "blink_100_1000.h"
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(5000);
|
||||
|
||||
Serial.printf("Writing OTA image of blinker...");
|
||||
LittleFS.begin();
|
||||
File f = LittleFS.open("blink.bin", "w");
|
||||
if (sizeof(blink) != f.write(blink, sizeof(blink))) {
|
||||
Serial.printf("Unable to write OTA binary. Is the filesystem size set?\n");
|
||||
return;
|
||||
}
|
||||
f.close();
|
||||
Serial.printf("done\n\n");
|
||||
Serial.printf("Programming OTA commands...");
|
||||
picoOTA.begin();
|
||||
picoOTA.addFile("blink.bin");
|
||||
picoOTA.commit();
|
||||
LittleFS.end();
|
||||
Serial.printf("done\n\n");
|
||||
Serial.printf("Rebooting in 5 seconds, should begin blinker instead of this app...\n");
|
||||
delay(5000);
|
||||
rp2040.reboot();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map For Ultrasound
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
picoOTA KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
addFile KEYWORD1
|
||||
commit KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
name=PicoOTA
|
||||
version=1.0.0
|
||||
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
sentence=Configures requests for OTA bootloader
|
||||
paragraph=Example repository for Ethernet drivers
|
||||
category=Device Control
|
||||
url=https://github.com/earlephilhower.arduino-pico
|
||||
architectures=rp2040
|
||||
dot_a_linkage=true
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
Pico_OTA.cpp - Programs OTA requests for RP2040 OTA
|
||||
Copyright (c) 2022 Earle F. Philhower, III. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <PicoOTA.h>
|
||||
|
||||
PicoOTA picoOTA;
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
Pico_OTA.cpp - Programs OTA requests for RP2040 OTA
|
||||
Copyright (c) 2022 Earle F. Philhower, III. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LittleFS.h>
|
||||
#include <pico_base/pico/ota_command.h>
|
||||
#include <hardware/resets.h>
|
||||
|
||||
extern uint8_t _FS_start;
|
||||
extern uint8_t _FS_end;
|
||||
|
||||
// Simple and low code CRC calculation for the OTA page
|
||||
class OTACRC32 {
|
||||
public:
|
||||
OTACRC32() {
|
||||
crc = 0xffffffff;
|
||||
}
|
||||
|
||||
~OTACRC32() {
|
||||
}
|
||||
|
||||
void add(const void *d, uint32_t len) {
|
||||
const uint8_t *data = (const uint8_t *)d;
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
crc ^= data[i];
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (crc & 1) {
|
||||
crc = (crc >> 1) ^ 0xedb88320;
|
||||
} else {
|
||||
crc >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t get() {
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t crc;
|
||||
};
|
||||
|
||||
|
||||
// Handles generating valid OTA blocks
|
||||
class PicoOTA {
|
||||
public:
|
||||
PicoOTA() { }
|
||||
~PicoOTA() { }
|
||||
|
||||
void begin() {
|
||||
if (_page) {
|
||||
delete _page;
|
||||
}
|
||||
_page = new OTACmdPage;
|
||||
memset(_page, 0, sizeof(*_page));
|
||||
memcpy(_page->sign, "Pico OTA Format\0", sizeof(_page->sign));
|
||||
_page->count = 0;
|
||||
}
|
||||
|
||||
bool addFile(const char *filename, uint32_t offset = 0, uint32_t flashaddr = XIP_BASE, uint32_t len = 0) {
|
||||
if (!_page || _page->count == 8) {
|
||||
return false;
|
||||
}
|
||||
File f = LittleFS.open(filename, "r");
|
||||
if (!f) {
|
||||
return false;
|
||||
}
|
||||
if (!len) {
|
||||
// Check for GZIP header, and if so read real length from file
|
||||
uint8_t hdr[4];
|
||||
if (2 != f.read(hdr, 2)) {
|
||||
// Error, this can't be valid
|
||||
f.close();
|
||||
return false;
|
||||
}
|
||||
if ((hdr[0] == 0x1f) && (hdr[1] == 0x8b)) {
|
||||
// GZIP signature matched. Find real size as encoded at the end
|
||||
f.seek(f.size() - 4);
|
||||
if (4 != f.read(hdr, 4)) {
|
||||
f.close();
|
||||
return false;
|
||||
}
|
||||
len = hdr[0];
|
||||
len += hdr[1] << 8;
|
||||
len += hdr[2] << 16;
|
||||
len += hdr[3] << 24;
|
||||
} else {
|
||||
len = f.size();
|
||||
}
|
||||
len -= offset;
|
||||
}
|
||||
f.close();
|
||||
_page->cmd[_page->count].command = _OTA_WRITE;
|
||||
strncpy(_page->cmd[_page->count].write.filename, filename, sizeof(_page->cmd[_page->count].write.filename));
|
||||
_page->cmd[_page->count].write.fileOffset = offset;
|
||||
_page->cmd[_page->count].write.fileLength = len;
|
||||
_page->cmd[_page->count].write.flashAddress = flashaddr;
|
||||
_page->count++;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool commit() {
|
||||
if (!_page) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OTACRC32 crc;
|
||||
crc.add(_page, offsetof(OTACmdPage, crc32));
|
||||
_page->crc32 = crc.get();
|
||||
|
||||
File f = LittleFS.open(_OTA_COMMAND_FILE, "w");
|
||||
if (!f) {
|
||||
return false;
|
||||
}
|
||||
auto len = f.write((uint8_t *)_page, sizeof(*_page));
|
||||
f.close();
|
||||
|
||||
return len == sizeof(*_page);
|
||||
}
|
||||
|
||||
private:
|
||||
OTACmdPage *_page = nullptr;
|
||||
};
|
||||
|
||||
extern PicoOTA picoOTA;
|
||||
|
||||
+23
-25
@@ -1,29 +1,29 @@
|
||||
/*
|
||||
SDFS.cpp - file system wrapper for SdFat
|
||||
Copyright (c) 2019 Earle F. Philhower, III. All rights reserved.
|
||||
SDFS.cpp - file system wrapper for SdFat
|
||||
Copyright (c) 2019 Earle F. Philhower, III. All rights reserved.
|
||||
|
||||
Based on spiffs_api.cpp which is:
|
||||
| Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
|
||||
Based on spiffs_api.cpp which is:
|
||||
| Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
|
||||
|
||||
This code was influenced by NodeMCU and Sming libraries, and first version of
|
||||
Arduino wrapper written by Hristo Gochkov.
|
||||
This code was influenced by NodeMCU and Sming libraries, and first version of
|
||||
Arduino wrapper written by Hristo Gochkov.
|
||||
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#include "SDFS.h"
|
||||
#include <FS.h>
|
||||
|
||||
@@ -39,8 +39,7 @@ namespace sdfs {
|
||||
// Required to be global because SDFAT doesn't allow a this pointer in it's own time call
|
||||
time_t (*__sdfs_timeCallback)(void) = nullptr;
|
||||
|
||||
FileImplPtr SDFSImpl::open(const char* path, OpenMode openMode, AccessMode accessMode)
|
||||
{
|
||||
FileImplPtr SDFSImpl::open(const char* path, OpenMode openMode, AccessMode accessMode) {
|
||||
if (!_mounted) {
|
||||
DEBUGV("SDFSImpl::open() called on unmounted FS\n");
|
||||
return FileImplPtr();
|
||||
@@ -74,8 +73,7 @@ FileImplPtr SDFSImpl::open(const char* path, OpenMode openMode, AccessMode acces
|
||||
return std::make_shared<SDFSFileImpl>(this, sharedFd, path);
|
||||
}
|
||||
|
||||
DirImplPtr SDFSImpl::openDir(const char* path)
|
||||
{
|
||||
DirImplPtr SDFSImpl::openDir(const char* path) {
|
||||
if (!_mounted) {
|
||||
return DirImplPtr();
|
||||
}
|
||||
@@ -85,8 +83,8 @@ DirImplPtr SDFSImpl::openDir(const char* path)
|
||||
return DirImplPtr();
|
||||
}
|
||||
// Get rid of any trailing slashes
|
||||
while (strlen(pathStr) && (pathStr[strlen(pathStr)-1]=='/')) {
|
||||
pathStr[strlen(pathStr)-1] = 0;
|
||||
while (strlen(pathStr) && (pathStr[strlen(pathStr) - 1] == '/')) {
|
||||
pathStr[strlen(pathStr) - 1] = 0;
|
||||
}
|
||||
// At this point we have a name of "/blah/blah/blah" or "blah" or ""
|
||||
// If that references a directory, just open it and we're done.
|
||||
|
||||
+72
-105
@@ -2,31 +2,31 @@
|
||||
#define SDFS_H
|
||||
|
||||
/*
|
||||
SDFS.h - file system wrapper for SdLib
|
||||
Copyright (c) 2019 Earle F. Philhower, III. All rights reserved.
|
||||
SDFS.h - file system wrapper for SdLib
|
||||
Copyright (c) 2019 Earle F. Philhower, III. All rights reserved.
|
||||
|
||||
Based on spiffs_api.h, which is:
|
||||
| Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
|
||||
Based on spiffs_api.h, which is:
|
||||
| Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
|
||||
|
||||
This code was influenced by NodeMCU and Sming libraries, and first version of
|
||||
Arduino wrapper written by Hristo Gochkov.
|
||||
This code was influenced by NodeMCU and Sming libraries, and first version of
|
||||
Arduino wrapper written by Hristo Gochkov.
|
||||
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#include <limits>
|
||||
#include <assert.h>
|
||||
#include "FS.h"
|
||||
@@ -41,8 +41,7 @@ namespace sdfs {
|
||||
|
||||
class SDFSFileImpl;
|
||||
class SDFSDirImpl;
|
||||
class SDFSConfig : public FSConfig
|
||||
{
|
||||
class SDFSConfig : public FSConfig {
|
||||
public:
|
||||
static constexpr uint32_t FSId = 0x53444653;
|
||||
|
||||
@@ -71,11 +70,9 @@ public:
|
||||
uint32_t _spiSettings;
|
||||
};
|
||||
|
||||
class SDFSImpl : public FSImpl
|
||||
{
|
||||
class SDFSImpl : public FSImpl {
|
||||
public:
|
||||
SDFSImpl() : _mounted(false)
|
||||
{
|
||||
SDFSImpl() : _mounted(false) {
|
||||
}
|
||||
|
||||
FileImplPtr open(const char* path, OpenMode openMode, AccessMode accessMode) override;
|
||||
@@ -99,7 +96,7 @@ public:
|
||||
info.blockSize = _fs.vol()->bytesPerCluster();
|
||||
info.pageSize = 0; // TODO ?
|
||||
info.maxPathLength = 255; // TODO ?
|
||||
info.totalBytes =_fs.vol()->clusterCount() * info.blockSize;
|
||||
info.totalBytes = _fs.vol()->clusterCount() * info.blockSize;
|
||||
info.usedBytes = info.totalBytes - (_fs.vol()->freeClusterCount() * _fs.vol()->bytesPerCluster());
|
||||
return true;
|
||||
}
|
||||
@@ -133,16 +130,15 @@ public:
|
||||
}
|
||||
|
||||
bool rmdir(const char* path) override {
|
||||
return _mounted ?_fs.rmdir(path) : false;
|
||||
return _mounted ? _fs.rmdir(path) : false;
|
||||
}
|
||||
|
||||
bool setConfig(const FSConfig &cfg) override
|
||||
{
|
||||
bool setConfig(const FSConfig &cfg) override {
|
||||
if ((cfg._type != SDFSConfig::FSId) || _mounted) {
|
||||
DEBUGV("SDFS::setConfig: invalid config or already mounted\n");
|
||||
return false;
|
||||
}
|
||||
_cfg = *static_cast<const SDFSConfig *>(&cfg);
|
||||
_cfg = *static_cast<const SDFSConfig *>(&cfg);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -155,7 +151,7 @@ public:
|
||||
format();
|
||||
_mounted = _fs.begin(_cfg._csPin, _cfg._spiSettings);
|
||||
}
|
||||
FsDateTime::setCallback(dateTimeCB);
|
||||
FsDateTime::setCallback(dateTimeCB);
|
||||
return _mounted;
|
||||
}
|
||||
|
||||
@@ -213,7 +209,7 @@ public:
|
||||
// static member of our class to return the time/date.
|
||||
static void dateTimeCB(uint16_t *dosYear, uint16_t *dosTime) {
|
||||
time_t now;
|
||||
extern time_t (*__sdfs_timeCallback)(void);
|
||||
extern time_t (*__sdfs_timeCallback)(void);
|
||||
if (__sdfs_timeCallback) {
|
||||
now = __sdfs_timeCallback();
|
||||
} else {
|
||||
@@ -228,8 +224,7 @@ protected:
|
||||
friend class SDFileImpl;
|
||||
friend class SDFSDirImpl;
|
||||
|
||||
SdFat* getFs()
|
||||
{
|
||||
SdFat* getFs() {
|
||||
return &_fs;
|
||||
}
|
||||
|
||||
@@ -261,76 +256,65 @@ protected:
|
||||
};
|
||||
|
||||
|
||||
class SDFSFileImpl : public FileImpl
|
||||
{
|
||||
class SDFSFileImpl : public FileImpl {
|
||||
public:
|
||||
SDFSFileImpl(SDFSImpl *fs, std::shared_ptr<File32> fd, const char *name)
|
||||
: _fs(fs), _fd(fd), _opened(true)
|
||||
{
|
||||
: _fs(fs), _fd(fd), _opened(true) {
|
||||
_name = std::shared_ptr<char>(new char[strlen(name) + 1], std::default_delete<char[]>());
|
||||
strcpy(_name.get(), name);
|
||||
}
|
||||
|
||||
~SDFSFileImpl() override
|
||||
{
|
||||
~SDFSFileImpl() override {
|
||||
flush();
|
||||
close();
|
||||
}
|
||||
|
||||
int availableForWrite() override
|
||||
{
|
||||
int availableForWrite() override {
|
||||
return _opened ? _fd->availableSpaceForWrite() : 0;
|
||||
}
|
||||
|
||||
size_t write(const uint8_t *buf, size_t size) override
|
||||
{
|
||||
size_t write(const uint8_t *buf, size_t size) override {
|
||||
return _opened ? _fd->write(buf, size) : -1;
|
||||
}
|
||||
|
||||
int read(uint8_t* buf, size_t size) override
|
||||
{
|
||||
int read(uint8_t* buf, size_t size) override {
|
||||
return _opened ? _fd->read(buf, size) : -1;
|
||||
}
|
||||
|
||||
void flush() override
|
||||
{
|
||||
void flush() override {
|
||||
if (_opened) {
|
||||
_fd->sync();
|
||||
}
|
||||
}
|
||||
|
||||
bool seek(uint32_t pos, SeekMode mode) override
|
||||
{
|
||||
bool seek(uint32_t pos, SeekMode mode) override {
|
||||
if (!_opened) {
|
||||
return false;
|
||||
}
|
||||
switch (mode) {
|
||||
case SeekSet:
|
||||
return _fd->seekSet(pos);
|
||||
case SeekEnd:
|
||||
return _fd->seekEnd(-pos); // TODO again, odd from POSIX
|
||||
case SeekCur:
|
||||
return _fd->seekCur(pos);
|
||||
default:
|
||||
// Should not be hit, we've got an invalid seek mode
|
||||
DEBUGV("SDFSFileImpl::seek: invalid seek mode %d\n", mode);
|
||||
assert((mode==SeekSet) || (mode==SeekEnd) || (mode==SeekCur)); // Will fail and give meaningful assert message
|
||||
return false;
|
||||
case SeekSet:
|
||||
return _fd->seekSet(pos);
|
||||
case SeekEnd:
|
||||
return _fd->seekEnd(-pos); // TODO again, odd from POSIX
|
||||
case SeekCur:
|
||||
return _fd->seekCur(pos);
|
||||
default:
|
||||
// Should not be hit, we've got an invalid seek mode
|
||||
DEBUGV("SDFSFileImpl::seek: invalid seek mode %d\n", mode);
|
||||
assert((mode == SeekSet) || (mode == SeekEnd) || (mode == SeekCur)); // Will fail and give meaningful assert message
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
size_t position() const override
|
||||
{
|
||||
size_t position() const override {
|
||||
return _opened ? _fd->curPosition() : 0;
|
||||
}
|
||||
|
||||
size_t size() const override
|
||||
{
|
||||
size_t size() const override {
|
||||
return _opened ? _fd->fileSize() : 0;
|
||||
}
|
||||
|
||||
bool truncate(uint32_t size) override
|
||||
{
|
||||
bool truncate(uint32_t size) override {
|
||||
if (!_opened) {
|
||||
DEBUGV("SDFSFileImpl::truncate: file not opened\n");
|
||||
return false;
|
||||
@@ -338,16 +322,14 @@ public:
|
||||
return _fd->truncate(size);
|
||||
}
|
||||
|
||||
void close() override
|
||||
{
|
||||
void close() override {
|
||||
if (_opened) {
|
||||
_fd->close();
|
||||
_opened = false;
|
||||
}
|
||||
}
|
||||
|
||||
const char* name() const override
|
||||
{
|
||||
const char* name() const override {
|
||||
if (!_opened) {
|
||||
DEBUGV("SDFSFileImpl::name: file not opened\n");
|
||||
return nullptr;
|
||||
@@ -362,18 +344,15 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
const char* fullName() const override
|
||||
{
|
||||
const char* fullName() const override {
|
||||
return _opened ? _name.get() : nullptr;
|
||||
}
|
||||
|
||||
bool isFile() const override
|
||||
{
|
||||
bool isFile() const override {
|
||||
return _opened ? _fd->isFile() : false;;
|
||||
}
|
||||
|
||||
bool isDirectory() const override
|
||||
{
|
||||
bool isDirectory() const override {
|
||||
return _opened ? _fd->isDir() : false;
|
||||
}
|
||||
|
||||
@@ -406,36 +385,31 @@ protected:
|
||||
bool _opened;
|
||||
};
|
||||
|
||||
class SDFSDirImpl : public DirImpl
|
||||
{
|
||||
class SDFSDirImpl : public DirImpl {
|
||||
public:
|
||||
SDFSDirImpl(const String& pattern, SDFSImpl* fs, std::shared_ptr<File32> dir, const char *dirPath = nullptr)
|
||||
: _pattern(pattern), _fs(fs), _dir(dir), _valid(false), _dirPath(nullptr)
|
||||
{
|
||||
: _pattern(pattern), _fs(fs), _dir(dir), _valid(false), _dirPath(nullptr) {
|
||||
if (dirPath) {
|
||||
_dirPath = std::shared_ptr<char>(new char[strlen(dirPath) + 1], std::default_delete<char[]>());
|
||||
strcpy(_dirPath.get(), dirPath);
|
||||
}
|
||||
}
|
||||
|
||||
~SDFSDirImpl() override
|
||||
{
|
||||
~SDFSDirImpl() override {
|
||||
_dir->close();
|
||||
}
|
||||
|
||||
FileImplPtr openFile(OpenMode openMode, AccessMode accessMode) override
|
||||
{
|
||||
FileImplPtr openFile(OpenMode openMode, AccessMode accessMode) override {
|
||||
if (!_valid) {
|
||||
return FileImplPtr();
|
||||
}
|
||||
// MAX_PATH on FAT32 is potentially 260 bytes per most implementations
|
||||
char tmpName[260];
|
||||
snprintf(tmpName, sizeof(tmpName), "%s%s%s", _dirPath.get() ? _dirPath.get() : "", _dirPath.get()&&_dirPath.get()[0]?"/":"", _lfn);
|
||||
snprintf(tmpName, sizeof(tmpName), "%s%s%s", _dirPath.get() ? _dirPath.get() : "", _dirPath.get() && _dirPath.get()[0] ? "/" : "", _lfn);
|
||||
return _fs->open((const char *)tmpName, openMode, accessMode);
|
||||
}
|
||||
|
||||
const char* fileName() override
|
||||
{
|
||||
const char* fileName() override {
|
||||
if (!_valid) {
|
||||
DEBUGV("SDFSDirImpl::fileName: directory not valid\n");
|
||||
return nullptr;
|
||||
@@ -443,8 +417,7 @@ public:
|
||||
return (const char*) _lfn; //_dirent.name;
|
||||
}
|
||||
|
||||
size_t fileSize() override
|
||||
{
|
||||
size_t fileSize() override {
|
||||
if (!_valid) {
|
||||
return 0;
|
||||
}
|
||||
@@ -452,8 +425,7 @@ public:
|
||||
return _size;
|
||||
}
|
||||
|
||||
time_t fileTime() override
|
||||
{
|
||||
time_t fileTime() override {
|
||||
if (!_valid) {
|
||||
return 0;
|
||||
}
|
||||
@@ -461,8 +433,7 @@ public:
|
||||
return _time;
|
||||
}
|
||||
|
||||
time_t fileCreationTime() override
|
||||
{
|
||||
time_t fileCreationTime() override {
|
||||
if (!_valid) {
|
||||
return 0;
|
||||
}
|
||||
@@ -470,18 +441,15 @@ public:
|
||||
return _creation;
|
||||
}
|
||||
|
||||
bool isFile() const override
|
||||
{
|
||||
bool isFile() const override {
|
||||
return _valid ? _isFile : false;
|
||||
}
|
||||
|
||||
bool isDirectory() const override
|
||||
{
|
||||
bool isDirectory() const override {
|
||||
return _valid ? _isDirectory : false;
|
||||
}
|
||||
|
||||
bool next() override
|
||||
{
|
||||
bool next() override {
|
||||
const int n = _pattern.length();
|
||||
do {
|
||||
File32 file;
|
||||
@@ -495,21 +463,20 @@ public:
|
||||
if (file.dirEntry(&tmp)) {
|
||||
_time = SDFSImpl::FatToTimeT(*(uint16_t*)tmp.modifyDate, *(uint16_t*)tmp.modifyTime);
|
||||
_creation = SDFSImpl::FatToTimeT(*(uint16_t*)tmp.createDate, *(uint16_t*)tmp.createTime);
|
||||
} else {
|
||||
} else {
|
||||
_time = 0;
|
||||
_creation = 0;
|
||||
}
|
||||
}
|
||||
file.getName(_lfn, sizeof(_lfn));
|
||||
file.close();
|
||||
} else {
|
||||
_valid = 0;
|
||||
}
|
||||
} while(_valid && strncmp((const char*) _lfn, _pattern.c_str(), n) != 0);
|
||||
} while (_valid && strncmp((const char*) _lfn, _pattern.c_str(), n) != 0);
|
||||
return _valid;
|
||||
}
|
||||
|
||||
bool rewind() override
|
||||
{
|
||||
bool rewind() override {
|
||||
_valid = false;
|
||||
_dir->rewind();
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map For Ultrasound
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
Updater KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
begin KEYWORD2
|
||||
setup KEYWORD2
|
||||
handle KEYWORD2
|
||||
onStart KEYWORD2
|
||||
onEnd KEYWORD2
|
||||
onError KEYWORD2
|
||||
onProgress KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
name=Updater
|
||||
version=1.0
|
||||
author=The ESP8266 Team
|
||||
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.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 announces to get discovered by the arduino IDE.
|
||||
category=Communication
|
||||
url=https://github.com/earlephilhower/arduino-pico
|
||||
architectures=rp2040
|
||||
depends=MD5Builder,PicoOTA
|
||||
dot_a_linkage=true
|
||||
@@ -0,0 +1,443 @@
|
||||
/*
|
||||
Updater - Handles FS or app updates using PicoOTA
|
||||
Adapted from the ESP8266 Updater class
|
||||
Copyright (c) 2022 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "Updater.h"
|
||||
#include <PolledTimeout.h>
|
||||
#include "StackThunk.h"
|
||||
#include "LittleFS.h"
|
||||
#include <hardware/flash.h>
|
||||
#include <PicoOTA.h>
|
||||
|
||||
#define DEBUG_UPDATER Serial
|
||||
|
||||
#include <Updater_Signing.h>
|
||||
#ifndef ARDUINO_SIGNING
|
||||
#define ARDUINO_SIGNING 0
|
||||
#endif
|
||||
|
||||
extern uint8_t _FS_start;
|
||||
extern uint8_t _FS_end;
|
||||
|
||||
|
||||
#if ARDUINO_SIGNING
|
||||
namespace esp8266 {
|
||||
extern UpdaterHashClass& updaterSigningHash;
|
||||
extern UpdaterVerifyClass& updaterSigningVerifier;
|
||||
}
|
||||
#endif
|
||||
|
||||
UpdaterClass::UpdaterClass() {
|
||||
#if ARDUINO_SIGNING
|
||||
installSignature(&esp8266::updaterSigningHash, &esp8266::updaterSigningVerifier);
|
||||
stack_thunk_add_ref();
|
||||
#endif
|
||||
}
|
||||
|
||||
UpdaterClass::~UpdaterClass() {
|
||||
#if ARDUINO_SIGNING
|
||||
stack_thunk_del_ref();
|
||||
#endif
|
||||
}
|
||||
|
||||
UpdaterClass& UpdaterClass::onProgress(THandlerFunction_Progress fn) {
|
||||
_progress_callback = fn;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void UpdaterClass::_reset() {
|
||||
if (_buffer) {
|
||||
delete[] _buffer;
|
||||
}
|
||||
_buffer = 0;
|
||||
_bufferLen = 0;
|
||||
_startAddress = 0;
|
||||
_currentAddress = 0;
|
||||
_size = 0;
|
||||
_command = U_FLASH;
|
||||
}
|
||||
|
||||
bool UpdaterClass::begin(size_t size, int command) {
|
||||
uint32_t updateStartAddress;
|
||||
if (_size > 0) {
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.println(F("[begin] already running"));
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_UPDATER
|
||||
if (command == U_FS) {
|
||||
DEBUG_UPDATER.println(F("[begin] Update Filesystem."));
|
||||
}
|
||||
#endif
|
||||
|
||||
if (size == 0) {
|
||||
_setError(UPDATE_ERROR_SIZE);
|
||||
return false;
|
||||
}
|
||||
|
||||
_reset();
|
||||
clearError(); // _error = 0
|
||||
_target_md5 = "";
|
||||
_md5 = MD5Builder();
|
||||
|
||||
if (command == U_FLASH) {
|
||||
LittleFS.begin();
|
||||
_fp = LittleFS.open("firmware.bin", "w+");
|
||||
if (!_fp) {
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.println(F("[begin] unable to create file"));
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
updateStartAddress = 0; // Not used
|
||||
} else if (command == U_FS) {
|
||||
if (&_FS_start + size > &_FS_end) {
|
||||
_setError(UPDATE_ERROR_SPACE);
|
||||
return false;
|
||||
}
|
||||
|
||||
updateStartAddress = (uint32_t)&_FS_start;
|
||||
} else {
|
||||
// unknown command
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.println(F("[begin] Unknown update command."));
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
//initialize
|
||||
_startAddress = updateStartAddress;
|
||||
_size = size;
|
||||
_bufferSize = 4096;
|
||||
_buffer = new uint8_t[_bufferSize];
|
||||
_command = command;
|
||||
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.printf_P(PSTR("[begin] _startAddress: 0x%08X (%d)\n"), _startAddress, _startAddress);
|
||||
DEBUG_UPDATER.printf_P(PSTR("[begin] _size: 0x%08zX (%zd)\n"), _size, _size);
|
||||
#endif
|
||||
|
||||
if (!_verify) {
|
||||
_md5.begin();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UpdaterClass::setMD5(const char * expected_md5) {
|
||||
if (strlen(expected_md5) != 32) {
|
||||
return false;
|
||||
}
|
||||
_target_md5 = expected_md5;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UpdaterClass::end(bool evenIfRemaining) {
|
||||
if (_size == 0) {
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.println(F("no update"));
|
||||
#endif
|
||||
_reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Updating w/o any data is an error we detect here
|
||||
if (!progress()) {
|
||||
_setError(UPDATE_ERROR_NO_DATA);
|
||||
}
|
||||
|
||||
if (hasError() || (!isFinished() && !evenIfRemaining)) {
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.printf_P(PSTR("premature end: res:%u, pos:%zu/%zu\n"), getError(), progress(), _size);
|
||||
#endif
|
||||
_reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (evenIfRemaining) {
|
||||
if (_bufferLen > 0) {
|
||||
_writeBuffer();
|
||||
}
|
||||
_size = progress();
|
||||
}
|
||||
|
||||
if (_verify && (_command == U_FLASH)) {
|
||||
const uint32_t expectedSigLen = _verify->length();
|
||||
// If expectedSigLen is non-zero, we expect the last four bytes of the buffer to
|
||||
// contain a matching length field, preceded by the bytes of the signature itself.
|
||||
// But if expectedSigLen is zero, we expect neither a signature nor a length field;
|
||||
uint32_t sigLen = 0;
|
||||
|
||||
if (expectedSigLen > 0) {
|
||||
_fp.seek(_size - sizeof(uint32_t));
|
||||
_fp.read((uint8_t *)&sigLen, sizeof(uint32_t));
|
||||
}
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.printf_P(PSTR("[Updater] sigLen: %d\n"), sigLen);
|
||||
#endif
|
||||
if (sigLen != expectedSigLen) {
|
||||
_setError(UPDATE_ERROR_SIGN);
|
||||
_reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
int binSize = _size;
|
||||
if (expectedSigLen > 0) {
|
||||
_size -= (sigLen + sizeof(uint32_t) /* The siglen word */);
|
||||
}
|
||||
_hash->begin();
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.printf_P(PSTR("[Updater] Adjusted binsize: %d\n"), binSize);
|
||||
#endif
|
||||
// Calculate the MD5 and hash using proper size
|
||||
uint8_t buff[128] __attribute__((aligned(4)));
|
||||
_fp.seek(0);
|
||||
for (int i = 0; i < binSize; i += sizeof(buff)) {
|
||||
_fp.read(buff, sizeof(buff));
|
||||
size_t read = std::min((int)sizeof(buff), binSize - i);
|
||||
_hash->add(buff, read);
|
||||
}
|
||||
_hash->end();
|
||||
#ifdef DEBUG_UPDATER
|
||||
unsigned char *ret = (unsigned char *)_hash->hash();
|
||||
DEBUG_UPDATER.printf_P(PSTR("[Updater] Computed Hash:"));
|
||||
for (int i = 0; i < _hash->len(); i++) {
|
||||
DEBUG_UPDATER.printf(" %02x", ret[i]);
|
||||
}
|
||||
DEBUG_UPDATER.printf("\n");
|
||||
#endif
|
||||
|
||||
uint8_t *sig = nullptr; // Safe to free if we don't actually malloc
|
||||
if (expectedSigLen > 0) {
|
||||
sig = (uint8_t*)malloc(sigLen);
|
||||
if (!sig) {
|
||||
_setError(UPDATE_ERROR_SIGN);
|
||||
_reset();
|
||||
return false;
|
||||
}
|
||||
_fp.seek(_startAddress + binSize);
|
||||
_fp.read((uint8_t *)sig, sigLen);
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.printf_P(PSTR("[Updater] Received Signature:"));
|
||||
for (size_t i = 0; i < sigLen; i++) {
|
||||
DEBUG_UPDATER.printf(" %02x", sig[i]);
|
||||
}
|
||||
DEBUG_UPDATER.printf("\n");
|
||||
#endif
|
||||
}
|
||||
if (!_verify->verify(_hash, (void *)sig, sigLen)) {
|
||||
free(sig);
|
||||
_setError(UPDATE_ERROR_SIGN);
|
||||
_reset();
|
||||
return false;
|
||||
}
|
||||
free(sig);
|
||||
_size = binSize; // Adjust size to remove signature, not part of bin payload
|
||||
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.printf_P(PSTR("[Updater] Signature matches\n"));
|
||||
#endif
|
||||
} else if (_target_md5.length()) {
|
||||
_md5.calculate();
|
||||
if (strcasecmp(_target_md5.c_str(), _md5.toString().c_str())) {
|
||||
_setError(UPDATE_ERROR_MD5);
|
||||
return false;
|
||||
}
|
||||
#ifdef DEBUG_UPDATER
|
||||
else {
|
||||
DEBUG_UPDATER.printf_P(PSTR("MD5 Success: %s\n"), _target_md5.c_str());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!_verifyEnd()) {
|
||||
_reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_command == U_FLASH) {
|
||||
_fp.close();
|
||||
picoOTA.begin();
|
||||
picoOTA.addFile("firmware.bin");
|
||||
picoOTA.commit();
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.printf_P(PSTR("Staged: address:0x%08X, size:0x%08zX\n"), _startAddress, _size);
|
||||
#endif
|
||||
}
|
||||
|
||||
_reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UpdaterClass::_writeBuffer() {
|
||||
if (_command == U_FLASH) {
|
||||
if (_bufferLen != _fp.write(_buffer, _bufferLen)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
noInterrupts();
|
||||
rp2040.idleOtherCore();
|
||||
flash_range_erase((intptr_t)_currentAddress - (intptr_t)XIP_BASE, 4096);
|
||||
flash_range_program((intptr_t)_currentAddress - (intptr_t)XIP_BASE, _buffer, 4096);
|
||||
rp2040.resumeOtherCore();
|
||||
interrupts();
|
||||
}
|
||||
if (!_verify) {
|
||||
_md5.add(_buffer, _bufferLen);
|
||||
}
|
||||
_currentAddress += _bufferLen;
|
||||
_bufferLen = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t UpdaterClass::write(uint8_t *data, size_t len) {
|
||||
if (hasError() || !isRunning()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (progress() + _bufferLen + len > _size) {
|
||||
_setError(UPDATE_ERROR_SPACE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t left = len;
|
||||
|
||||
while ((_bufferLen + left) > _bufferSize) {
|
||||
size_t toBuff = _bufferSize - _bufferLen;
|
||||
memcpy(_buffer + _bufferLen, data + (len - left), toBuff);
|
||||
_bufferLen += toBuff;
|
||||
if (!_writeBuffer()) {
|
||||
return len - left;
|
||||
}
|
||||
left -= toBuff;
|
||||
if (!_async) {
|
||||
yield();
|
||||
}
|
||||
}
|
||||
//lets see what's left
|
||||
memcpy(_buffer + _bufferLen, data + (len - left), left);
|
||||
_bufferLen += left;
|
||||
if (_bufferLen == remaining()) {
|
||||
//we are at the end of the update, so should write what's left to flash
|
||||
if (!_writeBuffer()) {
|
||||
return len - left;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
bool UpdaterClass::_verifyHeader(uint8_t data) {
|
||||
(void) data;
|
||||
// No special header on RP2040
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UpdaterClass::_verifyEnd() {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t UpdaterClass::writeStream(Stream &data, uint16_t streamTimeout) {
|
||||
size_t written = 0;
|
||||
size_t toRead = 0;
|
||||
if (hasError() || !isRunning()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!_verifyHeader(data.peek())) {
|
||||
#ifdef DEBUG_UPDATER
|
||||
printError(DEBUG_UPDATER);
|
||||
#endif
|
||||
_reset();
|
||||
return 0;
|
||||
}
|
||||
esp8266::polledTimeout::oneShotMs timeOut(streamTimeout);
|
||||
if (_progress_callback) {
|
||||
_progress_callback(0, _size);
|
||||
}
|
||||
|
||||
while (remaining()) {
|
||||
size_t bytesToRead = _bufferSize - _bufferLen;
|
||||
if (bytesToRead > remaining()) {
|
||||
bytesToRead = remaining();
|
||||
}
|
||||
toRead = data.readBytes(_buffer + _bufferLen, bytesToRead);
|
||||
if (toRead == 0) { //Timeout
|
||||
if (timeOut) {
|
||||
_currentAddress = (_startAddress + _size);
|
||||
_setError(UPDATE_ERROR_STREAM);
|
||||
_reset();
|
||||
return written;
|
||||
}
|
||||
delay(100);
|
||||
} else {
|
||||
timeOut.reset();
|
||||
}
|
||||
_bufferLen += toRead;
|
||||
if ((_bufferLen == remaining() || _bufferLen == _bufferSize) && !_writeBuffer()) {
|
||||
return written;
|
||||
}
|
||||
written += toRead;
|
||||
if (_progress_callback) {
|
||||
_progress_callback(progress(), _size);
|
||||
}
|
||||
yield();
|
||||
}
|
||||
if (_progress_callback) {
|
||||
_progress_callback(progress(), _size);
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
void UpdaterClass::_setError(int error) {
|
||||
_error = error;
|
||||
#ifdef DEBUG_UPDATER
|
||||
printError(DEBUG_UPDATER);
|
||||
#endif
|
||||
_reset(); // Any error condition invalidates the entire update, so clear partial status
|
||||
}
|
||||
|
||||
void UpdaterClass::printError(Print &out) {
|
||||
out.printf_P(PSTR("ERROR[%u]: "), _error);
|
||||
if (_error == UPDATE_ERROR_OK) {
|
||||
out.println(F("No Error"));
|
||||
} else if (_error == UPDATE_ERROR_WRITE) {
|
||||
out.println(F("Flash Write Failed"));
|
||||
} else if (_error == UPDATE_ERROR_ERASE) {
|
||||
out.println(F("Flash Erase Failed"));
|
||||
} else if (_error == UPDATE_ERROR_READ) {
|
||||
out.println(F("Flash Read Failed"));
|
||||
} else if (_error == UPDATE_ERROR_SPACE) {
|
||||
out.println(F("Not Enough Space"));
|
||||
} else if (_error == UPDATE_ERROR_SIZE) {
|
||||
out.println(F("Bad Size Given"));
|
||||
} else if (_error == UPDATE_ERROR_STREAM) {
|
||||
out.println(F("Stream Read Timeout"));
|
||||
} else if (_error == UPDATE_ERROR_NO_DATA) {
|
||||
out.println(F("No data supplied"));
|
||||
} else if (_error == UPDATE_ERROR_MD5) {
|
||||
out.printf_P(PSTR("MD5 Failed: expected:%s, calculated:%s\n"), _target_md5.c_str(), _md5.toString().c_str());
|
||||
} else if (_error == UPDATE_ERROR_SIGN) {
|
||||
out.println(F("Signature verification failed"));
|
||||
} else {
|
||||
out.println(F("UNKNOWN"));
|
||||
}
|
||||
}
|
||||
|
||||
UpdaterClass Update;
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
Updater - Handles FS or app updates using PicoOTA
|
||||
Adapted from the ESP8266 Updater class
|
||||
Copyright (c) 2022 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <MD5Builder.h>
|
||||
#include <functional>
|
||||
#include <FS.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_FLASH_CONFIG (8)
|
||||
#define UPDATE_ERROR_NEW_FLASH_CONFIG (9)
|
||||
#define UPDATE_ERROR_MAGIC_BYTE (10)
|
||||
#define UPDATE_ERROR_BOOTSTRAP (11)
|
||||
#define UPDATE_ERROR_SIGN (12)
|
||||
#define UPDATE_ERROR_NO_DATA (13)
|
||||
|
||||
#define U_FLASH 0
|
||||
#define U_FS 100
|
||||
#define U_AUTH 200
|
||||
|
||||
#ifdef DEBUG_RP2040_PORT
|
||||
#define DEBUG_UPDATER DEBUG_RP2040_PORT
|
||||
#endif
|
||||
|
||||
// Abstract class to implement whatever signing hash desired
|
||||
class UpdaterHashClass {
|
||||
public:
|
||||
virtual void begin() = 0;
|
||||
virtual void add(const void *data, uint32_t len) = 0;
|
||||
virtual void end() = 0;
|
||||
virtual int len() = 0;
|
||||
virtual const void *hash() = 0;
|
||||
virtual const unsigned char *oid() = 0;
|
||||
};
|
||||
|
||||
// Abstract class to implement a signature verifier
|
||||
class UpdaterVerifyClass {
|
||||
public:
|
||||
virtual uint32_t length() = 0; // How many bytes of signature are expected
|
||||
virtual bool verify(UpdaterHashClass *hash, const void *signature, uint32_t signatureLen) = 0; // Verify, return "true" on success
|
||||
};
|
||||
|
||||
class UpdaterClass {
|
||||
public:
|
||||
typedef std::function<void(size_t, size_t)> THandlerFunction_Progress;
|
||||
|
||||
UpdaterClass();
|
||||
~UpdaterClass();
|
||||
|
||||
/* Optionally add a cryptographic signature verification hash and method */
|
||||
void installSignature(UpdaterHashClass *hash, UpdaterVerifyClass *verify) {
|
||||
_hash = hash;
|
||||
_verify = verify;
|
||||
}
|
||||
|
||||
/*
|
||||
Call this to check the space needed for the update
|
||||
Will return false if there is not enough space
|
||||
*/
|
||||
bool begin(size_t size, int command = U_FLASH);
|
||||
|
||||
/*
|
||||
Run Updater from asynchronous callbacs
|
||||
*/
|
||||
void runAsync(bool async) {
|
||||
_async = async;
|
||||
}
|
||||
|
||||
/*
|
||||
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, uint16_t streamTimeout = 60000);
|
||||
|
||||
/*
|
||||
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 !evenIfRemaining
|
||||
or there is an error
|
||||
this will clear everything and return false
|
||||
the last error is available through getError()
|
||||
evenIfRemaining is helpful when you update without knowing the final size first
|
||||
*/
|
||||
bool end(bool evenIfRemaining = false);
|
||||
|
||||
/*
|
||||
Prints the last error to an output stream
|
||||
*/
|
||||
void printError(Print &out);
|
||||
|
||||
/*
|
||||
sets the expected MD5 for the firmware (hexString)
|
||||
*/
|
||||
bool setMD5(const char * expected_md5);
|
||||
|
||||
/*
|
||||
returns the MD5 String of the successfully ended firmware
|
||||
*/
|
||||
String md5String(void) {
|
||||
return _md5.toString();
|
||||
}
|
||||
|
||||
/*
|
||||
populated the result with the md5 bytes of the successfully ended firmware
|
||||
*/
|
||||
void md5(uint8_t * result) {
|
||||
return _md5.getBytes(result);
|
||||
}
|
||||
|
||||
/*
|
||||
This callback will be called when Updater is receiving data
|
||||
*/
|
||||
UpdaterClass& onProgress(THandlerFunction_Progress fn);
|
||||
|
||||
//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 _currentAddress == (_startAddress + _size);
|
||||
}
|
||||
size_t size() {
|
||||
return _size;
|
||||
}
|
||||
size_t progress() {
|
||||
return _currentAddress - _startAddress;
|
||||
}
|
||||
size_t remaining() {
|
||||
return _size - (_currentAddress - _startAddress);
|
||||
}
|
||||
|
||||
/*
|
||||
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 > _bufferSize) {
|
||||
size_t toBuff = _bufferSize - _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;
|
||||
}
|
||||
delay(1);
|
||||
available = data.available();
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
private:
|
||||
void _reset();
|
||||
bool _writeBuffer();
|
||||
|
||||
bool _verifyHeader(uint8_t data);
|
||||
bool _verifyEnd();
|
||||
|
||||
void _setError(int error);
|
||||
|
||||
bool _async = false;
|
||||
uint8_t _error = 0;
|
||||
uint8_t *_buffer = nullptr;
|
||||
size_t _bufferLen = 0; // amount of data written into _buffer
|
||||
size_t _bufferSize = 0; // total size of _buffer
|
||||
size_t _size = 0;
|
||||
uint32_t _startAddress = 0;
|
||||
uint32_t _currentAddress = 0;
|
||||
uint32_t _command = U_FLASH;
|
||||
File _fp;
|
||||
|
||||
String _target_md5;
|
||||
MD5Builder _md5;
|
||||
|
||||
// Optional signed binary verification
|
||||
UpdaterHashClass *_hash = nullptr;
|
||||
UpdaterVerifyClass *_verify = nullptr;
|
||||
// Optional progress callback function
|
||||
THandlerFunction_Progress _progress_callback = nullptr;
|
||||
};
|
||||
|
||||
extern UpdaterClass Update;
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file will be overridden when automatic signing is used.
|
||||
// By default, no signing.
|
||||
#define ARDUINO_SIGNING 0
|
||||
@@ -57,13 +57,12 @@ void setClock() {
|
||||
NTP.begin("pool.ntp.org", "time.nist.gov");
|
||||
|
||||
Serial.print("Waiting for NTP time sync: ");
|
||||
time_t now = time(nullptr);
|
||||
while (now < 8 * 3600 * 2) {
|
||||
delay(500);
|
||||
NTP.waitSet([]() {
|
||||
Serial.print(".");
|
||||
now = time(nullptr);
|
||||
}
|
||||
});
|
||||
Serial.println("");
|
||||
|
||||
time_t now = time(nullptr);
|
||||
struct tm timeinfo;
|
||||
gmtime_r(&now, &timeinfo);
|
||||
Serial.print("Current time: ");
|
||||
|
||||
@@ -164,13 +164,10 @@ void setClock() {
|
||||
NTP.begin("pool.ntp.org", "time.nist.gov");
|
||||
|
||||
Serial.print("Waiting for NTP time sync: ");
|
||||
time_t now = time(nullptr);
|
||||
while (now < 8 * 3600 * 2) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
now = time(nullptr);
|
||||
}
|
||||
NTP.waitSet([]() { Serial.print("."); } );
|
||||
Serial.println("");
|
||||
|
||||
time_t now = time(nullptr);
|
||||
struct tm timeinfo;
|
||||
gmtime_r(&now, &timeinfo);
|
||||
Serial.print("Current time: ");
|
||||
|
||||
@@ -45,13 +45,12 @@ void setup() {
|
||||
NTP.begin("pool.ntp.org", "time.nist.gov");
|
||||
|
||||
Serial.print("Waiting for NTP time sync: ");
|
||||
time_t now = time(nullptr);
|
||||
while (now < 8 * 3600 * 2) {
|
||||
delay(500);
|
||||
NTP.waitSet([]() {
|
||||
Serial.print(".");
|
||||
now = time(nullptr);
|
||||
}
|
||||
});
|
||||
Serial.println("");
|
||||
|
||||
time_t now = time(nullptr);
|
||||
struct tm timeinfo;
|
||||
gmtime_r(&now, &timeinfo);
|
||||
Serial.print("Current time: ");
|
||||
|
||||
@@ -22,16 +22,14 @@ const char *path = "/";
|
||||
|
||||
// Set time via NTP, as required for x.509 validation
|
||||
void setClock() {
|
||||
configTime(3 * 3600, 0, "pool.ntp.org", "time.nist.gov");
|
||||
NTP.begin("pool.ntp.org", "time.nist.gov");
|
||||
|
||||
Serial.print("Waiting for NTP time sync: ");
|
||||
time_t now = time(nullptr);
|
||||
while (now < 8 * 3600 * 2) {
|
||||
delay(500);
|
||||
NTP.waitSet([]() {
|
||||
Serial.print(".");
|
||||
now = time(nullptr);
|
||||
}
|
||||
});
|
||||
Serial.println("");
|
||||
time_t now = time(nullptr);
|
||||
struct tm timeinfo;
|
||||
gmtime_r(&now, &timeinfo);
|
||||
Serial.print("Current time: ");
|
||||
|
||||
@@ -59,6 +59,7 @@ noLowPowerMode KEYWORD2
|
||||
ping KEYWORD2
|
||||
beginMulticast KEYWORD2
|
||||
setTimeout KEYWORD2
|
||||
waitSet KEYWORD2
|
||||
|
||||
|
||||
#######################################
|
||||
|
||||
@@ -9,3 +9,4 @@ url=http://github.com/earlephilhower/arduino-pico
|
||||
architectures=rp2040
|
||||
includes=WiFi.h
|
||||
dot_a_linkage=true
|
||||
depends=MD5Header
|
||||
|
||||
@@ -893,7 +893,7 @@ ServerSessions::ServerSessions(ServerSession *sessions, uint32_t size, bool isDy
|
||||
const br_ssl_session_cache_class **ServerSessions::getCache() {
|
||||
return _size > 0 ? &_cache.vtable : nullptr;
|
||||
}
|
||||
#if 0
|
||||
|
||||
// SHA256 hash for updater
|
||||
void HashSHA256::begin() {
|
||||
br_sha256_init(&_cc);
|
||||
@@ -954,7 +954,7 @@ extern "C" bool SigningVerifier_verify(PublicKey *_pubKey, UpdaterHashClass *has
|
||||
};
|
||||
|
||||
#if !CORE_MOCK
|
||||
make_stack_thunk(SigningVerifier_verify);
|
||||
make_stack_thunk_bool(SigningVerifier_verify, (PublicKey *_pubKey, UpdaterHashClass *hash, const void *signature, uint32_t signatureLen), (_pubKey, hash, signature, signatureLen));
|
||||
extern "C" bool thunk_SigningVerifier_verify(PublicKey *_pubKey, UpdaterHashClass *hash, const void *signature, uint32_t signatureLen);
|
||||
#endif
|
||||
|
||||
@@ -970,8 +970,6 @@ bool SigningVerifier::verify(UpdaterHashClass *hash, const void *signature, uint
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
#if ARDUINO_SIGNING
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <bearssl/bearssl.h>
|
||||
//#include <Updater.h>
|
||||
#include <Updater.h>
|
||||
#include <StackThunk.h>
|
||||
|
||||
// Internal opaque structures, not needed by user applications
|
||||
namespace brssl {
|
||||
@@ -196,7 +197,6 @@ private:
|
||||
br_ssl_session_cache_lru _cache;
|
||||
};
|
||||
|
||||
#if 0
|
||||
|
||||
// Updater SHA256 hash and signature verification
|
||||
class HashSHA256 : public UpdaterHashClass {
|
||||
@@ -230,7 +230,6 @@ private:
|
||||
PublicKey *_pubKey;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -43,6 +43,16 @@ extern "C" unsigned char * thunk_##fcnToThunk proto { \
|
||||
return x; \
|
||||
}
|
||||
|
||||
#define make_stack_thunk_bool(fcnToThunk, proto, params) \
|
||||
extern "C" bool thunk_##fcnToThunk proto { \
|
||||
register uint32_t* sp asm("sp"); \
|
||||
stack_thunk_save = sp; \
|
||||
sp = stack_thunk_top; \
|
||||
auto x = fcnToThunk params; \
|
||||
sp = stack_thunk_save; \
|
||||
return x; \
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -152,7 +152,7 @@ uint8_t WiFiClass::beginAP(const char *ssid, const char* passphrase) {
|
||||
#endif
|
||||
|
||||
bool WiFiClass::connected() {
|
||||
return _wifi.connected() && localIP().isSet();
|
||||
return (_apMode && _wifiHWInitted) || (_wifi.connected() && localIP().isSet());
|
||||
}
|
||||
|
||||
/* Change Ip configuration settings disabling the dhcp client
|
||||
@@ -470,9 +470,12 @@ int32_t WiFiClass::RSSI(uint8_t networkItem) {
|
||||
return: one of the value defined in wl_status_t
|
||||
*/
|
||||
uint8_t WiFiClass::status() {
|
||||
if (_apMode && _wifiHWInitted) {
|
||||
return WL_CONNECTED;
|
||||
}
|
||||
switch (cyw43_wifi_link_status(&cyw43_state, _apMode ? 1 : 0)) {
|
||||
case CYW43_LINK_DOWN: return WL_IDLE_STATUS;
|
||||
case CYW43_LINK_JOIN: return localIP().isSet() ? WL_CONNECTED : WL_CONNECTING;
|
||||
case CYW43_LINK_JOIN: return localIP().isSet() ? WL_CONNECTED : WL_DISCONNECTED;
|
||||
case CYW43_LINK_FAIL: return WL_CONNECT_FAILED;
|
||||
case CYW43_LINK_NONET: return WL_CONNECT_FAILED;
|
||||
case CYW43_LINK_BADAUTH: return WL_CONNECT_FAILED;
|
||||
|
||||
@@ -75,6 +75,16 @@ public:
|
||||
int begin(const char* ssid, const char *passphrase);
|
||||
|
||||
bool connected();
|
||||
int8_t waitForConnectResult(unsigned long timeoutLength = 60000) {
|
||||
uint32_t now = millis();
|
||||
while (millis() - now < timeoutLength) {
|
||||
if (status() != WL_DISCONNECTED) {
|
||||
return status();
|
||||
}
|
||||
delay(10);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t beginAP(const char *ssid);
|
||||
uint8_t beginAP(const char *ssid, uint8_t channel);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
*/
|
||||
#include "WiFi.h"
|
||||
#include "WiFiClient.h"
|
||||
#include "WiFiClientSecureBearSSL.h"
|
||||
|
||||
|
||||
@@ -23,15 +23,11 @@
|
||||
#include <list>
|
||||
#include <errno.h>
|
||||
#include <algorithm>
|
||||
//#include <Esp.h>
|
||||
|
||||
|
||||
|
||||
//#include "debug.h"
|
||||
#include "WiFi.h"
|
||||
//#include "PolledTimeout.h"
|
||||
#include "WiFiClient.h"
|
||||
#include "WiFiClientSecureBearSSL.h"
|
||||
#include "WiFiNTP.h"
|
||||
#include "StackThunk.h"
|
||||
#include "lwip/opt.h"
|
||||
#include "lwip/ip.h"
|
||||
@@ -95,6 +91,10 @@ void WiFiClientSecureCtx::_clear() {
|
||||
_cipher_cnt = 0;
|
||||
_tls_min = BR_TLS10;
|
||||
_tls_max = BR_TLS12;
|
||||
if (_esp32_ta) {
|
||||
delete _esp32_ta;
|
||||
_esp32_ta = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiClientSecureCtx::_clearAuthenticationSettings() {
|
||||
@@ -103,6 +103,10 @@ void WiFiClientSecureCtx::_clearAuthenticationSettings() {
|
||||
_use_self_signed = false;
|
||||
_knownkey = nullptr;
|
||||
_ta = nullptr;
|
||||
if (_esp32_ta) {
|
||||
delete _esp32_ta;
|
||||
_esp32_ta = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -166,12 +170,28 @@ WiFiClientSecureCtx::WiFiClientSecureCtx(ClientContext *client,
|
||||
}
|
||||
|
||||
void WiFiClientSecureCtx::setClientRSACert(const X509List *chain, const PrivateKey *sk) {
|
||||
if (_esp32_chain) {
|
||||
delete _esp32_chain;
|
||||
_esp32_chain = nullptr;
|
||||
}
|
||||
if (_esp32_sk) {
|
||||
delete _esp32_sk;
|
||||
_esp32_sk = nullptr;
|
||||
}
|
||||
_chain = chain;
|
||||
_sk = sk;
|
||||
}
|
||||
|
||||
void WiFiClientSecureCtx::setClientECCert(const X509List *chain,
|
||||
const PrivateKey *sk, unsigned allowed_usages, unsigned cert_issuer_key_type) {
|
||||
if (_esp32_chain) {
|
||||
delete _esp32_chain;
|
||||
_esp32_chain = nullptr;
|
||||
}
|
||||
if (_esp32_sk) {
|
||||
delete _esp32_sk;
|
||||
_esp32_sk = nullptr;
|
||||
}
|
||||
_chain = chain;
|
||||
_sk = sk;
|
||||
_allowed_usages = allowed_usages;
|
||||
@@ -1071,7 +1091,11 @@ bool WiFiClientSecureCtx::_installClientX509Validator() {
|
||||
DEBUG_BSSL("_installClientX509Validator: OOM for _x509_minimal\n");
|
||||
return false;
|
||||
}
|
||||
br_x509_minimal_init(_x509_minimal.get(), &br_sha256_vtable, _ta ? _ta->getTrustAnchors() : nullptr, _ta ? _ta->getCount() : 0);
|
||||
if (_esp32_ta) {
|
||||
br_x509_minimal_init(_x509_minimal.get(), &br_sha256_vtable, _esp32_ta->getTrustAnchors(), _esp32_ta->getCount());
|
||||
} else {
|
||||
br_x509_minimal_init(_x509_minimal.get(), &br_sha256_vtable, _ta ? _ta->getTrustAnchors() : nullptr, _ta ? _ta->getCount() : 0);
|
||||
}
|
||||
br_x509_minimal_set_rsa(_x509_minimal.get(), br_ssl_engine_get_rsavrfy(_eng));
|
||||
#ifndef BEARSSL_SSL_BASIC
|
||||
br_x509_minimal_set_ecdsa(_x509_minimal.get(), br_ssl_engine_get_ec(_eng), br_ssl_engine_get_ecdsa(_eng));
|
||||
@@ -1107,6 +1131,14 @@ bool WiFiClientSecureCtx::_connectSSL(const char* hostName) {
|
||||
_freeSSL();
|
||||
_oom_err = false;
|
||||
|
||||
// The ESP32 doesn't check cert time, but we do. So enable NTP silently
|
||||
if (_esp32_ta || _esp32_chain || _esp32_sk) {
|
||||
if (!NTP.running()) {
|
||||
NTP.begin("pool.ntp.org");
|
||||
NTP.waitSet();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ESP_SSL
|
||||
// BearSSL will reject all connections unless an authentication option is set, warn in DEBUG builds
|
||||
if (!_use_insecure && !_use_fingerprint && !_use_self_signed && !_knownkey && !_certStore && !_ta) {
|
||||
@@ -1160,6 +1192,9 @@ bool WiFiClientSecureCtx::_connectSSL(const char* hostName) {
|
||||
DEBUG_BSSL("_connectSSL: Attempting to use EC cert in minimal cipher mode (no EC)\n");
|
||||
return false;
|
||||
#endif
|
||||
} else if (_esp32_sk && _esp32_chain) {
|
||||
br_ssl_client_set_single_rsa(_sc.get(), _esp32_chain->getX509Certs(), _esp32_chain->getCount(),
|
||||
_esp32_sk->getRSA(), br_rsa_pkcs1_sign_get_default());
|
||||
}
|
||||
|
||||
// Restore session from the storage spot, if present
|
||||
|
||||
@@ -159,6 +159,101 @@ public:
|
||||
// consume bytes after use (see peekBuffer)
|
||||
virtual void peekConsume(size_t consume) override;
|
||||
#endif
|
||||
|
||||
// ESP32 compatibility
|
||||
void setCACert(const char *rootCA) {
|
||||
if (_esp32_ta) {
|
||||
delete _esp32_ta;
|
||||
}
|
||||
_esp32_ta = new X509List(rootCA);
|
||||
}
|
||||
void setCertificate(const char *client_ca) {
|
||||
if (_esp32_chain) {
|
||||
delete _esp32_chain;
|
||||
}
|
||||
_esp32_chain = new X509List(client_ca);
|
||||
}
|
||||
void setPrivateKey(const char *private_key) {
|
||||
if (_esp32_sk) {
|
||||
delete _esp32_sk;
|
||||
}
|
||||
_esp32_sk = new PrivateKey(private_key);
|
||||
}
|
||||
bool loadCACert(Stream& stream, size_t size) {
|
||||
bool ret = false;
|
||||
auto buff = new char[size];
|
||||
if (size == stream.readBytes(buff, size)) {
|
||||
setCACert(buff);
|
||||
ret = true;
|
||||
}
|
||||
delete[] buff;
|
||||
return ret;
|
||||
}
|
||||
bool loadCertificate(Stream& stream, size_t size) {
|
||||
bool ret = false;
|
||||
auto buff = new char[size];
|
||||
if (size == stream.readBytes(buff, size)) {
|
||||
setCertificate(buff);
|
||||
ret = true;
|
||||
}
|
||||
delete[] buff;
|
||||
return ret;
|
||||
}
|
||||
bool loadPrivateKey(Stream& stream, size_t size) {
|
||||
bool ret = false;
|
||||
auto buff = new char[size];
|
||||
if (size == stream.readBytes(buff, size)) {
|
||||
setPrivateKey(buff);
|
||||
ret = true;
|
||||
}
|
||||
delete[] buff;
|
||||
return ret;
|
||||
}
|
||||
int connect(IPAddress ip, uint16_t port, int32_t timeout) {
|
||||
auto save = _timeout;
|
||||
_timeout = timeout * 1000; // timeout is in secs, _timeout in milliseconds
|
||||
auto ret = connect(ip, port);
|
||||
_timeout = save;
|
||||
return ret;
|
||||
}
|
||||
int connect(const char *host, uint16_t port, int32_t timeout) {
|
||||
auto save = _timeout;
|
||||
_timeout = timeout * 1000; // timeout is in secs, _timeout in milliseconds
|
||||
auto ret = connect(host, port);
|
||||
_timeout = save;
|
||||
return ret;
|
||||
}
|
||||
int connect(IPAddress ip, uint16_t port, const char *rootCABuff, const char *cli_cert, const char *cli_key) {
|
||||
if (_esp32_ta) {
|
||||
delete _esp32_ta;
|
||||
_esp32_ta = nullptr;
|
||||
}
|
||||
if (_esp32_chain) {
|
||||
delete _esp32_chain;
|
||||
_esp32_chain = nullptr;
|
||||
}
|
||||
if (_esp32_sk) {
|
||||
delete _esp32_sk;
|
||||
_esp32_sk = nullptr;
|
||||
}
|
||||
if (rootCABuff) {
|
||||
setCertificate(rootCABuff);
|
||||
}
|
||||
if (cli_cert && cli_key) {
|
||||
setCertificate(cli_cert);
|
||||
setPrivateKey(cli_key);
|
||||
}
|
||||
return connect(ip, port);
|
||||
}
|
||||
int connect(const char *host, uint16_t port, const char *rootCABuff, const char *cli_cert, const char *cli_key) {
|
||||
IPAddress ip;
|
||||
if (WiFi.hostByName(host, ip, _timeout)) {
|
||||
return connect(ip, port, rootCABuff, cli_cert, cli_key);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
bool _connectSSL(const char *hostName); // Do initial SSL handshake
|
||||
|
||||
@@ -241,6 +336,11 @@ private:
|
||||
bool _installServerX509Validator(const X509List *client_CA_ta); // Setup X509 client cert validation, if supplied
|
||||
|
||||
uint8_t *_streamLoad(Stream& stream, size_t size);
|
||||
|
||||
// ESP32 compatibility
|
||||
X509List *_esp32_ta = nullptr;
|
||||
X509List *_esp32_chain = nullptr;
|
||||
PrivateKey *_esp32_sk = nullptr;
|
||||
}; // class WiFiClientSecureCtx
|
||||
|
||||
|
||||
@@ -443,6 +543,40 @@ public:
|
||||
return _ctx->peekConsume(consume);
|
||||
}
|
||||
#endif
|
||||
|
||||
// ESP32 compatibility
|
||||
void setCACert(const char *rootCA) {
|
||||
return _ctx->setCACert(rootCA);
|
||||
}
|
||||
void setCertificate(const char *client_ca) {
|
||||
return _ctx->setCertificate(client_ca);
|
||||
}
|
||||
void setPrivateKey(const char *private_key) {
|
||||
return _ctx->setPrivateKey(private_key);
|
||||
}
|
||||
bool loadCACert(Stream& stream, size_t size) {
|
||||
return _ctx->loadCACert(stream, size);
|
||||
}
|
||||
bool loadCertificate(Stream& stream, size_t size) {
|
||||
return _ctx->loadCertificate(stream, size);
|
||||
}
|
||||
bool loadPrivateKey(Stream& stream, size_t size) {
|
||||
return _ctx->loadPrivateKey(stream, size);
|
||||
}
|
||||
|
||||
int connect(IPAddress ip, uint16_t port, int32_t timeout) {
|
||||
return _ctx->connect(ip, port, timeout);
|
||||
}
|
||||
int connect(const char *host, uint16_t port, int32_t timeout) {
|
||||
return _ctx->connect(host, port, timeout);
|
||||
}
|
||||
int connect(IPAddress ip, uint16_t port, const char *rootCABuff, const char *cli_cert, const char *cli_key) {
|
||||
return _ctx->connect(ip, port, rootCABuff, cli_cert, cli_key);
|
||||
}
|
||||
int connect(const char *host, uint16_t port, const char *rootCABuff, const char *cli_cert, const char *cli_key) {
|
||||
return _ctx->connect(host, port, rootCABuff, cli_cert, cli_key);
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<WiFiClientSecureCtx> _ctx;
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <time.h>
|
||||
#include <lwip/apps/sntp.h>
|
||||
|
||||
class NTPClass {
|
||||
@@ -37,6 +38,7 @@ public:
|
||||
sntp_setserver(0, server);
|
||||
sntp_setoperatingmode(SNTP_OPMODE_POLL);
|
||||
sntp_init();
|
||||
_running = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,15 +53,17 @@ public:
|
||||
}
|
||||
sntp_setoperatingmode(SNTP_OPMODE_POLL);
|
||||
sntp_init();
|
||||
_running = true;
|
||||
}
|
||||
|
||||
|
||||
void begin(const char *server, int timeout = 3600) {
|
||||
IPAddress addr;
|
||||
if (WiFi.hostByName(server, addr)) {
|
||||
begin(addr, timeout);
|
||||
}
|
||||
_running = true;
|
||||
}
|
||||
|
||||
void begin(const char *s1, const char *s2, int timeout = 3600) {
|
||||
IPAddress a1, a2;
|
||||
if (WiFi.hostByName(s1, a1)) {
|
||||
@@ -69,7 +73,33 @@ public:
|
||||
begin(a1, timeout);
|
||||
}
|
||||
}
|
||||
_running = true;
|
||||
}
|
||||
|
||||
bool waitSet(uint32_t timeout = 10000) {
|
||||
return waitSet(nullptr, timeout);
|
||||
}
|
||||
|
||||
bool waitSet(void (*cb)(), uint32_t timeout = 10000) {
|
||||
if (!running()) {
|
||||
begin("pool.ntp.org");
|
||||
}
|
||||
uint32_t start = millis();
|
||||
while ((time(nullptr) < 10000000) && (millis() - start < timeout)) {
|
||||
delay(100);
|
||||
if (cb) {
|
||||
cb();
|
||||
}
|
||||
}
|
||||
return time(nullptr) < 10000000;
|
||||
}
|
||||
|
||||
bool running() {
|
||||
return _running;
|
||||
}
|
||||
|
||||
private:
|
||||
bool _running = false;
|
||||
};
|
||||
|
||||
// ESP8266 compat
|
||||
|
||||
@@ -187,6 +187,10 @@ public:
|
||||
}
|
||||
|
||||
void setTimeout(int timeout_ms) {
|
||||
if (timeout_ms < 100) {
|
||||
// Crude logic to allow for seconds or milliseconds to work. Timeouts of < 100ms don't make much sense, so assume the user meant seconds, not milliseconds
|
||||
timeout_ms *= 1000;
|
||||
}
|
||||
_timeout_ms = timeout_ms;
|
||||
}
|
||||
|
||||
@@ -487,6 +491,7 @@ protected:
|
||||
DEBUGV(":wr %d %d\r\n", _datalen - _written, _written);
|
||||
|
||||
bool has_written = false;
|
||||
int scale = 0;
|
||||
|
||||
while (_written < _datalen) {
|
||||
if (state() == CLOSED) {
|
||||
@@ -497,6 +502,10 @@ protected:
|
||||
{
|
||||
LWIPMutex m; // Block the timer sys_check_timeouts call, just for this call
|
||||
next_chunk_size = std::min((size_t)tcp_sndbuf(_pcb), remaining);
|
||||
// Potentially reduce transmit size if we are tight on memory, but only if it doesn't return a 0 chunk size
|
||||
if (next_chunk_size > (size_t)(1 << scale)) {
|
||||
next_chunk_size >>= scale;
|
||||
}
|
||||
}
|
||||
if (!next_chunk_size) {
|
||||
break;
|
||||
@@ -527,6 +536,13 @@ protected:
|
||||
if (err == ERR_OK) {
|
||||
_written += next_chunk_size;
|
||||
has_written = true;
|
||||
} else if (err == ERR_MEM) {
|
||||
if (scale < 4) {
|
||||
// Retry sending at 1/2 the chunk size
|
||||
scale ++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// ERR_MEM(-1) is a valid error meaning
|
||||
// "come back later". It leaves state() opened
|
||||
|
||||
@@ -28,6 +28,7 @@ extern "C" {
|
||||
}
|
||||
|
||||
#include <AddrList.h>
|
||||
#include <Arduino.h>
|
||||
//#include <PolledTimeout.h>
|
||||
|
||||
#define PBUF_ALIGNER_ADJUST 4
|
||||
@@ -50,6 +51,11 @@ public:
|
||||
, _tx_buf_cur(0)
|
||||
, _tx_buf_offset(0) {
|
||||
_pcb = udp_new();
|
||||
#if LWIP_IPV6
|
||||
// local_ip defaults to 0.0.0.0
|
||||
// which is problematic for sending IPv6 packets
|
||||
ip_addr_set_ipaddr(&_pcb->local_ip, IP_ANY_TYPE);
|
||||
#endif
|
||||
#ifdef LWIP_MAYBE_XCC
|
||||
_mcast_ttl = 1;
|
||||
#endif
|
||||
|
||||
@@ -62,8 +62,7 @@ typedef enum {
|
||||
WL_DISCONNECTED,
|
||||
WL_AP_LISTENING,
|
||||
WL_AP_CONNECTED,
|
||||
WL_AP_FAILED,
|
||||
WL_CONNECTING
|
||||
WL_AP_FAILED
|
||||
} wl_status_t;
|
||||
|
||||
/* Encryption modes */
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user