Add OTA update support (#711)
Adds a 12K OTA stub 3rd stage bootloader, which reads new firmware from the LittleFS filesystem and flashes on reboot. By storing the OTA commands in a file in flash, it is possible to recover from a power failure during OTA programming. On power resume, the OTA block will simply re-program from the beginning. Support cryptographic signed OTA updates, if desired. Includes host-side signing logic via openssl. Add PicoOTA library which encapsulates the file format for the updater, including CRC32 checking. Add LEAmDNS support to allow Arduino IDE discovery Add ArduinoOTA class for IDE uploads Add MD5Builder class Add Updater class which supports writing and validating cryptographically signed binaries from any source (http, Ethernet, WiFi, Serial, etc.) Add documentation and readmes.
This commit is contained in:
parent
71be07e69f
commit
da86a8942b
87 changed files with 23806 additions and 313 deletions
2
.github/workflows/pull-request.yml
vendored
2
.github/workflows/pull-request.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,4 +1,6 @@
|
|||
.DS_Store
|
||||
system
|
||||
tools/dist
|
||||
tools/libpico/build
|
||||
docs/_build
|
||||
ota/build
|
||||
tools/libpico/build
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -31,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
|
||||
|
|
|
|||
|
|
@ -146,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
|
||||
|
|
@ -184,6 +186,8 @@ If you want to contribute or have bugfixes, drop me a note at <earlephilhower@ya
|
|||
* [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
|
||||
earlephilhower@yahoo.com
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -311,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;
|
||||
|
||||
|
|
|
|||
247
cores/rp2040/StreamString.h
Normal file
247
cores/rp2040/StreamString.h
Normal file
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
239
docs/ota.rst
Executable file
239
docs/ota.rst
Executable file
|
|
@ -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]
|
||||
|
||||
|
|
@ -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
|
||||
------------------
|
||||
|
||||
|
|
|
|||
50
include/pico_base/pico/ota_command.h
Normal file
50
include/pico_base/pico/ota_command.h
Normal file
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
BIN
lib/ota.o
Normal file
BIN
lib/ota.o
Normal file
Binary file not shown.
77
libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino
Normal file
77
libraries/ArduinoOTA/examples/BasicOTA/BasicOTA.ino
Normal file
|
|
@ -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();
|
||||
}
|
||||
74
libraries/ArduinoOTA/examples/OTALeds/OTALeds.ino
Normal file
74
libraries/ArduinoOTA/examples/OTALeds/OTALeds.ino
Normal file
|
|
@ -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);
|
||||
}
|
||||
27
libraries/ArduinoOTA/examples/SignedOTA-blink/private.key
Normal file
27
libraries/ArduinoOTA/examples/SignedOTA-blink/private.key
Normal file
|
|
@ -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-----
|
||||
9
libraries/ArduinoOTA/examples/SignedOTA-blink/public.key
Normal file
9
libraries/ArduinoOTA/examples/SignedOTA-blink/public.key
Normal file
|
|
@ -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-----
|
||||
91
libraries/ArduinoOTA/examples/SignedOTA/SignedOTA.ino
Normal file
91
libraries/ArduinoOTA/examples/SignedOTA/SignedOTA.ino
Normal file
|
|
@ -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();
|
||||
}
|
||||
27
libraries/ArduinoOTA/examples/SignedOTA/private.key
Normal file
27
libraries/ArduinoOTA/examples/SignedOTA/private.key
Normal file
|
|
@ -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-----
|
||||
9
libraries/ArduinoOTA/examples/SignedOTA/public.key
Normal file
9
libraries/ArduinoOTA/examples/SignedOTA/public.key
Normal file
|
|
@ -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-----
|
||||
26
libraries/ArduinoOTA/keywords.txt
Normal file
26
libraries/ArduinoOTA/keywords.txt
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#######################################
|
||||
# Syntax Coloring Map For Ultrasound
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
ArduinoOTA KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
begin KEYWORD2
|
||||
setup KEYWORD2
|
||||
handle KEYWORD2
|
||||
onStart KEYWORD2
|
||||
onEnd KEYWORD2
|
||||
onError KEYWORD2
|
||||
onProgress KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
|
||||
10
libraries/ArduinoOTA/library.properties
Normal file
10
libraries/ArduinoOTA/library.properties
Normal file
|
|
@ -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
|
||||
398
libraries/ArduinoOTA/src/ArduinoOTA.cpp
Normal file
398
libraries/ArduinoOTA/src/ArduinoOTA.cpp
Normal file
|
|
@ -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
|
||||
103
libraries/ArduinoOTA/src/ArduinoOTA.h
Normal file
103
libraries/ArduinoOTA/src/ArduinoOTA.h
Normal file
|
|
@ -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
|
||||
|
|
@ -35,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()
|
||||
|
|
@ -51,8 +51,8 @@ unsigned long ulMainGetRunTimeCounterValue( void );
|
|||
#define configTIMER_QUEUE_LENGTH 5
|
||||
#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
|
||||
|
|
@ -63,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
|
||||
|
|
@ -74,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,28 +1,28 @@
|
|||
/*
|
||||
* 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 */
|
||||
|
|
@ -42,8 +42,7 @@
|
|||
|
||||
/*-----------------------------------------------------------*/
|
||||
|
||||
void initFreeRTOS(void)
|
||||
{
|
||||
void initFreeRTOS(void) {
|
||||
}
|
||||
|
||||
extern void setup() __attribute__((weak));
|
||||
|
|
@ -54,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) {
|
||||
|
|
@ -77,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) {
|
||||
|
|
@ -97,7 +94,7 @@ static void __core1(void *params)
|
|||
vTaskDelay(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void delay(unsigned long ms) {
|
||||
vTaskDelay(ms / portTICK_PERIOD_MS);
|
||||
|
|
@ -111,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.
|
||||
|
|
@ -135,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...
|
||||
}
|
||||
|
||||
|
|
@ -170,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 */
|
||||
|
|
@ -193,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 */
|
||||
|
|
@ -217,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);
|
||||
|
|
@ -256,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.
|
||||
}
|
||||
|
|
@ -292,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.
|
||||
}
|
||||
|
|
@ -313,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 ];
|
||||
|
||||
|
|
@ -332,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 ];
|
||||
|
||||
|
|
@ -353,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"
|
||||
|
|
@ -363,8 +344,7 @@ void vApplicationAssertHook() {
|
|||
taskDISABLE_INTERRUPTS(); // Disable task interrupts
|
||||
|
||||
prvSetMainLedOn(); // Main LED on.
|
||||
for(;;)
|
||||
{
|
||||
for (;;) {
|
||||
sleep_ms(100);
|
||||
prvBlinkMainLed(); // Led off.
|
||||
|
||||
|
|
@ -381,8 +361,7 @@ void vApplicationAssertHook() {
|
|||
#endif
|
||||
|
||||
|
||||
static void __usb(void *param)
|
||||
{
|
||||
static void __usb(void *param) {
|
||||
(void) param;
|
||||
|
||||
tusb_init();
|
||||
|
|
@ -403,8 +382,7 @@ static void __usb(void *param)
|
|||
extern void __SetupDescHIDReport();
|
||||
extern void __SetupUSBDescriptor();
|
||||
|
||||
void __USBStart()
|
||||
{
|
||||
void __USBStart() {
|
||||
mutex_init(&__usb_mutex);
|
||||
|
||||
__SetupDescHIDReport();
|
||||
|
|
@ -412,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);
|
||||
}
|
||||
|
|
|
|||
61
libraries/LEAmDNS/README.rst
Normal file
61
libraries/LEAmDNS/README.rst
Normal file
|
|
@ -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.
|
||||
25
libraries/LEAmDNS/keywords.txt
Normal file
25
libraries/LEAmDNS/keywords.txt
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#######################################
|
||||
# Syntax Coloring Map For Ultrasound
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
ESP8266mDNS KEYWORD1
|
||||
MDNSResponder KEYWORD1
|
||||
MDNS KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
begin KEYWORD2
|
||||
update KEYWORD2
|
||||
addService KEYWORD2
|
||||
enableArduino KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
|
||||
10
libraries/LEAmDNS/library.properties
Normal file
10
libraries/LEAmDNS/library.properties
Normal file
|
|
@ -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
|
||||
32
libraries/LEAmDNS/src/ESP8266mDNS.cpp
Normal file
32
libraries/LEAmDNS/src/ESP8266mDNS.cpp
Normal file
|
|
@ -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
|
||||
54
libraries/LEAmDNS/src/ESP8266mDNS.h
Normal file
54
libraries/LEAmDNS/src/ESP8266mDNS.h
Normal file
|
|
@ -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
|
||||
1244
libraries/LEAmDNS/src/LEAmDNS.cpp
Normal file
1244
libraries/LEAmDNS/src/LEAmDNS.cpp
Normal file
File diff suppressed because it is too large
Load diff
1350
libraries/LEAmDNS/src/LEAmDNS.h
Normal file
1350
libraries/LEAmDNS/src/LEAmDNS.h
Normal file
File diff suppressed because it is too large
Load diff
2089
libraries/LEAmDNS/src/LEAmDNS_Control.cpp
Normal file
2089
libraries/LEAmDNS/src/LEAmDNS_Control.cpp
Normal file
File diff suppressed because it is too large
Load diff
671
libraries/LEAmDNS/src/LEAmDNS_Helpers.cpp
Normal file
671
libraries/LEAmDNS/src/LEAmDNS_Helpers.cpp
Normal file
|
|
@ -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
|
||||
190
libraries/LEAmDNS/src/LEAmDNS_Priv.h
Normal file
190
libraries/LEAmDNS/src/LEAmDNS_Priv.h
Normal file
|
|
@ -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
|
||||
2031
libraries/LEAmDNS/src/LEAmDNS_Structs.cpp
Normal file
2031
libraries/LEAmDNS/src/LEAmDNS_Structs.cpp
Normal file
File diff suppressed because it is too large
Load diff
1747
libraries/LEAmDNS/src/LEAmDNS_Transfer.cpp
Normal file
1747
libraries/LEAmDNS/src/LEAmDNS_Transfer.cpp
Normal file
File diff suppressed because it is too large
Load diff
30
libraries/LEAmDNS/src/LEAmDNS_lwIPdefs.h
Normal file
30
libraries/LEAmDNS/src/LEAmDNS_lwIPdefs.h
Normal file
|
|
@ -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
|
||||
20
libraries/LittleFS/keywords.txt
Normal file
20
libraries/LittleFS/keywords.txt
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#######################################
|
||||
# Syntax Coloring Map For Ultrasound
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
LittleFS KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
format KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
|
||||
27
libraries/MD5Builder/keywords.txt
Normal file
27
libraries/MD5Builder/keywords.txt
Normal file
|
|
@ -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)
|
||||
#######################################
|
||||
|
||||
10
libraries/MD5Builder/library.properties
Normal file
10
libraries/MD5Builder/library.properties
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
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
|
||||
117
libraries/MD5Builder/src/MD5Builder.cpp
Normal file
117
libraries/MD5Builder/src/MD5Builder.cpp
Normal file
|
|
@ -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);
|
||||
}
|
||||
59
libraries/MD5Builder/src/MD5Builder.h
Normal file
59
libraries/MD5Builder/src/MD5Builder.h
Normal file
|
|
@ -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() {
|
||||
}
|
||||
3898
libraries/PicoOTA/examples/OTAfromFile-Compressed/blink_100_1000.h
Normal file
3898
libraries/PicoOTA/examples/OTAfromFile-Compressed/blink_100_1000.h
Normal file
File diff suppressed because it is too large
Load diff
39
libraries/PicoOTA/examples/OTAfromFile/OTAfromFile.ino
Normal file
39
libraries/PicoOTA/examples/OTAfromFile/OTAfromFile.ino
Normal file
|
|
@ -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() {
|
||||
}
|
||||
5828
libraries/PicoOTA/examples/OTAfromFile/blink_100_1000.h
Normal file
5828
libraries/PicoOTA/examples/OTAfromFile/blink_100_1000.h
Normal file
File diff suppressed because it is too large
Load diff
21
libraries/PicoOTA/keywords.txt
Normal file
21
libraries/PicoOTA/keywords.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#######################################
|
||||
# Syntax Coloring Map For Ultrasound
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
picoOTA KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
addFile KEYWORD1
|
||||
commit KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
|
||||
10
libraries/PicoOTA/library.properties
Normal file
10
libraries/PicoOTA/library.properties
Normal file
|
|
@ -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
|
||||
22
libraries/PicoOTA/src/PicoOTA.cpp
Normal file
22
libraries/PicoOTA/src/PicoOTA.cpp
Normal file
|
|
@ -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;
|
||||
144
libraries/PicoOTA/src/PicoOTA.h
Normal file
144
libraries/PicoOTA/src/PicoOTA.h
Normal file
|
|
@ -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;
|
||||
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
26
libraries/Updater/keywords.txt
Normal file
26
libraries/Updater/keywords.txt
Normal file
|
|
@ -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)
|
||||
#######################################
|
||||
|
||||
10
libraries/Updater/library.properties
Normal file
10
libraries/Updater/library.properties
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
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
|
||||
dot_a_linkage=true
|
||||
443
libraries/Updater/src/Updater.cpp
Normal file
443
libraries/Updater/src/Updater.cpp
Normal file
|
|
@ -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;
|
||||
252
libraries/Updater/src/Updater.h
Normal file
252
libraries/Updater/src/Updater.h
Normal file
|
|
@ -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;
|
||||
3
libraries/Updater/src/Updater_Signing.h
Normal file
3
libraries/Updater/src/Updater_Signing.h
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// This file will be overridden when automatic signing is used.
|
||||
// By default, no signing.
|
||||
#define ARDUINO_SIGNING 0
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -475,7 +475,7 @@ uint8_t WiFiClass::status() {
|
|||
}
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ extern "C" {
|
|||
}
|
||||
|
||||
#include <AddrList.h>
|
||||
#include <Arduino.h>
|
||||
//#include <PolledTimeout.h>
|
||||
|
||||
#define PBUF_ALIGNER_ADJUST 4
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -76,6 +76,9 @@
|
|||
#define __ADDRLIST_H
|
||||
|
||||
#include <IPAddress.h>
|
||||
#include <api/String.h>
|
||||
using IPAddress = arduino::IPAddress;
|
||||
using String = arduino::String;
|
||||
#include <lwip/netif.h>
|
||||
|
||||
#if LWIP_IPV6
|
||||
|
|
|
|||
85
ota/CMakeLists.txt
Normal file
85
ota/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
cmake_minimum_required(VERSION 3.12)
|
||||
|
||||
include(pico_sdk_import.cmake)
|
||||
|
||||
project(pico_lib C CXX ASM)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_BUILD_TYPE RelWithDebInfo)
|
||||
|
||||
set(PICO_BOARD pico)
|
||||
set(PICO_COPY_TO_RAM 1)
|
||||
|
||||
# Initialize the SDK
|
||||
pico_sdk_init()
|
||||
|
||||
add_executable(ota
|
||||
ota.c
|
||||
ota_lfs.c
|
||||
ota_clocks.c
|
||||
../libraries/LittleFS/lib/littlefs/lfs.c
|
||||
../libraries/LittleFS/lib/littlefs/lfs_util.c
|
||||
./uzlib/src/tinflate.c
|
||||
./uzlib/src/tinfgzip.c
|
||||
)
|
||||
pico_add_extra_outputs(ota)
|
||||
pico_enable_stdio_usb(ota 0)
|
||||
pico_enable_stdio_uart(ota 0)
|
||||
|
||||
# Use a longer XOSC startup time, to accommodate Adafruit and other boards that may need it.
|
||||
target_compile_definitions(ota PUBLIC
|
||||
PICO_FLASH_SIZE_BYTES=16777216
|
||||
PICO_XOSC_STARTUP_DELAY_MULTIPLIER=64
|
||||
PICO_RP2040_B0_SUPPORTED=1
|
||||
PICO_RP2040_B1_SUPPORTED=1
|
||||
PICO_RP2040_B2_SUPPORTED=1
|
||||
PICO_PRINTF_SUPPORT_FLOAT=0
|
||||
PICO_PRINTF_SUPPORT_LONG_LONG=0
|
||||
LIB_PICO_PRINTF_NONE=1
|
||||
LFS_READONLY=1
|
||||
LFS_NO_DEBUG=1
|
||||
LFS_NO_WARN=1
|
||||
LFS_NO_ERROR=1
|
||||
LFS_NO_ASSERT=1
|
||||
LFS_NO_MALLOC=1
|
||||
PICO_PANIC_FUNCTION=
|
||||
PICO_TIME_DEFAULT_ALARM_POOL_DISABLED=1
|
||||
)
|
||||
|
||||
target_compile_options(ota PUBLIC
|
||||
-fno-exceptions
|
||||
-Os
|
||||
$<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>
|
||||
)
|
||||
|
||||
target_link_options(ota PUBLIC
|
||||
-Wl,--wrap=clocks_init
|
||||
-Wl,--wrap=exit
|
||||
-Wl,--wrap=atexit
|
||||
-Wl,--wrap=panic_unsupported
|
||||
-Wl,--wrap=panic
|
||||
-Wl,--wrap=hard_assertion_failure
|
||||
-Wl,--cref
|
||||
)
|
||||
|
||||
set_target_properties(ota PROPERTIES PICO_TARGET_LINKER_SCRIPT
|
||||
${CMAKE_SOURCE_DIR}/memmap_ota.ld)
|
||||
|
||||
target_link_libraries(ota
|
||||
pico_platform
|
||||
pico_standard_link
|
||||
hardware_irq
|
||||
hardware_flash
|
||||
pico_time
|
||||
hardware_gpio
|
||||
hardware_uart
|
||||
hardware_resets
|
||||
hardware_clocks
|
||||
pico_stdlib
|
||||
)
|
||||
|
||||
add_custom_command(TARGET ota POST_BUILD
|
||||
COMMAND ../../system/arm-none-eabi/bin/arm-none-eabi-ld -r -A armv6-m -b binary -o ota.o ota.bin
|
||||
COMMAND ../../system/arm-none-eabi/bin/arm-none-eabi-objcopy --rename-section .data=.OTA ota.o ../../lib/ota.o
|
||||
COMMAND cp ../ota_command.h ../../include/pico_base/pico/.
|
||||
)
|
||||
17
ota/README.md
Normal file
17
ota/README.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Arduino-Pico OTA Bootloader
|
||||
|
||||
This directory contains a small "stage 3" bootloader (after the boot ROM and the ``boot2.S`` flash configuration) which implements a power fail safe, generic OTA method.
|
||||
|
||||
The bootloader is built here into an ``.ELF``, without ``boot2.S`` (which will come from the main app), configured to copy itself into RAM (so that it can update itself), and included in the main applications. Exactly ``12KB`` for all sketches is consumed by this OTA bootloader.
|
||||
|
||||
It works by mounting the LittleFS file system (the parameters are stored by the main app at 0x3000-16), checking for a specially named command file. If that file exists, and its contents pass a checksum, the bootloader reads from the filesystem (optionally, automatically decompressing ``GZIP`` compressed files) and writes to application flash.
|
||||
|
||||
Every block is checked to see if it identical to the block already in flash, and if so it is skipped. This allows silently skipping bootloader writes in many cases.
|
||||
|
||||
Should a power failure happen, as long as it was not in the middle of writing a new OTA bootloader, it should simply begin copying the same program from scratch.
|
||||
|
||||
When the copy is completed, the command file's contents are erased so that on a reboot it won't attempt to write the same firmware over and over. It then reboots the chip (and re-runs the potentially new bootloader).
|
||||
|
||||
If there is no special file, or its contents don't have a proper checksum, the bootloaer simply adjusts the ARM internal vector pointers and jumps to the main application.
|
||||
|
||||
The files in the LittleFS filesystem can come over ``WiFi``, over an ``Ethernet`` object, or even over a serial port.
|
||||
12
ota/make-ota.sh
Executable file
12
ota/make-ota.sh
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
export PICO_SDK_PATH="$(cd ../pico-sdk/; pwd)"
|
||||
export PATH="$(cd ../../system/arm-none-eabi/bin; pwd):$PATH"
|
||||
|
||||
rm -rf build
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make -j # VERBOSE=1
|
||||
253
ota/memmap_ota.ld
Normal file
253
ota/memmap_ota.ld
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
/* Based on GCC ARM embedded samples.
|
||||
Defines the following symbols for use by code:
|
||||
__exidx_start
|
||||
__exidx_end
|
||||
__etext
|
||||
__data_start__
|
||||
__preinit_array_start
|
||||
__preinit_array_end
|
||||
__init_array_start
|
||||
__init_array_end
|
||||
__fini_array_start
|
||||
__fini_array_end
|
||||
__data_end__
|
||||
__bss_start__
|
||||
__bss_end__
|
||||
__end__
|
||||
end
|
||||
__HeapLimit
|
||||
__StackLimit
|
||||
__StackTop
|
||||
__stack (== StackTop)
|
||||
*/
|
||||
|
||||
MEMORY
|
||||
{
|
||||
FLASH(rx) : ORIGIN = 0x10000100, LENGTH = 16384k
|
||||
RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k
|
||||
SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k
|
||||
SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k
|
||||
}
|
||||
|
||||
ENTRY(_entry_point)
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* Second stage bootloader is prepended to the image. It must be 256 bytes big
|
||||
and checksummed. It is usually built by the boot_stage2 target
|
||||
in the Raspberry Pi Pico SDK
|
||||
*/
|
||||
|
||||
.flash_begin : {
|
||||
__flash_binary_start = .;
|
||||
} > FLASH
|
||||
/*
|
||||
.boot2 : {
|
||||
__boot2_start__ = .;
|
||||
KEEP (*(.boot2))
|
||||
__boot2_end__ = .;
|
||||
} > FLASH
|
||||
|
||||
ASSERT(__boot2_end__ - __boot2_start__ == 256,
|
||||
"ERROR: Pico second stage bootloader must be 256 bytes in size")
|
||||
*/
|
||||
/* 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.
|
||||
This can be used to transfer control back to the bootrom on debugger
|
||||
launches only, to perform proper flash setup.
|
||||
*/
|
||||
|
||||
.flashtext : {
|
||||
__logical_binary_start = .;
|
||||
KEEP (*(.vectors))
|
||||
KEEP (*(.binary_info_header))
|
||||
__binary_info_header_end = .;
|
||||
KEEP (*(.reset))
|
||||
}
|
||||
|
||||
.rodata : {
|
||||
/* segments not marked as .flashdata are instead pulled into .data (in RAM) to avoid accidental flash accesses */
|
||||
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*)))
|
||||
. = ALIGN(4);
|
||||
} > FLASH
|
||||
|
||||
.ARM.extab :
|
||||
{
|
||||
*(.ARM.extab* .gnu.linkonce.armextab.*)
|
||||
} > FLASH
|
||||
|
||||
__exidx_start = .;
|
||||
.ARM.exidx :
|
||||
{
|
||||
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
|
||||
} > FLASH
|
||||
__exidx_end = .;
|
||||
|
||||
/* Machine inspectable binary information */
|
||||
. = ALIGN(4);
|
||||
__binary_info_start = .;
|
||||
.binary_info :
|
||||
{
|
||||
KEEP(*(.binary_info.keep.*))
|
||||
*(.binary_info.*)
|
||||
} > FLASH
|
||||
__binary_info_end = .;
|
||||
. = ALIGN(4);
|
||||
|
||||
/* Vector table goes first in RAM, to avoid large alignment hole */
|
||||
.ram_vector_table (COPY): {
|
||||
*(.ram_vector_table)
|
||||
} > RAM
|
||||
|
||||
.text : {
|
||||
__ram_text_start__ = .;
|
||||
*(.init)
|
||||
*(.text*)
|
||||
*(.fini)
|
||||
/* Pull all c'tors into .text */
|
||||
*crtbegin.o(.ctors)
|
||||
*crtbegin?.o(.ctors)
|
||||
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
|
||||
*(SORT(.ctors.*))
|
||||
*(.ctors)
|
||||
/* Followed by destructors */
|
||||
*crtbegin.o(.dtors)
|
||||
*crtbegin?.o(.dtors)
|
||||
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
|
||||
*(SORT(.dtors.*))
|
||||
*(.dtors)
|
||||
|
||||
*(.eh_frame*)
|
||||
. = ALIGN(4);
|
||||
__ram_text_end__ = .;
|
||||
} > RAM AT> FLASH
|
||||
__ram_text_source__ = LOADADDR(.text);
|
||||
|
||||
|
||||
.data : {
|
||||
__data_start__ = .;
|
||||
*(vtable)
|
||||
|
||||
*(.time_critical*)
|
||||
|
||||
. = ALIGN(4);
|
||||
*(.rodata*)
|
||||
. = ALIGN(4);
|
||||
|
||||
*(.data*)
|
||||
|
||||
. = ALIGN(4);
|
||||
*(.after_data.*)
|
||||
. = ALIGN(4);
|
||||
/* preinit data */
|
||||
PROVIDE_HIDDEN (__mutex_array_start = .);
|
||||
KEEP(*(SORT(.mutex_array.*)))
|
||||
KEEP(*(.mutex_array))
|
||||
PROVIDE_HIDDEN (__mutex_array_end = .);
|
||||
|
||||
. = ALIGN(4);
|
||||
/* preinit data */
|
||||
PROVIDE_HIDDEN (__preinit_array_start = .);
|
||||
KEEP(*(SORT(.preinit_array.*)))
|
||||
KEEP(*(.preinit_array))
|
||||
PROVIDE_HIDDEN (__preinit_array_end = .);
|
||||
|
||||
. = ALIGN(4);
|
||||
/* init data */
|
||||
PROVIDE_HIDDEN (__init_array_start = .);
|
||||
KEEP(*(SORT(.init_array.*)))
|
||||
KEEP(*(.init_array))
|
||||
PROVIDE_HIDDEN (__init_array_end = .);
|
||||
|
||||
. = ALIGN(4);
|
||||
/* finit data */
|
||||
PROVIDE_HIDDEN (__fini_array_start = .);
|
||||
*(SORT(.fini_array.*))
|
||||
*(.fini_array)
|
||||
PROVIDE_HIDDEN (__fini_array_end = .);
|
||||
|
||||
*(.jcr)
|
||||
. = ALIGN(4);
|
||||
/* All data end */
|
||||
__data_end__ = .;
|
||||
} > RAM AT> FLASH
|
||||
/* __etext is the name of the .data init source pointer (...) */
|
||||
__etext = LOADADDR(.data);
|
||||
|
||||
.uninitialized_data (COPY): {
|
||||
. = ALIGN(4);
|
||||
*(.uninitialized_data*)
|
||||
} > RAM
|
||||
|
||||
/* Start and end symbols must be word-aligned */
|
||||
.scratch_x : {
|
||||
__scratch_x_start__ = .;
|
||||
*(.scratch_x.*)
|
||||
. = ALIGN(4);
|
||||
__scratch_x_end__ = .;
|
||||
} > SCRATCH_X AT > FLASH
|
||||
__scratch_x_source__ = LOADADDR(.scratch_x);
|
||||
|
||||
.scratch_y : {
|
||||
__scratch_y_start__ = .;
|
||||
*(.scratch_y.*)
|
||||
. = ALIGN(4);
|
||||
__scratch_y_end__ = .;
|
||||
} > SCRATCH_Y AT > FLASH
|
||||
__scratch_y_source__ = LOADADDR(.scratch_y);
|
||||
|
||||
.bss : {
|
||||
. = ALIGN(4);
|
||||
__bss_start__ = .;
|
||||
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*)))
|
||||
*(COMMON)
|
||||
. = ALIGN(4);
|
||||
__bss_end__ = .;
|
||||
} > RAM
|
||||
|
||||
.heap (COPY):
|
||||
{
|
||||
__end__ = .;
|
||||
end = __end__;
|
||||
*(.heap*)
|
||||
__HeapLimit = .;
|
||||
} > RAM
|
||||
|
||||
/* .stack*_dummy section doesn't contains any symbols. It is only
|
||||
* used for linker to calculate size of stack sections, and assign
|
||||
* values to stack symbols later
|
||||
*
|
||||
* stack1 section may be empty/missing if platform_launch_core1 is not used */
|
||||
|
||||
/* by default we put core 0 stack at the end of scratch Y, so that if core 1
|
||||
* stack is not used then all of SCRATCH_X is free.
|
||||
*/
|
||||
.stack1_dummy (COPY):
|
||||
{
|
||||
*(.stack1*)
|
||||
} > SCRATCH_X
|
||||
.stack_dummy (COPY):
|
||||
{
|
||||
*(.stack*)
|
||||
} > SCRATCH_Y
|
||||
|
||||
.flash_end : {
|
||||
__flash_binary_end = .;
|
||||
} > FLASH
|
||||
|
||||
/* stack limit is poorly named, but historically is maximum heap ptr */
|
||||
__StackLimit = ORIGIN(RAM) + LENGTH(RAM);
|
||||
__StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X);
|
||||
__StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y);
|
||||
__StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy);
|
||||
__StackBottom = __StackTop - SIZEOF(.stack_dummy);
|
||||
PROVIDE(__stack = __StackTop);
|
||||
|
||||
/* Check if data + heap + stack exceeds RAM limit */
|
||||
ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed")
|
||||
|
||||
ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary")
|
||||
/* todo assert on extra code */
|
||||
}
|
||||
|
||||
220
ota/ota.c
Normal file
220
ota/ota.c
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
/*
|
||||
ota.c - 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
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <hardware/irq.h>
|
||||
#include <hardware/structs/scb.h>
|
||||
#include <hardware/sync.h>
|
||||
#include <hardware/flash.h>
|
||||
#include <pico/time.h>
|
||||
#include <hardware/gpio.h>
|
||||
#include <hardware/uart.h>
|
||||
#include <hardware/watchdog.h>
|
||||
#include "ota_lfs.h"
|
||||
#include "ota_command.h"
|
||||
|
||||
//#define DEBUG 1
|
||||
|
||||
#ifndef DEBUG
|
||||
#define uart_putc(a, b)
|
||||
#define uart_puts(a, b)
|
||||
#endif
|
||||
|
||||
uint8_t **__FS_START__ = (uint8_t **)(XIP_BASE + 0x3000 - 0x10 + 0x0);
|
||||
uint8_t **__FS_END__ = (uint8_t **)(XIP_BASE + 0x3000 - 0x10 + 0x4);
|
||||
|
||||
void dumphex(uint32_t x) {
|
||||
#ifndef DEBUG
|
||||
(void) x;
|
||||
#else
|
||||
uart_puts(uart0, "0x");
|
||||
for (int nibble = 7; nibble >= 0; nibble--) {
|
||||
uint32_t n = 0x0f & (x >> (nibble * 4));
|
||||
char c = n < 10 ? '0' + n : 'A' + n - 10;
|
||||
uart_putc(uart0, c);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static OTACmdPage _ota_cmd;
|
||||
|
||||
void do_ota() {
|
||||
if (*__FS_START__ == *__FS_END__) {
|
||||
return;
|
||||
}
|
||||
if (!lfsMount(*__FS_START__, 4096, *__FS_END__ - *__FS_START__)) {
|
||||
uart_puts(uart0, "mount failed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// We are very naughty and record the last block read, since it should be the actual data block of the
|
||||
// OTA structure. We'll erase it behind the scenes to avoid bringing in all of LittleFS write infra.
|
||||
uint32_t blockToErase;
|
||||
if (!lfsReadOTA(&_ota_cmd, &blockToErase)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (memcmp(_ota_cmd.sign, "Pico OTA", 8)) {
|
||||
return; // No signature
|
||||
}
|
||||
|
||||
uint32_t crc = 0xffffffff;
|
||||
const uint8_t *data = (const uint8_t *)&_ota_cmd;
|
||||
for (uint32_t i = 0; i < offsetof(OTACmdPage, crc32); i++) {
|
||||
crc ^= data[i];
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (crc & 1) {
|
||||
crc = (crc >> 1) ^ 0xedb88320;
|
||||
} else {
|
||||
crc >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
crc = ~crc;
|
||||
if (crc != _ota_cmd.crc32) {
|
||||
uart_puts(uart0, "\ncrc32 mismatch\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_ota_cmd.count) {
|
||||
uart_puts(uart0, "\nno ota count\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < _ota_cmd.count; i++) {
|
||||
switch (_ota_cmd.cmd[i].command) {
|
||||
case _OTA_WRITE:
|
||||
uart_puts(uart0, "write: open ");
|
||||
uart_puts(uart0, _ota_cmd.cmd[i].write.filename);
|
||||
uart_puts(uart0, " = ");
|
||||
if (!lfsOpen(_ota_cmd.cmd[i].write.filename)) {
|
||||
uart_puts(uart0, "failed\n");
|
||||
return;
|
||||
}
|
||||
uart_puts(uart0, "success\n");
|
||||
uart_puts(uart0, "seek ");
|
||||
dumphex(_ota_cmd.cmd[i].write.fileOffset);
|
||||
uart_puts(uart0, " = ");
|
||||
if (!lfsSeek(_ota_cmd.cmd[i].write.fileOffset)) {
|
||||
uart_puts(uart0, "failed\n");
|
||||
return;
|
||||
}
|
||||
uart_puts(uart0, "success\n");
|
||||
uint32_t toRead = _ota_cmd.cmd[i].write.fileLength;
|
||||
uint32_t toWrite = _ota_cmd.cmd[i].write.flashAddress;
|
||||
|
||||
while (toRead) {
|
||||
uint32_t len = (toRead < 4096) ? toRead : 4096;
|
||||
uint8_t *p = lfsRead(len);
|
||||
if (!p) {
|
||||
uart_puts(uart0, "read failed\n");
|
||||
return;
|
||||
}
|
||||
uart_puts(uart0, "toread = ");
|
||||
dumphex(toRead);
|
||||
uart_puts(uart0, "\ntowrite = ");
|
||||
dumphex(toWrite);
|
||||
uart_puts(uart0, "\n");
|
||||
// Only write pages which differ (i.e. preserve OTA pages unless the OTA shim changes)
|
||||
if (memcmp(p, (void*)toWrite, 4096)) {
|
||||
uart_puts(uart0, "writing\n");
|
||||
int save = save_and_disable_interrupts();
|
||||
flash_range_erase((intptr_t)toWrite - XIP_BASE, 4096);
|
||||
flash_range_program((intptr_t)toWrite - XIP_BASE, (const uint8_t *)p, 4096);
|
||||
restore_interrupts(save);
|
||||
} else {
|
||||
uart_puts(uart0, "identical to flash, skipping\n");
|
||||
}
|
||||
toRead -= len;
|
||||
toWrite += 4096;
|
||||
}
|
||||
lfsClose();
|
||||
break;
|
||||
default:
|
||||
// TODO - verify
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uart_puts(uart0, "\nota completed\n");
|
||||
|
||||
// Work completed, erase record.
|
||||
lfsEraseBlock(blockToErase);
|
||||
|
||||
// Do a hard reset just in case the start up sequence is not the same
|
||||
watchdog_reboot(0, 0, 100);
|
||||
}
|
||||
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize("O0")
|
||||
int main(int a, unsigned char **b) {
|
||||
(void) a;
|
||||
(void) b;
|
||||
|
||||
#ifdef DEBUG
|
||||
uart_init(uart0, 115200);
|
||||
gpio_set_function(0, GPIO_FUNC_UART);
|
||||
gpio_set_function(1, GPIO_FUNC_UART);
|
||||
uart_set_hw_flow(uart0, false, false);
|
||||
uart_set_format(uart0, 8, 1, UART_PARITY_NONE);
|
||||
#endif
|
||||
|
||||
do_ota();
|
||||
|
||||
// Reset the interrupt/etc. vectors to the real app. Will be copied to RAM in app's runtime_init
|
||||
scb_hw->vtor = (uint32_t)0x10003000;
|
||||
|
||||
// Jump to it
|
||||
register uint32_t* sp asm("sp");
|
||||
register uint32_t _sp = *(uint32_t *)0x10003000;
|
||||
register void (*fcn)(void) = (void (*)(void)) *(uint32_t *)0x10003004;
|
||||
sp = (uint32_t *)_sp;
|
||||
fcn();
|
||||
|
||||
// Should never get here!
|
||||
return 0;
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
|
||||
// Clear out some unwanted extra code
|
||||
int __wrap_atexit(void (*function)(void)) {
|
||||
(void) function;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Clear out some unwanted extra code
|
||||
void __wrap_exit(int status) {
|
||||
(void) status;
|
||||
while (1) continue;
|
||||
}
|
||||
|
||||
void __wrap_panic(const char *x) {
|
||||
(void) x;
|
||||
while (1) continue;
|
||||
}
|
||||
|
||||
void __wrap_panic_unsupported() {
|
||||
while (1) continue;
|
||||
}
|
||||
|
||||
void __wrap_hard_assertion_failure() {
|
||||
while (1) continue;
|
||||
}
|
||||
178
ota/ota_clocks.c
Normal file
178
ota/ota_clocks.c
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
/* This file taken from the Pico SDK. clocks_init will now not require 64-bit division code */
|
||||
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/regs/clocks.h"
|
||||
#include "hardware/platform_defs.h"
|
||||
#include "hardware/clocks.h"
|
||||
#include "hardware/watchdog.h"
|
||||
#include "hardware/pll.h"
|
||||
#include "hardware/xosc.h"
|
||||
#include "hardware/irq.h"
|
||||
#include "hardware/gpio.h"
|
||||
|
||||
// Clock muxing consists of two components:
|
||||
// - A glitchless mux, which can be switched freely, but whose inputs must be
|
||||
// free-running
|
||||
// - An auxiliary (glitchy) mux, whose output glitches when switched, but has
|
||||
// no constraints on its inputs
|
||||
// Not all clocks have both types of mux.
|
||||
static inline bool has_glitchless_mux(enum clock_index clk_index) {
|
||||
return clk_index == clk_sys || clk_index == clk_ref;
|
||||
}
|
||||
|
||||
/// \tag::clock_configure[]
|
||||
bool _clock_configure(enum clock_index clk_index, uint32_t src, uint32_t auxsrc, uint32_t src_freq, uint32_t freq, uint32_t div) {
|
||||
//uint32_t div;
|
||||
|
||||
assert(src_freq >= freq);
|
||||
|
||||
if (freq > src_freq)
|
||||
return false;
|
||||
|
||||
// Div register is 24.8 int.frac divider so multiply by 2^8 (left shift by 8)
|
||||
//div = freq; //(uint32_t) (((uint64_t) src_freq << 8) / freq);
|
||||
|
||||
clock_hw_t *clock = &clocks_hw->clk[clk_index];
|
||||
|
||||
// If increasing divisor, set divisor before source. Otherwise set source
|
||||
// before divisor. This avoids a momentary overspeed when e.g. switching
|
||||
// to a faster source and increasing divisor to compensate.
|
||||
if (div > clock->div)
|
||||
clock->div = div;
|
||||
|
||||
// If switching a glitchless slice (ref or sys) to an aux source, switch
|
||||
// away from aux *first* to avoid passing glitches when changing aux mux.
|
||||
// Assume (!!!) glitchless source 0 is no faster than the aux source.
|
||||
if (has_glitchless_mux(clk_index) && src == CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX) {
|
||||
hw_clear_bits(&clock->ctrl, CLOCKS_CLK_REF_CTRL_SRC_BITS);
|
||||
while (!(clock->selected & 1u))
|
||||
tight_loop_contents();
|
||||
}
|
||||
// If no glitchless mux, cleanly stop the clock to avoid glitches
|
||||
// propagating when changing aux mux. Note it would be a really bad idea
|
||||
// to do this on one of the glitchless clocks (clk_sys, clk_ref).
|
||||
else {
|
||||
// Disable clock. On clk_ref and clk_sys this does nothing,
|
||||
// all other clocks have the ENABLE bit in the same position.
|
||||
hw_clear_bits(&clock->ctrl, CLOCKS_CLK_GPOUT0_CTRL_ENABLE_BITS);
|
||||
}
|
||||
|
||||
// Set aux mux first, and then glitchless mux if this clock has one
|
||||
hw_write_masked(&clock->ctrl,
|
||||
(auxsrc << CLOCKS_CLK_SYS_CTRL_AUXSRC_LSB),
|
||||
CLOCKS_CLK_SYS_CTRL_AUXSRC_BITS
|
||||
);
|
||||
|
||||
if (has_glitchless_mux(clk_index)) {
|
||||
hw_write_masked(&clock->ctrl,
|
||||
src << CLOCKS_CLK_REF_CTRL_SRC_LSB,
|
||||
CLOCKS_CLK_REF_CTRL_SRC_BITS
|
||||
);
|
||||
while (!(clock->selected & (1u << src)))
|
||||
tight_loop_contents();
|
||||
}
|
||||
|
||||
// Enable clock. On clk_ref and clk_sys this does nothing,
|
||||
// all other clocks have the ENABLE bit in the same position.
|
||||
hw_set_bits(&clock->ctrl, CLOCKS_CLK_GPOUT0_CTRL_ENABLE_BITS);
|
||||
|
||||
// Now that the source is configured, we can trust that the user-supplied
|
||||
// divisor is a safe value.
|
||||
clock->div = div;
|
||||
|
||||
// Store the configured frequency
|
||||
//configured_freq[clk_index] = (uint32_t)(((uint64_t) src_freq << 8) / div);
|
||||
|
||||
return true;
|
||||
}
|
||||
/// \end::clock_configure[]
|
||||
|
||||
void __wrap_clocks_init(void) {
|
||||
// Start tick in watchdog
|
||||
watchdog_start_tick(XOSC_MHZ);
|
||||
|
||||
// Disable resus that may be enabled from previous software
|
||||
clocks_hw->resus.ctrl = 0;
|
||||
|
||||
// Enable the xosc
|
||||
xosc_init();
|
||||
|
||||
// Before we touch PLLs, switch sys and ref cleanly away from their aux sources.
|
||||
hw_clear_bits(&clocks_hw->clk[clk_sys].ctrl, CLOCKS_CLK_SYS_CTRL_SRC_BITS);
|
||||
while (clocks_hw->clk[clk_sys].selected != 0x1)
|
||||
tight_loop_contents();
|
||||
hw_clear_bits(&clocks_hw->clk[clk_ref].ctrl, CLOCKS_CLK_REF_CTRL_SRC_BITS);
|
||||
while (clocks_hw->clk[clk_ref].selected != 0x1)
|
||||
tight_loop_contents();
|
||||
|
||||
/// \tag::pll_settings[]
|
||||
// Configure PLLs
|
||||
// REF FBDIV VCO POSTDIV
|
||||
// PLL SYS: 12 / 1 = 12MHz * 125 = 1500MHz / 6 / 2 = 125MHz
|
||||
// PLL USB: 12 / 1 = 12MHz * 100 = 1200MHz / 5 / 5 = 48MHz
|
||||
/// \end::pll_settings[]
|
||||
|
||||
/// \tag::pll_init[]
|
||||
pll_init(pll_sys, 1, 1500 * MHZ, 6, 2);
|
||||
pll_init(pll_usb, 1, 1200 * MHZ, 5, 5);
|
||||
/// \end::pll_init[]
|
||||
|
||||
// Configure clocks
|
||||
// CLK_REF = XOSC (12MHz) / 1 = 12MHz
|
||||
_clock_configure(clk_ref,
|
||||
CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC,
|
||||
0, // No aux mux
|
||||
12 * MHZ,
|
||||
12 * MHZ,
|
||||
1 << 8);
|
||||
|
||||
/// \tag::configure_clk_sys[]
|
||||
// CLK SYS = PLL SYS (125MHz) / 1 = 125MHz
|
||||
_clock_configure(clk_sys,
|
||||
CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX,
|
||||
CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS,
|
||||
125 * MHZ,
|
||||
125 * MHZ,
|
||||
1 << 8);
|
||||
/// \end::configure_clk_sys[]
|
||||
|
||||
// CLK USB = PLL USB (48MHz) / 1 = 48MHz
|
||||
_clock_configure(clk_usb,
|
||||
0, // No GLMUX
|
||||
CLOCKS_CLK_USB_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
|
||||
48 * MHZ,
|
||||
48 * MHZ,
|
||||
1 << 8);
|
||||
|
||||
// CLK ADC = PLL USB (48MHZ) / 1 = 48MHz
|
||||
_clock_configure(clk_adc,
|
||||
0, // No GLMUX
|
||||
CLOCKS_CLK_ADC_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
|
||||
48 * MHZ,
|
||||
48 * MHZ,
|
||||
1 << 8);
|
||||
|
||||
// CLK RTC = PLL USB (48MHz) / 1024 = 46875Hz
|
||||
_clock_configure(clk_rtc,
|
||||
0, // No GLMUX
|
||||
CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
|
||||
48 * MHZ,
|
||||
46875,
|
||||
32 << 8);
|
||||
|
||||
// CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select and enable
|
||||
// Normally choose clk_sys or clk_usb
|
||||
_clock_configure(clk_peri,
|
||||
0,
|
||||
CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS,
|
||||
125 * MHZ,
|
||||
125 * MHZ,
|
||||
1 << 8);
|
||||
}
|
||||
50
ota/ota_command.h
Normal file
50
ota/ota_command.h
Normal file
|
|
@ -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"
|
||||
196
ota/ota_lfs.c
Normal file
196
ota/ota_lfs.c
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
ota_lfs.c - LittleFS+GZIP support for OTA operations
|
||||
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 "ota_lfs.h"
|
||||
#include "ota_command.h"
|
||||
#include "hardware/sync.h"
|
||||
#include "hardware/flash.h"
|
||||
|
||||
#include "../libraries/LittleFS/lib/littlefs/lfs.h"
|
||||
#include "../libraries/LittleFS/lib/littlefs/lfs_util.h"
|
||||
#include "./uzlib/src/uzlib.h"
|
||||
|
||||
static lfs_t _lfs;
|
||||
static struct lfs_config _lfs_cfg;
|
||||
|
||||
static uint8_t *_start;
|
||||
static uint32_t _blockSize;
|
||||
static uint32_t _size;
|
||||
|
||||
// The actual flash accessing routines
|
||||
static lfs_block_t _lastBlock;
|
||||
static int lfs_flash_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *dst, lfs_size_t size) {
|
||||
(void) c;
|
||||
memcpy(dst, _start + (block * _blockSize) + off, size);
|
||||
_lastBlock = block;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lfs_flash_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) {
|
||||
(void) c;
|
||||
uint8_t *addr = _start + (block * _blockSize) + off;
|
||||
int save = save_and_disable_interrupts();
|
||||
flash_range_program((intptr_t)addr - (intptr_t)XIP_BASE, (const uint8_t *)buffer, size);
|
||||
restore_interrupts(save);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lfs_flash_erase(const struct lfs_config *c, lfs_block_t block) {
|
||||
(void) c;
|
||||
uint8_t *addr = _start + (block * _blockSize);
|
||||
int save = save_and_disable_interrupts();
|
||||
flash_range_erase((intptr_t)addr - (intptr_t)XIP_BASE, _blockSize);
|
||||
restore_interrupts(save);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void lfsEraseBlock(uint32_t blockToErase) {
|
||||
lfs_flash_erase(&_lfs_cfg, blockToErase);
|
||||
}
|
||||
|
||||
static int lfs_flash_sync(const struct lfs_config *c) {
|
||||
/* NOOP */
|
||||
(void) c;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t _read_buffer[256];
|
||||
uint8_t _prog_buffer[256];
|
||||
uint8_t _lookahead_buffer[256];
|
||||
bool lfsMount(uint8_t *start, uint32_t blockSize, uint32_t size) {
|
||||
_start = start;
|
||||
_blockSize = blockSize;
|
||||
_size = size;
|
||||
|
||||
memset(&_lfs, 0, sizeof(_lfs));
|
||||
memset(&_lfs_cfg, 0, sizeof(_lfs_cfg));
|
||||
_lfs_cfg.context = NULL;
|
||||
_lfs_cfg.read = lfs_flash_read;
|
||||
_lfs_cfg.prog = lfs_flash_prog;
|
||||
_lfs_cfg.erase = lfs_flash_erase;
|
||||
_lfs_cfg.sync = lfs_flash_sync;
|
||||
_lfs_cfg.read_size = 256;
|
||||
_lfs_cfg.prog_size = 256;
|
||||
_lfs_cfg.block_size = _blockSize;
|
||||
_lfs_cfg.block_count = _blockSize ? _size / _blockSize : 0;
|
||||
_lfs_cfg.block_cycles = 16; // TODO - need better explanation
|
||||
_lfs_cfg.cache_size = 256;
|
||||
_lfs_cfg.lookahead_size = 256;
|
||||
_lfs_cfg.read_buffer = _read_buffer;
|
||||
_lfs_cfg.prog_buffer = _prog_buffer;
|
||||
_lfs_cfg.lookahead_buffer = _lookahead_buffer;
|
||||
_lfs_cfg.name_max = 0;
|
||||
_lfs_cfg.file_max = 0;
|
||||
_lfs_cfg.attr_max = 0;
|
||||
return lfs_mount(&_lfs, &_lfs_cfg) < 0 ? false : true;
|
||||
}
|
||||
|
||||
static bool _gzip = false;
|
||||
static lfs_file_t _file;
|
||||
|
||||
static unsigned char __attribute__((aligned(4))) uzlib_read_buff[4096];
|
||||
static unsigned char gzip_dict[32768];
|
||||
static uint8_t _flash_buff[4096]; // no room for this on the stack
|
||||
static struct uzlib_uncomp m_uncomp;
|
||||
|
||||
static uint8_t _ota_buff[256];
|
||||
static struct lfs_file_config _ota_cfg = { (void *)_ota_buff, NULL, 0 };
|
||||
|
||||
static uint8_t _file_buff[256];
|
||||
static struct lfs_file_config _file_cfg = { (void *)_file_buff, NULL, 0 };
|
||||
|
||||
bool lfsReadOTA(OTACmdPage *ota, uint32_t *blockToErase) {
|
||||
lfs_file_t f;
|
||||
|
||||
if (lfs_file_opencfg(&_lfs, &f, _OTA_COMMAND_FILE, LFS_O_RDONLY, &_ota_cfg) < 0) {
|
||||
return false;
|
||||
}
|
||||
if (sizeof(*ota) != lfs_file_read(&_lfs, &f, ota, sizeof(*ota))) {
|
||||
return false;
|
||||
}
|
||||
*blockToErase = _lastBlock;
|
||||
// No need to close the file, it's open read-only and we're just going to reboot anyway
|
||||
//lfs_file_close(&_lfs, &f);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int uzlib_read_cb(struct uzlib_uncomp *m) {
|
||||
m->source = uzlib_read_buff;
|
||||
int len = lfs_file_read(&_lfs, &_file, uzlib_read_buff, sizeof(uzlib_read_buff));
|
||||
m->source_limit = uzlib_read_buff + len;
|
||||
return *(m->source++);
|
||||
}
|
||||
|
||||
bool lfsOpen(const char *filename) {
|
||||
_gzip = false;
|
||||
if (lfs_file_opencfg(&_lfs, &_file, filename, LFS_O_RDONLY, &_file_cfg) < 0) {
|
||||
return false;
|
||||
}
|
||||
char b[2];
|
||||
if (sizeof(b) != lfs_file_read(&_lfs, &_file, b, sizeof(b))) {
|
||||
return false;
|
||||
}
|
||||
lfs_file_rewind(&_lfs, &_file);
|
||||
if ((b[0] == 0x1f) && (b[1] == 0x8b)) {
|
||||
uzlib_init();
|
||||
m_uncomp.source = NULL;
|
||||
m_uncomp.source_limit = NULL;
|
||||
m_uncomp.source_read_cb = uzlib_read_cb;
|
||||
uzlib_uncompress_init(&m_uncomp, gzip_dict, sizeof(gzip_dict));
|
||||
int res = uzlib_gzip_parse_header(&m_uncomp);
|
||||
if (res != TINF_OK) {
|
||||
lfs_file_rewind(&_lfs, &_file);
|
||||
return false; // Error uncompress header read, could have been false alarm
|
||||
}
|
||||
_gzip = true;
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool lfsSeek(uint32_t offset) {
|
||||
while (offset) {
|
||||
uint32_t to_read = (offset > sizeof(_flash_buff)) ? sizeof(_flash_buff) : offset;
|
||||
if (!lfsRead(to_read)) {
|
||||
return false;
|
||||
}
|
||||
offset -= to_read;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t *lfsRead(uint32_t len) {
|
||||
if (!_gzip) {
|
||||
int ret = lfs_file_read(&_lfs, &_file, _flash_buff, len);
|
||||
return (len == ret) ? _flash_buff : NULL;
|
||||
}
|
||||
m_uncomp.dest_start = _flash_buff;
|
||||
m_uncomp.dest = _flash_buff;
|
||||
m_uncomp.dest_limit = _flash_buff + len;
|
||||
int res = uzlib_uncompress(&m_uncomp);
|
||||
if ((res != TINF_DONE) && (res != TINF_OK)) {
|
||||
return NULL;
|
||||
}
|
||||
return _flash_buff;
|
||||
}
|
||||
|
||||
void lfsClose() {
|
||||
// No need to close the file, it's open read-only and we're just going to reboot anyway
|
||||
//lfs_file_close(&_lfs, &_file);
|
||||
}
|
||||
33
ota/ota_lfs.h
Normal file
33
ota/ota_lfs.h
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
ota_lfs.h - LittleFS+GZIP support for OTA operations
|
||||
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>
|
||||
#include <stdbool.h>
|
||||
#include "ota_command.h"
|
||||
|
||||
bool lfsMount(uint8_t *start, uint32_t blockSize, uint32_t size);
|
||||
bool lfsOpen(const char *filename);
|
||||
bool lfsSeek(uint32_t offset);
|
||||
uint8_t *lfsRead(uint32_t len);
|
||||
void lfsClose();
|
||||
|
||||
bool lfsReadOTA(OTACmdPage *ota, uint32_t *blockToErase);
|
||||
void lfsEraseBlock(uint32_t blockToErase);
|
||||
62
ota/pico_sdk_import.cmake
Normal file
62
ota/pico_sdk_import.cmake
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
|
||||
|
||||
# This can be dropped into an external project to help locate this SDK
|
||||
# It should be include()ed prior to project()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
|
||||
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
|
||||
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
|
||||
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
|
||||
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
|
||||
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
|
||||
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
|
||||
|
||||
if (NOT PICO_SDK_PATH)
|
||||
if (PICO_SDK_FETCH_FROM_GIT)
|
||||
include(FetchContent)
|
||||
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
|
||||
if (PICO_SDK_FETCH_FROM_GIT_PATH)
|
||||
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
|
||||
endif ()
|
||||
FetchContent_Declare(
|
||||
pico_sdk
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG master
|
||||
)
|
||||
if (NOT pico_sdk)
|
||||
message("Downloading Raspberry Pi Pico SDK")
|
||||
FetchContent_Populate(pico_sdk)
|
||||
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
|
||||
endif ()
|
||||
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
|
||||
else ()
|
||||
message(FATAL_ERROR
|
||||
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
|
||||
)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
|
||||
if (NOT EXISTS ${PICO_SDK_PATH})
|
||||
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
|
||||
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
|
||||
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
|
||||
|
||||
include(${PICO_SDK_INIT_CMAKE_FILE})
|
||||
1
ota/uzlib
Submodule
1
ota/uzlib
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 6d60d651a4499a64f2e5b21b4cc08d98cb84b5c1
|
||||
|
|
@ -108,6 +108,8 @@ cat $srcdir/platform.txt | \
|
|||
sed 's/^runtime.tools.pqt-.*.path=.*//g' | \
|
||||
sed 's/^tools.uf2conv.cmd=.*//g' | \
|
||||
sed 's/^#tools.uf2conv.cmd=/tools.uf2conv.cmd=/g' | \
|
||||
sed 's/^tools.uf2conv.network_cmd=.*//g' | \
|
||||
sed 's/^#tools.uf2conv.network_cmd=/tools.uf2conv.network_cmd=/g' | \
|
||||
sed 's/^tools.picoprobe.cmd=.*//g' | \
|
||||
sed 's/^#tools.picoprobe.cmd=/tools.picoprobe.cmd=/g' | \
|
||||
sed 's/^tools.picodebug.cmd=.*//g' | \
|
||||
|
|
|
|||
15
platform.txt
15
platform.txt
|
|
@ -42,7 +42,7 @@ compiler.warning_flags.more=-Wall -Werror=return-type -Wno-ignored-qualifiers
|
|||
compiler.warning_flags.all=-Wall -Wextra -Werror=return-type -Wno-ignored-qualifiers
|
||||
|
||||
compiler.netdefines=-DPICO_CYW43_ARCH_THREADSAFE_BACKGROUND=1 -DCYW43_LWIP=0 {build.lwipdefs} -DLWIP_IGMP=1 -DLWIP_CHECKSUM_CTRL_PER_NETIF=1
|
||||
compiler.defines={build.led} {build.usbstack_flags} -DCFG_TUSB_MCU=OPT_MCU_RP2040 -DUSB_VID={build.vid} -DUSB_PID={build.pid} '-DUSB_MANUFACTURER={build.usb_manufacturer}' '-DUSB_PRODUCT={build.usb_product}' {compiler.netdefines}
|
||||
compiler.defines={build.led} {build.usbstack_flags} -DCFG_TUSB_MCU=OPT_MCU_RP2040 -DUSB_VID={build.vid} -DUSB_PID={build.pid} '-DUSB_MANUFACTURER={build.usb_manufacturer}' '-DUSB_PRODUCT={build.usb_product}' {compiler.netdefines} -DARDUINO_VARIANT="{build.variant}"
|
||||
compiler.includes="-iprefix{runtime.platform.path}/" "@{runtime.platform.path}/lib/platform_inc.txt" "-I{runtime.platform.path}/include"
|
||||
compiler.flags=-march=armv6-m -mcpu=cortex-m0plus -mthumb -ffunction-sections -fdata-sections {build.flags.exceptions} {build.flags.stackprotect} {build.flags.cmsis}
|
||||
compiler.wrap="@{runtime.platform.path}/lib/platform_wrap.txt"
|
||||
|
|
@ -103,6 +103,9 @@ discovery.rp2040.pattern="{runtime.tools.pqt-python3.path}/python3" -I "{runtime
|
|||
# Compile patterns
|
||||
# ----------------
|
||||
|
||||
## Generate signing header
|
||||
recipe.hooks.sketch.prebuild.pattern="{runtime.tools.pqt-python3.path}/python3" -I "{runtime.platform.path}/tools/signing.py" --mode header --publickey "{build.source.path}/public.key" --out "{build.path}/core/Updater_Signing.h"
|
||||
|
||||
## Compile c files
|
||||
recipe.c.o.pattern="{compiler.path}{compiler.c.cmd}" {compiler.c.flags} {build.usbpid} {build.usbpwr} -DF_CPU={build.f_cpu} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DBOARD_NAME="{build.board}" -DARDUINO_ARCH_{build.arch} {compiler.c.extra_flags} {build.extra_flags} {build.debug_port} {build.debug_level} {build.flags.optimize} {includes} "{source_file}" -o "{object_file}"
|
||||
|
||||
|
|
@ -124,11 +127,16 @@ recipe.hooks.linking.prelink.1.pattern="{runtime.tools.pqt-python3.path}/python3
|
|||
recipe.hooks.linking.prelink.2.pattern="{compiler.path}{compiler.S.cmd}" {compiler.c.elf.flags} {compiler.c.elf.extra_flags} -c "{runtime.platform.path}/boot2/{build.boot2}.S" "-I{runtime.platform.path}/pico-sdk/src/rp2040/hardware_regs/include/" "-I{runtime.platform.path}/pico-sdk/src/common/pico_binary_info/include" -o "{build.path}/boot2.o"
|
||||
|
||||
## Combine gc-sections, archives, and objects
|
||||
recipe.c.combine.pattern="{compiler.path}{compiler.c.elf.cmd}" "-L{build.path}" {compiler.c.elf.flags} {compiler.c.elf.extra_flags} {compiler.ldflags} "-Wl,--script={build.path}/memmap_default.ld" "-Wl,-Map,{build.path}/{build.project_name}.map" -o "{build.path}/{build.project_name}.elf" -Wl,--start-group {object_files} "{build.path}/{archive_file}" "{build.path}/boot2.o" {compiler.libraries.ldflags} "{runtime.platform.path}/lib/{build.libpico}" {compiler.libbearssl} -lm -lc {build.flags.libstdcpp} -lc -Wl,--end-group
|
||||
recipe.c.combine.pattern="{compiler.path}{compiler.c.elf.cmd}" "-L{build.path}" {compiler.c.elf.flags} {compiler.c.elf.extra_flags} {compiler.ldflags} "-Wl,--script={build.path}/memmap_default.ld" "-Wl,-Map,{build.path}/{build.project_name}.map" -o "{build.path}/{build.project_name}.elf" -Wl,--start-group {object_files} "{build.path}/{archive_file}" "{build.path}/boot2.o" "{runtime.platform.path}/lib/ota.o" {compiler.libraries.ldflags} "{runtime.platform.path}/lib/{build.libpico}" {compiler.libbearssl} -lm -lc {build.flags.libstdcpp} -lc -Wl,--end-group
|
||||
|
||||
## Create output (UF2 file)
|
||||
recipe.objcopy.uf2.pattern="{runtime.tools.pqt-elf2uf2.path}/elf2uf2" "{build.path}/{build.project_name}.elf" "{build.path}/{build.project_name}.uf2"
|
||||
|
||||
## Create output BIN (for OTA)
|
||||
recipe.objcopy.bin.1.pattern="{compiler.path}/{compiler.objcopy.cmd}" -Obinary "{build.path}/{build.project_name}.elf" "{build.path}/{build.project_name}.bin"
|
||||
recipe.objcopy.bin.2.pattern="{runtime.tools.pqt-python3.path}/python3" -I "{runtime.platform.path}/tools/signing.py" --mode sign --privatekey "{build.source.path}/private.key" --bin "{build.path}/{build.project_name}.bin" --out "{build.path}/{build.project_name}.bin.signed"
|
||||
|
||||
|
||||
build.preferred_out_format=uf2
|
||||
|
||||
## Save hex
|
||||
|
|
@ -145,11 +153,14 @@ tools.uf2conv.path=
|
|||
# will point to "{runtime.platform.path}/tools/python3/python3" in GIT and
|
||||
# "{runtime.tools.pqt-python3.path}/python3" for JSON board manager releases.
|
||||
#tools.uf2conv.cmd={runtime.tools.pqt-python3.path}/python3
|
||||
#tools.uf2conv.network_cmd={runtime.tools.pqt-python3.path}/python3
|
||||
tools.uf2conv.cmd={runtime.platform.path}/system/python3/python3
|
||||
tools.uf2conv.network_cmd={runtime.platform.path}/system/python3/python3
|
||||
tools.uf2conv.upload.protocol=uf2
|
||||
tools.uf2conv.upload.params.verbose=
|
||||
tools.uf2conv.upload.params.quiet=
|
||||
tools.uf2conv.upload.pattern="{cmd}" -I "{runtime.platform.path}/tools/uf2conv.py" --serial "{serial.port}" --family RP2040 --deploy "{build.path}/{build.project_name}.uf2"
|
||||
tools.uf2conv.upload.network_pattern="{network_cmd}" -I "{runtime.platform.path}/tools/espota.py" -i "{serial.port}" -p "{network.port}" "--auth={network.password}" -f "{build.path}/{build.project_name}.bin"
|
||||
|
||||
#tools.picoprobe.cmd={runtime.tools.pqt-openocd.path}
|
||||
tools.picoprobe.cmd={runtime.platform.path}/system/openocd
|
||||
|
|
|
|||
|
|
@ -4,7 +4,11 @@ for dir in ./cores/rp2040 ./libraries/EEPROM ./libraries/I2S \
|
|||
./libraries/LittleFS/src ./libraries/LittleFS/examples \
|
||||
./libraries/rp2040 ./libraries/SD ./libraries/ESP8266SdFat \
|
||||
./libraries/Servo ./libraries/SPI ./libraries/Wire \
|
||||
./libraries/WiFi ./libraries/lwIP_Ethernet ./libraries/lwIP_CYW43; do
|
||||
./libraries/WiFi ./libraries/lwIP_Ethernet ./libraries/lwIP_CYW43 \
|
||||
./libraries/FreeRTOS/src ./libraries/LEAmDNS ./libraries/MD5Builder \
|
||||
./libraries/PicoOTA ./libraries/SDFS ./libraries/ArduinoOTA \
|
||||
./libraries/Updater; do
|
||||
find $dir -type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" \) -a \! -path '*api*' -exec astyle --suffix=none --options=./tests/astyle_core.conf \{\} \;
|
||||
find $dir -type f -name "*.ino" -exec astyle --suffix=none --options=./tests/astyle_examples.conf \{\} \;
|
||||
done
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@ def compile(tmp_dir, sketch, cache, tools_dir, hardware_dir, ide_path, f, args):
|
|||
'usbstack={usbstack}'.format(**vars(args))
|
||||
if "/WiFi" in sketch:
|
||||
fqbn = fqbn.replace("rpipico", "rpipicow")
|
||||
if "/ArduinoOTA" in sketch:
|
||||
fqbn = fqbn.replace("rpipico", "rpipicow")
|
||||
cmd += [fqbn]
|
||||
cmd += ['-built-in-libraries', ide_path + '/libraries']
|
||||
cmd += ['-ide-version=10607']
|
||||
|
|
|
|||
345
tools/espota.py
Executable file
345
tools/espota.py
Executable file
|
|
@ -0,0 +1,345 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# Original espota.py by Ivan Grokhotkov:
|
||||
# https://gist.github.com/igrr/d35ab8446922179dc58c
|
||||
#
|
||||
# Modified since 2015-09-18 from Pascal Gollor (https://github.com/pgollor)
|
||||
# Modified since 2015-11-09 from Hristo Gochkov (https://github.com/me-no-dev)
|
||||
# Modified since 2016-01-03 from Matthew O'Gorman (https://githumb.com/mogorman)
|
||||
#
|
||||
# This script will push an OTA update to the ESP
|
||||
# use it like: python3 espota.py -i <ESP_IP_address> -I <Host_IP_address> -p <ESP_port> -P <Host_port> [-a password] -f <sketch.bin>
|
||||
# Or to upload SPIFFS image:
|
||||
# python3 espota.py -i <ESP_IP_address> -I <Host_IP_address> -p <ESP_port> -P <HOST_port> [-a password] -s -f <spiffs.bin>
|
||||
#
|
||||
# Changes
|
||||
# 2015-09-18:
|
||||
# - Add option parser.
|
||||
# - Add logging.
|
||||
# - Send command to controller to differ between flashing and transmitting SPIFFS image.
|
||||
#
|
||||
# Changes
|
||||
# 2015-11-09:
|
||||
# - Added digest authentication
|
||||
# - Enhanced error tracking and reporting
|
||||
#
|
||||
# Changes
|
||||
# 2016-01-03:
|
||||
# - Added more options to parser.
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
import socket
|
||||
import sys
|
||||
import os
|
||||
import optparse
|
||||
import logging
|
||||
import hashlib
|
||||
import random
|
||||
|
||||
# Commands
|
||||
FLASH = 0
|
||||
SPIFFS = 100
|
||||
AUTH = 200
|
||||
PROGRESS = False
|
||||
# update_progress() : Displays or updates a console progress bar
|
||||
## Accepts a float between 0 and 1. Any int will be converted to a float.
|
||||
## A value under 0 represents a 'halt'.
|
||||
## A value at 1 or bigger represents 100%
|
||||
def update_progress(progress):
|
||||
if (PROGRESS):
|
||||
barLength = 60 # Modify this to change the length of the progress bar
|
||||
status = ""
|
||||
if isinstance(progress, int):
|
||||
progress = float(progress)
|
||||
if not isinstance(progress, float):
|
||||
progress = 0
|
||||
status = "error: progress var must be float\r\n"
|
||||
if progress < 0:
|
||||
progress = 0
|
||||
status = "Halt...\r\n"
|
||||
if progress >= 1:
|
||||
progress = 1
|
||||
status = "Done...\r\n"
|
||||
block = int(round(barLength*progress))
|
||||
text = "\rUploading: [{0}] {1}% {2}".format( "="*block + " "*(barLength-block), int(progress*100), status)
|
||||
sys.stderr.write(text)
|
||||
sys.stderr.flush()
|
||||
else:
|
||||
sys.stderr.write('.')
|
||||
sys.stderr.flush()
|
||||
|
||||
def serve(remoteAddr, localAddr, remotePort, localPort, password, filename, command = FLASH):
|
||||
# Create a TCP/IP socket
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server_address = (localAddr, localPort)
|
||||
logging.info('Starting on %s:%s', str(server_address[0]), str(server_address[1]))
|
||||
try:
|
||||
sock.bind(server_address)
|
||||
sock.listen(1)
|
||||
except Exception:
|
||||
logging.error("Listen Failed")
|
||||
return 1
|
||||
|
||||
# Check whether Signed Update is used.
|
||||
if ( os.path.isfile(filename + '.signed') ):
|
||||
filename = filename + '.signed'
|
||||
file_check_msg = 'Detected Signed Update. %s will be uploaded instead.' % (filename)
|
||||
sys.stderr.write(file_check_msg + '\n')
|
||||
sys.stderr.flush()
|
||||
logging.info(file_check_msg)
|
||||
|
||||
content_size = os.path.getsize(filename)
|
||||
f = open(filename,'rb')
|
||||
file_md5 = hashlib.md5(f.read()).hexdigest()
|
||||
f.close()
|
||||
logging.info('Upload size: %d', content_size)
|
||||
message = '%d %d %d %s\n' % (command, localPort, content_size, file_md5)
|
||||
|
||||
# Wait for a connection
|
||||
logging.info('Sending invitation to: %s', remoteAddr)
|
||||
sock2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
remote_address = (remoteAddr, int(remotePort))
|
||||
sock2.sendto(message.encode(), remote_address)
|
||||
sock2.settimeout(10)
|
||||
try:
|
||||
data = sock2.recv(128).decode()
|
||||
except Exception:
|
||||
logging.error('No Answer')
|
||||
sock2.close()
|
||||
return 1
|
||||
if (data != "OK"):
|
||||
if(data.startswith('AUTH')):
|
||||
nonce = data.split()[1]
|
||||
cnonce_text = '%s%u%s%s' % (filename, content_size, file_md5, remoteAddr)
|
||||
cnonce = hashlib.md5(cnonce_text.encode()).hexdigest()
|
||||
passmd5 = hashlib.md5(password.encode()).hexdigest()
|
||||
result_text = '%s:%s:%s' % (passmd5 ,nonce, cnonce)
|
||||
result = hashlib.md5(result_text.encode()).hexdigest()
|
||||
sys.stderr.write('Authenticating...')
|
||||
sys.stderr.flush()
|
||||
message = '%d %s %s\n' % (AUTH, cnonce, result)
|
||||
sock2.sendto(message.encode(), remote_address)
|
||||
sock2.settimeout(10)
|
||||
try:
|
||||
data = sock2.recv(32).decode()
|
||||
except Exception:
|
||||
sys.stderr.write('FAIL\n')
|
||||
logging.error('No Answer to our Authentication')
|
||||
sock2.close()
|
||||
return 1
|
||||
if (data != "OK"):
|
||||
sys.stderr.write('FAIL\n')
|
||||
logging.error('%s', data)
|
||||
sock2.close()
|
||||
sys.exit(1)
|
||||
return 1
|
||||
sys.stderr.write('OK\n')
|
||||
else:
|
||||
logging.error('Bad Answer: %s', data)
|
||||
sock2.close()
|
||||
return 1
|
||||
sock2.close()
|
||||
|
||||
logging.info('Waiting for device...')
|
||||
try:
|
||||
sock.settimeout(10)
|
||||
connection, client_address = sock.accept()
|
||||
sock.settimeout(None)
|
||||
connection.settimeout(None)
|
||||
except Exception:
|
||||
logging.error('No response from device')
|
||||
sock.close()
|
||||
return 1
|
||||
|
||||
received_ok = False
|
||||
|
||||
try:
|
||||
f = open(filename, "rb")
|
||||
if (PROGRESS):
|
||||
update_progress(0)
|
||||
else:
|
||||
sys.stderr.write('Uploading')
|
||||
sys.stderr.flush()
|
||||
offset = 0
|
||||
while True:
|
||||
chunk = f.read(1460)
|
||||
if not chunk: break
|
||||
offset += len(chunk)
|
||||
update_progress(offset/float(content_size))
|
||||
connection.settimeout(10)
|
||||
try:
|
||||
connection.sendall(chunk)
|
||||
if connection.recv(32).decode().find('O') >= 0:
|
||||
# connection will receive only digits or 'OK'
|
||||
received_ok = True
|
||||
except Exception:
|
||||
sys.stderr.write('\n')
|
||||
logging.error('Error Uploading')
|
||||
connection.close()
|
||||
f.close()
|
||||
sock.close()
|
||||
return 1
|
||||
|
||||
sys.stderr.write('\n')
|
||||
logging.info('Waiting for result...')
|
||||
# libraries/ArduinoOTA/ArduinoOTA.cpp L311 L320
|
||||
# only sends digits or 'OK'. We must not not close
|
||||
# the connection before receiving the 'O' of 'OK'
|
||||
try:
|
||||
connection.settimeout(60)
|
||||
received_ok = False
|
||||
received_error = False
|
||||
while not (received_ok or received_error):
|
||||
reply = connection.recv(64).decode()
|
||||
# Look for either the "E" in ERROR or the "O" in OK response
|
||||
# Check for "E" first, since both strings contain "O"
|
||||
if reply.find('E') >= 0:
|
||||
sys.stderr.write('\n')
|
||||
logging.error('%s', reply)
|
||||
received_error = True
|
||||
elif reply.find('O') >= 0:
|
||||
logging.info('Result: OK')
|
||||
received_ok = True
|
||||
connection.close()
|
||||
f.close()
|
||||
sock.close()
|
||||
if received_ok:
|
||||
sys.stderr.write("Complete\n")
|
||||
sys.stderr.flush()
|
||||
return 0
|
||||
return 1
|
||||
except Exception:
|
||||
logging.error('No Result!')
|
||||
connection.close()
|
||||
f.close()
|
||||
sock.close()
|
||||
return 1
|
||||
|
||||
finally:
|
||||
connection.close()
|
||||
f.close()
|
||||
|
||||
sock.close()
|
||||
return 1
|
||||
# end serve
|
||||
|
||||
|
||||
def parser(unparsed_args):
|
||||
parser = optparse.OptionParser(
|
||||
usage = "%prog [options]",
|
||||
description = "Transmit image over the air to the esp8266 module with OTA support."
|
||||
)
|
||||
|
||||
# destination ip and port
|
||||
group = optparse.OptionGroup(parser, "Destination")
|
||||
group.add_option("-i", "--ip",
|
||||
dest = "esp_ip",
|
||||
action = "store",
|
||||
help = "ESP8266 IP Address.",
|
||||
default = False
|
||||
)
|
||||
group.add_option("-I", "--host_ip",
|
||||
dest = "host_ip",
|
||||
action = "store",
|
||||
help = "Host IP Address.",
|
||||
default = "0.0.0.0"
|
||||
)
|
||||
group.add_option("-p", "--port",
|
||||
dest = "esp_port",
|
||||
type = "int",
|
||||
help = "ESP8266 ota Port. Default 8266",
|
||||
default = 8266
|
||||
)
|
||||
group.add_option("-P", "--host_port",
|
||||
dest = "host_port",
|
||||
type = "int",
|
||||
help = "Host server ota Port. Default random 10000-60000",
|
||||
default = random.randint(10000,60000)
|
||||
)
|
||||
parser.add_option_group(group)
|
||||
|
||||
# auth
|
||||
group = optparse.OptionGroup(parser, "Authentication")
|
||||
group.add_option("-a", "--auth",
|
||||
dest = "auth",
|
||||
help = "Set authentication password.",
|
||||
action = "store",
|
||||
default = ""
|
||||
)
|
||||
parser.add_option_group(group)
|
||||
|
||||
# image
|
||||
group = optparse.OptionGroup(parser, "Image")
|
||||
group.add_option("-f", "--file",
|
||||
dest = "image",
|
||||
help = "Image file.",
|
||||
metavar="FILE",
|
||||
default = None
|
||||
)
|
||||
group.add_option("-s", "--spiffs",
|
||||
dest = "spiffs",
|
||||
action = "store_true",
|
||||
help = "Use this option to transmit a SPIFFS image and do not flash the module.",
|
||||
default = False
|
||||
)
|
||||
parser.add_option_group(group)
|
||||
|
||||
# output group
|
||||
group = optparse.OptionGroup(parser, "Output")
|
||||
group.add_option("-d", "--debug",
|
||||
dest = "debug",
|
||||
help = "Show debug output. And override loglevel with debug.",
|
||||
action = "store_true",
|
||||
default = False
|
||||
)
|
||||
group.add_option("-r", "--progress",
|
||||
dest = "progress",
|
||||
help = "Show progress output. Does not work for ArduinoIDE",
|
||||
action = "store_true",
|
||||
default = False
|
||||
)
|
||||
parser.add_option_group(group)
|
||||
|
||||
(options, args) = parser.parse_args(unparsed_args)
|
||||
|
||||
return options
|
||||
# end parser
|
||||
|
||||
|
||||
def main(args):
|
||||
# get options
|
||||
options = parser(args)
|
||||
|
||||
# adapt log level
|
||||
loglevel = logging.WARNING
|
||||
if (options.debug):
|
||||
loglevel = logging.DEBUG
|
||||
# end if
|
||||
|
||||
# logging
|
||||
logging.basicConfig(level = loglevel, format = '%(asctime)-8s [%(levelname)s]: %(message)s', datefmt = '%H:%M:%S')
|
||||
|
||||
logging.debug("Options: %s", str(options))
|
||||
|
||||
# check options
|
||||
global PROGRESS
|
||||
PROGRESS = options.progress
|
||||
if (not options.esp_ip or not options.image):
|
||||
logging.critical("Not enough arguments.")
|
||||
|
||||
return 1
|
||||
# end if
|
||||
|
||||
command = FLASH
|
||||
if (options.spiffs):
|
||||
command = SPIFFS
|
||||
# end if
|
||||
|
||||
return serve(options.esp_ip, options.host_ip, options.esp_port, options.host_port, options.auth, options.image, command)
|
||||
# end main
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
# end if
|
||||
|
|
@ -300,6 +300,11 @@ if variant != "":
|
|||
os.path.join(FRAMEWORK_DIR, "variants", variant)
|
||||
])
|
||||
|
||||
env.Append(CPPDEFINES=[
|
||||
("ARDUINO_VARIANT", '"' + variant + '"'),
|
||||
])
|
||||
|
||||
|
||||
# link variant's source files as object files into the binary.
|
||||
# otherwise weak function overriding won't work in the linking stage.
|
||||
env.BuildSources(
|
||||
|
|
|
|||
87
tools/signing.py
Executable file
87
tools/signing.py
Executable file
|
|
@ -0,0 +1,87 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import argparse
|
||||
import hashlib
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description='Binary signing tool')
|
||||
parser.add_argument('-m', '--mode', help='Mode (header, sign)')
|
||||
parser.add_argument('-b', '--bin', help='Unsigned binary')
|
||||
parser.add_argument('-o', '--out', help='Output file');
|
||||
parser.add_argument('-p', '--publickey', help='Public key file');
|
||||
parser.add_argument('-s', '--privatekey', help='Private(secret) key file');
|
||||
return parser.parse_args()
|
||||
|
||||
def sign_and_write(data, priv_key, out_file):
|
||||
"""Signs the data (bytes) with the private key (file path)."""
|
||||
"""Save the signed firmware to out_file (file path)."""
|
||||
|
||||
signcmd = [ 'openssl', 'dgst', '-sha256', '-sign', priv_key ]
|
||||
proc = subprocess.Popen(signcmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
signout, signerr = proc.communicate(input=data)
|
||||
if proc.returncode:
|
||||
sys.stderr.write("OpenSSL returned an error signing the binary: " + str(proc.returncode) + "\nSTDERR: " + str(signerr))
|
||||
else:
|
||||
with open(out_file, "wb") as out:
|
||||
out.write(data)
|
||||
out.write(signout)
|
||||
out.write(b'\x00\x01\x00\x00')
|
||||
sys.stderr.write("Signed binary: " + out_file + "\n")
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
if args.mode == "header":
|
||||
val = ""
|
||||
try:
|
||||
with open(args.publickey, "rb") as f:
|
||||
pub = f.read()
|
||||
val += "#include <pgmspace.h>\n"
|
||||
val += "#define ARDUINO_SIGNING 1\n"
|
||||
val += "static const char signing_pubkey[] PROGMEM = {\n"
|
||||
for i in bytearray(pub):
|
||||
val += "0x%02x, \n" % i
|
||||
val = val[:-3]
|
||||
val +="\n};\n"
|
||||
sys.stderr.write("Enabling binary signing\n")
|
||||
except IOError:
|
||||
# Silence the default case to avoid people thinking something is wrong.
|
||||
# Only people who care about signing will know what it means, anyway,
|
||||
# and they can check for the positive acknowledgement above.
|
||||
# sys.stderr.write("Not enabling binary signing\n")
|
||||
val += "#define ARDUINO_SIGNING 0\n"
|
||||
outdir = os.path.dirname(args.out)
|
||||
if not os.path.exists(outdir):
|
||||
os.makedirs(outdir)
|
||||
try:
|
||||
with open(args.out, "r") as inp:
|
||||
old_val = inp.read()
|
||||
if old_val == val:
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
with open(args.out, "w") as f:
|
||||
f.write(val)
|
||||
return 0
|
||||
elif args.mode == "sign":
|
||||
if not os.path.isfile(args.privatekey):
|
||||
return
|
||||
try:
|
||||
with open(args.bin, "rb") as b:
|
||||
bin = b.read()
|
||||
|
||||
sign_and_write(bin, args.privatekey, args.out)
|
||||
|
||||
except Exception as e:
|
||||
sys.stderr.write(str(e))
|
||||
sys.stderr.write("Not signing the generated binary\n")
|
||||
return 0
|
||||
else:
|
||||
sys.stderr.write("ERROR: Mode not specified as header or sign\n")
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
||||
Loading…
Reference in a new issue