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:
Earle F. Philhower, III 2022-08-12 00:26:51 -07:00 committed by GitHub
parent 71be07e69f
commit da86a8942b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
87 changed files with 23806 additions and 313 deletions

View file

@ -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
View file

@ -1,4 +1,6 @@
.DS_Store
system
tools/dist
tools/libpico/build
docs/_build
ota/build
tools/libpico/build

3
.gitmodules vendored
View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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
View 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

View file

@ -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
View 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 ESPs 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]

View file

@ -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
------------------

View 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"

View file

@ -23,6 +23,8 @@ pop KEYWORD2
pop_nb KEYWORD2
rp2040 KEYWORD2
reboot KEYWORD2
restart KEYWORD2
RP2040 KEYWORD2
usToPIOCycles KEYWORD2
f_cpu KEYWORD2

View file

@ -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

Binary file not shown.

View 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();
}

View 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();
}

View file

@ -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);
}

View 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-----

View 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-----

View 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();
}

View 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-----

View 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-----

View 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)
#######################################

View 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

View 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

View 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

View file

@ -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

View file

@ -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);
}

View 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.

View 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)
#######################################

View 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

View 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

View 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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View 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

View 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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View 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

View file

@ -0,0 +1,20 @@
#######################################
# Syntax Coloring Map For Ultrasound
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
LittleFS KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
format KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################

View 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)
#######################################

View 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

View 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);
}

View 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;
};

View file

@ -0,0 +1,42 @@
// This example overwrites itself with a serial blinker sketch using OTA
// In general, you will get a file from the Internet, over a serial port, etc.
// and not include it in a header like we do here for simplicity.
//
// The blinker.BIN file was compressed with `gzip -9` and will be expanded
// during OTA
//
// You need to have at least 256K of filesystem configured in
// Tools->Flash Size
//
// Released to the public domain August 2022 by Earle F. Philhower, III
#include <PicoOTA.h>
#include <LittleFS.h>
#include "blink_100_1000.h"
void setup() {
Serial.begin(115200);
delay(5000);
Serial.printf("Writing OTA image of blinker...");
LittleFS.begin();
File f = LittleFS.open("blink.bin.gz", "w");
if (sizeof(blink_gz) != f.write(blink_gz, sizeof(blink_gz))) {
Serial.printf("Unable to write OTA binary. Is the filesystem size set?\n");
return;
}
f.close();
Serial.printf("done\n\n");
Serial.printf("Programming OTA commands...");
picoOTA.begin();
picoOTA.addFile("blink.bin.gz");
picoOTA.commit();
LittleFS.end();
Serial.printf("done\n\n");
Serial.printf("Rebooting in 5 seconds, should begin blinker instead of this app...\n");
delay(5000);
rp2040.reboot();
}
void loop() {
}

File diff suppressed because it is too large Load diff

View 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() {
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,21 @@
#######################################
# Syntax Coloring Map For Ultrasound
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
picoOTA KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
addFile KEYWORD1
commit KEYWORD1
#######################################
# Constants (LITERAL1)
#######################################

View 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

View 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;

View 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;

View file

@ -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.

View file

@ -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;

View 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)
#######################################

View 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

View 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;

View 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;

View file

@ -0,0 +1,3 @@
// This file will be overridden when automatic signing is used.
// By default, no signing.
#define ARDUINO_SIGNING 0

View file

@ -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

View file

@ -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
};

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -28,6 +28,7 @@ extern "C" {
}
#include <AddrList.h>
#include <Arduino.h>
//#include <PolledTimeout.h>
#define PBUF_ALIGNER_ADJUST 4

View file

@ -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 */

View file

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

@ -0,0 +1 @@
Subproject commit 6d60d651a4499a64f2e5b21b4cc08d98cb84b5c1

View file

@ -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' | \

View file

@ -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

View file

@ -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

View file

@ -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
View 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

View file

@ -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
View 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())