Compare commits
37 Commits
copilot/fi
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f706c6cbed | ||
|
|
18dfc7081c | ||
|
|
5c0c84eb6b | ||
|
|
691c058ae8 | ||
|
|
7d550baf94 | ||
|
|
42ff73ffe7 | ||
|
|
2d8edfcb24 | ||
|
|
fb077ecadc | ||
|
|
caf3d900fd | ||
|
|
e920d2e101 | ||
|
|
54746c9730 | ||
|
|
8225a2a07c | ||
|
|
75f6de9dc2 | ||
|
|
16cfbf7500 | ||
|
|
aecac2c56c | ||
|
|
385504e6db | ||
|
|
a0321170d0 | ||
|
|
d70018ae9f | ||
|
|
9359f0b7fc | ||
|
|
be74196a62 | ||
|
|
705f2035f4 | ||
|
|
65efcb351e | ||
|
|
72ad39d6a7 | ||
|
|
5950204d34 | ||
|
|
46df9410b3 | ||
|
|
b7e4cd0d9a | ||
|
|
0becd61323 | ||
|
|
3f2e92c4c5 | ||
|
|
c8757d45c8 | ||
|
|
62fad4dcdf | ||
|
|
a60be251d2 | ||
|
|
f8ce5980a1 | ||
|
|
4b5c3a396d | ||
|
|
550b4d9dea | ||
|
|
f3e3f585df | ||
|
|
2082b01a3c | ||
|
|
4de6656bc4 |
4
.github/copilot-instructions.md
vendored
4
.github/copilot-instructions.md
vendored
@@ -8,7 +8,7 @@ Always reference these instructions first and fallback to search or bash command
|
|||||||
|
|
||||||
### Initial Setup
|
### Initial Setup
|
||||||
- Install Node.js 20+ (specified in `.nvmrc`): Check your version with `node --version`
|
- Install Node.js 20+ (specified in `.nvmrc`): Check your version with `node --version`
|
||||||
- Install dependencies: `npm install` (takes ~5 seconds)
|
- Install dependencies: `npm ci` (takes ~5 seconds)
|
||||||
- Install PlatformIO for hardware builds: `pip install -r requirements.txt` (takes ~60 seconds)
|
- Install PlatformIO for hardware builds: `pip install -r requirements.txt` (takes ~60 seconds)
|
||||||
|
|
||||||
### Build and Test Workflow
|
### Build and Test Workflow
|
||||||
@@ -135,4 +135,4 @@ The GitHub Actions workflow:
|
|||||||
4. Compiles firmware for multiple hardware targets
|
4. Compiles firmware for multiple hardware targets
|
||||||
5. Uploads build artifacts
|
5. Uploads build artifacts
|
||||||
|
|
||||||
Match this workflow in your local development to ensure CI success.
|
Match this workflow in your local development to ensure CI success.
|
||||||
|
|||||||
2
.github/workflows/nightly.yml
vendored
2
.github/workflows/nightly.yml
vendored
@@ -27,6 +27,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
sinceTag: v0.15.0
|
sinceTag: v0.15.0
|
||||||
|
# Exclude issues that were closed without resolution from changelog
|
||||||
|
exclude-labels: 'stale,wontfix,duplicate,invalid'
|
||||||
- name: Update Nightly Release
|
- name: Update Nightly Release
|
||||||
uses: andelf/nightly-release@main
|
uses: andelf/nightly-release@main
|
||||||
env:
|
env:
|
||||||
|
|||||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -24,7 +24,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
sinceTag: v0.15.0
|
sinceTag: v0.15.0
|
||||||
maxIssues: 500
|
maxIssues: 500
|
||||||
|
# Exclude issues that were closed without resolution from changelog
|
||||||
|
exclude-labels: 'stale,wontfix,duplicate,invalid'
|
||||||
- name: Create draft release
|
- name: Create draft release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
# Bootloader Compatibility Checking
|
|
||||||
|
|
||||||
As of WLED 0.16, the firmware includes bootloader version checking to prevent incompatible OTA updates that could cause boot loops.
|
|
||||||
|
|
||||||
## Background
|
|
||||||
|
|
||||||
ESP32 devices use different bootloader versions:
|
|
||||||
- **V2 Bootloaders**: Legacy bootloaders (ESP-IDF < 4.4)
|
|
||||||
- **V3 Bootloaders**: Intermediate bootloaders (ESP-IDF 4.4+)
|
|
||||||
- **V4 Bootloaders**: Modern bootloaders (ESP-IDF 5.0+) with rollback support
|
|
||||||
|
|
||||||
WLED 0.16+ requires V4 bootloaders for full compatibility and safety features.
|
|
||||||
|
|
||||||
## Checking Your Bootloader Version
|
|
||||||
|
|
||||||
### Method 1: Web Interface
|
|
||||||
Visit your WLED device at: `http://your-device-ip/json/bootloader`
|
|
||||||
|
|
||||||
This will return JSON like:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"version": 4,
|
|
||||||
"rollback_capable": true,
|
|
||||||
"esp_idf_version": 50002
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Method 2: Serial Console
|
|
||||||
Enable debug output and look for bootloader version messages during startup.
|
|
||||||
|
|
||||||
## OTA Update Behavior
|
|
||||||
|
|
||||||
When uploading firmware via OTA:
|
|
||||||
|
|
||||||
1. **Compatible Bootloader**: Update proceeds normally
|
|
||||||
2. **Incompatible Bootloader**: Update is blocked with error message:
|
|
||||||
> "Bootloader incompatible! Please update to a newer bootloader first."
|
|
||||||
3. **No Metadata**: Update proceeds (for backward compatibility with older firmware)
|
|
||||||
|
|
||||||
## Upgrading Your Bootloader
|
|
||||||
|
|
||||||
If you have an incompatible bootloader, you have several options:
|
|
||||||
|
|
||||||
### Option 1: Serial Flash (Recommended)
|
|
||||||
Use the [WLED web installer](https://install.wled.me) to flash via USB cable. This will install the latest bootloader and firmware.
|
|
||||||
|
|
||||||
### Option 2: Staged Update
|
|
||||||
1. First update to WLED 0.15.x (which supports your current bootloader)
|
|
||||||
2. Then update to WLED 0.16+ (0.15.x may include bootloader update)
|
|
||||||
|
|
||||||
### Option 3: ESP Tool
|
|
||||||
Use esptool.py to manually flash a new bootloader (advanced users only).
|
|
||||||
|
|
||||||
## For Firmware Builders
|
|
||||||
|
|
||||||
When building custom firmware that requires V4 bootloader:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Add bootloader requirement to your binary
|
|
||||||
python3 tools/add_bootloader_metadata.py firmware.bin 4
|
|
||||||
```
|
|
||||||
|
|
||||||
## Technical Details
|
|
||||||
|
|
||||||
- Metadata format: ASCII string `WLED_BOOTLOADER:X` where X is required version (1-9)
|
|
||||||
- Checked in first 512 bytes of uploaded firmware
|
|
||||||
- Uses ESP-IDF version and rollback capability to detect current bootloader
|
|
||||||
- Backward compatible with firmware without metadata
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
**Error: "Bootloader incompatible!"**
|
|
||||||
- Use web installer to update via USB
|
|
||||||
- Or use staged update through 0.15.x
|
|
||||||
|
|
||||||
**How to check if I need an update?**
|
|
||||||
- Visit `/json/bootloader` endpoint
|
|
||||||
- If version < 4, you may need to update for future firmware
|
|
||||||
|
|
||||||
**Can I force an update?**
|
|
||||||
- Not recommended - could brick your device
|
|
||||||
- Use proper upgrade path instead
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
Import("env")
|
Import("env")
|
||||||
import shutil
|
import shutil
|
||||||
|
import os
|
||||||
|
|
||||||
node_ex = shutil.which("node")
|
node_ex = shutil.which("node")
|
||||||
# Check if Node.js is installed and present in PATH if it failed, abort the build
|
# Check if Node.js is installed and present in PATH if it failed, abort the build
|
||||||
@@ -12,6 +13,21 @@ else:
|
|||||||
print('\x1b[6;33;42m' + 'Installing node packages' + '\x1b[0m')
|
print('\x1b[6;33;42m' + 'Installing node packages' + '\x1b[0m')
|
||||||
env.Execute("npm ci")
|
env.Execute("npm ci")
|
||||||
|
|
||||||
|
# Extract the release name from build flags
|
||||||
|
release_name = "Custom"
|
||||||
|
build_flags = env.get("BUILD_FLAGS", [])
|
||||||
|
for flag in build_flags:
|
||||||
|
if 'WLED_RELEASE_NAME=' in flag:
|
||||||
|
# Extract the release name, remove quotes and handle different formats
|
||||||
|
parts = flag.split('WLED_RELEASE_NAME=')
|
||||||
|
if len(parts) > 1:
|
||||||
|
release_name = parts[1].split()[0].strip('\"\\')
|
||||||
|
break
|
||||||
|
|
||||||
|
# Set environment variable for cdata.js to use
|
||||||
|
os.environ['WLED_RELEASE_NAME'] = release_name
|
||||||
|
print(f'Building web UI with release name: {release_name}')
|
||||||
|
|
||||||
# Call the bundling script
|
# Call the bundling script
|
||||||
exitCode = env.Execute("npm run build")
|
exitCode = env.Execute("npm run build")
|
||||||
|
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ lib_deps =
|
|||||||
makuna/NeoPixelBus @ 2.8.3
|
makuna/NeoPixelBus @ 2.8.3
|
||||||
#https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta
|
#https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta
|
||||||
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.2
|
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.2
|
||||||
|
marvinroger/AsyncMqttClient @ 0.9.0
|
||||||
# for I2C interface
|
# for I2C interface
|
||||||
;Wire
|
;Wire
|
||||||
# ESP-NOW library
|
# ESP-NOW library
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ See [here](https://kno.wled.ge/basics/compatible-hardware)!
|
|||||||
|
|
||||||
Licensed under the EUPL v1.2 license
|
Licensed under the EUPL v1.2 license
|
||||||
Credits [here](https://kno.wled.ge/about/contributors/)!
|
Credits [here](https://kno.wled.ge/about/contributors/)!
|
||||||
|
CORS proxy by [Corsfix](https://corsfix.com/)
|
||||||
|
|
||||||
Join the Discord server to discuss everything about WLED!
|
Join the Discord server to discuss everything about WLED!
|
||||||
|
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Simple script to add bootloader requirement metadata to WLED binary files.
|
|
||||||
This adds a metadata tag that the OTA handler can detect.
|
|
||||||
|
|
||||||
Usage: python add_bootloader_metadata.py <binary_file> <required_version>
|
|
||||||
Example: python add_bootloader_metadata.py firmware.bin 4
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
def add_bootloader_metadata(binary_file, required_version):
|
|
||||||
"""Add bootloader metadata to a binary file"""
|
|
||||||
if not os.path.exists(binary_file):
|
|
||||||
print(f"Error: File {binary_file} does not exist")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Validate version
|
|
||||||
try:
|
|
||||||
version = int(required_version)
|
|
||||||
if version < 1 or version > 9:
|
|
||||||
print("Error: Bootloader version must be between 1 and 9")
|
|
||||||
return False
|
|
||||||
except ValueError:
|
|
||||||
print("Error: Bootloader version must be a number")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Create metadata string
|
|
||||||
metadata = f"WLED_BOOTLOADER:{version}"
|
|
||||||
|
|
||||||
# Check if metadata already exists
|
|
||||||
try:
|
|
||||||
with open(binary_file, 'rb') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
if metadata.encode('ascii') in content:
|
|
||||||
print(f"File already contains bootloader v{version} requirement")
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Check for any bootloader metadata
|
|
||||||
if b"WLED_BOOTLOADER:" in content:
|
|
||||||
print("Warning: File already contains bootloader metadata. Adding new requirement.")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error reading file: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Append metadata to file
|
|
||||||
try:
|
|
||||||
with open(binary_file, 'ab') as f:
|
|
||||||
f.write(metadata.encode('ascii'))
|
|
||||||
print(f"Successfully added bootloader v{version} requirement to {binary_file}")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error writing to file: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) != 3:
|
|
||||||
print("Usage: python add_bootloader_metadata.py <binary_file> <required_version>")
|
|
||||||
print("Example: python add_bootloader_metadata.py firmware.bin 4")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
binary_file = sys.argv[1]
|
|
||||||
required_version = sys.argv[2]
|
|
||||||
|
|
||||||
if add_bootloader_metadata(binary_file, required_version):
|
|
||||||
sys.exit(0)
|
|
||||||
else:
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
# Bootloader Metadata Tool
|
|
||||||
|
|
||||||
This tool adds bootloader version requirements to WLED firmware binaries to prevent incompatible OTA updates.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 tools/add_bootloader_metadata.py <binary_file> <required_version>
|
|
||||||
```
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```bash
|
|
||||||
python3 tools/add_bootloader_metadata.py firmware.bin 4
|
|
||||||
```
|
|
||||||
|
|
||||||
## Bootloader Versions
|
|
||||||
|
|
||||||
- **Version 2**: Legacy bootloader (ESP-IDF < 4.4)
|
|
||||||
- **Version 3**: Intermediate bootloader (ESP-IDF 4.4+)
|
|
||||||
- **Version 4**: Modern bootloader (ESP-IDF 5.0+) with rollback support
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
1. The script appends a metadata tag `WLED_BOOTLOADER:X` to the binary file
|
|
||||||
2. During OTA upload, WLED checks the first 512 bytes for this metadata
|
|
||||||
3. If found, WLED compares the required version with the current bootloader
|
|
||||||
4. The update is blocked if the current bootloader is incompatible
|
|
||||||
|
|
||||||
## Metadata Format
|
|
||||||
|
|
||||||
The metadata is a simple ASCII string: `WLED_BOOTLOADER:X` where X is the required bootloader version (1-9).
|
|
||||||
|
|
||||||
This approach was chosen over filename-based detection because users often rename firmware files.
|
|
||||||
|
|
||||||
## Integration with Build Process
|
|
||||||
|
|
||||||
To automatically add metadata during builds, add this to your platformio.ini:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[env:your_env]
|
|
||||||
extra_scripts = post:add_metadata.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Create `add_metadata.py`:
|
|
||||||
```python
|
|
||||||
Import("env")
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
def add_metadata(source, target, env):
|
|
||||||
firmware_path = str(target[0])
|
|
||||||
subprocess.run(["python3", "tools/add_bootloader_metadata.py", firmware_path, "4"])
|
|
||||||
|
|
||||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", add_metadata)
|
|
||||||
```
|
|
||||||
@@ -95,6 +95,11 @@ function adoptVersionAndRepo(html) {
|
|||||||
if (version) {
|
if (version) {
|
||||||
html = html.replaceAll("##VERSION##", version);
|
html = html.replaceAll("##VERSION##", version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace ##RELEASE## with the actual release name from build environment
|
||||||
|
const releaseName = process.env.WLED_RELEASE_NAME || 'Custom';
|
||||||
|
html = html.replaceAll("##RELEASE##", releaseName);
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
5
usermods/udp_name_sync/library.json
Normal file
5
usermods/udp_name_sync/library.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "udp_name_sync",
|
||||||
|
"build": { "libArchive": false },
|
||||||
|
"dependencies": {}
|
||||||
|
}
|
||||||
85
usermods/udp_name_sync/udp_name_sync.cpp
Normal file
85
usermods/udp_name_sync/udp_name_sync.cpp
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#include "wled.h"
|
||||||
|
|
||||||
|
class UdpNameSync : public Usermod {
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool enabled = false;
|
||||||
|
char segmentName[WLED_MAX_SEGNAME_LEN] = {0};
|
||||||
|
static constexpr uint8_t kPacketType = 200; // custom usermod packet type
|
||||||
|
static const char _name[];
|
||||||
|
static const char _enabled[];
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Enable/Disable the usermod
|
||||||
|
*/
|
||||||
|
inline void enable(bool value) { enabled = value; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get usermod enabled/disabled state
|
||||||
|
*/
|
||||||
|
inline bool isEnabled() const { return enabled; }
|
||||||
|
|
||||||
|
void setup() override {
|
||||||
|
// Enabled when this usermod is compiled, set to false if you prefer runtime opt-in
|
||||||
|
enable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() override {
|
||||||
|
if (!enabled) return;
|
||||||
|
if (!WLED_CONNECTED) return;
|
||||||
|
if (!udpConnected) return;
|
||||||
|
Segment& mainseg = strip.getMainSegment();
|
||||||
|
if (segmentName[0] == '\0' && !mainseg.name) return; //name was never set, do nothing
|
||||||
|
|
||||||
|
const char* curName = mainseg.name ? mainseg.name : "";
|
||||||
|
if (strncmp(curName, segmentName, sizeof(segmentName)) == 0) return; // same name, do nothing
|
||||||
|
|
||||||
|
IPAddress broadcastIp = uint32_t(Network.localIP()) | ~uint32_t(Network.subnetMask());
|
||||||
|
byte udpOut[WLED_MAX_SEGNAME_LEN + 2];
|
||||||
|
udpOut[0] = kPacketType; // custom usermod packet type (avoid 0..5 used by core protocols)
|
||||||
|
|
||||||
|
if (segmentName[0] != '\0' && !mainseg.name) { // name cleared
|
||||||
|
notifierUdp.beginPacket(broadcastIp, udpPort);
|
||||||
|
segmentName[0] = '\0';
|
||||||
|
DEBUG_PRINTLN(F("UdpNameSync: sending empty name"));
|
||||||
|
udpOut[1] = 0; // explicit empty string
|
||||||
|
notifierUdp.write(udpOut, 2);
|
||||||
|
notifierUdp.endPacket();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifierUdp.beginPacket(broadcastIp, udpPort);
|
||||||
|
DEBUG_PRINT(F("UdpNameSync: saving segment name "));
|
||||||
|
DEBUG_PRINTLN(curName);
|
||||||
|
strlcpy(segmentName, curName, sizeof(segmentName));
|
||||||
|
strlcpy((char *)&udpOut[1], segmentName, sizeof(udpOut) - 1); // leave room for header byte
|
||||||
|
size_t nameLen = strnlen((char *)&udpOut[1], sizeof(udpOut) - 1);
|
||||||
|
notifierUdp.write(udpOut, 2 + nameLen);
|
||||||
|
notifierUdp.endPacket();
|
||||||
|
DEBUG_PRINT(F("UdpNameSync: Sent segment name : "));
|
||||||
|
DEBUG_PRINTLN(segmentName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool onUdpPacket(uint8_t * payload, size_t len) override {
|
||||||
|
DEBUG_PRINT(F("UdpNameSync: Received packet"));
|
||||||
|
if (!enabled) return false;
|
||||||
|
if (receiveDirect) return false;
|
||||||
|
if (len < 2) return false; // need type + at least 1 byte for name (can be 0)
|
||||||
|
if (payload[0] != kPacketType) return false;
|
||||||
|
Segment& mainseg = strip.getMainSegment();
|
||||||
|
char tmp[WLED_MAX_SEGNAME_LEN] = {0};
|
||||||
|
size_t copyLen = len - 1;
|
||||||
|
if (copyLen > sizeof(tmp) - 1) copyLen = sizeof(tmp) - 1;
|
||||||
|
memcpy(tmp, &payload[1], copyLen);
|
||||||
|
tmp[copyLen] = '\0';
|
||||||
|
mainseg.setName(tmp);
|
||||||
|
DEBUG_PRINT(F("UdpNameSync: set segment name"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static UdpNameSync udp_name_sync;
|
||||||
|
REGISTER_USERMOD(udp_name_sync);
|
||||||
@@ -2328,7 +2328,7 @@ uint16_t mode_colortwinkle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cur == prev) { //fix "stuck" pixels
|
if (cur == prev) { //fix "stuck" pixels
|
||||||
color_add(col, col);
|
col = color_add(col, col);
|
||||||
SEGMENT.setPixelColor(i, col);
|
SEGMENT.setPixelColor(i, col);
|
||||||
}
|
}
|
||||||
else SEGMENT.setPixelColor(i, col);
|
else SEGMENT.setPixelColor(i, col);
|
||||||
@@ -3940,7 +3940,7 @@ uint16_t mode_percent(void) {
|
|||||||
|
|
||||||
return FRAMETIME;
|
return FRAMETIME;
|
||||||
}
|
}
|
||||||
static const char _data_FX_MODE_PERCENT[] PROGMEM = "Percent@,% of fill,,,,One color;!,!;!";
|
static const char _data_FX_MODE_PERCENT[] PROGMEM = "Percent@!,% of fill,,,,One color;!,!;!";
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ Y: <input name="P${i}Y" type="number" min="0" max="255" value="0" oninput="UI()"
|
|||||||
Sf[`P${p}H`].value = ph;
|
Sf[`P${p}H`].value = ph;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
UI(); // Update the preview after generating panels
|
||||||
}
|
}
|
||||||
|
|
||||||
function expand(o,i)
|
function expand(o,i)
|
||||||
|
|||||||
@@ -28,10 +28,13 @@
|
|||||||
<h2>WLED Software Update</h2>
|
<h2>WLED Software Update</h2>
|
||||||
<form method='POST' action='./update' id='upd' enctype='multipart/form-data' onsubmit="toggle('upd')">
|
<form method='POST' action='./update' id='upd' enctype='multipart/form-data' onsubmit="toggle('upd')">
|
||||||
Installed version: <span class="sip">WLED ##VERSION##</span><br>
|
Installed version: <span class="sip">WLED ##VERSION##</span><br>
|
||||||
|
Release: <span class="sip">##RELEASE##</span><br>
|
||||||
Download the latest binary: <a href="https://github.com/wled-dev/WLED/releases" target="_blank"
|
Download the latest binary: <a href="https://github.com/wled-dev/WLED/releases" target="_blank"
|
||||||
style="vertical-align: text-bottom; display: inline-flex;">
|
style="vertical-align: text-bottom; display: inline-flex;">
|
||||||
<img src="https://img.shields.io/github/release/wled-dev/WLED.svg?style=flat-square"></a><br>
|
<img src="https://img.shields.io/github/release/wled-dev/WLED.svg?style=flat-square"></a><br>
|
||||||
<input type='file' name='update' required><br> <!--should have accept='.bin', but it prevents file upload from android app-->
|
<input type='file' name='update' required><br> <!--should have accept='.bin', but it prevents file upload from android app-->
|
||||||
|
<input type='checkbox' name='skipValidation' id='skipValidation'>
|
||||||
|
<label for='skipValidation'>Ignore firmware validation</label><br>
|
||||||
<button type="submit">Update!</button><br>
|
<button type="submit">Update!</button><br>
|
||||||
<hr class="sml">
|
<hr class="sml">
|
||||||
<button id="rev" type="button" onclick="cR()">Revert update</button><br>
|
<button id="rev" type="button" onclick="cR()">Revert update</button><br>
|
||||||
|
|||||||
@@ -315,6 +315,7 @@ class Usermod {
|
|||||||
virtual void onMqttConnect(bool sessionPresent) {} // fired when MQTT connection is established (so usermod can subscribe)
|
virtual void onMqttConnect(bool sessionPresent) {} // fired when MQTT connection is established (so usermod can subscribe)
|
||||||
virtual bool onMqttMessage(char* topic, char* payload) { return false; } // fired upon MQTT message received (wled topic)
|
virtual bool onMqttMessage(char* topic, char* payload) { return false; } // fired upon MQTT message received (wled topic)
|
||||||
virtual bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len) { return false; } // fired upon ESP-NOW message received
|
virtual bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len) { return false; } // fired upon ESP-NOW message received
|
||||||
|
virtual bool onUdpPacket(uint8_t* payload, size_t len) { return false; } //fired upon UDP packet received
|
||||||
virtual void onUpdateBegin(bool) {} // fired prior to and after unsuccessful firmware update
|
virtual void onUpdateBegin(bool) {} // fired prior to and after unsuccessful firmware update
|
||||||
virtual void onStateChange(uint8_t mode) {} // fired upon WLED state change
|
virtual void onStateChange(uint8_t mode) {} // fired upon WLED state change
|
||||||
virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;}
|
virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;}
|
||||||
@@ -354,6 +355,7 @@ namespace UsermodManager {
|
|||||||
#ifndef WLED_DISABLE_ESPNOW
|
#ifndef WLED_DISABLE_ESPNOW
|
||||||
bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len);
|
bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len);
|
||||||
#endif
|
#endif
|
||||||
|
bool onUdpPacket(uint8_t* payload, size_t len);
|
||||||
void onUpdateBegin(bool);
|
void onUpdateBegin(bool);
|
||||||
void onStateChange(uint8_t);
|
void onStateChange(uint8_t);
|
||||||
Usermod* lookup(uint16_t mod_id);
|
Usermod* lookup(uint16_t mod_id);
|
||||||
@@ -466,10 +468,6 @@ void handleBootLoop(); // detect and handle bootloops
|
|||||||
#ifndef ESP8266
|
#ifndef ESP8266
|
||||||
void bootloopCheckOTA(); // swap boot image if bootloop is detected instead of restoring config
|
void bootloopCheckOTA(); // swap boot image if bootloop is detected instead of restoring config
|
||||||
#endif
|
#endif
|
||||||
#ifndef WLED_DISABLE_OTA
|
|
||||||
uint32_t getBootloaderVersion(); // get current bootloader version
|
|
||||||
bool isBootloaderCompatible(uint32_t required_version); // check bootloader compatibility
|
|
||||||
#endif
|
|
||||||
// RAII guard class for the JSON Buffer lock
|
// RAII guard class for the JSON Buffer lock
|
||||||
// Modeled after std::lock_guard
|
// Modeled after std::lock_guard
|
||||||
class JSONBufferGuard {
|
class JSONBufferGuard {
|
||||||
|
|||||||
122
wled00/ota_release_check.cpp
Normal file
122
wled00/ota_release_check.cpp
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
#include "ota_release_check.h"
|
||||||
|
#include "wled.h"
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
#include <esp_app_format.h>
|
||||||
|
#include <esp_ota_ops.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Same hash function used at compile time (must match wled_custom_desc.cpp)
|
||||||
|
static uint32_t djb2_hash(const char* str) {
|
||||||
|
uint32_t hash = 5381;
|
||||||
|
while (*str) {
|
||||||
|
hash = ((hash << 5) + hash) + *str++;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool extractReleaseFromCustomDesc(const uint8_t* binaryData, size_t dataSize, char* extractedRelease) {
|
||||||
|
if (!binaryData || !extractedRelease || dataSize < 64) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search in first 8KB only - ESP32 .rodata.wled_desc and ESP8266 .ver_number
|
||||||
|
// sections appear early in binary. 8KB should be sufficient for metadata discovery
|
||||||
|
// while minimizing processing time for large firmware files.
|
||||||
|
const size_t search_limit = min(dataSize, (size_t)8192);
|
||||||
|
|
||||||
|
for (size_t offset = 0; offset <= search_limit - sizeof(wled_custom_desc_t); offset++) {
|
||||||
|
const wled_custom_desc_t* custom_desc = (const wled_custom_desc_t*)(binaryData + offset);
|
||||||
|
|
||||||
|
// Check for magic number
|
||||||
|
if (custom_desc->magic == WLED_CUSTOM_DESC_MAGIC) {
|
||||||
|
// Found potential match, validate version
|
||||||
|
if (custom_desc->version != WLED_CUSTOM_DESC_VERSION) {
|
||||||
|
DEBUG_PRINTF_P(PSTR("Found WLED structure at offset %u but version mismatch: %u\n"),
|
||||||
|
offset, custom_desc->version);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate hash using same algorithm as compile-time
|
||||||
|
uint32_t expected_hash = djb2_hash(custom_desc->release_name);
|
||||||
|
if (custom_desc->crc32 != expected_hash) {
|
||||||
|
DEBUG_PRINTF_P(PSTR("Found WLED structure at offset %u but hash mismatch\n"), offset);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid structure found
|
||||||
|
strncpy(extractedRelease, custom_desc->release_name, WLED_RELEASE_NAME_MAX_LEN - 1);
|
||||||
|
extractedRelease[WLED_RELEASE_NAME_MAX_LEN - 1] = '\0';
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
DEBUG_PRINTF_P(PSTR("Extracted ESP32 release name from .rodata.wled_desc section at offset %u: '%s'\n"),
|
||||||
|
offset, extractedRelease);
|
||||||
|
#else
|
||||||
|
DEBUG_PRINTF_P(PSTR("Extracted ESP8266 release name from .ver_number section at offset %u: '%s'\n"),
|
||||||
|
offset, extractedRelease);
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_PRINTLN(F("No WLED custom description found in binary"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validateReleaseCompatibility(const char* extractedRelease) {
|
||||||
|
if (!extractedRelease || strlen(extractedRelease) == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple string comparison - releases must match exactly
|
||||||
|
bool match = strcmp(releaseString, extractedRelease) == 0;
|
||||||
|
|
||||||
|
DEBUG_PRINTF_P(PSTR("Release compatibility check: current='%s', uploaded='%s', match=%s\n"),
|
||||||
|
releaseString, extractedRelease, match ? "YES" : "NO");
|
||||||
|
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shouldAllowOTA(const uint8_t* binaryData, size_t dataSize, bool skipValidation, char* errorMessage) {
|
||||||
|
// Clear error message
|
||||||
|
if (errorMessage) {
|
||||||
|
errorMessage[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure our custom description structure is referenced (prevents optimization)
|
||||||
|
const wled_custom_desc_t* local_desc = getWledCustomDesc();
|
||||||
|
(void)local_desc; // Suppress unused variable warning
|
||||||
|
|
||||||
|
// If user chose to ignore release check, allow OTA
|
||||||
|
if (skipValidation) {
|
||||||
|
DEBUG_PRINTLN(F("OTA release check bypassed by user"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to extract release name directly from binary data
|
||||||
|
char extractedRelease[WLED_RELEASE_NAME_MAX_LEN];
|
||||||
|
bool hasCustomDesc = extractReleaseFromCustomDesc(binaryData, dataSize, extractedRelease);
|
||||||
|
|
||||||
|
if (!hasCustomDesc) {
|
||||||
|
// No custom description - this could be a legacy binary
|
||||||
|
if (errorMessage) {
|
||||||
|
strcpy(errorMessage, "Binary has no release compatibility metadata. Check 'Ignore validation' to proceed.");
|
||||||
|
}
|
||||||
|
DEBUG_PRINTLN(F("OTA blocked: No custom description found"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate compatibility using extracted release name
|
||||||
|
if (!validateReleaseCompatibility(extractedRelease)) {
|
||||||
|
if (errorMessage) {
|
||||||
|
snprintf(errorMessage, 127, "Release mismatch: current='%s', uploaded='%s'. Check 'Ignore validation' to proceed.",
|
||||||
|
releaseString, extractedRelease);
|
||||||
|
}
|
||||||
|
DEBUG_PRINTF_P(PSTR("OTA blocked: Release mismatch current='%s', uploaded='%s'\n"),
|
||||||
|
releaseString, extractedRelease);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_PRINTLN(F("OTA allowed: Release names match"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
64
wled00/ota_release_check.h
Normal file
64
wled00/ota_release_check.h
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#ifndef WLED_OTA_RELEASE_CHECK_H
|
||||||
|
#define WLED_OTA_RELEASE_CHECK_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* OTA Release Compatibility Checking using ESP-IDF Custom Description Section
|
||||||
|
* Functions to extract and validate release names from uploaded binary files using embedded metadata
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
#include <esp_app_format.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define WLED_CUSTOM_DESC_MAGIC 0x57535453 // "WSTS" (WLED System Tag Structure)
|
||||||
|
#define WLED_CUSTOM_DESC_VERSION 1
|
||||||
|
#define WLED_RELEASE_NAME_MAX_LEN 48
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WLED Custom Description Structure
|
||||||
|
* This structure is embedded in platform-specific sections at a fixed offset
|
||||||
|
* in ESP32/ESP8266 binaries, allowing extraction without modifying the binary format
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint32_t magic; // Magic number to identify WLED custom description
|
||||||
|
uint32_t version; // Structure version for future compatibility
|
||||||
|
char release_name[WLED_RELEASE_NAME_MAX_LEN]; // Release name (null-terminated)
|
||||||
|
uint32_t crc32; // CRC32 of the above fields for integrity check
|
||||||
|
} __attribute__((packed)) wled_custom_desc_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract release name from binary using ESP-IDF custom description section
|
||||||
|
* @param binaryData Pointer to binary file data
|
||||||
|
* @param dataSize Size of binary data in bytes
|
||||||
|
* @param extractedRelease Buffer to store extracted release name (should be at least WLED_RELEASE_NAME_MAX_LEN bytes)
|
||||||
|
* @return true if release name was found and extracted, false otherwise
|
||||||
|
*/
|
||||||
|
bool extractReleaseFromCustomDesc(const uint8_t* binaryData, size_t dataSize, char* extractedRelease);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate if extracted release name matches current release
|
||||||
|
* @param extractedRelease Release name from uploaded binary
|
||||||
|
* @return true if releases match (OTA should proceed), false if they don't match
|
||||||
|
*/
|
||||||
|
bool validateReleaseCompatibility(const char* extractedRelease);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if OTA should be allowed based on release compatibility using custom description
|
||||||
|
* @param binaryData Pointer to binary file data (not modified)
|
||||||
|
* @param dataSize Size of binary data in bytes
|
||||||
|
* @param skipValidation If true, skip release validation
|
||||||
|
* @param errorMessage Buffer to store error message if validation fails (should be at least 128 bytes)
|
||||||
|
* @return true if OTA should proceed, false if it should be blocked
|
||||||
|
*/
|
||||||
|
bool shouldAllowOTA(const uint8_t* binaryData, size_t dataSize, bool skipValidation, char* errorMessage);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get pointer to the embedded custom description structure
|
||||||
|
* This ensures the structure is referenced and not optimized out
|
||||||
|
* @return pointer to the custom description structure
|
||||||
|
*/
|
||||||
|
const wled_custom_desc_t* getWledCustomDesc();
|
||||||
|
|
||||||
|
#endif // WLED_OTA_RELEASE_CHECK_H
|
||||||
@@ -1,877 +0,0 @@
|
|||||||
#include "AsyncMqttClient.hpp"
|
|
||||||
|
|
||||||
AsyncMqttClient::AsyncMqttClient()
|
|
||||||
: _connected(false)
|
|
||||||
, _connectPacketNotEnoughSpace(false)
|
|
||||||
, _disconnectFlagged(false)
|
|
||||||
, _tlsBadFingerprint(false)
|
|
||||||
, _lastClientActivity(0)
|
|
||||||
, _lastServerActivity(0)
|
|
||||||
, _lastPingRequestTime(0)
|
|
||||||
, _host(nullptr)
|
|
||||||
, _useIp(false)
|
|
||||||
#if ASYNC_TCP_SSL_ENABLED
|
|
||||||
, _secure(false)
|
|
||||||
#endif
|
|
||||||
, _port(0)
|
|
||||||
, _keepAlive(15)
|
|
||||||
, _cleanSession(true)
|
|
||||||
, _clientId(nullptr)
|
|
||||||
, _username(nullptr)
|
|
||||||
, _password(nullptr)
|
|
||||||
, _willTopic(nullptr)
|
|
||||||
, _willPayload(nullptr)
|
|
||||||
, _willPayloadLength(0)
|
|
||||||
, _willQos(0)
|
|
||||||
, _willRetain(false)
|
|
||||||
, _parsingInformation { .bufferState = AsyncMqttClientInternals::BufferState::NONE }
|
|
||||||
, _currentParsedPacket(nullptr)
|
|
||||||
, _remainingLengthBufferPosition(0)
|
|
||||||
, _nextPacketId(1) {
|
|
||||||
_client.onConnect([](void* obj, AsyncClient* c) { (static_cast<AsyncMqttClient*>(obj))->_onConnect(c); }, this);
|
|
||||||
_client.onDisconnect([](void* obj, AsyncClient* c) { (static_cast<AsyncMqttClient*>(obj))->_onDisconnect(c); }, this);
|
|
||||||
_client.onError([](void* obj, AsyncClient* c, int8_t error) { (static_cast<AsyncMqttClient*>(obj))->_onError(c, error); }, this);
|
|
||||||
_client.onTimeout([](void* obj, AsyncClient* c, uint32_t time) { (static_cast<AsyncMqttClient*>(obj))->_onTimeout(c, time); }, this);
|
|
||||||
_client.onAck([](void* obj, AsyncClient* c, size_t len, uint32_t time) { (static_cast<AsyncMqttClient*>(obj))->_onAck(c, len, time); }, this);
|
|
||||||
_client.onData([](void* obj, AsyncClient* c, void* data, size_t len) { (static_cast<AsyncMqttClient*>(obj))->_onData(c, static_cast<char*>(data), len); }, this);
|
|
||||||
_client.onPoll([](void* obj, AsyncClient* c) { (static_cast<AsyncMqttClient*>(obj))->_onPoll(c); }, this);
|
|
||||||
|
|
||||||
#ifdef ESP32
|
|
||||||
sprintf(_generatedClientId, "esp32%06x", (uint32_t)ESP.getEfuseMac());
|
|
||||||
_xSemaphore = xSemaphoreCreateMutex();
|
|
||||||
#elif defined(ESP8266)
|
|
||||||
sprintf(_generatedClientId, "esp8266%06x", (uint32_t)ESP.getChipId());
|
|
||||||
#endif
|
|
||||||
_clientId = _generatedClientId;
|
|
||||||
|
|
||||||
setMaxTopicLength(128);
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncMqttClient::~AsyncMqttClient() {
|
|
||||||
delete _currentParsedPacket;
|
|
||||||
delete[] _parsingInformation.topicBuffer;
|
|
||||||
#ifdef ESP32
|
|
||||||
vSemaphoreDelete(_xSemaphore);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncMqttClient& AsyncMqttClient::setKeepAlive(uint16_t keepAlive) {
|
|
||||||
_keepAlive = keepAlive;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncMqttClient& AsyncMqttClient::setClientId(const char* clientId) {
|
|
||||||
_clientId = clientId;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncMqttClient& AsyncMqttClient::setCleanSession(bool cleanSession) {
|
|
||||||
_cleanSession = cleanSession;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncMqttClient& AsyncMqttClient::setMaxTopicLength(uint16_t maxTopicLength) {
|
|
||||||
_parsingInformation.maxTopicLength = maxTopicLength;
|
|
||||||
delete[] _parsingInformation.topicBuffer;
|
|
||||||
_parsingInformation.topicBuffer = new char[maxTopicLength + 1];
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncMqttClient& AsyncMqttClient::setCredentials(const char* username, const char* password) {
|
|
||||||
_username = username;
|
|
||||||
_password = password;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncMqttClient& AsyncMqttClient::setWill(const char* topic, uint8_t qos, bool retain, const char* payload, size_t length) {
|
|
||||||
_willTopic = topic;
|
|
||||||
_willQos = qos;
|
|
||||||
_willRetain = retain;
|
|
||||||
_willPayload = payload;
|
|
||||||
_willPayloadLength = length;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncMqttClient& AsyncMqttClient::setServer(IPAddress ip, uint16_t port) {
|
|
||||||
_useIp = true;
|
|
||||||
_ip = ip;
|
|
||||||
_port = port;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncMqttClient& AsyncMqttClient::setServer(const char* host, uint16_t port) {
|
|
||||||
_useIp = false;
|
|
||||||
_host = host;
|
|
||||||
_port = port;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if ASYNC_TCP_SSL_ENABLED
|
|
||||||
AsyncMqttClient& AsyncMqttClient::setSecure(bool secure) {
|
|
||||||
_secure = secure;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncMqttClient& AsyncMqttClient::addServerFingerprint(const uint8_t* fingerprint) {
|
|
||||||
std::array<uint8_t, SHA1_SIZE> newFingerprint;
|
|
||||||
memcpy(newFingerprint.data(), fingerprint, SHA1_SIZE);
|
|
||||||
_secureServerFingerprints.push_back(newFingerprint);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
AsyncMqttClient& AsyncMqttClient::onConnect(AsyncMqttClientInternals::OnConnectUserCallback callback) {
|
|
||||||
_onConnectUserCallbacks.push_back(callback);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncMqttClient& AsyncMqttClient::onDisconnect(AsyncMqttClientInternals::OnDisconnectUserCallback callback) {
|
|
||||||
_onDisconnectUserCallbacks.push_back(callback);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncMqttClient& AsyncMqttClient::onSubscribe(AsyncMqttClientInternals::OnSubscribeUserCallback callback) {
|
|
||||||
_onSubscribeUserCallbacks.push_back(callback);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncMqttClient& AsyncMqttClient::onUnsubscribe(AsyncMqttClientInternals::OnUnsubscribeUserCallback callback) {
|
|
||||||
_onUnsubscribeUserCallbacks.push_back(callback);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncMqttClient& AsyncMqttClient::onMessage(AsyncMqttClientInternals::OnMessageUserCallback callback) {
|
|
||||||
_onMessageUserCallbacks.push_back(callback);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncMqttClient& AsyncMqttClient::onPublish(AsyncMqttClientInternals::OnPublishUserCallback callback) {
|
|
||||||
_onPublishUserCallbacks.push_back(callback);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncMqttClient::_freeCurrentParsedPacket() {
|
|
||||||
delete _currentParsedPacket;
|
|
||||||
_currentParsedPacket = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncMqttClient::_clear() {
|
|
||||||
_lastPingRequestTime = 0;
|
|
||||||
_connected = false;
|
|
||||||
_disconnectFlagged = false;
|
|
||||||
_connectPacketNotEnoughSpace = false;
|
|
||||||
_tlsBadFingerprint = false;
|
|
||||||
_freeCurrentParsedPacket();
|
|
||||||
|
|
||||||
_pendingPubRels.clear();
|
|
||||||
_pendingPubRels.shrink_to_fit();
|
|
||||||
|
|
||||||
_toSendAcks.clear();
|
|
||||||
_toSendAcks.shrink_to_fit();
|
|
||||||
|
|
||||||
_nextPacketId = 1;
|
|
||||||
_parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TCP */
|
|
||||||
void AsyncMqttClient::_onConnect(AsyncClient* client) {
|
|
||||||
(void)client;
|
|
||||||
|
|
||||||
#if ASYNC_TCP_SSL_ENABLED
|
|
||||||
if (_secure && _secureServerFingerprints.size() > 0) {
|
|
||||||
SSL* clientSsl = _client.getSSL();
|
|
||||||
|
|
||||||
bool sslFoundFingerprint = false;
|
|
||||||
for (std::array<uint8_t, SHA1_SIZE> fingerprint : _secureServerFingerprints) {
|
|
||||||
if (ssl_match_fingerprint(clientSsl, fingerprint.data()) == SSL_OK) {
|
|
||||||
sslFoundFingerprint = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sslFoundFingerprint) {
|
|
||||||
_tlsBadFingerprint = true;
|
|
||||||
_client.close(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
char fixedHeader[5];
|
|
||||||
fixedHeader[0] = AsyncMqttClientInternals::PacketType.CONNECT;
|
|
||||||
fixedHeader[0] = fixedHeader[0] << 4;
|
|
||||||
fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.CONNECT_RESERVED;
|
|
||||||
|
|
||||||
uint16_t protocolNameLength = 4;
|
|
||||||
char protocolNameLengthBytes[2];
|
|
||||||
protocolNameLengthBytes[0] = protocolNameLength >> 8;
|
|
||||||
protocolNameLengthBytes[1] = protocolNameLength & 0xFF;
|
|
||||||
|
|
||||||
char protocolLevel[1];
|
|
||||||
protocolLevel[0] = 0x04;
|
|
||||||
|
|
||||||
char connectFlags[1];
|
|
||||||
connectFlags[0] = 0;
|
|
||||||
if (_cleanSession) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.CLEAN_SESSION;
|
|
||||||
if (_username != nullptr) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.USERNAME;
|
|
||||||
if (_password != nullptr) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.PASSWORD;
|
|
||||||
if (_willTopic != nullptr) {
|
|
||||||
connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL;
|
|
||||||
if (_willRetain) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_RETAIN;
|
|
||||||
switch (_willQos) {
|
|
||||||
case 0:
|
|
||||||
connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_QOS0;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_QOS1;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_QOS2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char keepAliveBytes[2];
|
|
||||||
keepAliveBytes[0] = _keepAlive >> 8;
|
|
||||||
keepAliveBytes[1] = _keepAlive & 0xFF;
|
|
||||||
|
|
||||||
uint16_t clientIdLength = strlen(_clientId);
|
|
||||||
char clientIdLengthBytes[2];
|
|
||||||
clientIdLengthBytes[0] = clientIdLength >> 8;
|
|
||||||
clientIdLengthBytes[1] = clientIdLength & 0xFF;
|
|
||||||
|
|
||||||
// Optional fields
|
|
||||||
uint16_t willTopicLength = 0;
|
|
||||||
char willTopicLengthBytes[2];
|
|
||||||
uint16_t willPayloadLength = _willPayloadLength;
|
|
||||||
char willPayloadLengthBytes[2];
|
|
||||||
if (_willTopic != nullptr) {
|
|
||||||
willTopicLength = strlen(_willTopic);
|
|
||||||
willTopicLengthBytes[0] = willTopicLength >> 8;
|
|
||||||
willTopicLengthBytes[1] = willTopicLength & 0xFF;
|
|
||||||
|
|
||||||
if (_willPayload != nullptr && willPayloadLength == 0) willPayloadLength = strlen(_willPayload);
|
|
||||||
|
|
||||||
willPayloadLengthBytes[0] = willPayloadLength >> 8;
|
|
||||||
willPayloadLengthBytes[1] = willPayloadLength & 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t usernameLength = 0;
|
|
||||||
char usernameLengthBytes[2];
|
|
||||||
if (_username != nullptr) {
|
|
||||||
usernameLength = strlen(_username);
|
|
||||||
usernameLengthBytes[0] = usernameLength >> 8;
|
|
||||||
usernameLengthBytes[1] = usernameLength & 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t passwordLength = 0;
|
|
||||||
char passwordLengthBytes[2];
|
|
||||||
if (_password != nullptr) {
|
|
||||||
passwordLength = strlen(_password);
|
|
||||||
passwordLengthBytes[0] = passwordLength >> 8;
|
|
||||||
passwordLengthBytes[1] = passwordLength & 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t remainingLength = 2 + protocolNameLength + 1 + 1 + 2 + 2 + clientIdLength; // always present
|
|
||||||
if (_willTopic != nullptr) remainingLength += 2 + willTopicLength + 2 + willPayloadLength;
|
|
||||||
if (_username != nullptr) remainingLength += 2 + usernameLength;
|
|
||||||
if (_password != nullptr) remainingLength += 2 + passwordLength;
|
|
||||||
uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(remainingLength, fixedHeader + 1);
|
|
||||||
|
|
||||||
uint32_t neededSpace = 1 + remainingLengthLength;
|
|
||||||
neededSpace += 2;
|
|
||||||
neededSpace += protocolNameLength;
|
|
||||||
neededSpace += 1;
|
|
||||||
neededSpace += 1;
|
|
||||||
neededSpace += 2;
|
|
||||||
neededSpace += 2;
|
|
||||||
neededSpace += clientIdLength;
|
|
||||||
if (_willTopic != nullptr) {
|
|
||||||
neededSpace += 2;
|
|
||||||
neededSpace += willTopicLength;
|
|
||||||
|
|
||||||
neededSpace += 2;
|
|
||||||
if (_willPayload != nullptr) neededSpace += willPayloadLength;
|
|
||||||
}
|
|
||||||
if (_username != nullptr) {
|
|
||||||
neededSpace += 2;
|
|
||||||
neededSpace += usernameLength;
|
|
||||||
}
|
|
||||||
if (_password != nullptr) {
|
|
||||||
neededSpace += 2;
|
|
||||||
neededSpace += passwordLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
SEMAPHORE_TAKE();
|
|
||||||
if (_client.space() < neededSpace) {
|
|
||||||
_connectPacketNotEnoughSpace = true;
|
|
||||||
_client.close(true);
|
|
||||||
SEMAPHORE_GIVE();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_client.add(fixedHeader, 1 + remainingLengthLength);
|
|
||||||
_client.add(protocolNameLengthBytes, 2);
|
|
||||||
_client.add("MQTT", protocolNameLength);
|
|
||||||
_client.add(protocolLevel, 1);
|
|
||||||
_client.add(connectFlags, 1);
|
|
||||||
_client.add(keepAliveBytes, 2);
|
|
||||||
_client.add(clientIdLengthBytes, 2);
|
|
||||||
_client.add(_clientId, clientIdLength);
|
|
||||||
if (_willTopic != nullptr) {
|
|
||||||
_client.add(willTopicLengthBytes, 2);
|
|
||||||
_client.add(_willTopic, willTopicLength);
|
|
||||||
|
|
||||||
_client.add(willPayloadLengthBytes, 2);
|
|
||||||
if (_willPayload != nullptr) _client.add(_willPayload, willPayloadLength);
|
|
||||||
}
|
|
||||||
if (_username != nullptr) {
|
|
||||||
_client.add(usernameLengthBytes, 2);
|
|
||||||
_client.add(_username, usernameLength);
|
|
||||||
}
|
|
||||||
if (_password != nullptr) {
|
|
||||||
_client.add(passwordLengthBytes, 2);
|
|
||||||
_client.add(_password, passwordLength);
|
|
||||||
}
|
|
||||||
_client.send();
|
|
||||||
_lastClientActivity = millis();
|
|
||||||
SEMAPHORE_GIVE();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncMqttClient::_onDisconnect(AsyncClient* client) {
|
|
||||||
(void)client;
|
|
||||||
if (!_disconnectFlagged) {
|
|
||||||
AsyncMqttClientDisconnectReason reason;
|
|
||||||
|
|
||||||
if (_connectPacketNotEnoughSpace) {
|
|
||||||
reason = AsyncMqttClientDisconnectReason::ESP8266_NOT_ENOUGH_SPACE;
|
|
||||||
} else if (_tlsBadFingerprint) {
|
|
||||||
reason = AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT;
|
|
||||||
} else {
|
|
||||||
reason = AsyncMqttClientDisconnectReason::TCP_DISCONNECTED;
|
|
||||||
}
|
|
||||||
for (auto callback : _onDisconnectUserCallbacks) callback(reason);
|
|
||||||
}
|
|
||||||
_clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncMqttClient::_onError(AsyncClient* client, int8_t error) {
|
|
||||||
(void)client;
|
|
||||||
(void)error;
|
|
||||||
// _onDisconnect called anyway
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncMqttClient::_onTimeout(AsyncClient* client, uint32_t time) {
|
|
||||||
(void)client;
|
|
||||||
(void)time;
|
|
||||||
// disconnection will be handled by ping/pong management
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncMqttClient::_onAck(AsyncClient* client, size_t len, uint32_t time) {
|
|
||||||
(void)client;
|
|
||||||
(void)len;
|
|
||||||
(void)time;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncMqttClient::_onData(AsyncClient* client, char* data, size_t len) {
|
|
||||||
(void)client;
|
|
||||||
size_t currentBytePosition = 0;
|
|
||||||
char currentByte;
|
|
||||||
do {
|
|
||||||
switch (_parsingInformation.bufferState) {
|
|
||||||
case AsyncMqttClientInternals::BufferState::NONE:
|
|
||||||
currentByte = data[currentBytePosition++];
|
|
||||||
_parsingInformation.packetType = currentByte >> 4;
|
|
||||||
_parsingInformation.packetFlags = (currentByte << 4) >> 4;
|
|
||||||
_parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::REMAINING_LENGTH;
|
|
||||||
_lastServerActivity = millis();
|
|
||||||
switch (_parsingInformation.packetType) {
|
|
||||||
case AsyncMqttClientInternals::PacketType.CONNACK:
|
|
||||||
_currentParsedPacket = new AsyncMqttClientInternals::ConnAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onConnAck, this, std::placeholders::_1, std::placeholders::_2));
|
|
||||||
break;
|
|
||||||
case AsyncMqttClientInternals::PacketType.PINGRESP:
|
|
||||||
_currentParsedPacket = new AsyncMqttClientInternals::PingRespPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPingResp, this));
|
|
||||||
break;
|
|
||||||
case AsyncMqttClientInternals::PacketType.SUBACK:
|
|
||||||
_currentParsedPacket = new AsyncMqttClientInternals::SubAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onSubAck, this, std::placeholders::_1, std::placeholders::_2));
|
|
||||||
break;
|
|
||||||
case AsyncMqttClientInternals::PacketType.UNSUBACK:
|
|
||||||
_currentParsedPacket = new AsyncMqttClientInternals::UnsubAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onUnsubAck, this, std::placeholders::_1));
|
|
||||||
break;
|
|
||||||
case AsyncMqttClientInternals::PacketType.PUBLISH:
|
|
||||||
_currentParsedPacket = new AsyncMqttClientInternals::PublishPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6, std::placeholders::_7, std::placeholders::_8, std::placeholders::_9), std::bind(&AsyncMqttClient::_onPublish, this, std::placeholders::_1, std::placeholders::_2));
|
|
||||||
break;
|
|
||||||
case AsyncMqttClientInternals::PacketType.PUBREL:
|
|
||||||
_currentParsedPacket = new AsyncMqttClientInternals::PubRelPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubRel, this, std::placeholders::_1));
|
|
||||||
break;
|
|
||||||
case AsyncMqttClientInternals::PacketType.PUBACK:
|
|
||||||
_currentParsedPacket = new AsyncMqttClientInternals::PubAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubAck, this, std::placeholders::_1));
|
|
||||||
break;
|
|
||||||
case AsyncMqttClientInternals::PacketType.PUBREC:
|
|
||||||
_currentParsedPacket = new AsyncMqttClientInternals::PubRecPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubRec, this, std::placeholders::_1));
|
|
||||||
break;
|
|
||||||
case AsyncMqttClientInternals::PacketType.PUBCOMP:
|
|
||||||
_currentParsedPacket = new AsyncMqttClientInternals::PubCompPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubComp, this, std::placeholders::_1));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case AsyncMqttClientInternals::BufferState::REMAINING_LENGTH:
|
|
||||||
currentByte = data[currentBytePosition++];
|
|
||||||
_remainingLengthBuffer[_remainingLengthBufferPosition++] = currentByte;
|
|
||||||
if (currentByte >> 7 == 0) {
|
|
||||||
_parsingInformation.remainingLength = AsyncMqttClientInternals::Helpers::decodeRemainingLength(_remainingLengthBuffer);
|
|
||||||
_remainingLengthBufferPosition = 0;
|
|
||||||
if (_parsingInformation.remainingLength > 0) {
|
|
||||||
_parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::VARIABLE_HEADER;
|
|
||||||
} else {
|
|
||||||
// PINGRESP is a special case where it has no variable header, so the packet ends right here
|
|
||||||
_parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::NONE;
|
|
||||||
_onPingResp();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case AsyncMqttClientInternals::BufferState::VARIABLE_HEADER:
|
|
||||||
_currentParsedPacket->parseVariableHeader(data, len, ¤tBytePosition);
|
|
||||||
break;
|
|
||||||
case AsyncMqttClientInternals::BufferState::PAYLOAD:
|
|
||||||
_currentParsedPacket->parsePayload(data, len, ¤tBytePosition);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
currentBytePosition = len;
|
|
||||||
}
|
|
||||||
} while (currentBytePosition != len);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncMqttClient::_onPoll(AsyncClient* client) {
|
|
||||||
if (!_connected) return;
|
|
||||||
|
|
||||||
// if there is too much time the client has sent a ping request without a response, disconnect client to avoid half open connections
|
|
||||||
if (_lastPingRequestTime != 0 && (millis() - _lastPingRequestTime) >= (_keepAlive * 1000 * 2)) {
|
|
||||||
disconnect();
|
|
||||||
return;
|
|
||||||
// send ping to ensure the server will receive at least one message inside keepalive window
|
|
||||||
} else if (_lastPingRequestTime == 0 && (millis() - _lastClientActivity) >= (_keepAlive * 1000 * 0.7)) {
|
|
||||||
_sendPing();
|
|
||||||
|
|
||||||
// send ping to verify if the server is still there (ensure this is not a half connection)
|
|
||||||
} else if (_connected && _lastPingRequestTime == 0 && (millis() - _lastServerActivity) >= (_keepAlive * 1000 * 0.7)) {
|
|
||||||
_sendPing();
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle to send ack packets
|
|
||||||
|
|
||||||
_sendAcks();
|
|
||||||
|
|
||||||
// handle disconnect
|
|
||||||
|
|
||||||
if (_disconnectFlagged) {
|
|
||||||
_sendDisconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* MQTT */
|
|
||||||
void AsyncMqttClient::_onPingResp() {
|
|
||||||
_freeCurrentParsedPacket();
|
|
||||||
_lastPingRequestTime = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncMqttClient::_onConnAck(bool sessionPresent, uint8_t connectReturnCode) {
|
|
||||||
(void)sessionPresent;
|
|
||||||
_freeCurrentParsedPacket();
|
|
||||||
|
|
||||||
if (connectReturnCode == 0) {
|
|
||||||
_connected = true;
|
|
||||||
for (auto callback : _onConnectUserCallbacks) callback(sessionPresent);
|
|
||||||
} else {
|
|
||||||
for (auto callback : _onDisconnectUserCallbacks) callback(static_cast<AsyncMqttClientDisconnectReason>(connectReturnCode));
|
|
||||||
_disconnectFlagged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncMqttClient::_onSubAck(uint16_t packetId, char status) {
|
|
||||||
_freeCurrentParsedPacket();
|
|
||||||
|
|
||||||
for (auto callback : _onSubscribeUserCallbacks) callback(packetId, status);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncMqttClient::_onUnsubAck(uint16_t packetId) {
|
|
||||||
_freeCurrentParsedPacket();
|
|
||||||
|
|
||||||
for (auto callback : _onUnsubscribeUserCallbacks) callback(packetId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncMqttClient::_onMessage(char* topic, char* payload, uint8_t qos, bool dup, bool retain, size_t len, size_t index, size_t total, uint16_t packetId) {
|
|
||||||
bool notifyPublish = true;
|
|
||||||
|
|
||||||
if (qos == 2) {
|
|
||||||
for (AsyncMqttClientInternals::PendingPubRel pendingPubRel : _pendingPubRels) {
|
|
||||||
if (pendingPubRel.packetId == packetId) {
|
|
||||||
notifyPublish = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notifyPublish) {
|
|
||||||
AsyncMqttClientMessageProperties properties;
|
|
||||||
properties.qos = qos;
|
|
||||||
properties.dup = dup;
|
|
||||||
properties.retain = retain;
|
|
||||||
|
|
||||||
for (auto callback : _onMessageUserCallbacks) callback(topic, payload, properties, len, index, total);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncMqttClient::_onPublish(uint16_t packetId, uint8_t qos) {
|
|
||||||
AsyncMqttClientInternals::PendingAck pendingAck;
|
|
||||||
|
|
||||||
if (qos == 1) {
|
|
||||||
pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBACK;
|
|
||||||
pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBACK_RESERVED;
|
|
||||||
pendingAck.packetId = packetId;
|
|
||||||
_toSendAcks.push_back(pendingAck);
|
|
||||||
} else if (qos == 2) {
|
|
||||||
pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBREC;
|
|
||||||
pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBREC_RESERVED;
|
|
||||||
pendingAck.packetId = packetId;
|
|
||||||
_toSendAcks.push_back(pendingAck);
|
|
||||||
|
|
||||||
bool pubRelAwaiting = false;
|
|
||||||
for (AsyncMqttClientInternals::PendingPubRel pendingPubRel : _pendingPubRels) {
|
|
||||||
if (pendingPubRel.packetId == packetId) {
|
|
||||||
pubRelAwaiting = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pubRelAwaiting) {
|
|
||||||
AsyncMqttClientInternals::PendingPubRel pendingPubRel;
|
|
||||||
pendingPubRel.packetId = packetId;
|
|
||||||
_pendingPubRels.push_back(pendingPubRel);
|
|
||||||
}
|
|
||||||
|
|
||||||
_sendAcks();
|
|
||||||
}
|
|
||||||
|
|
||||||
_freeCurrentParsedPacket();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncMqttClient::_onPubRel(uint16_t packetId) {
|
|
||||||
_freeCurrentParsedPacket();
|
|
||||||
|
|
||||||
AsyncMqttClientInternals::PendingAck pendingAck;
|
|
||||||
pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBCOMP;
|
|
||||||
pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBCOMP_RESERVED;
|
|
||||||
pendingAck.packetId = packetId;
|
|
||||||
_toSendAcks.push_back(pendingAck);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < _pendingPubRels.size(); i++) {
|
|
||||||
if (_pendingPubRels[i].packetId == packetId) {
|
|
||||||
_pendingPubRels.erase(_pendingPubRels.begin() + i);
|
|
||||||
_pendingPubRels.shrink_to_fit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_sendAcks();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncMqttClient::_onPubAck(uint16_t packetId) {
|
|
||||||
_freeCurrentParsedPacket();
|
|
||||||
|
|
||||||
for (auto callback : _onPublishUserCallbacks) callback(packetId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncMqttClient::_onPubRec(uint16_t packetId) {
|
|
||||||
_freeCurrentParsedPacket();
|
|
||||||
|
|
||||||
AsyncMqttClientInternals::PendingAck pendingAck;
|
|
||||||
pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBREL;
|
|
||||||
pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBREL_RESERVED;
|
|
||||||
pendingAck.packetId = packetId;
|
|
||||||
_toSendAcks.push_back(pendingAck);
|
|
||||||
|
|
||||||
_sendAcks();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncMqttClient::_onPubComp(uint16_t packetId) {
|
|
||||||
_freeCurrentParsedPacket();
|
|
||||||
|
|
||||||
for (auto callback : _onPublishUserCallbacks) callback(packetId);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AsyncMqttClient::_sendPing() {
|
|
||||||
char fixedHeader[2];
|
|
||||||
fixedHeader[0] = AsyncMqttClientInternals::PacketType.PINGREQ;
|
|
||||||
fixedHeader[0] = fixedHeader[0] << 4;
|
|
||||||
fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.PINGREQ_RESERVED;
|
|
||||||
fixedHeader[1] = 0;
|
|
||||||
|
|
||||||
size_t neededSpace = 2;
|
|
||||||
|
|
||||||
SEMAPHORE_TAKE(false);
|
|
||||||
if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return false; }
|
|
||||||
|
|
||||||
_client.add(fixedHeader, 2);
|
|
||||||
_client.send();
|
|
||||||
_lastClientActivity = millis();
|
|
||||||
_lastPingRequestTime = millis();
|
|
||||||
|
|
||||||
SEMAPHORE_GIVE();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncMqttClient::_sendAcks() {
|
|
||||||
uint8_t neededAckSpace = 2 + 2;
|
|
||||||
|
|
||||||
SEMAPHORE_TAKE();
|
|
||||||
for (size_t i = 0; i < _toSendAcks.size(); i++) {
|
|
||||||
if (_client.space() < neededAckSpace) break;
|
|
||||||
|
|
||||||
AsyncMqttClientInternals::PendingAck pendingAck = _toSendAcks[i];
|
|
||||||
|
|
||||||
char fixedHeader[2];
|
|
||||||
fixedHeader[0] = pendingAck.packetType;
|
|
||||||
fixedHeader[0] = fixedHeader[0] << 4;
|
|
||||||
fixedHeader[0] = fixedHeader[0] | pendingAck.headerFlag;
|
|
||||||
fixedHeader[1] = 2;
|
|
||||||
|
|
||||||
char packetIdBytes[2];
|
|
||||||
packetIdBytes[0] = pendingAck.packetId >> 8;
|
|
||||||
packetIdBytes[1] = pendingAck.packetId & 0xFF;
|
|
||||||
|
|
||||||
_client.add(fixedHeader, 2);
|
|
||||||
_client.add(packetIdBytes, 2);
|
|
||||||
_client.send();
|
|
||||||
|
|
||||||
_toSendAcks.erase(_toSendAcks.begin() + i);
|
|
||||||
_toSendAcks.shrink_to_fit();
|
|
||||||
|
|
||||||
_lastClientActivity = millis();
|
|
||||||
}
|
|
||||||
SEMAPHORE_GIVE();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AsyncMqttClient::_sendDisconnect() {
|
|
||||||
if (!_connected) return true;
|
|
||||||
|
|
||||||
const uint8_t neededSpace = 2;
|
|
||||||
|
|
||||||
SEMAPHORE_TAKE(false);
|
|
||||||
|
|
||||||
if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return false; }
|
|
||||||
|
|
||||||
char fixedHeader[2];
|
|
||||||
fixedHeader[0] = AsyncMqttClientInternals::PacketType.DISCONNECT;
|
|
||||||
fixedHeader[0] = fixedHeader[0] << 4;
|
|
||||||
fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.DISCONNECT_RESERVED;
|
|
||||||
fixedHeader[1] = 0;
|
|
||||||
|
|
||||||
_client.add(fixedHeader, 2);
|
|
||||||
_client.send();
|
|
||||||
_client.close(true);
|
|
||||||
|
|
||||||
_disconnectFlagged = false;
|
|
||||||
|
|
||||||
SEMAPHORE_GIVE();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t AsyncMqttClient::_getNextPacketId() {
|
|
||||||
uint16_t nextPacketId = _nextPacketId;
|
|
||||||
|
|
||||||
if (_nextPacketId == 65535) _nextPacketId = 0; // 0 is forbidden
|
|
||||||
_nextPacketId++;
|
|
||||||
|
|
||||||
return nextPacketId;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AsyncMqttClient::connected() const {
|
|
||||||
return _connected;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncMqttClient::connect() {
|
|
||||||
if (_connected) return;
|
|
||||||
|
|
||||||
#if ASYNC_TCP_SSL_ENABLED
|
|
||||||
if (_useIp) {
|
|
||||||
_client.connect(_ip, _port, _secure);
|
|
||||||
} else {
|
|
||||||
_client.connect(_host, _port, _secure);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (_useIp) {
|
|
||||||
_client.connect(_ip, _port);
|
|
||||||
} else {
|
|
||||||
_client.connect(_host, _port);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncMqttClient::disconnect(bool force) {
|
|
||||||
if (!_connected) return;
|
|
||||||
|
|
||||||
if (force) {
|
|
||||||
_client.close(true);
|
|
||||||
} else {
|
|
||||||
_disconnectFlagged = true;
|
|
||||||
_sendDisconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t AsyncMqttClient::subscribe(const char* topic, uint8_t qos) {
|
|
||||||
if (!_connected) return 0;
|
|
||||||
|
|
||||||
char fixedHeader[5];
|
|
||||||
fixedHeader[0] = AsyncMqttClientInternals::PacketType.SUBSCRIBE;
|
|
||||||
fixedHeader[0] = fixedHeader[0] << 4;
|
|
||||||
fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.SUBSCRIBE_RESERVED;
|
|
||||||
|
|
||||||
uint16_t topicLength = strlen(topic);
|
|
||||||
char topicLengthBytes[2];
|
|
||||||
topicLengthBytes[0] = topicLength >> 8;
|
|
||||||
topicLengthBytes[1] = topicLength & 0xFF;
|
|
||||||
|
|
||||||
char qosByte[1];
|
|
||||||
qosByte[0] = qos;
|
|
||||||
|
|
||||||
uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(2 + 2 + topicLength + 1, fixedHeader + 1);
|
|
||||||
|
|
||||||
size_t neededSpace = 0;
|
|
||||||
neededSpace += 1 + remainingLengthLength;
|
|
||||||
neededSpace += 2;
|
|
||||||
neededSpace += 2;
|
|
||||||
neededSpace += topicLength;
|
|
||||||
neededSpace += 1;
|
|
||||||
|
|
||||||
SEMAPHORE_TAKE(0);
|
|
||||||
if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return 0; }
|
|
||||||
|
|
||||||
uint16_t packetId = _getNextPacketId();
|
|
||||||
char packetIdBytes[2];
|
|
||||||
packetIdBytes[0] = packetId >> 8;
|
|
||||||
packetIdBytes[1] = packetId & 0xFF;
|
|
||||||
|
|
||||||
_client.add(fixedHeader, 1 + remainingLengthLength);
|
|
||||||
_client.add(packetIdBytes, 2);
|
|
||||||
_client.add(topicLengthBytes, 2);
|
|
||||||
_client.add(topic, topicLength);
|
|
||||||
_client.add(qosByte, 1);
|
|
||||||
_client.send();
|
|
||||||
_lastClientActivity = millis();
|
|
||||||
|
|
||||||
SEMAPHORE_GIVE();
|
|
||||||
return packetId;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t AsyncMqttClient::unsubscribe(const char* topic) {
|
|
||||||
if (!_connected) return 0;
|
|
||||||
|
|
||||||
char fixedHeader[5];
|
|
||||||
fixedHeader[0] = AsyncMqttClientInternals::PacketType.UNSUBSCRIBE;
|
|
||||||
fixedHeader[0] = fixedHeader[0] << 4;
|
|
||||||
fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.UNSUBSCRIBE_RESERVED;
|
|
||||||
|
|
||||||
uint16_t topicLength = strlen(topic);
|
|
||||||
char topicLengthBytes[2];
|
|
||||||
topicLengthBytes[0] = topicLength >> 8;
|
|
||||||
topicLengthBytes[1] = topicLength & 0xFF;
|
|
||||||
|
|
||||||
uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(2 + 2 + topicLength, fixedHeader + 1);
|
|
||||||
|
|
||||||
size_t neededSpace = 0;
|
|
||||||
neededSpace += 1 + remainingLengthLength;
|
|
||||||
neededSpace += 2;
|
|
||||||
neededSpace += 2;
|
|
||||||
neededSpace += topicLength;
|
|
||||||
|
|
||||||
SEMAPHORE_TAKE(0);
|
|
||||||
if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return 0; }
|
|
||||||
|
|
||||||
uint16_t packetId = _getNextPacketId();
|
|
||||||
char packetIdBytes[2];
|
|
||||||
packetIdBytes[0] = packetId >> 8;
|
|
||||||
packetIdBytes[1] = packetId & 0xFF;
|
|
||||||
|
|
||||||
_client.add(fixedHeader, 1 + remainingLengthLength);
|
|
||||||
_client.add(packetIdBytes, 2);
|
|
||||||
_client.add(topicLengthBytes, 2);
|
|
||||||
_client.add(topic, topicLength);
|
|
||||||
_client.send();
|
|
||||||
_lastClientActivity = millis();
|
|
||||||
|
|
||||||
SEMAPHORE_GIVE();
|
|
||||||
return packetId;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t AsyncMqttClient::publish(const char* topic, uint8_t qos, bool retain, const char* payload, size_t length, bool dup, uint16_t message_id) {
|
|
||||||
if (!_connected) return 0;
|
|
||||||
|
|
||||||
char fixedHeader[5];
|
|
||||||
fixedHeader[0] = AsyncMqttClientInternals::PacketType.PUBLISH;
|
|
||||||
fixedHeader[0] = fixedHeader[0] << 4;
|
|
||||||
if (dup) fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_DUP;
|
|
||||||
if (retain) fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_RETAIN;
|
|
||||||
switch (qos) {
|
|
||||||
case 0:
|
|
||||||
fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_QOS0;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_QOS1;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_QOS2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t topicLength = strlen(topic);
|
|
||||||
char topicLengthBytes[2];
|
|
||||||
topicLengthBytes[0] = topicLength >> 8;
|
|
||||||
topicLengthBytes[1] = topicLength & 0xFF;
|
|
||||||
|
|
||||||
uint32_t payloadLength = length;
|
|
||||||
if (payload != nullptr && payloadLength == 0) payloadLength = strlen(payload);
|
|
||||||
|
|
||||||
uint32_t remainingLength = 2 + topicLength + payloadLength;
|
|
||||||
if (qos != 0) remainingLength += 2;
|
|
||||||
uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(remainingLength, fixedHeader + 1);
|
|
||||||
|
|
||||||
size_t neededSpace = 0;
|
|
||||||
neededSpace += 1 + remainingLengthLength;
|
|
||||||
neededSpace += 2;
|
|
||||||
neededSpace += topicLength;
|
|
||||||
if (qos != 0) neededSpace += 2;
|
|
||||||
if (payload != nullptr) neededSpace += payloadLength;
|
|
||||||
|
|
||||||
SEMAPHORE_TAKE(0);
|
|
||||||
if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return 0; }
|
|
||||||
|
|
||||||
uint16_t packetId = 0;
|
|
||||||
char packetIdBytes[2];
|
|
||||||
if (qos != 0) {
|
|
||||||
if (dup && message_id > 0) {
|
|
||||||
packetId = message_id;
|
|
||||||
} else {
|
|
||||||
packetId = _getNextPacketId();
|
|
||||||
}
|
|
||||||
|
|
||||||
packetIdBytes[0] = packetId >> 8;
|
|
||||||
packetIdBytes[1] = packetId & 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
_client.add(fixedHeader, 1 + remainingLengthLength);
|
|
||||||
_client.add(topicLengthBytes, 2);
|
|
||||||
_client.add(topic, topicLength);
|
|
||||||
if (qos != 0) _client.add(packetIdBytes, 2);
|
|
||||||
if (payload != nullptr) _client.add(payload, payloadLength);
|
|
||||||
_client.send();
|
|
||||||
_lastClientActivity = millis();
|
|
||||||
|
|
||||||
SEMAPHORE_GIVE();
|
|
||||||
if (qos != 0) {
|
|
||||||
return packetId;
|
|
||||||
} else {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#ifndef SRC_ASYNCMQTTCLIENT_H_
|
|
||||||
#define SRC_ASYNCMQTTCLIENT_H_
|
|
||||||
|
|
||||||
#include "AsyncMqttClient.hpp"
|
|
||||||
|
|
||||||
#endif // SRC_ASYNCMQTTCLIENT_H_
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "Arduino.h"
|
|
||||||
|
|
||||||
#ifdef ESP32
|
|
||||||
#include <AsyncTCP.h>
|
|
||||||
#include <freertos/semphr.h>
|
|
||||||
#elif defined(ESP8266)
|
|
||||||
#include <ESPAsyncTCP.h>
|
|
||||||
#else
|
|
||||||
#error Platform not supported
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if ASYNC_TCP_SSL_ENABLED
|
|
||||||
#include <tcp_axtls.h>
|
|
||||||
#define SHA1_SIZE 20
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "AsyncMqttClient/Flags.hpp"
|
|
||||||
#include "AsyncMqttClient/ParsingInformation.hpp"
|
|
||||||
#include "AsyncMqttClient/MessageProperties.hpp"
|
|
||||||
#include "AsyncMqttClient/Helpers.hpp"
|
|
||||||
#include "AsyncMqttClient/Callbacks.hpp"
|
|
||||||
#include "AsyncMqttClient/DisconnectReasons.hpp"
|
|
||||||
#include "AsyncMqttClient/Storage.hpp"
|
|
||||||
|
|
||||||
#include "AsyncMqttClient/Packets/Packet.hpp"
|
|
||||||
#include "AsyncMqttClient/Packets/ConnAckPacket.hpp"
|
|
||||||
#include "AsyncMqttClient/Packets/PingRespPacket.hpp"
|
|
||||||
#include "AsyncMqttClient/Packets/SubAckPacket.hpp"
|
|
||||||
#include "AsyncMqttClient/Packets/UnsubAckPacket.hpp"
|
|
||||||
#include "AsyncMqttClient/Packets/PublishPacket.hpp"
|
|
||||||
#include "AsyncMqttClient/Packets/PubRelPacket.hpp"
|
|
||||||
#include "AsyncMqttClient/Packets/PubAckPacket.hpp"
|
|
||||||
#include "AsyncMqttClient/Packets/PubRecPacket.hpp"
|
|
||||||
#include "AsyncMqttClient/Packets/PubCompPacket.hpp"
|
|
||||||
|
|
||||||
#if ESP32
|
|
||||||
#define SEMAPHORE_TAKE(X) if (xSemaphoreTake(_xSemaphore, 1000 / portTICK_PERIOD_MS) != pdTRUE) { return X; } // Waits max 1000ms
|
|
||||||
#define SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore);
|
|
||||||
#elif defined(ESP8266)
|
|
||||||
#define SEMAPHORE_TAKE(X) void()
|
|
||||||
#define SEMAPHORE_GIVE() void()
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class AsyncMqttClient {
|
|
||||||
public:
|
|
||||||
AsyncMqttClient();
|
|
||||||
~AsyncMqttClient();
|
|
||||||
|
|
||||||
AsyncMqttClient& setKeepAlive(uint16_t keepAlive);
|
|
||||||
AsyncMqttClient& setClientId(const char* clientId);
|
|
||||||
AsyncMqttClient& setCleanSession(bool cleanSession);
|
|
||||||
AsyncMqttClient& setMaxTopicLength(uint16_t maxTopicLength);
|
|
||||||
AsyncMqttClient& setCredentials(const char* username, const char* password = nullptr);
|
|
||||||
AsyncMqttClient& setWill(const char* topic, uint8_t qos, bool retain, const char* payload = nullptr, size_t length = 0);
|
|
||||||
AsyncMqttClient& setServer(IPAddress ip, uint16_t port);
|
|
||||||
AsyncMqttClient& setServer(const char* host, uint16_t port);
|
|
||||||
#if ASYNC_TCP_SSL_ENABLED
|
|
||||||
AsyncMqttClient& setSecure(bool secure);
|
|
||||||
AsyncMqttClient& addServerFingerprint(const uint8_t* fingerprint);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
AsyncMqttClient& onConnect(AsyncMqttClientInternals::OnConnectUserCallback callback);
|
|
||||||
AsyncMqttClient& onDisconnect(AsyncMqttClientInternals::OnDisconnectUserCallback callback);
|
|
||||||
AsyncMqttClient& onSubscribe(AsyncMqttClientInternals::OnSubscribeUserCallback callback);
|
|
||||||
AsyncMqttClient& onUnsubscribe(AsyncMqttClientInternals::OnUnsubscribeUserCallback callback);
|
|
||||||
AsyncMqttClient& onMessage(AsyncMqttClientInternals::OnMessageUserCallback callback);
|
|
||||||
AsyncMqttClient& onPublish(AsyncMqttClientInternals::OnPublishUserCallback callback);
|
|
||||||
|
|
||||||
bool connected() const;
|
|
||||||
void connect();
|
|
||||||
void disconnect(bool force = false);
|
|
||||||
uint16_t subscribe(const char* topic, uint8_t qos);
|
|
||||||
uint16_t unsubscribe(const char* topic);
|
|
||||||
uint16_t publish(const char* topic, uint8_t qos, bool retain, const char* payload = nullptr, size_t length = 0, bool dup = false, uint16_t message_id = 0);
|
|
||||||
|
|
||||||
private:
|
|
||||||
AsyncClient _client;
|
|
||||||
|
|
||||||
bool _connected;
|
|
||||||
bool _connectPacketNotEnoughSpace;
|
|
||||||
bool _disconnectFlagged;
|
|
||||||
bool _tlsBadFingerprint;
|
|
||||||
uint32_t _lastClientActivity;
|
|
||||||
uint32_t _lastServerActivity;
|
|
||||||
uint32_t _lastPingRequestTime;
|
|
||||||
|
|
||||||
char _generatedClientId[13 + 1]; // esp8266abc123
|
|
||||||
IPAddress _ip;
|
|
||||||
const char* _host;
|
|
||||||
bool _useIp;
|
|
||||||
#if ASYNC_TCP_SSL_ENABLED
|
|
||||||
bool _secure;
|
|
||||||
#endif
|
|
||||||
uint16_t _port;
|
|
||||||
uint16_t _keepAlive;
|
|
||||||
bool _cleanSession;
|
|
||||||
const char* _clientId;
|
|
||||||
const char* _username;
|
|
||||||
const char* _password;
|
|
||||||
const char* _willTopic;
|
|
||||||
const char* _willPayload;
|
|
||||||
uint16_t _willPayloadLength;
|
|
||||||
uint8_t _willQos;
|
|
||||||
bool _willRetain;
|
|
||||||
|
|
||||||
#if ASYNC_TCP_SSL_ENABLED
|
|
||||||
std::vector<std::array<uint8_t, SHA1_SIZE>> _secureServerFingerprints;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
std::vector<AsyncMqttClientInternals::OnConnectUserCallback> _onConnectUserCallbacks;
|
|
||||||
std::vector<AsyncMqttClientInternals::OnDisconnectUserCallback> _onDisconnectUserCallbacks;
|
|
||||||
std::vector<AsyncMqttClientInternals::OnSubscribeUserCallback> _onSubscribeUserCallbacks;
|
|
||||||
std::vector<AsyncMqttClientInternals::OnUnsubscribeUserCallback> _onUnsubscribeUserCallbacks;
|
|
||||||
std::vector<AsyncMqttClientInternals::OnMessageUserCallback> _onMessageUserCallbacks;
|
|
||||||
std::vector<AsyncMqttClientInternals::OnPublishUserCallback> _onPublishUserCallbacks;
|
|
||||||
|
|
||||||
AsyncMqttClientInternals::ParsingInformation _parsingInformation;
|
|
||||||
AsyncMqttClientInternals::Packet* _currentParsedPacket;
|
|
||||||
uint8_t _remainingLengthBufferPosition;
|
|
||||||
char _remainingLengthBuffer[4];
|
|
||||||
|
|
||||||
uint16_t _nextPacketId;
|
|
||||||
|
|
||||||
std::vector<AsyncMqttClientInternals::PendingPubRel> _pendingPubRels;
|
|
||||||
|
|
||||||
std::vector<AsyncMqttClientInternals::PendingAck> _toSendAcks;
|
|
||||||
|
|
||||||
#ifdef ESP32
|
|
||||||
SemaphoreHandle_t _xSemaphore = nullptr;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void _clear();
|
|
||||||
void _freeCurrentParsedPacket();
|
|
||||||
|
|
||||||
// TCP
|
|
||||||
void _onConnect(AsyncClient* client);
|
|
||||||
void _onDisconnect(AsyncClient* client);
|
|
||||||
static void _onError(AsyncClient* client, int8_t error);
|
|
||||||
void _onTimeout(AsyncClient* client, uint32_t time);
|
|
||||||
static void _onAck(AsyncClient* client, size_t len, uint32_t time);
|
|
||||||
void _onData(AsyncClient* client, char* data, size_t len);
|
|
||||||
void _onPoll(AsyncClient* client);
|
|
||||||
|
|
||||||
// MQTT
|
|
||||||
void _onPingResp();
|
|
||||||
void _onConnAck(bool sessionPresent, uint8_t connectReturnCode);
|
|
||||||
void _onSubAck(uint16_t packetId, char status);
|
|
||||||
void _onUnsubAck(uint16_t packetId);
|
|
||||||
void _onMessage(char* topic, char* payload, uint8_t qos, bool dup, bool retain, size_t len, size_t index, size_t total, uint16_t packetId);
|
|
||||||
void _onPublish(uint16_t packetId, uint8_t qos);
|
|
||||||
void _onPubRel(uint16_t packetId);
|
|
||||||
void _onPubAck(uint16_t packetId);
|
|
||||||
void _onPubRec(uint16_t packetId);
|
|
||||||
void _onPubComp(uint16_t packetId);
|
|
||||||
|
|
||||||
bool _sendPing();
|
|
||||||
void _sendAcks();
|
|
||||||
bool _sendDisconnect();
|
|
||||||
|
|
||||||
uint16_t _getNextPacketId();
|
|
||||||
};
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
#include "DisconnectReasons.hpp"
|
|
||||||
#include "MessageProperties.hpp"
|
|
||||||
|
|
||||||
namespace AsyncMqttClientInternals {
|
|
||||||
// user callbacks
|
|
||||||
typedef std::function<void(bool sessionPresent)> OnConnectUserCallback;
|
|
||||||
typedef std::function<void(AsyncMqttClientDisconnectReason reason)> OnDisconnectUserCallback;
|
|
||||||
typedef std::function<void(uint16_t packetId, uint8_t qos)> OnSubscribeUserCallback;
|
|
||||||
typedef std::function<void(uint16_t packetId)> OnUnsubscribeUserCallback;
|
|
||||||
typedef std::function<void(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)> OnMessageUserCallback;
|
|
||||||
typedef std::function<void(uint16_t packetId)> OnPublishUserCallback;
|
|
||||||
|
|
||||||
// internal callbacks
|
|
||||||
typedef std::function<void(bool sessionPresent, uint8_t connectReturnCode)> OnConnAckInternalCallback;
|
|
||||||
typedef std::function<void()> OnPingRespInternalCallback;
|
|
||||||
typedef std::function<void(uint16_t packetId, char status)> OnSubAckInternalCallback;
|
|
||||||
typedef std::function<void(uint16_t packetId)> OnUnsubAckInternalCallback;
|
|
||||||
typedef std::function<void(char* topic, char* payload, uint8_t qos, bool dup, bool retain, size_t len, size_t index, size_t total, uint16_t packetId)> OnMessageInternalCallback;
|
|
||||||
typedef std::function<void(uint16_t packetId, uint8_t qos)> OnPublishInternalCallback;
|
|
||||||
typedef std::function<void(uint16_t packetId)> OnPubRelInternalCallback;
|
|
||||||
typedef std::function<void(uint16_t packetId)> OnPubAckInternalCallback;
|
|
||||||
typedef std::function<void(uint16_t packetId)> OnPubRecInternalCallback;
|
|
||||||
typedef std::function<void(uint16_t packetId)> OnPubCompInternalCallback;
|
|
||||||
} // namespace AsyncMqttClientInternals
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
enum class AsyncMqttClientDisconnectReason : int8_t {
|
|
||||||
TCP_DISCONNECTED = 0,
|
|
||||||
|
|
||||||
MQTT_UNACCEPTABLE_PROTOCOL_VERSION = 1,
|
|
||||||
MQTT_IDENTIFIER_REJECTED = 2,
|
|
||||||
MQTT_SERVER_UNAVAILABLE = 3,
|
|
||||||
MQTT_MALFORMED_CREDENTIALS = 4,
|
|
||||||
MQTT_NOT_AUTHORIZED = 5,
|
|
||||||
|
|
||||||
ESP8266_NOT_ENOUGH_SPACE = 6,
|
|
||||||
|
|
||||||
TLS_BAD_FINGERPRINT = 7
|
|
||||||
};
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace AsyncMqttClientInternals {
|
|
||||||
constexpr struct {
|
|
||||||
const uint8_t RESERVED = 0;
|
|
||||||
const uint8_t CONNECT = 1;
|
|
||||||
const uint8_t CONNACK = 2;
|
|
||||||
const uint8_t PUBLISH = 3;
|
|
||||||
const uint8_t PUBACK = 4;
|
|
||||||
const uint8_t PUBREC = 5;
|
|
||||||
const uint8_t PUBREL = 6;
|
|
||||||
const uint8_t PUBCOMP = 7;
|
|
||||||
const uint8_t SUBSCRIBE = 8;
|
|
||||||
const uint8_t SUBACK = 9;
|
|
||||||
const uint8_t UNSUBSCRIBE = 10;
|
|
||||||
const uint8_t UNSUBACK = 11;
|
|
||||||
const uint8_t PINGREQ = 12;
|
|
||||||
const uint8_t PINGRESP = 13;
|
|
||||||
const uint8_t DISCONNECT = 14;
|
|
||||||
const uint8_t RESERVED2 = 1;
|
|
||||||
} PacketType;
|
|
||||||
|
|
||||||
constexpr struct {
|
|
||||||
const uint8_t CONNECT_RESERVED = 0x00;
|
|
||||||
const uint8_t CONNACK_RESERVED = 0x00;
|
|
||||||
const uint8_t PUBLISH_DUP = 0x08;
|
|
||||||
const uint8_t PUBLISH_QOS0 = 0x00;
|
|
||||||
const uint8_t PUBLISH_QOS1 = 0x02;
|
|
||||||
const uint8_t PUBLISH_QOS2 = 0x04;
|
|
||||||
const uint8_t PUBLISH_QOSRESERVED = 0x06;
|
|
||||||
const uint8_t PUBLISH_RETAIN = 0x01;
|
|
||||||
const uint8_t PUBACK_RESERVED = 0x00;
|
|
||||||
const uint8_t PUBREC_RESERVED = 0x00;
|
|
||||||
const uint8_t PUBREL_RESERVED = 0x02;
|
|
||||||
const uint8_t PUBCOMP_RESERVED = 0x00;
|
|
||||||
const uint8_t SUBSCRIBE_RESERVED = 0x02;
|
|
||||||
const uint8_t SUBACK_RESERVED = 0x00;
|
|
||||||
const uint8_t UNSUBSCRIBE_RESERVED = 0x02;
|
|
||||||
const uint8_t UNSUBACK_RESERVED = 0x00;
|
|
||||||
const uint8_t PINGREQ_RESERVED = 0x00;
|
|
||||||
const uint8_t PINGRESP_RESERVED = 0x00;
|
|
||||||
const uint8_t DISCONNECT_RESERVED = 0x00;
|
|
||||||
const uint8_t RESERVED2_RESERVED = 0x00;
|
|
||||||
} HeaderFlag;
|
|
||||||
|
|
||||||
constexpr struct {
|
|
||||||
const uint8_t USERNAME = 0x80;
|
|
||||||
const uint8_t PASSWORD = 0x40;
|
|
||||||
const uint8_t WILL_RETAIN = 0x20;
|
|
||||||
const uint8_t WILL_QOS0 = 0x00;
|
|
||||||
const uint8_t WILL_QOS1 = 0x08;
|
|
||||||
const uint8_t WILL_QOS2 = 0x10;
|
|
||||||
const uint8_t WILL = 0x04;
|
|
||||||
const uint8_t CLEAN_SESSION = 0x02;
|
|
||||||
const uint8_t RESERVED = 0x00;
|
|
||||||
} ConnectFlag;
|
|
||||||
} // namespace AsyncMqttClientInternals
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace AsyncMqttClientInternals {
|
|
||||||
class Helpers {
|
|
||||||
public:
|
|
||||||
static uint32_t decodeRemainingLength(char* bytes) {
|
|
||||||
uint32_t multiplier = 1;
|
|
||||||
uint32_t value = 0;
|
|
||||||
uint8_t currentByte = 0;
|
|
||||||
uint8_t encodedByte;
|
|
||||||
do {
|
|
||||||
encodedByte = bytes[currentByte++];
|
|
||||||
value += (encodedByte & 127) * multiplier;
|
|
||||||
multiplier *= 128;
|
|
||||||
} while ((encodedByte & 128) != 0);
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint8_t encodeRemainingLength(uint32_t remainingLength, char* destination) {
|
|
||||||
uint8_t currentByte = 0;
|
|
||||||
uint8_t bytesNeeded = 0;
|
|
||||||
|
|
||||||
do {
|
|
||||||
uint8_t encodedByte = remainingLength % 128;
|
|
||||||
remainingLength /= 128;
|
|
||||||
if (remainingLength > 0) {
|
|
||||||
encodedByte = encodedByte | 128;
|
|
||||||
}
|
|
||||||
|
|
||||||
destination[currentByte++] = encodedByte;
|
|
||||||
bytesNeeded++;
|
|
||||||
} while (remainingLength > 0);
|
|
||||||
|
|
||||||
return bytesNeeded;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace AsyncMqttClientInternals
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
struct AsyncMqttClientMessageProperties {
|
|
||||||
uint8_t qos;
|
|
||||||
bool dup;
|
|
||||||
bool retain;
|
|
||||||
};
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#include "ConnAckPacket.hpp"
|
|
||||||
|
|
||||||
using AsyncMqttClientInternals::ConnAckPacket;
|
|
||||||
|
|
||||||
ConnAckPacket::ConnAckPacket(ParsingInformation* parsingInformation, OnConnAckInternalCallback callback)
|
|
||||||
: _parsingInformation(parsingInformation)
|
|
||||||
, _callback(callback)
|
|
||||||
, _bytePosition(0)
|
|
||||||
, _sessionPresent(false)
|
|
||||||
, _connectReturnCode(0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnAckPacket::~ConnAckPacket() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConnAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
|
|
||||||
char currentByte = data[(*currentBytePosition)++];
|
|
||||||
if (_bytePosition++ == 0) {
|
|
||||||
_sessionPresent = (currentByte << 7) >> 7;
|
|
||||||
} else {
|
|
||||||
_connectReturnCode = currentByte;
|
|
||||||
_parsingInformation->bufferState = BufferState::NONE;
|
|
||||||
_callback(_sessionPresent, _connectReturnCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConnAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
|
|
||||||
(void)data;
|
|
||||||
(void)currentBytePosition;
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Arduino.h"
|
|
||||||
#include "Packet.hpp"
|
|
||||||
#include "../ParsingInformation.hpp"
|
|
||||||
#include "../Callbacks.hpp"
|
|
||||||
|
|
||||||
namespace AsyncMqttClientInternals {
|
|
||||||
class ConnAckPacket : public Packet {
|
|
||||||
public:
|
|
||||||
explicit ConnAckPacket(ParsingInformation* parsingInformation, OnConnAckInternalCallback callback);
|
|
||||||
~ConnAckPacket();
|
|
||||||
|
|
||||||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
|
|
||||||
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
|
|
||||||
|
|
||||||
private:
|
|
||||||
ParsingInformation* _parsingInformation;
|
|
||||||
OnConnAckInternalCallback _callback;
|
|
||||||
|
|
||||||
uint8_t _bytePosition;
|
|
||||||
bool _sessionPresent;
|
|
||||||
uint8_t _connectReturnCode;
|
|
||||||
};
|
|
||||||
} // namespace AsyncMqttClientInternals
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace AsyncMqttClientInternals {
|
|
||||||
class Packet {
|
|
||||||
public:
|
|
||||||
virtual ~Packet() {}
|
|
||||||
|
|
||||||
virtual void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) = 0;
|
|
||||||
virtual void parsePayload(char* data, size_t len, size_t* currentBytePosition) = 0;
|
|
||||||
};
|
|
||||||
} // namespace AsyncMqttClientInternals
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#include "PingRespPacket.hpp"
|
|
||||||
|
|
||||||
using AsyncMqttClientInternals::PingRespPacket;
|
|
||||||
|
|
||||||
PingRespPacket::PingRespPacket(ParsingInformation* parsingInformation, OnPingRespInternalCallback callback)
|
|
||||||
: _parsingInformation(parsingInformation)
|
|
||||||
, _callback(callback) {
|
|
||||||
}
|
|
||||||
|
|
||||||
PingRespPacket::~PingRespPacket() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void PingRespPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
|
|
||||||
(void)data;
|
|
||||||
(void)currentBytePosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PingRespPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
|
|
||||||
(void)data;
|
|
||||||
(void)currentBytePosition;
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Arduino.h"
|
|
||||||
#include "Packet.hpp"
|
|
||||||
#include "../ParsingInformation.hpp"
|
|
||||||
#include "../Callbacks.hpp"
|
|
||||||
|
|
||||||
namespace AsyncMqttClientInternals {
|
|
||||||
class PingRespPacket : public Packet {
|
|
||||||
public:
|
|
||||||
explicit PingRespPacket(ParsingInformation* parsingInformation, OnPingRespInternalCallback callback);
|
|
||||||
~PingRespPacket();
|
|
||||||
|
|
||||||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
|
|
||||||
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
|
|
||||||
|
|
||||||
private:
|
|
||||||
ParsingInformation* _parsingInformation;
|
|
||||||
OnPingRespInternalCallback _callback;
|
|
||||||
};
|
|
||||||
} // namespace AsyncMqttClientInternals
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#include "PubAckPacket.hpp"
|
|
||||||
|
|
||||||
using AsyncMqttClientInternals::PubAckPacket;
|
|
||||||
|
|
||||||
PubAckPacket::PubAckPacket(ParsingInformation* parsingInformation, OnPubAckInternalCallback callback)
|
|
||||||
: _parsingInformation(parsingInformation)
|
|
||||||
, _callback(callback)
|
|
||||||
, _bytePosition(0)
|
|
||||||
, _packetIdMsb(0)
|
|
||||||
, _packetId(0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
PubAckPacket::~PubAckPacket() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
|
|
||||||
char currentByte = data[(*currentBytePosition)++];
|
|
||||||
if (_bytePosition++ == 0) {
|
|
||||||
_packetIdMsb = currentByte;
|
|
||||||
} else {
|
|
||||||
_packetId = currentByte | _packetIdMsb << 8;
|
|
||||||
_parsingInformation->bufferState = BufferState::NONE;
|
|
||||||
_callback(_packetId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
|
|
||||||
(void)data;
|
|
||||||
(void)currentBytePosition;
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Arduino.h"
|
|
||||||
#include "Packet.hpp"
|
|
||||||
#include "../ParsingInformation.hpp"
|
|
||||||
#include "../Callbacks.hpp"
|
|
||||||
|
|
||||||
namespace AsyncMqttClientInternals {
|
|
||||||
class PubAckPacket : public Packet {
|
|
||||||
public:
|
|
||||||
explicit PubAckPacket(ParsingInformation* parsingInformation, OnPubAckInternalCallback callback);
|
|
||||||
~PubAckPacket();
|
|
||||||
|
|
||||||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
|
|
||||||
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
|
|
||||||
|
|
||||||
private:
|
|
||||||
ParsingInformation* _parsingInformation;
|
|
||||||
OnPubAckInternalCallback _callback;
|
|
||||||
|
|
||||||
uint8_t _bytePosition;
|
|
||||||
char _packetIdMsb;
|
|
||||||
uint16_t _packetId;
|
|
||||||
};
|
|
||||||
} // namespace AsyncMqttClientInternals
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#include "PubCompPacket.hpp"
|
|
||||||
|
|
||||||
using AsyncMqttClientInternals::PubCompPacket;
|
|
||||||
|
|
||||||
PubCompPacket::PubCompPacket(ParsingInformation* parsingInformation, OnPubCompInternalCallback callback)
|
|
||||||
: _parsingInformation(parsingInformation)
|
|
||||||
, _callback(callback)
|
|
||||||
, _bytePosition(0)
|
|
||||||
, _packetIdMsb(0)
|
|
||||||
, _packetId(0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
PubCompPacket::~PubCompPacket() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubCompPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
|
|
||||||
char currentByte = data[(*currentBytePosition)++];
|
|
||||||
if (_bytePosition++ == 0) {
|
|
||||||
_packetIdMsb = currentByte;
|
|
||||||
} else {
|
|
||||||
_packetId = currentByte | _packetIdMsb << 8;
|
|
||||||
_parsingInformation->bufferState = BufferState::NONE;
|
|
||||||
_callback(_packetId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubCompPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
|
|
||||||
(void)data;
|
|
||||||
(void)currentBytePosition;
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Arduino.h"
|
|
||||||
#include "Packet.hpp"
|
|
||||||
#include "../ParsingInformation.hpp"
|
|
||||||
#include "../Callbacks.hpp"
|
|
||||||
|
|
||||||
namespace AsyncMqttClientInternals {
|
|
||||||
class PubCompPacket : public Packet {
|
|
||||||
public:
|
|
||||||
explicit PubCompPacket(ParsingInformation* parsingInformation, OnPubCompInternalCallback callback);
|
|
||||||
~PubCompPacket();
|
|
||||||
|
|
||||||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
|
|
||||||
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
|
|
||||||
|
|
||||||
private:
|
|
||||||
ParsingInformation* _parsingInformation;
|
|
||||||
OnPubCompInternalCallback _callback;
|
|
||||||
|
|
||||||
uint8_t _bytePosition;
|
|
||||||
char _packetIdMsb;
|
|
||||||
uint16_t _packetId;
|
|
||||||
};
|
|
||||||
} // namespace AsyncMqttClientInternals
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#include "PubRecPacket.hpp"
|
|
||||||
|
|
||||||
using AsyncMqttClientInternals::PubRecPacket;
|
|
||||||
|
|
||||||
PubRecPacket::PubRecPacket(ParsingInformation* parsingInformation, OnPubRecInternalCallback callback)
|
|
||||||
: _parsingInformation(parsingInformation)
|
|
||||||
, _callback(callback)
|
|
||||||
, _bytePosition(0)
|
|
||||||
, _packetIdMsb(0)
|
|
||||||
, _packetId(0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
PubRecPacket::~PubRecPacket() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubRecPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
|
|
||||||
char currentByte = data[(*currentBytePosition)++];
|
|
||||||
if (_bytePosition++ == 0) {
|
|
||||||
_packetIdMsb = currentByte;
|
|
||||||
} else {
|
|
||||||
_packetId = currentByte | _packetIdMsb << 8;
|
|
||||||
_parsingInformation->bufferState = BufferState::NONE;
|
|
||||||
_callback(_packetId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubRecPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
|
|
||||||
(void)data;
|
|
||||||
(void)currentBytePosition;
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Arduino.h"
|
|
||||||
#include "Packet.hpp"
|
|
||||||
#include "../ParsingInformation.hpp"
|
|
||||||
#include "../Callbacks.hpp"
|
|
||||||
|
|
||||||
namespace AsyncMqttClientInternals {
|
|
||||||
class PubRecPacket : public Packet {
|
|
||||||
public:
|
|
||||||
explicit PubRecPacket(ParsingInformation* parsingInformation, OnPubRecInternalCallback callback);
|
|
||||||
~PubRecPacket();
|
|
||||||
|
|
||||||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
|
|
||||||
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
|
|
||||||
|
|
||||||
private:
|
|
||||||
ParsingInformation* _parsingInformation;
|
|
||||||
OnPubRecInternalCallback _callback;
|
|
||||||
|
|
||||||
uint8_t _bytePosition;
|
|
||||||
char _packetIdMsb;
|
|
||||||
uint16_t _packetId;
|
|
||||||
};
|
|
||||||
} // namespace AsyncMqttClientInternals
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#include "PubRelPacket.hpp"
|
|
||||||
|
|
||||||
using AsyncMqttClientInternals::PubRelPacket;
|
|
||||||
|
|
||||||
PubRelPacket::PubRelPacket(ParsingInformation* parsingInformation, OnPubRelInternalCallback callback)
|
|
||||||
: _parsingInformation(parsingInformation)
|
|
||||||
, _callback(callback)
|
|
||||||
, _bytePosition(0)
|
|
||||||
, _packetIdMsb(0)
|
|
||||||
, _packetId(0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
PubRelPacket::~PubRelPacket() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubRelPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
|
|
||||||
char currentByte = data[(*currentBytePosition)++];
|
|
||||||
if (_bytePosition++ == 0) {
|
|
||||||
_packetIdMsb = currentByte;
|
|
||||||
} else {
|
|
||||||
_packetId = currentByte | _packetIdMsb << 8;
|
|
||||||
_parsingInformation->bufferState = BufferState::NONE;
|
|
||||||
_callback(_packetId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubRelPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
|
|
||||||
(void)data;
|
|
||||||
(void)currentBytePosition;
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Arduino.h"
|
|
||||||
#include "Packet.hpp"
|
|
||||||
#include "../ParsingInformation.hpp"
|
|
||||||
#include "../Callbacks.hpp"
|
|
||||||
|
|
||||||
namespace AsyncMqttClientInternals {
|
|
||||||
class PubRelPacket : public Packet {
|
|
||||||
public:
|
|
||||||
explicit PubRelPacket(ParsingInformation* parsingInformation, OnPubRelInternalCallback callback);
|
|
||||||
~PubRelPacket();
|
|
||||||
|
|
||||||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
|
|
||||||
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
|
|
||||||
|
|
||||||
private:
|
|
||||||
ParsingInformation* _parsingInformation;
|
|
||||||
OnPubRelInternalCallback _callback;
|
|
||||||
|
|
||||||
uint8_t _bytePosition;
|
|
||||||
char _packetIdMsb;
|
|
||||||
uint16_t _packetId;
|
|
||||||
};
|
|
||||||
} // namespace AsyncMqttClientInternals
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
#include "PublishPacket.hpp"
|
|
||||||
|
|
||||||
using AsyncMqttClientInternals::PublishPacket;
|
|
||||||
|
|
||||||
PublishPacket::PublishPacket(ParsingInformation* parsingInformation, OnMessageInternalCallback dataCallback, OnPublishInternalCallback completeCallback)
|
|
||||||
: _parsingInformation(parsingInformation)
|
|
||||||
, _dataCallback(dataCallback)
|
|
||||||
, _completeCallback(completeCallback)
|
|
||||||
, _dup(false)
|
|
||||||
, _qos(0)
|
|
||||||
, _retain(0)
|
|
||||||
, _bytePosition(0)
|
|
||||||
, _topicLengthMsb(0)
|
|
||||||
, _topicLength(0)
|
|
||||||
, _ignore(false)
|
|
||||||
, _packetIdMsb(0)
|
|
||||||
, _packetId(0)
|
|
||||||
, _payloadLength(0)
|
|
||||||
, _payloadBytesRead(0) {
|
|
||||||
_dup = _parsingInformation->packetFlags & HeaderFlag.PUBLISH_DUP;
|
|
||||||
_retain = _parsingInformation->packetFlags & HeaderFlag.PUBLISH_RETAIN;
|
|
||||||
char qosMasked = _parsingInformation->packetFlags & 0x06;
|
|
||||||
switch (qosMasked) {
|
|
||||||
case HeaderFlag.PUBLISH_QOS0:
|
|
||||||
_qos = 0;
|
|
||||||
break;
|
|
||||||
case HeaderFlag.PUBLISH_QOS1:
|
|
||||||
_qos = 1;
|
|
||||||
break;
|
|
||||||
case HeaderFlag.PUBLISH_QOS2:
|
|
||||||
_qos = 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PublishPacket::~PublishPacket() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void PublishPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
|
|
||||||
char currentByte = data[(*currentBytePosition)++];
|
|
||||||
if (_bytePosition == 0) {
|
|
||||||
_topicLengthMsb = currentByte;
|
|
||||||
} else if (_bytePosition == 1) {
|
|
||||||
_topicLength = currentByte | _topicLengthMsb << 8;
|
|
||||||
if (_topicLength > _parsingInformation->maxTopicLength) {
|
|
||||||
_ignore = true;
|
|
||||||
} else {
|
|
||||||
_parsingInformation->topicBuffer[_topicLength] = '\0';
|
|
||||||
}
|
|
||||||
} else if (_bytePosition >= 2 && _bytePosition < 2 + _topicLength) {
|
|
||||||
// Starting from here, _ignore might be true
|
|
||||||
if (!_ignore) _parsingInformation->topicBuffer[_bytePosition - 2] = currentByte;
|
|
||||||
if (_bytePosition == 2 + _topicLength - 1 && _qos == 0) {
|
|
||||||
_preparePayloadHandling(_parsingInformation->remainingLength - (_bytePosition + 1));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (_bytePosition == 2 + _topicLength) {
|
|
||||||
_packetIdMsb = currentByte;
|
|
||||||
} else {
|
|
||||||
_packetId = currentByte | _packetIdMsb << 8;
|
|
||||||
_preparePayloadHandling(_parsingInformation->remainingLength - (_bytePosition + 1));
|
|
||||||
}
|
|
||||||
_bytePosition++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PublishPacket::_preparePayloadHandling(uint32_t payloadLength) {
|
|
||||||
_payloadLength = payloadLength;
|
|
||||||
if (payloadLength == 0) {
|
|
||||||
_parsingInformation->bufferState = BufferState::NONE;
|
|
||||||
if (!_ignore) {
|
|
||||||
_dataCallback(_parsingInformation->topicBuffer, nullptr, _qos, _dup, _retain, 0, 0, 0, _packetId);
|
|
||||||
_completeCallback(_packetId, _qos);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_parsingInformation->bufferState = BufferState::PAYLOAD;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PublishPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
|
|
||||||
size_t remainToRead = len - (*currentBytePosition);
|
|
||||||
if (_payloadBytesRead + remainToRead > _payloadLength) remainToRead = _payloadLength - _payloadBytesRead;
|
|
||||||
|
|
||||||
if (!_ignore) _dataCallback(_parsingInformation->topicBuffer, data + (*currentBytePosition), _qos, _dup, _retain, remainToRead, _payloadBytesRead, _payloadLength, _packetId);
|
|
||||||
_payloadBytesRead += remainToRead;
|
|
||||||
(*currentBytePosition) += remainToRead;
|
|
||||||
|
|
||||||
if (_payloadBytesRead == _payloadLength) {
|
|
||||||
_parsingInformation->bufferState = BufferState::NONE;
|
|
||||||
if (!_ignore) _completeCallback(_packetId, _qos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Arduino.h"
|
|
||||||
#include "Packet.hpp"
|
|
||||||
#include "../Flags.hpp"
|
|
||||||
#include "../ParsingInformation.hpp"
|
|
||||||
#include "../Callbacks.hpp"
|
|
||||||
|
|
||||||
namespace AsyncMqttClientInternals {
|
|
||||||
class PublishPacket : public Packet {
|
|
||||||
public:
|
|
||||||
explicit PublishPacket(ParsingInformation* parsingInformation, OnMessageInternalCallback dataCallback, OnPublishInternalCallback completeCallback);
|
|
||||||
~PublishPacket();
|
|
||||||
|
|
||||||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
|
|
||||||
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
|
|
||||||
|
|
||||||
private:
|
|
||||||
ParsingInformation* _parsingInformation;
|
|
||||||
OnMessageInternalCallback _dataCallback;
|
|
||||||
OnPublishInternalCallback _completeCallback;
|
|
||||||
|
|
||||||
void _preparePayloadHandling(uint32_t payloadLength);
|
|
||||||
|
|
||||||
bool _dup;
|
|
||||||
uint8_t _qos;
|
|
||||||
bool _retain;
|
|
||||||
|
|
||||||
uint8_t _bytePosition;
|
|
||||||
char _topicLengthMsb;
|
|
||||||
uint16_t _topicLength;
|
|
||||||
bool _ignore;
|
|
||||||
char _packetIdMsb;
|
|
||||||
uint16_t _packetId;
|
|
||||||
uint32_t _payloadLength;
|
|
||||||
uint32_t _payloadBytesRead;
|
|
||||||
};
|
|
||||||
} // namespace AsyncMqttClientInternals
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
#include "SubAckPacket.hpp"
|
|
||||||
|
|
||||||
using AsyncMqttClientInternals::SubAckPacket;
|
|
||||||
|
|
||||||
SubAckPacket::SubAckPacket(ParsingInformation* parsingInformation, OnSubAckInternalCallback callback)
|
|
||||||
: _parsingInformation(parsingInformation)
|
|
||||||
, _callback(callback)
|
|
||||||
, _bytePosition(0)
|
|
||||||
, _packetIdMsb(0)
|
|
||||||
, _packetId(0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
SubAckPacket::~SubAckPacket() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void SubAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
|
|
||||||
char currentByte = data[(*currentBytePosition)++];
|
|
||||||
if (_bytePosition++ == 0) {
|
|
||||||
_packetIdMsb = currentByte;
|
|
||||||
} else {
|
|
||||||
_packetId = currentByte | _packetIdMsb << 8;
|
|
||||||
_parsingInformation->bufferState = BufferState::PAYLOAD;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SubAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
|
|
||||||
char status = data[(*currentBytePosition)++];
|
|
||||||
|
|
||||||
/* switch (status) {
|
|
||||||
case 0:
|
|
||||||
Serial.println("Success QoS 0");
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
Serial.println("Success QoS 1");
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
Serial.println("Success QoS 2");
|
|
||||||
break;
|
|
||||||
case 0x80:
|
|
||||||
Serial.println("Failure");
|
|
||||||
break;
|
|
||||||
} */
|
|
||||||
|
|
||||||
_parsingInformation->bufferState = BufferState::NONE;
|
|
||||||
_callback(_packetId, status);
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Arduino.h"
|
|
||||||
#include "Packet.hpp"
|
|
||||||
#include "../ParsingInformation.hpp"
|
|
||||||
#include "../Callbacks.hpp"
|
|
||||||
|
|
||||||
namespace AsyncMqttClientInternals {
|
|
||||||
class SubAckPacket : public Packet {
|
|
||||||
public:
|
|
||||||
explicit SubAckPacket(ParsingInformation* parsingInformation, OnSubAckInternalCallback callback);
|
|
||||||
~SubAckPacket();
|
|
||||||
|
|
||||||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
|
|
||||||
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
|
|
||||||
|
|
||||||
private:
|
|
||||||
ParsingInformation* _parsingInformation;
|
|
||||||
OnSubAckInternalCallback _callback;
|
|
||||||
|
|
||||||
uint8_t _bytePosition;
|
|
||||||
char _packetIdMsb;
|
|
||||||
uint16_t _packetId;
|
|
||||||
};
|
|
||||||
} // namespace AsyncMqttClientInternals
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#include "UnsubAckPacket.hpp"
|
|
||||||
|
|
||||||
using AsyncMqttClientInternals::UnsubAckPacket;
|
|
||||||
|
|
||||||
UnsubAckPacket::UnsubAckPacket(ParsingInformation* parsingInformation, OnUnsubAckInternalCallback callback)
|
|
||||||
: _parsingInformation(parsingInformation)
|
|
||||||
, _callback(callback)
|
|
||||||
, _bytePosition(0)
|
|
||||||
, _packetIdMsb(0)
|
|
||||||
, _packetId(0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
UnsubAckPacket::~UnsubAckPacket() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void UnsubAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
|
|
||||||
char currentByte = data[(*currentBytePosition)++];
|
|
||||||
if (_bytePosition++ == 0) {
|
|
||||||
_packetIdMsb = currentByte;
|
|
||||||
} else {
|
|
||||||
_packetId = currentByte | _packetIdMsb << 8;
|
|
||||||
_parsingInformation->bufferState = BufferState::NONE;
|
|
||||||
_callback(_packetId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void UnsubAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
|
|
||||||
(void)data;
|
|
||||||
(void)currentBytePosition;
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Arduino.h"
|
|
||||||
#include "Packet.hpp"
|
|
||||||
#include "../ParsingInformation.hpp"
|
|
||||||
#include "../Callbacks.hpp"
|
|
||||||
|
|
||||||
namespace AsyncMqttClientInternals {
|
|
||||||
class UnsubAckPacket : public Packet {
|
|
||||||
public:
|
|
||||||
explicit UnsubAckPacket(ParsingInformation* parsingInformation, OnUnsubAckInternalCallback callback);
|
|
||||||
~UnsubAckPacket();
|
|
||||||
|
|
||||||
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
|
|
||||||
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
|
|
||||||
|
|
||||||
private:
|
|
||||||
ParsingInformation* _parsingInformation;
|
|
||||||
OnUnsubAckInternalCallback _callback;
|
|
||||||
|
|
||||||
uint8_t _bytePosition;
|
|
||||||
char _packetIdMsb;
|
|
||||||
uint16_t _packetId;
|
|
||||||
};
|
|
||||||
} // namespace AsyncMqttClientInternals
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace AsyncMqttClientInternals {
|
|
||||||
enum class BufferState : uint8_t {
|
|
||||||
NONE = 0,
|
|
||||||
REMAINING_LENGTH = 2,
|
|
||||||
VARIABLE_HEADER = 3,
|
|
||||||
PAYLOAD = 4
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ParsingInformation {
|
|
||||||
BufferState bufferState;
|
|
||||||
|
|
||||||
uint16_t maxTopicLength;
|
|
||||||
char* topicBuffer;
|
|
||||||
|
|
||||||
uint8_t packetType;
|
|
||||||
uint16_t packetFlags;
|
|
||||||
uint32_t remainingLength;
|
|
||||||
};
|
|
||||||
} // namespace AsyncMqttClientInternals
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace AsyncMqttClientInternals {
|
|
||||||
struct PendingPubRel {
|
|
||||||
uint16_t packetId;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PendingAck {
|
|
||||||
uint8_t packetType;
|
|
||||||
uint8_t headerFlag;
|
|
||||||
uint16_t packetId;
|
|
||||||
};
|
|
||||||
} // namespace AsyncMqttClientInternals
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2015 Marvin Roger
|
|
||||||
|
|
||||||
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.
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
Async MQTT client for ESP8266 and ESP32 (Github: https://github.com/marvinroger/async-mqtt-client)
|
|
||||||
=============================
|
|
||||||
|
|
||||||
[](https://travis-ci.org/marvinroger/async-mqtt-client)
|
|
||||||
|
|
||||||
An Arduino for ESP8266 and ESP32 asynchronous [MQTT](http://mqtt.org/) client implementation, built on [me-no-dev/ESPAsyncTCP (ESP8266)](https://github.com/me-no-dev/ESPAsyncTCP) | [me-no-dev/AsyncTCP (ESP32)](https://github.com/me-no-dev/AsyncTCP) .
|
|
||||||
## Features
|
|
||||||
|
|
||||||
* Compliant with the 3.1.1 version of the protocol
|
|
||||||
* Fully asynchronous
|
|
||||||
* Subscribe at QoS 0, 1 and 2
|
|
||||||
* Publish at QoS 0, 1 and 2
|
|
||||||
* SSL/TLS support
|
|
||||||
* Available in the [PlatformIO registry](http://platformio.org/lib/show/346/AsyncMqttClient)
|
|
||||||
|
|
||||||
## Requirements, installation and usage
|
|
||||||
|
|
||||||
The project is documented in the [/docs folder](docs).
|
|
||||||
147
wled00/udp.cpp
147
wled00/udp.cpp
@@ -565,93 +565,82 @@ void handleNotifications()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!receiveDirect) return;
|
if (receiveDirect) {
|
||||||
|
//TPM2.NET
|
||||||
|
if (udpIn[0] == 0x9c) {
|
||||||
|
//WARNING: this code assumes that the final TMP2.NET payload is evenly distributed if using multiple packets (ie. frame size is constant)
|
||||||
|
//if the number of LEDs in your installation doesn't allow that, please include padding bytes at the end of the last packet
|
||||||
|
byte tpmType = udpIn[1];
|
||||||
|
if (tpmType == 0xaa) { //TPM2.NET polling, expect answer
|
||||||
|
sendTPM2Ack(); return;
|
||||||
|
}
|
||||||
|
if (tpmType != 0xda) return; //return if notTPM2.NET data
|
||||||
|
|
||||||
//TPM2.NET
|
realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP();
|
||||||
if (udpIn[0] == 0x9c)
|
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_TPM2NET);
|
||||||
{
|
if (realtimeOverride) return;
|
||||||
//WARNING: this code assumes that the final TMP2.NET payload is evenly distributed if using multiple packets (ie. frame size is constant)
|
|
||||||
//if the number of LEDs in your installation doesn't allow that, please include padding bytes at the end of the last packet
|
tpmPacketCount++; //increment the packet count
|
||||||
byte tpmType = udpIn[1];
|
if (tpmPacketCount == 1) tpmPayloadFrameSize = (udpIn[2] << 8) + udpIn[3]; //save frame size for the whole payload if this is the first packet
|
||||||
if (tpmType == 0xaa) { //TPM2.NET polling, expect answer
|
byte packetNum = udpIn[4]; //starts with 1!
|
||||||
sendTPM2Ack(); return;
|
byte numPackets = udpIn[5];
|
||||||
|
|
||||||
|
unsigned id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED
|
||||||
|
unsigned totalLen = strip.getLengthTotal();
|
||||||
|
for (size_t i = 6; i < tpmPayloadFrameSize + 4U && id < totalLen; i += 3, id++) {
|
||||||
|
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
|
||||||
|
}
|
||||||
|
if (tpmPacketCount == numPackets) { //reset packet count and show if all packets were received
|
||||||
|
tpmPacketCount = 0;
|
||||||
|
if (useMainSegmentOnly) strip.trigger();
|
||||||
|
else strip.show();
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (tpmType != 0xda) return; //return if notTPM2.NET data
|
|
||||||
|
|
||||||
realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP();
|
//UDP realtime: 1 warls 2 drgb 3 drgbw 4 dnrgb 5 dnrgbw
|
||||||
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_TPM2NET);
|
if (udpIn[0] > 0 && udpIn[0] < 6) {
|
||||||
if (realtimeOverride) return;
|
realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP();
|
||||||
|
DEBUG_PRINTLN(realtimeIP);
|
||||||
|
if (packetSize < 2) return;
|
||||||
|
|
||||||
tpmPacketCount++; //increment the packet count
|
if (udpIn[1] == 0) {
|
||||||
if (tpmPacketCount == 1) tpmPayloadFrameSize = (udpIn[2] << 8) + udpIn[3]; //save frame size for the whole payload if this is the first packet
|
realtimeTimeout = 0; // cancel realtime mode immediately
|
||||||
byte packetNum = udpIn[4]; //starts with 1!
|
return;
|
||||||
byte numPackets = udpIn[5];
|
} else {
|
||||||
|
realtimeLock(udpIn[1]*1000 +1, REALTIME_MODE_UDP);
|
||||||
|
}
|
||||||
|
if (realtimeOverride) return;
|
||||||
|
|
||||||
unsigned id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED
|
unsigned totalLen = strip.getLengthTotal();
|
||||||
unsigned totalLen = strip.getLengthTotal();
|
if (udpIn[0] == 1 && packetSize > 5) { //warls
|
||||||
for (size_t i = 6; i < tpmPayloadFrameSize + 4U && id < totalLen; i += 3, id++) {
|
for (size_t i = 2; i < packetSize -3; i += 4) {
|
||||||
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
|
setRealtimePixel(udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3], 0);
|
||||||
}
|
}
|
||||||
if (tpmPacketCount == numPackets) { //reset packet count and show if all packets were received
|
} else if (udpIn[0] == 2 && packetSize > 4) { //drgb
|
||||||
tpmPacketCount = 0;
|
for (size_t i = 2, id = 0; i < packetSize -2 && id < totalLen; i += 3, id++)
|
||||||
|
{
|
||||||
|
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
|
||||||
|
}
|
||||||
|
} else if (udpIn[0] == 3 && packetSize > 6) { //drgbw
|
||||||
|
for (size_t i = 2, id = 0; i < packetSize -3 && id < totalLen; i += 4, id++) {
|
||||||
|
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
|
||||||
|
}
|
||||||
|
} else if (udpIn[0] == 4 && packetSize > 7) { //dnrgb
|
||||||
|
unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00);
|
||||||
|
for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 3, id++) {
|
||||||
|
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
|
||||||
|
}
|
||||||
|
} else if (udpIn[0] == 5 && packetSize > 8) { //dnrgbw
|
||||||
|
unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00);
|
||||||
|
for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 4, id++) {
|
||||||
|
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (useMainSegmentOnly) strip.trigger();
|
if (useMainSegmentOnly) strip.trigger();
|
||||||
else strip.show();
|
else strip.show();
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//UDP realtime: 1 warls 2 drgb 3 drgbw 4 dnrgb 5 dnrgbw
|
|
||||||
if (udpIn[0] > 0 && udpIn[0] < 6)
|
|
||||||
{
|
|
||||||
realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP();
|
|
||||||
DEBUG_PRINTLN(realtimeIP);
|
|
||||||
if (packetSize < 2) return;
|
|
||||||
|
|
||||||
if (udpIn[1] == 0) {
|
|
||||||
realtimeTimeout = 0; // cancel realtime mode immediately
|
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
realtimeLock(udpIn[1]*1000 +1, REALTIME_MODE_UDP);
|
|
||||||
}
|
}
|
||||||
if (realtimeOverride) return;
|
|
||||||
|
|
||||||
unsigned totalLen = strip.getLengthTotal();
|
|
||||||
if (udpIn[0] == 1 && packetSize > 5) //warls
|
|
||||||
{
|
|
||||||
for (size_t i = 2; i < packetSize -3; i += 4)
|
|
||||||
{
|
|
||||||
setRealtimePixel(udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3], 0);
|
|
||||||
}
|
|
||||||
} else if (udpIn[0] == 2 && packetSize > 4) //drgb
|
|
||||||
{
|
|
||||||
for (size_t i = 2, id = 0; i < packetSize -2 && id < totalLen; i += 3, id++)
|
|
||||||
{
|
|
||||||
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
|
|
||||||
}
|
|
||||||
} else if (udpIn[0] == 3 && packetSize > 6) //drgbw
|
|
||||||
{
|
|
||||||
for (size_t i = 2, id = 0; i < packetSize -3 && id < totalLen; i += 4, id++)
|
|
||||||
{
|
|
||||||
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
|
|
||||||
}
|
|
||||||
} else if (udpIn[0] == 4 && packetSize > 7) //dnrgb
|
|
||||||
{
|
|
||||||
unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00);
|
|
||||||
for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 3, id++)
|
|
||||||
{
|
|
||||||
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0);
|
|
||||||
}
|
|
||||||
} else if (udpIn[0] == 5 && packetSize > 8) //dnrgbw
|
|
||||||
{
|
|
||||||
unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00);
|
|
||||||
for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 4, id++)
|
|
||||||
{
|
|
||||||
setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (useMainSegmentOnly) strip.trigger();
|
|
||||||
else strip.show();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// API over UDP
|
// API over UDP
|
||||||
@@ -669,6 +658,8 @@ void handleNotifications()
|
|||||||
}
|
}
|
||||||
releaseJSONBufferLock();
|
releaseJSONBufferLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UsermodManager::onUdpPacket(udpIn, packetSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,10 @@ bool UsermodManager::onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
bool UsermodManager::onUdpPacket(uint8_t* payload, size_t len) {
|
||||||
|
for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) if ((*mod)->onUdpPacket(payload, len)) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
void UsermodManager::onUpdateBegin(bool init) { for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) (*mod)->onUpdateBegin(init); } // notify usermods that update is to begin
|
void UsermodManager::onUpdateBegin(bool init) { for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) (*mod)->onUpdateBegin(init); } // notify usermods that update is to begin
|
||||||
void UsermodManager::onStateChange(uint8_t mode) { for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) (*mod)->onStateChange(mode); } // notify usermods that WLED state changed
|
void UsermodManager::onStateChange(uint8_t mode) { for (auto mod = _usermod_table_begin; mod < _usermod_table_end; ++mod) (*mod)->onStateChange(mode); } // notify usermods that WLED state changed
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,6 @@
|
|||||||
#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0)
|
#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0)
|
||||||
#include "soc/rtc.h"
|
#include "soc/rtc.h"
|
||||||
#endif
|
#endif
|
||||||
#ifndef WLED_DISABLE_OTA
|
|
||||||
|
|
||||||
#include "esp_flash.h" // for direct flash access
|
|
||||||
#include "esp_log.h" // for error handling
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@@ -862,65 +857,6 @@ void handleBootLoop() {
|
|||||||
ESP.restart(); // restart cleanly and don't wait for another crash
|
ESP.restart(); // restart cleanly and don't wait for another crash
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef WLED_DISABLE_OTA
|
|
||||||
#ifdef ESP32
|
|
||||||
|
|
||||||
// Get bootloader version for OTA compatibility checking
|
|
||||||
// Uses rollback capability as primary indicator since bootloader description
|
|
||||||
// structure is only available in ESP-IDF v5+ bootloaders
|
|
||||||
uint32_t getBootloaderVersion() {
|
|
||||||
static uint32_t cached_version = 0;
|
|
||||||
if (cached_version != 0) return cached_version;
|
|
||||||
|
|
||||||
DEBUG_PRINTF_P(PSTR("Determining bootloader version...\n"));
|
|
||||||
|
|
||||||
#ifndef WLED_DISABLE_OTA
|
|
||||||
bool can_rollback = Update.canRollBack();
|
|
||||||
#else
|
|
||||||
bool can_rollback = false;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
DEBUG_PRINTF_P(PSTR("Rollback capability: %s\n"), can_rollback ? "YES" : "NO");
|
|
||||||
|
|
||||||
if (can_rollback) {
|
|
||||||
// Rollback capability indicates v4+ bootloader
|
|
||||||
cached_version = 4;
|
|
||||||
DEBUG_PRINTF_P(PSTR("Bootloader v4+ detected (rollback capable)\n"));
|
|
||||||
} else {
|
|
||||||
// No rollback capability - check ESP-IDF version for best guess
|
|
||||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
|
|
||||||
cached_version = 3;
|
|
||||||
DEBUG_PRINTF_P(PSTR("Bootloader v3 detected (ESP-IDF 4.4+)\n"));
|
|
||||||
#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0)
|
|
||||||
cached_version = 2;
|
|
||||||
DEBUG_PRINTF_P(PSTR("Bootloader v2 detected (ESP-IDF 4.x)\n"));
|
|
||||||
#else
|
|
||||||
cached_version = 1;
|
|
||||||
DEBUG_PRINTF_P(PSTR("Bootloader v1/legacy detected (ESP-IDF 3.x)\n"));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
DEBUG_PRINTF_P(PSTR("getBootloaderVersion() returning: %d\n"), cached_version);
|
|
||||||
return cached_version;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if current bootloader is compatible with given required version
|
|
||||||
bool isBootloaderCompatible(uint32_t required_version) {
|
|
||||||
uint32_t current_version = getBootloaderVersion();
|
|
||||||
bool compatible = current_version >= required_version;
|
|
||||||
|
|
||||||
DEBUG_PRINTF_P(PSTR("Bootloader compatibility check: current=%d, required=%d, compatible=%s\n"),
|
|
||||||
current_version, required_version, compatible ? "YES" : "NO");
|
|
||||||
|
|
||||||
return compatible;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// ESP8266 compatibility functions - always assume compatible for now
|
|
||||||
uint32_t getBootloaderVersion() { return 1; }
|
|
||||||
bool isBootloaderCompatible(uint32_t required_version) { return true; }
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Fixed point integer based Perlin noise functions by @dedehai
|
* Fixed point integer based Perlin noise functions by @dedehai
|
||||||
* Note: optimized for speed and to mimic fastled inoise functions, not for accuracy or best randomness
|
* Note: optimized for speed and to mimic fastled inoise functions, not for accuracy or best randomness
|
||||||
|
|||||||
@@ -106,10 +106,6 @@
|
|||||||
#include <LittleFS.h>
|
#include <LittleFS.h>
|
||||||
#endif
|
#endif
|
||||||
#include "esp_task_wdt.h"
|
#include "esp_task_wdt.h"
|
||||||
|
|
||||||
#ifndef WLED_DISABLE_OTA
|
|
||||||
#include "esp_ota_ops.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef WLED_DISABLE_ESPNOW
|
#ifndef WLED_DISABLE_ESPNOW
|
||||||
#include <esp_now.h>
|
#include <esp_now.h>
|
||||||
@@ -159,7 +155,7 @@
|
|||||||
|
|
||||||
#include "src/dependencies/e131/ESPAsyncE131.h"
|
#include "src/dependencies/e131/ESPAsyncE131.h"
|
||||||
#ifndef WLED_DISABLE_MQTT
|
#ifndef WLED_DISABLE_MQTT
|
||||||
#include "src/dependencies/async-mqtt-client/AsyncMqttClient.h"
|
#include <AsyncMqttClient.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define ARDUINOJSON_DECODE_UNICODE 0
|
#define ARDUINOJSON_DECODE_UNICODE 0
|
||||||
|
|||||||
31
wled00/wled_custom_desc.cpp
Normal file
31
wled00/wled_custom_desc.cpp
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#include "ota_release_check.h"
|
||||||
|
#include "wled.h"
|
||||||
|
|
||||||
|
// Simple compile-time hash function for release name validation
|
||||||
|
constexpr uint32_t djb2_hash(const char* str) {
|
||||||
|
uint32_t hash = 5381;
|
||||||
|
while (*str) {
|
||||||
|
hash = ((hash << 5) + hash) + *str++;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single structure definition for both platforms
|
||||||
|
#ifdef ESP32
|
||||||
|
const wled_custom_desc_t __attribute__((section(".rodata.wled_desc"))) wled_custom_description = {
|
||||||
|
#elif defined(ESP8266)
|
||||||
|
const wled_custom_desc_t __attribute__((section(".ver_number"))) wled_custom_description = {
|
||||||
|
#endif
|
||||||
|
WLED_CUSTOM_DESC_MAGIC, // magic
|
||||||
|
WLED_CUSTOM_DESC_VERSION, // version
|
||||||
|
WLED_RELEASE_NAME, // release_name
|
||||||
|
djb2_hash(WLED_RELEASE_NAME) // crc32 - computed at compile time
|
||||||
|
};
|
||||||
|
|
||||||
|
// Single reference to ensure it's not optimized away
|
||||||
|
const wled_custom_desc_t* __attribute__((used)) wled_custom_desc_ref = &wled_custom_description;
|
||||||
|
|
||||||
|
// Function to ensure the structure is referenced by code
|
||||||
|
const wled_custom_desc_t* getWledCustomDesc() {
|
||||||
|
return &wled_custom_description;
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
#else
|
#else
|
||||||
#include <Update.h>
|
#include <Update.h>
|
||||||
#endif
|
#endif
|
||||||
|
#include "ota_release_check.h"
|
||||||
#endif
|
#endif
|
||||||
#include "html_ui.h"
|
#include "html_ui.h"
|
||||||
#include "html_settings.h"
|
#include "html_settings.h"
|
||||||
@@ -388,28 +389,6 @@ void initServer()
|
|||||||
|
|
||||||
createEditHandler(correctPIN);
|
createEditHandler(correctPIN);
|
||||||
|
|
||||||
// Bootloader info endpoint for troubleshooting
|
|
||||||
server.on("/bootloader", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
||||||
AsyncJsonResponse *response = new AsyncJsonResponse(128);
|
|
||||||
JsonObject root = response->getRoot();
|
|
||||||
|
|
||||||
#ifdef ESP32
|
|
||||||
root[F("version")] = getBootloaderVersion();
|
|
||||||
#ifndef WLED_DISABLE_OTA
|
|
||||||
root[F("rollback_capable")] = Update.canRollBack();
|
|
||||||
#else
|
|
||||||
root[F("rollback_capable")] = false;
|
|
||||||
#endif
|
|
||||||
root[F("esp_idf_version")] = ESP_IDF_VERSION;
|
|
||||||
#else
|
|
||||||
root[F("rollback_capable")] = false;
|
|
||||||
root[F("platform")] = F("ESP8266");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
response->setLength();
|
|
||||||
request->send(response);
|
|
||||||
});
|
|
||||||
|
|
||||||
static const char _update[] PROGMEM = "/update";
|
static const char _update[] PROGMEM = "/update";
|
||||||
#ifndef WLED_DISABLE_OTA
|
#ifndef WLED_DISABLE_OTA
|
||||||
//init ota page
|
//init ota page
|
||||||
@@ -446,67 +425,81 @@ void initServer()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!correctPIN || otaLock) return;
|
if (!correctPIN || otaLock) return;
|
||||||
|
|
||||||
|
// Static variable to track release check status across chunks
|
||||||
|
static bool releaseCheckPassed = false;
|
||||||
|
|
||||||
if(!index){
|
if(!index){
|
||||||
DEBUG_PRINTLN(F("OTA Update Start"));
|
DEBUG_PRINTLN(F("OTA Update Start"));
|
||||||
|
|
||||||
#ifndef WLED_DISABLE_OTA
|
// Check if user wants to ignore release check
|
||||||
// Check for bootloader compatibility metadata in first chunk
|
bool skipValidation = request->hasParam("skipValidation", true);
|
||||||
if (len >= 32) {
|
|
||||||
// Look for metadata header: "WLED_BOOTLOADER:X" where X is required version
|
// Validate OTA release compatibility using the first chunk data directly
|
||||||
const char* metadata_prefix = "WLED_BOOTLOADER:";
|
char errorMessage[128];
|
||||||
size_t prefix_len = strlen(metadata_prefix);
|
releaseCheckPassed = shouldAllowOTA(data, len, skipValidation, errorMessage);
|
||||||
|
|
||||||
// Search for metadata in first 512 bytes or available data, whichever is smaller
|
if (!releaseCheckPassed) {
|
||||||
size_t search_len = (len > 512) ? 512 : len;
|
DEBUG_PRINTF_P(PSTR("OTA blocked: %s\n"), errorMessage);
|
||||||
for (size_t i = 0; i <= search_len - prefix_len - 1; i++) {
|
request->send(400, FPSTR(CONTENT_TYPE_PLAIN), errorMessage);
|
||||||
if (memcmp(data + i, metadata_prefix, prefix_len) == 0) {
|
return;
|
||||||
// Found metadata header, extract required version
|
|
||||||
char version_char = data[i + prefix_len];
|
|
||||||
if (version_char >= '1' && version_char <= '9') {
|
|
||||||
uint32_t required_version = version_char - '0';
|
|
||||||
|
|
||||||
DEBUG_PRINTF_P(PSTR("OTA file requires bootloader v%d\n"), required_version);
|
|
||||||
|
|
||||||
if (!isBootloaderCompatible(required_version)) {
|
|
||||||
DEBUG_PRINTF_P(PSTR("Bootloader incompatible! Current: v%d, Required: v%d\n"),
|
|
||||||
getBootloaderVersion(), required_version);
|
|
||||||
request->send(400, FPSTR(CONTENT_TYPE_PLAIN),
|
|
||||||
F("Bootloader incompatible! This firmware requires bootloader v4+. "
|
|
||||||
"Please update via USB using install.wled.me first, or use WLED 0.15.x."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
DEBUG_PRINTLN(F("Bootloader compatibility check passed"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
DEBUG_PRINTLN(F("Release check passed, starting OTA update"));
|
||||||
|
|
||||||
#if WLED_WATCHDOG_TIMEOUT > 0
|
#if WLED_WATCHDOG_TIMEOUT > 0
|
||||||
WLED::instance().disableWatchdog();
|
WLED::instance().disableWatchdog();
|
||||||
#endif
|
#endif
|
||||||
UsermodManager::onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init)
|
UsermodManager::onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init)
|
||||||
lastEditTime = millis(); // make sure PIN does not lock during update
|
lastEditTime = millis(); // make sure PIN does not lock during update
|
||||||
|
|
||||||
|
// Start the actual OTA update
|
||||||
strip.suspend();
|
strip.suspend();
|
||||||
backupConfig(); // backup current config in case the update ends badly
|
backupConfig(); // backup current config in case the update ends badly
|
||||||
strip.resetSegments(); // free as much memory as you can
|
strip.resetSegments(); // free as much memory as you can
|
||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
Update.runAsync(true);
|
Update.runAsync(true);
|
||||||
#endif
|
#endif
|
||||||
Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
|
|
||||||
}
|
// Begin update with the firmware size from content length
|
||||||
if(!Update.hasError()) Update.write(data, len);
|
size_t updateSize = request->contentLength() > 0 ? request->contentLength() : ((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
|
||||||
if(isFinal){
|
if (!Update.begin(updateSize)) {
|
||||||
if(Update.end(true)){
|
DEBUG_PRINTF_P(PSTR("OTA Failed to begin: %s\n"), Update.getErrorString().c_str());
|
||||||
DEBUG_PRINTLN(F("Update Success"));
|
|
||||||
} else {
|
|
||||||
DEBUG_PRINTLN(F("Update Failed"));
|
|
||||||
strip.resume();
|
strip.resume();
|
||||||
UsermodManager::onUpdateBegin(false); // notify usermods that update has failed (some may require task init)
|
UsermodManager::onUpdateBegin(false);
|
||||||
#if WLED_WATCHDOG_TIMEOUT > 0
|
#if WLED_WATCHDOG_TIMEOUT > 0
|
||||||
WLED::instance().enableWatchdog();
|
WLED::instance().enableWatchdog();
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef ESP32
|
||||||
|
request->send(500, FPSTR(CONTENT_TYPE_PLAIN), String("Update.begin failed: ") + Update.errorString());
|
||||||
|
#else
|
||||||
|
request->send(500, FPSTR(CONTENT_TYPE_PLAIN), String("Update.begin failed: ") + Update.getErrorString());
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write chunk data to OTA update (only if release check passed)
|
||||||
|
if (releaseCheckPassed && !Update.hasError()) {
|
||||||
|
if (Update.write(data, len) != len) {
|
||||||
|
DEBUG_PRINTF_P(PSTR("OTA write failed on chunk %zu: %s\n"), index, Update.getErrorString().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isFinal){
|
||||||
|
DEBUG_PRINTLN(F("OTA Update End"));
|
||||||
|
|
||||||
|
if (releaseCheckPassed) {
|
||||||
|
if(Update.end(true)){
|
||||||
|
DEBUG_PRINTLN(F("Update Success"));
|
||||||
|
} else {
|
||||||
|
DEBUG_PRINTLN(F("Update Failed"));
|
||||||
|
strip.resume();
|
||||||
|
UsermodManager::onUpdateBegin(false); // notify usermods that update has failed (some may require task init)
|
||||||
|
#if WLED_WATCHDOG_TIMEOUT > 0
|
||||||
|
WLED::instance().enableWatchdog();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user