Merge 10d7a87917 into eb75868adb
150
.github/workflows/ci.yaml
vendored
|
|
@ -2,150 +2,50 @@ name: ESPSomfy-RTS
|
|||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
ARDUINO_BOARD_MANAGER_ADDITIONAL_URLS: "https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json"
|
||||
ARDUINO_CLI_VERSION: "0.x"
|
||||
ARDUINO_ESP32_VERSION: "2.0.10"
|
||||
ARDUINO_JSON_VERSION: "6.21.3"
|
||||
ESPTOOL_VERSION: "4.6"
|
||||
LITTLEFS_VERSION: "v2.5.1"
|
||||
MKLITTLEFS_VERSION: "3.1.0"
|
||||
PUB_SUB_CLIENT_VERSION: "2.8.0"
|
||||
PYTHON_VERSION: "3.10"
|
||||
SMARTRC_CC1101_VERSION: "2.5.7"
|
||||
WEB_SOCKET_VERSION: "2.4.0"
|
||||
|
||||
jobs:
|
||||
littlefs:
|
||||
name: LittleFS
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Checkout mklittlefs
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: earlephilhower/mklittlefs
|
||||
path: mklittlefs
|
||||
ref: ${{ env.MKLITTLEFS_VERSION }}
|
||||
|
||||
- name: Checkout LittleFS
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: littlefs-project/littlefs
|
||||
path: mklittlefs/littlefs
|
||||
ref: ${{ env.LITTLEFS_VERSION }}
|
||||
|
||||
- name: Build mklittlefs
|
||||
run: |
|
||||
make -C mklittlefs
|
||||
|
||||
- name: Create LittleFS
|
||||
run: |
|
||||
./mklittlefs/mklittlefs --create data --size 1441792 SomfyController.littlefs.bin
|
||||
|
||||
- name: Upload binaries
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: LittleFS
|
||||
path: SomfyController.littlefs.bin
|
||||
retention-days: 5
|
||||
|
||||
arduino:
|
||||
build:
|
||||
name: ${{ matrix.name }}
|
||||
needs: [littlefs]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- board: esp32
|
||||
addr_bootloader: 0x1000
|
||||
chip: ESP32
|
||||
fqbn: esp32:esp32:esp32
|
||||
- env: esp32dev
|
||||
name: ESP32
|
||||
- board: lolin_c3_mini
|
||||
addr_bootloader: 0x0
|
||||
chip: ESP32-C3
|
||||
fqbn: esp32:esp32:lolin_c3_mini
|
||||
name: LOLIN-C3-mini
|
||||
- board: lolin_s2_mini
|
||||
addr_bootloader: 0x1000
|
||||
chip: ESP32-S2
|
||||
fqbn: esp32:esp32:lolin_s2_mini
|
||||
name: LOLIN-S2-mini
|
||||
- board: lolin_s3_mini
|
||||
addr_bootloader: 0x0
|
||||
chip: ESP32-S3
|
||||
fqbn: esp32:esp32:lolin_s3_mini
|
||||
name: LOLIN-S3-mini
|
||||
- env: esp32c3
|
||||
name: ESP32-C3
|
||||
- env: esp32s3
|
||||
name: ESP32-S3
|
||||
- env: esp32c6
|
||||
name: ESP32-C6
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
path: SomfyController
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Get LittleFS
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: LittleFS
|
||||
|
||||
- name: Install Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Upgrade pip
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip --version
|
||||
|
||||
- name: Install ESPTool
|
||||
run: |
|
||||
pip install esptool==${{ env.ESPTOOL_VERSION }}
|
||||
|
||||
- name: Install Arduino CLI
|
||||
uses: arduino/setup-arduino-cli@v1
|
||||
with:
|
||||
version: ${{ env.ARDUINO_CLI_VERSION }}
|
||||
|
||||
- name: Configure Arduino CLI
|
||||
run: |
|
||||
arduino-cli core update-index
|
||||
arduino-cli core install esp32:esp32@${{ env.ARDUINO_ESP32_VERSION }}
|
||||
|
||||
- name: Configure Arduino Libraries
|
||||
run: |
|
||||
arduino-cli lib install ArduinoJson@${{ env.ARDUINO_JSON_VERSION }}
|
||||
arduino-cli lib install PubSubClient@${{ env.PUB_SUB_CLIENT_VERSION }}
|
||||
arduino-cli lib install SmartRC-CC1101-Driver-Lib@${{ env.SMARTRC_CC1101_VERSION }}
|
||||
arduino-cli lib install WebSockets@${{ env.WEB_SOCKET_VERSION }}
|
||||
- name: Install PlatformIO
|
||||
run: pip install platformio
|
||||
|
||||
- name: Build ${{ matrix.name }}
|
||||
run: |
|
||||
mkdir -p build
|
||||
arduino-cli compile --clean --output-dir build --fqbn ${{ matrix.fqbn }} --warnings default ./SomfyController
|
||||
run: pio run -e ${{ matrix.env }}
|
||||
|
||||
- name: ${{ matrix.name }} Image
|
||||
run: |
|
||||
python -m esptool --chip ${{ matrix.chip }} \
|
||||
merge_bin -o build/SomfyController.onboard.bin \
|
||||
${{ matrix.addr_bootloader }} build/SomfyController.ino.bootloader.bin \
|
||||
0x8000 build/SomfyController.ino.partitions.bin \
|
||||
0x10000 build/SomfyController.ino.bin \
|
||||
0x290000 SomfyController.littlefs.bin
|
||||
- name: Build LittleFS image
|
||||
run: pio run -e ${{ matrix.env }} -t buildfs
|
||||
|
||||
- name: Upload ${{ matrix.name }}
|
||||
uses: actions/upload-artifact@v3
|
||||
- name: Upload firmware
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.name }}
|
||||
path: |
|
||||
build/SomfyController.ino.bin
|
||||
build/SomfyController.ino.bootloader.bin
|
||||
build/SomfyController.ino.partitions.bin
|
||||
build/SomfyController.onboard.bin
|
||||
.pio/build/${{ matrix.env }}/firmware.bin
|
||||
.pio/build/${{ matrix.env }}/firmware.elf
|
||||
.pio/build/${{ matrix.env }}/partitions.bin
|
||||
.pio/build/${{ matrix.env }}/bootloader.bin
|
||||
.pio/build/${{ matrix.env }}/littlefs.bin
|
||||
retention-days: 5
|
||||
|
|
|
|||
240
.github/workflows/release.yaml
vendored
|
|
@ -1,27 +1,57 @@
|
|||
name: ESPSomfy-RTS Release
|
||||
|
||||
on:
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
env:
|
||||
ARDUINO_BOARD_MANAGER_ADDITIONAL_URLS: "https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json"
|
||||
ARDUINO_CLI_VERSION: "0.x"
|
||||
ARDUINO_ESP32_VERSION: "2.0.17"
|
||||
ARDUINO_JSON_VERSION: "6.21.5"
|
||||
ESPTOOL_VERSION: "4.7"
|
||||
LITTLEFS_VERSION: "v2.5.1"
|
||||
MKLITTLEFS_VERSION: "3.1.0"
|
||||
PUB_SUB_CLIENT_VERSION: "2.8.0"
|
||||
PYTHON_VERSION: "3.10"
|
||||
SMARTRC_CC1101_VERSION: "2.5.7"
|
||||
WEB_SOCKET_VERSION: "2.4.0"
|
||||
|
||||
jobs:
|
||||
littlefs:
|
||||
name: LittleFS
|
||||
build:
|
||||
permissions: write-all
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# fwname: firmware-only binary for OTA updates
|
||||
# obname: onboard image (bootloader + partitions + firmware + littlefs merged)
|
||||
# for flashing a new chip via USB/serial
|
||||
# addr_bootloader: chip-dependent (ESP32: 0x1000, C3/S3: 0x0)
|
||||
# addr_fs: must match spiffs/littlefs offset in esp32_3MB.csv
|
||||
include:
|
||||
- env: esp32dev
|
||||
name: ESP32
|
||||
chip: ESP32
|
||||
addr_bootloader: "0x1000"
|
||||
addr_fs: "0x310000"
|
||||
fwname: SomfyController.ino.esp32.bin
|
||||
fsname: SomfyController.littlefs.esp32.bin
|
||||
obname: SomfyController.onboard.esp32.bin
|
||||
- env: esp32c3
|
||||
name: ESP32-C3
|
||||
chip: ESP32-C3
|
||||
addr_bootloader: "0x0"
|
||||
addr_fs: "0x310000"
|
||||
fwname: SomfyController.ino.esp32c3.bin
|
||||
fsname: SomfyController.littlefs.esp32c3.bin
|
||||
obname: SomfyController.onboard.esp32c3.bin
|
||||
- env: esp32s3
|
||||
name: ESP32-S3
|
||||
chip: ESP32-S3
|
||||
addr_bootloader: "0x0"
|
||||
addr_fs: "0x670000"
|
||||
fwname: SomfyController.ino.esp32s3.bin
|
||||
fsname: SomfyController.littlefs.esp32s3.bin
|
||||
obname: SomfyController.onboard.esp32s3.bin
|
||||
- env: esp32c6
|
||||
name: ESP32-C6
|
||||
chip: ESP32-C6
|
||||
addr_bootloader: "0x0"
|
||||
addr_fs: "0x310000"
|
||||
fwname: SomfyController.ino.esp32c6.bin
|
||||
fsname: SomfyController.littlefs.esp32c6.bin
|
||||
obname: SomfyController.onboard.esp32c6.bin
|
||||
|
||||
steps:
|
||||
- name: Get Release
|
||||
id: get_release
|
||||
|
|
@ -32,164 +62,61 @@ jobs:
|
|||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout mklittlefs
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: earlephilhower/mklittlefs
|
||||
path: mklittlefs
|
||||
ref: ${{ env.MKLITTLEFS_VERSION }}
|
||||
|
||||
- name: Checkout LittleFS
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: littlefs-project/littlefs
|
||||
path: mklittlefs/littlefs
|
||||
ref: ${{ env.LITTLEFS_VERSION }}
|
||||
|
||||
- name: Build mklittlefs
|
||||
- name: Update version from release tag
|
||||
run: |
|
||||
make -C mklittlefs
|
||||
VERSION="${{ github.event.release.tag_name }}"
|
||||
VERSION="${VERSION#v}"
|
||||
sed -i "s/#define FW_VERSION \"v[0-9.]*\"/#define FW_VERSION \"v${VERSION}\"/" src/ConfigSettings.h
|
||||
sed -i "s/^[0-9.].*/${VERSION}/" data-src/appversion
|
||||
sed -i "s/\?v=[0-9.]*c/?v=${VERSION}c/g" data-src/index.html
|
||||
sed -i "s/appVersion = 'v[0-9.]*'/appVersion = 'v${VERSION}'/" data-src/index.js
|
||||
|
||||
- name: Create LittleFS
|
||||
run: |
|
||||
./mklittlefs/mklittlefs --create data --size 1441792 SomfyController.littlefs.bin
|
||||
|
||||
- name: Upload binaries
|
||||
uses: actions/upload-artifact@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
name: LittleFS
|
||||
path: SomfyController.littlefs.bin
|
||||
retention-days: 5
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install PlatformIO and esptool
|
||||
run: pip install platformio esptool
|
||||
|
||||
- name: Build firmware
|
||||
run: pio run -e ${{ matrix.env }}
|
||||
|
||||
- name: Build LittleFS image
|
||||
run: pio run -e ${{ matrix.env }} -t buildfs
|
||||
|
||||
- name: Create onboard image
|
||||
run: |
|
||||
python -m esptool --chip ${{ matrix.chip }} \
|
||||
merge_bin -o ${{ matrix.obname }} \
|
||||
${{ matrix.addr_bootloader }} .pio/build/${{ matrix.env }}/bootloader.bin \
|
||||
0x8000 .pio/build/${{ matrix.env }}/partitions.bin \
|
||||
0x10000 .pio/build/${{ matrix.env }}/firmware.bin \
|
||||
${{ matrix.addr_fs }} .pio/build/${{ matrix.env }}/littlefs.bin
|
||||
|
||||
- name: Compress onboard image
|
||||
run: zip ${{ matrix.obname }}.zip ${{ matrix.obname }}
|
||||
|
||||
- name: Upload LittleFS
|
||||
uses: shogo82148/actions-upload-release-asset@v1.7.5
|
||||
with:
|
||||
github_token: ${{ github.token }}
|
||||
upload_url: ${{ steps.get_release.outputs.upload_url }}
|
||||
asset_name: SomfyController.littlefs.bin
|
||||
asset_path: SomfyController.littlefs.bin
|
||||
asset_name: ${{ matrix.fsname }}
|
||||
asset_path: .pio/build/${{ matrix.env }}/littlefs.bin
|
||||
overwrite: true
|
||||
|
||||
arduino:
|
||||
permissions: write-all
|
||||
name: ${{ matrix.name }}
|
||||
needs: [littlefs]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- board: esp32
|
||||
addr_bootloader: 0x1000
|
||||
chip: ESP32
|
||||
fqbn: esp32:esp32:esp32wrover:PartitionScheme=default,FlashMode=qio,FlashFreq=80,UploadSpeed=921600,DebugLevel=none,EraseFlash=none
|
||||
# esp32:esp32:esp32wrover:PartitionScheme=default,FlashMode=qio,FlashFreq=80,UploadSpeed=921600,DebugLevel=none,EraseFlash=none
|
||||
name: ESP32
|
||||
obname: SomfyController.onboard.esp32.bin
|
||||
fwname: SomfyController.ino.esp32.bin
|
||||
- board: esp32c3
|
||||
addr_bootloader: 0x0
|
||||
chip: ESP32-C3
|
||||
fqbn: esp32:esp32:esp32c3:JTAGAdapter=default,CDCOnBoot=cdc,PartitionScheme=default,CPUFreq=160,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=none,EraseFlash=none
|
||||
# esp32:esp32:esp32c3:JTAGAdapter=default,CDCOnBoot=default,PartitionScheme=default,CPUFreq=160,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=none,EraseFlash=none
|
||||
name: ESP32C3
|
||||
obname: SomfyController.onboard.esp32c3.bin
|
||||
fwname: SomfyController.ino.esp32c3.bin
|
||||
- board: esp32s2
|
||||
addr_bootloader: 0x1000
|
||||
chip: ESP32-S2
|
||||
fqbn: esp32:esp32:esp32s2:JTAGAdapter=default,CDCOnBoot=cdc,MSCOnBoot=default,DFUOnBoot=default,UploadMode=default,PSRAM=disabled,PartitionScheme=default,CPUFreq=240,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=none,EraseFlash=none
|
||||
# esp32:esp32:esp32s2:JTAGAdapter=default,CDCOnBoot=default,MSCOnBoot=default,DFUOnBoot=default,UploadMode=default,PSRAM=disabled,PartitionScheme=default,CPUFreq=240,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=none,EraseFlash=none
|
||||
name: ESP32S2
|
||||
obname: SomfyController.onboard.esp32s2.bin
|
||||
fwname: SomfyController.ino.esp32s2.bin
|
||||
- board: esp32s3
|
||||
addr_bootloader: 0x0
|
||||
chip: ESP32-S3
|
||||
fqbn: esp32:esp32:esp32s3:JTAGAdapter=default,PSRAM=disabled,FlashMode=qio,FlashSize=4M,LoopCore=1,EventsCore=1,USBMode=hwcdc,CDCOnBoot=cdc,MSCOnBoot=default,DFUOnBoot=default,UploadMode=default,PartitionScheme=default,CPUFreq=240,UploadSpeed=921600,DebugLevel=none,EraseFlash=none
|
||||
# esp32:esp32:esp32s3:JTAGAdapter=default,PSRAM=disabled,FlashMode=qio,FlashSize=4M,LoopCore=1,EventsCore=1,USBMode=hwcdc,CDCOnBoot=cdc,MSCOnBoot=default,DFUOnBoot=default,UploadMode=default,PartitionScheme=default,CPUFreq=240,UploadSpeed=921600,DebugLevel=none,EraseFlash=none
|
||||
name: ESP32S3
|
||||
fwname: SomfyController.ino.esp32s3.bin
|
||||
obname: SomfyController.onboard.esp32s3.bin
|
||||
steps:
|
||||
- name: Get Release
|
||||
id: get_release
|
||||
uses: bruceadams/get-release@v1.3.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: SomfyController
|
||||
|
||||
- name: Get LittleFS
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: LittleFS
|
||||
|
||||
- name: Install Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Upgrade pip
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip --version
|
||||
|
||||
- name: Install ESPTool
|
||||
run: |
|
||||
pip install esptool==${{ env.ESPTOOL_VERSION }}
|
||||
|
||||
- name: Install Arduino CLI
|
||||
uses: arduino/setup-arduino-cli@v1
|
||||
with:
|
||||
version: ${{ env.ARDUINO_CLI_VERSION }}
|
||||
|
||||
- name: Configure Arduino CLI
|
||||
run: |
|
||||
arduino-cli core update-index
|
||||
arduino-cli core install esp32:esp32@${{ env.ARDUINO_ESP32_VERSION }}
|
||||
|
||||
- name: Configure Arduino Libraries
|
||||
run: |
|
||||
arduino-cli lib install ArduinoJson@${{ env.ARDUINO_JSON_VERSION }}
|
||||
arduino-cli lib install PubSubClient@${{ env.PUB_SUB_CLIENT_VERSION }}
|
||||
arduino-cli lib install SmartRC-CC1101-Driver-Lib@${{ env.SMARTRC_CC1101_VERSION }}
|
||||
arduino-cli lib install WebSockets@${{ env.WEB_SOCKET_VERSION }}
|
||||
|
||||
- name: Build ${{ matrix.name }}
|
||||
run: |
|
||||
mkdir -p build${{ matrix.name }}
|
||||
arduino-cli compile --clean --output-dir build${{ matrix.name }} --fqbn ${{ matrix.fqbn }} --warnings none ./SomfyController
|
||||
|
||||
- name: ${{ matrix.name }} Image
|
||||
run: |
|
||||
python -m esptool --chip ${{ matrix.chip }} \
|
||||
merge_bin -o ${{ matrix.obname }} \
|
||||
${{ matrix.addr_bootloader }} build${{ matrix.name }}/SomfyController.ino.bootloader.bin \
|
||||
0x8000 build${{ matrix.name }}/SomfyController.ino.partitions.bin \
|
||||
0x10000 build${{ matrix.name }}/SomfyController.ino.bin \
|
||||
0x290000 SomfyController.littlefs.bin
|
||||
|
||||
- name: Upload Firmware ${{ matrix.name }}
|
||||
- name: Upload firmware
|
||||
uses: shogo82148/actions-upload-release-asset@v1.7.5
|
||||
with:
|
||||
github_token: ${{ github.token }}
|
||||
upload_url: ${{ steps.get_release.outputs.upload_url }}
|
||||
asset_name: ${{ matrix.fwname }}
|
||||
asset_path: build${{ matrix.name }}/SomfyController.ino.bin
|
||||
asset_path: .pio/build/${{ matrix.env }}/firmware.bin
|
||||
overwrite: true
|
||||
|
||||
- name: ${{ matrix.name }} Compress Onboard Image
|
||||
run: |
|
||||
zip ${{ matrix.obname }}.zip ./${{ matrix.obname }}
|
||||
|
||||
- name: Upload Onboard ${{ matrix.name }}
|
||||
- name: Upload onboard image
|
||||
uses: shogo82148/actions-upload-release-asset@v1.7.5
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
github_token: ${{ github.token }}
|
||||
upload_url: ${{ steps.get_release.outputs.upload_url }}
|
||||
|
|
@ -197,4 +124,3 @@ jobs:
|
|||
asset_path: ${{ matrix.obname }}.zip
|
||||
overwrite: true
|
||||
asset_content_type: application/zip
|
||||
|
||||
|
|
|
|||
10
.gitignore
vendored
|
|
@ -7,4 +7,12 @@ debug.cfg
|
|||
SomfyController.ino.XIAO_ESP32S3.bin
|
||||
SomfyController.ino.esp32c3.bin
|
||||
SomfyController.ino.esp32s2.bin
|
||||
.vscode/settings.json
|
||||
.vscode/
|
||||
.pio/
|
||||
.claude/
|
||||
data/
|
||||
build/
|
||||
coredump_report.txt
|
||||
coredump.bin
|
||||
logs/
|
||||
elf_archive/
|
||||
18
README.md
|
|
@ -1,3 +1,12 @@
|
|||
## This is a fork which was created because the original repo seems to be bandoned and I was annoyed by constant restarts of my ESPSomfy. I've fixed some of the major issues caused by watchdog restarts. There are also compiled binaries in my fork if someone is experiencing same issues
|
||||
|
||||
* Fixed a lot of bugs causing constant restarts by migrating to AsyncWebServer and AsyncSockets
|
||||
* Moved logging from serial to esp dedicated logging lib
|
||||
* Gzipped the resources
|
||||
* Changed to platformio build
|
||||
* Backup does no longer use LITTLEFS. Only RAM is used. Storage should not wear out so quick.
|
||||
* Added uptime info in web UI
|
||||
|
||||
# ESPSomfy-RTS <image src="https://user-images.githubusercontent.com/47839015/218898940-3541b360-5c49-4e38-a918-392cd0408b76.png" align="right" style="width:177px;display:inline-block;float:right"></image>
|
||||
|
||||
A controller for Somfy RTS blinds and shades that supports up to 32 individual shades and 16 groups over 433MHz RTx protocols. If you have IO Home Control motors this project is not for you but you can use the IO Remote protocol to connect the ESPSomfy RTS device to a disected remote. Look in the [Wiki](https://github.com/rstrouse/ESPSomfy-RTS/wiki/Controlling-Motors-with-GPIO) for options and verify whether the solution is workable for you.
|
||||
|
|
@ -73,12 +82,3 @@ You can find the documentation for the interfaces in the [Integrations](https://
|
|||
I spent some time reading about a myriad of topics but in the end the primary source for this project comes from https://pushstack.wordpress.com/somfy-rts-protocol/. The work done on pushstack regarding the protocol timing made this feasible without burning a bunch of time measuring pulses.
|
||||
|
||||
Configuration of the Transceiver is done with the ELECHOUSE_CC1101 library which you will need to include in your project should you want to compile the code. The one used for compiling this module can be found here. https://github.com/LSatan/SmartRC-CC1101-Driver-Lib
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
203
Sockets.cpp
|
|
@ -1,203 +0,0 @@
|
|||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <WebSocketsServer.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include "Sockets.h"
|
||||
#include "ConfigSettings.h"
|
||||
#include "Somfy.h"
|
||||
#include "Network.h"
|
||||
#include "GitOTA.h"
|
||||
|
||||
extern ConfigSettings settings;
|
||||
extern Network net;
|
||||
extern SomfyShadeController somfy;
|
||||
extern SocketEmitter sockEmit;
|
||||
extern GitUpdater git;
|
||||
|
||||
|
||||
WebSocketsServer sockServer = WebSocketsServer(8080);
|
||||
|
||||
#define MAX_SOCK_RESPONSE 2048
|
||||
static char g_response[MAX_SOCK_RESPONSE];
|
||||
|
||||
bool room_t::isJoined(uint8_t num) {
|
||||
for(uint8_t i = 0; i < sizeof(this->clients); i++) {
|
||||
if(this->clients[i] == num) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool room_t::join(uint8_t num) {
|
||||
if(this->isJoined(num)) return true;
|
||||
for(uint8_t i = 0; i < sizeof(this->clients); i++) {
|
||||
if(this->clients[i] == 255) {
|
||||
this->clients[i] = num;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool room_t::leave(uint8_t num) {
|
||||
if(!this->isJoined(num)) return false;
|
||||
for(uint8_t i = 0; i < sizeof(this->clients); i++) {
|
||||
if(this->clients[i] == num) this->clients[i] = 255;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void room_t::clear() {
|
||||
memset(this->clients, 255, sizeof(this->clients));
|
||||
}
|
||||
uint8_t room_t::activeClients() {
|
||||
uint8_t n = 0;
|
||||
for(uint8_t i = 0; i < sizeof(this->clients); i++) {
|
||||
if(this->clients[i] != 255) n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
/*********************************************************************
|
||||
* ClientSocketEvent class members
|
||||
********************************************************************/
|
||||
/*
|
||||
void ClientSocketEvent::prepareMessage(const char *evt, const char *payload) {
|
||||
if(strlen(payload) + 5 >= sizeof(this->msg)) Serial.printf("Socket buffer overflow %d > 2048\n", strlen(payload) + 5 + strlen(evt));
|
||||
snprintf(this->msg, sizeof(this->msg), "42[%s,%s]", evt, payload);
|
||||
}
|
||||
void ClientSocketEvent::prepareMessage(const char *evt, JsonDocument &doc) {
|
||||
memset(this->msg, 0x00, sizeof(this->msg));
|
||||
snprintf(this->msg, sizeof(this->msg), "42[%s,", evt);
|
||||
serializeJson(doc, &this->msg[strlen(this->msg)], sizeof(this->msg) - strlen(this->msg) - 2);
|
||||
strcat(this->msg, "]");
|
||||
}
|
||||
*/
|
||||
|
||||
/*********************************************************************
|
||||
* SocketEmitter class members
|
||||
********************************************************************/
|
||||
void SocketEmitter::startup() {
|
||||
|
||||
}
|
||||
void SocketEmitter::begin() {
|
||||
sockServer.begin();
|
||||
sockServer.enableHeartbeat(20000, 10000, 3);
|
||||
sockServer.onEvent(this->wsEvent);
|
||||
Serial.println("Socket Server Started...");
|
||||
//settings.printAvailHeap();
|
||||
}
|
||||
void SocketEmitter::loop() {
|
||||
this->initClients();
|
||||
sockServer.loop();
|
||||
}
|
||||
JsonSockEvent *SocketEmitter::beginEmit(const char *evt) {
|
||||
this->json.beginEvent(&sockServer, evt, g_response, sizeof(g_response));
|
||||
return &this->json;
|
||||
}
|
||||
void SocketEmitter::endEmit(uint8_t num) { this->json.endEvent(num); sockServer.loop(); }
|
||||
void SocketEmitter::endEmitRoom(uint8_t room) {
|
||||
if(room < SOCK_MAX_ROOMS) {
|
||||
room_t *r = &this->rooms[room];
|
||||
for(uint8_t i = 0; i < sizeof(r->clients); i++) {
|
||||
if(r->clients[i] != 255) this->json.endEvent(r->clients[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
uint8_t SocketEmitter::activeClients(uint8_t room) {
|
||||
if(room < SOCK_MAX_ROOMS) return this->rooms[room].activeClients();
|
||||
return 0;
|
||||
}
|
||||
void SocketEmitter::initClients() {
|
||||
for(uint8_t i = 0; i < sizeof(this->newClients); i++) {
|
||||
uint8_t num = this->newClients[i];
|
||||
if(num != 255) {
|
||||
if(sockServer.clientIsConnected(num)) {
|
||||
Serial.printf("Initializing Socket Client %u\n", num);
|
||||
esp_task_wdt_reset();
|
||||
settings.emitSockets(num);
|
||||
somfy.emitState(num);
|
||||
git.emitUpdateCheck(num);
|
||||
net.emitSockets(num);
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
this->newClients[i] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
void SocketEmitter::delayInit(uint8_t num) {
|
||||
for(uint8_t i=0; i < sizeof(this->newClients); i++) {
|
||||
if(this->newClients[i] == num) break;
|
||||
else if(this->newClients[i] == 255) {
|
||||
this->newClients[i] = num;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void SocketEmitter::end() {
|
||||
sockServer.close();
|
||||
for(uint8_t i = 0; i < SOCK_MAX_ROOMS; i++)
|
||||
this->rooms[i].clear();
|
||||
}
|
||||
void SocketEmitter::disconnect() { sockServer.disconnect(); }
|
||||
void SocketEmitter::wsEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length) {
|
||||
switch(type) {
|
||||
case WStype_ERROR:
|
||||
if(length > 0)
|
||||
Serial.printf("Socket Error: %s\n", payload);
|
||||
else
|
||||
Serial.println("Socket Error: \n");
|
||||
break;
|
||||
case WStype_DISCONNECTED:
|
||||
if(length > 0)
|
||||
Serial.printf("Socket [%u] Disconnected!\n [%s]", num, payload);
|
||||
else
|
||||
Serial.printf("Socket [%u] Disconnected!\n", num);
|
||||
for(uint8_t i = 0; i < SOCK_MAX_ROOMS; i++) {
|
||||
sockEmit.rooms[i].leave(num);
|
||||
}
|
||||
break;
|
||||
case WStype_CONNECTED:
|
||||
{
|
||||
IPAddress ip = sockServer.remoteIP(num);
|
||||
Serial.printf("Socket [%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
|
||||
// Send all the current shade settings to the client.
|
||||
sockServer.sendTXT(num, "Connected");
|
||||
//sockServer.loop();
|
||||
sockEmit.delayInit(num);
|
||||
}
|
||||
break;
|
||||
case WStype_TEXT:
|
||||
if(strncmp((char *)payload, "join:", 5) == 0) {
|
||||
// In this instance the client wants to join a room. Let's do some
|
||||
// work to get the ordinal of the room that the client wants to join.
|
||||
uint8_t roomNum = atoi((char *)&payload[5]);
|
||||
Serial.printf("Client %u joining room %u\n", num, roomNum);
|
||||
if(roomNum < SOCK_MAX_ROOMS) sockEmit.rooms[roomNum].join(num);
|
||||
}
|
||||
else if(strncmp((char *)payload, "leave:", 6) == 0) {
|
||||
uint8_t roomNum = atoi((char *)&payload[6]);
|
||||
Serial.printf("Client %u leaving room %u\n", num, roomNum);
|
||||
if(roomNum < SOCK_MAX_ROOMS) sockEmit.rooms[roomNum].leave(num);
|
||||
}
|
||||
else {
|
||||
Serial.printf("Socket [%u] text: %s\n", num, payload);
|
||||
}
|
||||
// send message to client
|
||||
// webSocket.sendTXT(num, "message here");
|
||||
|
||||
// send data to all connected clients
|
||||
// sockServer.broadcastTXT("message here");
|
||||
break;
|
||||
case WStype_BIN:
|
||||
Serial.printf("[%u] get binary length: %u\n", num, length);
|
||||
//hexdump(payload, length);
|
||||
|
||||
// send message to client
|
||||
// sockServer.sendBIN(num, payload, length);
|
||||
break;
|
||||
case WStype_PONG:
|
||||
//Serial.printf("Pong from %u\n", num);
|
||||
break;
|
||||
case WStype_PING:
|
||||
//Serial.printf("Ping from %u\n", num);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
#include <WiFi.h>
|
||||
#include <LittleFS.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include "ConfigSettings.h"
|
||||
#include "Network.h"
|
||||
#include "Web.h"
|
||||
#include "Sockets.h"
|
||||
#include "Utils.h"
|
||||
#include "Somfy.h"
|
||||
#include "MQTT.h"
|
||||
#include "GitOTA.h"
|
||||
|
||||
ConfigSettings settings;
|
||||
Web webServer;
|
||||
SocketEmitter sockEmit;
|
||||
Network net;
|
||||
rebootDelay_t rebootDelay;
|
||||
SomfyShadeController somfy;
|
||||
MQTTClass mqtt;
|
||||
GitUpdater git;
|
||||
|
||||
uint32_t oldheap = 0;
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.println();
|
||||
Serial.println("Startup/Boot....");
|
||||
Serial.println("Mounting File System...");
|
||||
if(LittleFS.begin()) Serial.println("File system mounted successfully");
|
||||
else Serial.println("Error mounting file system");
|
||||
settings.begin();
|
||||
if(WiFi.status() == WL_CONNECTED) WiFi.disconnect(true);
|
||||
delay(10);
|
||||
Serial.println();
|
||||
webServer.startup();
|
||||
webServer.begin();
|
||||
delay(1000);
|
||||
net.setup();
|
||||
somfy.begin();
|
||||
//git.checkForUpdate();
|
||||
esp_task_wdt_init(7, true); //enable panic so ESP32 restarts
|
||||
esp_task_wdt_add(NULL); //add current thread to WDT watch
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// put your main code here, to run repeatedly:
|
||||
//uint32_t heap = ESP.getFreeHeap();
|
||||
if(rebootDelay.reboot && millis() > rebootDelay.rebootTime) {
|
||||
Serial.print("Rebooting after ");
|
||||
Serial.print(rebootDelay.rebootTime);
|
||||
Serial.println("ms");
|
||||
net.end();
|
||||
ESP.restart();
|
||||
return;
|
||||
}
|
||||
uint32_t timing = millis();
|
||||
|
||||
net.loop();
|
||||
if(millis() - timing > 100) Serial.printf("Timing Net: %ldms\n", millis() - timing);
|
||||
timing = millis();
|
||||
esp_task_wdt_reset();
|
||||
somfy.loop();
|
||||
if(millis() - timing > 100) Serial.printf("Timing Somfy: %ldms\n", millis() - timing);
|
||||
timing = millis();
|
||||
esp_task_wdt_reset();
|
||||
if(net.connected() || net.softAPOpened) {
|
||||
if(!rebootDelay.reboot && net.connected() && !net.softAPOpened) {
|
||||
git.loop();
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
webServer.loop();
|
||||
esp_task_wdt_reset();
|
||||
if(millis() - timing > 100) Serial.printf("Timing WebServer: %ldms\n", millis() - timing);
|
||||
esp_task_wdt_reset();
|
||||
timing = millis();
|
||||
sockEmit.loop();
|
||||
if(millis() - timing > 100) Serial.printf("Timing Socket: %ldms\n", millis() - timing);
|
||||
esp_task_wdt_reset();
|
||||
timing = millis();
|
||||
}
|
||||
if(rebootDelay.reboot && millis() > rebootDelay.rebootTime) {
|
||||
net.end();
|
||||
ESP.restart();
|
||||
}
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
50
Web.h
|
|
@ -1,50 +0,0 @@
|
|||
#include <WebServer.h>
|
||||
#include "Somfy.h"
|
||||
#ifndef webserver_h
|
||||
#define webserver_h
|
||||
class Web {
|
||||
public:
|
||||
bool uploadSuccess = false;
|
||||
void sendCORSHeaders(WebServer &server);
|
||||
void sendCacheHeaders(uint32_t seconds=604800);
|
||||
void startup();
|
||||
void handleLogin(WebServer &server);
|
||||
void handleLogout(WebServer &server);
|
||||
void handleStreamFile(WebServer &server, const char *filename, const char *encoding);
|
||||
void handleController(WebServer &server);
|
||||
void handleLoginContext(WebServer &server);
|
||||
void handleGetRepeaters(WebServer &server);
|
||||
void handleGetRooms(WebServer &server);
|
||||
void handleGetShades(WebServer &server);
|
||||
void handleGetGroups(WebServer &server);
|
||||
void handleShadeCommand(WebServer &server);
|
||||
void handleRepeatCommand(WebServer &server);
|
||||
void handleGroupCommand(WebServer &server);
|
||||
void handleTiltCommand(WebServer &server);
|
||||
void handleDiscovery(WebServer &server);
|
||||
void handleNotFound(WebServer &server);
|
||||
void handleRoom(WebServer &server);
|
||||
void handleShade(WebServer &server);
|
||||
void handleGroup(WebServer &server);
|
||||
void handleSetPositions(WebServer &server);
|
||||
void handleSetSensor(WebServer &server);
|
||||
void handleDownloadFirmware(WebServer &server);
|
||||
void handleBackup(WebServer &server, bool attach = false);
|
||||
void handleReboot(WebServer &server);
|
||||
void handleDeserializationError(WebServer &server, DeserializationError &err);
|
||||
void begin();
|
||||
void loop();
|
||||
void end();
|
||||
// Web Handlers
|
||||
bool createAPIToken(const IPAddress ipAddress, char *token);
|
||||
bool createAPIToken(const char *payload, char *token);
|
||||
bool createAPIPinToken(const IPAddress ipAddress, const char *pin, char *token);
|
||||
bool createAPIPasswordToken(const IPAddress ipAddress, const char *username, const char *password, char *token);
|
||||
bool isAuthenticated(WebServer &server, bool cfg = false);
|
||||
|
||||
//void chunkRoomsResponse(WebServer &server, const char *elem = nullptr);
|
||||
//void chunkShadesResponse(WebServer &server, const char *elem = nullptr);
|
||||
//void chunkGroupsResponse(WebServer &server, const char *elem = nullptr);
|
||||
//void chunkGroupResponse(WebServer &server, SomfyGroup *, const char *prefix = nullptr);
|
||||
};
|
||||
#endif
|
||||
45
archive_elf.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
"""
|
||||
PlatformIO post-build script: archive firmware.elf files.
|
||||
|
||||
Copies firmware.elf to elf_archive/ with a timestamp after each build.
|
||||
Keeps only the last 10 files to avoid filling up disk space.
|
||||
|
||||
Usage in platformio.ini
|
||||
-----------------------
|
||||
extra_scripts = post:archive_elf.py
|
||||
"""
|
||||
|
||||
Import("env")
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
|
||||
MAX_ARCHIVES = 10
|
||||
ARCHIVE_DIR = os.path.join(env.subst("$PROJECT_DIR"), "elf_archive")
|
||||
|
||||
|
||||
def archive_elf(source, target, env):
|
||||
elf_path = os.path.join(env.subst("$BUILD_DIR"), "firmware.elf")
|
||||
if not os.path.isfile(elf_path):
|
||||
print("[archive_elf] firmware.elf not found, skipping.")
|
||||
return
|
||||
|
||||
os.makedirs(ARCHIVE_DIR, exist_ok=True)
|
||||
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
dest = os.path.join(ARCHIVE_DIR, f"firmware_{timestamp}.elf")
|
||||
shutil.copy2(elf_path, dest)
|
||||
print(f"[archive_elf] Saved {dest}")
|
||||
|
||||
# Keep only the last MAX_ARCHIVES files
|
||||
files = sorted(
|
||||
[f for f in os.listdir(ARCHIVE_DIR) if f.endswith(".elf")],
|
||||
)
|
||||
while len(files) > MAX_ARCHIVES:
|
||||
old = os.path.join(ARCHIVE_DIR, files.pop(0))
|
||||
os.remove(old)
|
||||
print(f"[archive_elf] Removed old archive {old}")
|
||||
|
||||
|
||||
env.AddPostAction("$BUILD_DIR/firmware.elf", archive_elf)
|
||||
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
1
data-src/appversion
Normal file
|
|
@ -0,0 +1 @@
|
|||
2.4.8
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
|
@ -8,9 +8,9 @@
|
|||
<meta name="apple-mobile-web-app-title" content="ESPSomfy RTS App">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
|
||||
<link rel="stylesheet" href="main.css?v=2.4.7c" type="text/css" />
|
||||
<link rel="stylesheet" href="widgets.css?v=2.4.7c" type="text/css" />
|
||||
<link rel="stylesheet" href="icons.css?v=2.4.7c" type="text/css" />
|
||||
<link rel="stylesheet" href="main.css?v=2.4.8c" type="text/css" />
|
||||
<link rel="stylesheet" href="widgets.css?v=2.4.8c" type="text/css" />
|
||||
<link rel="stylesheet" href="icons.css?v=2.4.8c" type="text/css" />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
|
||||
<!-- iPad retina icon -->
|
||||
|
|
@ -114,7 +114,7 @@
|
|||
rel="apple-touch-startup-image">
|
||||
|
||||
|
||||
<script type="text/javascript" src="index.js?v=2.4.7c"></script>
|
||||
<script type="text/javascript" src="index.js?v=2.4.8c"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="divContainer" class="container main" data-auth="false">
|
||||
|
|
@ -246,6 +246,8 @@
|
|||
<span id="spanMaxMemory" style="text-align:right;width:120px;"></span>
|
||||
<span style="text-align:right;display:inline-block;color:#00bcd4;">Min: </span>
|
||||
<span id="spanMinMemory" style="text-align:right;width:120px;"></span>
|
||||
<span style="text-align:right;display:inline-block;color:#00bcd4;">Uptime: </span>
|
||||
<span id="spanUptime" style="text-align:right;width:120px;"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-container">
|
||||
|
|
@ -1270,7 +1270,7 @@ var security = new Security();
|
|||
|
||||
class General {
|
||||
initialized = false;
|
||||
appVersion = 'v2.4.7';
|
||||
appVersion = 'v2.4.8';
|
||||
reloadApp = false;
|
||||
init() {
|
||||
if (this.initialized) return;
|
||||
|
|
@ -4335,7 +4335,7 @@ class Firmware {
|
|||
if (typeof overlay !== 'undefined') overlay.remove();
|
||||
reject({ htmlError: status, service: 'GET /backup' });
|
||||
};
|
||||
xhr.open('GET', baseUrl.length > 0 ? `${baseUrl}/backup` : '/backup', true);
|
||||
xhr.open('GET', baseUrl.length > 0 ? `${baseUrl}/backup?attach=true` : '/backup?attach=true', true);
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
|
@ -4385,6 +4385,20 @@ class Firmware {
|
|||
if (sp) sp.innerHTML = mem.max.fmt('#,##0');
|
||||
sp = document.getElementById('spanMinMemory');
|
||||
if (sp) sp.innerHTML = mem.min.fmt('#,##0');
|
||||
sp = document.getElementById('spanUptime');
|
||||
if (sp) {
|
||||
let t = Math.floor(mem.uptime / 1000);
|
||||
let d = Math.floor(t / 86400); t %= 86400;
|
||||
let h = Math.floor(t / 3600); t %= 3600;
|
||||
let m = Math.floor(t / 60);
|
||||
let s = t % 60;
|
||||
let parts = [];
|
||||
if (d > 0) parts.push(d + 'd');
|
||||
if (h > 0) parts.push(h + 'h');
|
||||
if (m > 0) parts.push(m + 'm');
|
||||
if (s > 0 || parts.length === 0) parts.push(s + 's');
|
||||
sp.innerHTML = parts.join(' ');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
2.4.7
|
||||
6
esp32_3MB.csv
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Name, Type, SubType, Offset, Size
|
||||
nvs, data, nvs, 0x9000, 0x5000
|
||||
otadata, data, ota, 0xE000, 0x2000
|
||||
app0, app, ota_0, 0x10000, 0x180000
|
||||
app1, app, ota_1, 0x190000, 0x180000
|
||||
spiffs, data, spiffs, 0x310000, 0x0F0000
|
||||
|
37
include/README
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
|
||||
This directory is intended for project header files.
|
||||
|
||||
A header file is a file containing C declarations and macro definitions
|
||||
to be shared between several project source files. You request the use of a
|
||||
header file in your project source file (C, C++, etc) located in `src` folder
|
||||
by including it, with the C preprocessing directive `#include'.
|
||||
|
||||
```src/main.c
|
||||
|
||||
#include "header.h"
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Including a header file produces the same results as copying the header file
|
||||
into each source file that needs it. Such copying would be time-consuming
|
||||
and error-prone. With a header file, the related declarations appear
|
||||
in only one place. If they need to be changed, they can be changed in one
|
||||
place, and programs that include the header file will automatically use the
|
||||
new version when next recompiled. The header file eliminates the labor of
|
||||
finding and changing all the copies as well as the risk that a failure to
|
||||
find one copy will result in inconsistencies within a program.
|
||||
|
||||
In C, the convention is to give header files names that end with `.h'.
|
||||
|
||||
Read more about using header files in official GCC documentation:
|
||||
|
||||
* Include Syntax
|
||||
* Include Operation
|
||||
* Once-Only Headers
|
||||
* Computed Includes
|
||||
|
||||
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||
46
lib/README
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
|
||||
This directory is intended for project specific (private) libraries.
|
||||
PlatformIO will compile them to static libraries and link into the executable file.
|
||||
|
||||
The source code of each library should be placed in a separate directory
|
||||
("lib/your_library_name/[Code]").
|
||||
|
||||
For example, see the structure of the following example libraries `Foo` and `Bar`:
|
||||
|
||||
|--lib
|
||||
| |
|
||||
| |--Bar
|
||||
| | |--docs
|
||||
| | |--examples
|
||||
| | |--src
|
||||
| | |- Bar.c
|
||||
| | |- Bar.h
|
||||
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||
| |
|
||||
| |--Foo
|
||||
| | |- Foo.c
|
||||
| | |- Foo.h
|
||||
| |
|
||||
| |- README --> THIS FILE
|
||||
|
|
||||
|- platformio.ini
|
||||
|--src
|
||||
|- main.c
|
||||
|
||||
Example contents of `src/main.c` using Foo and Bar:
|
||||
```
|
||||
#include <Foo.h>
|
||||
#include <Bar.h>
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The PlatformIO Library Dependency Finder will find automatically dependent
|
||||
libraries by scanning project source files.
|
||||
|
||||
More information about PlatformIO Library Dependency Finder
|
||||
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||
290
minify.py
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
"""
|
||||
PlatformIO pre-build script: minify & gzip web assets.
|
||||
|
||||
Copies files from data-src/ → data/
|
||||
• HTML – whitespace & comment removal, then gzip
|
||||
• CSS – whitespace & comment removal, then gzip
|
||||
• JS – whitespace & comment removal, then gzip
|
||||
• PNG – copied as-is (optipng if available)
|
||||
• Other – copied as-is
|
||||
|
||||
Usage in platformio.ini
|
||||
-----------------------
|
||||
extra_scripts = pre:minify.py
|
||||
|
||||
Directory layout
|
||||
----------------
|
||||
project/
|
||||
├── data-src/ ← original, human-readable assets
|
||||
│ ├── index.html
|
||||
│ ├── css/
|
||||
│ ├── js/
|
||||
│ └── img/
|
||||
├── data/ ← auto-generated (gitignore this!)
|
||||
├── minify.py
|
||||
└── platformio.ini
|
||||
|
||||
The script runs automatically before LittleFS / SPIFFS image is built.
|
||||
It also runs once at the start of every build so the data/ dir is always fresh.
|
||||
|
||||
Dependencies: none (pure Python ≥ 3.7).
|
||||
Optional: optipng (for PNG optimisation)
|
||||
"""
|
||||
|
||||
Import("env") # PlatformIO macro – gives us the build environment
|
||||
|
||||
import gzip
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Config
|
||||
# ──────────────────────────────────────────────
|
||||
SRC_DIR_NAME = "data-src"
|
||||
DST_DIR_NAME = "data"
|
||||
|
||||
# Extensions that will be minified + gzipped
|
||||
MINIFY_AND_GZIP = {".html", ".htm", ".css", ".js", ".json", ".svg", ".xml"}
|
||||
|
||||
# Extensions that optipng can optimise
|
||||
PNG_EXTENSIONS = {".png"}
|
||||
|
||||
# Everything else is copied verbatim
|
||||
# ──────────────────────────────────────────────
|
||||
|
||||
|
||||
def _project_dir():
|
||||
return env.subst("$PROJECT_DIR")
|
||||
|
||||
|
||||
def _src_dir():
|
||||
return os.path.join(_project_dir(), SRC_DIR_NAME)
|
||||
|
||||
|
||||
def _dst_dir():
|
||||
return os.path.join(_project_dir(), DST_DIR_NAME)
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Minifiers (pure Python, no npm needed)
|
||||
# ──────────────────────────────────────────────
|
||||
def minify_html(text: str) -> str:
|
||||
"""Simple but effective HTML minifier."""
|
||||
# Remove HTML comments (but keep IE conditional comments)
|
||||
text = re.sub(r"<!--(?!\[if).*?-->", "", text, flags=re.DOTALL)
|
||||
# Collapse whitespace between tags
|
||||
text = re.sub(r">\s+<", "> <", text)
|
||||
# Collapse runs of whitespace into a single space
|
||||
text = re.sub(r"\s{2,}", " ", text)
|
||||
# Remove whitespace around = in attributes
|
||||
text = re.sub(r'\s*=\s*', '=', text)
|
||||
# Strip leading/trailing whitespace per line, rejoin
|
||||
lines = [line.strip() for line in text.splitlines() if line.strip()]
|
||||
return " ".join(lines)
|
||||
|
||||
|
||||
def minify_css(text: str) -> str:
|
||||
"""Remove comments, collapse whitespace in CSS."""
|
||||
# Remove comments
|
||||
text = re.sub(r"/\*.*?\*/", "", text, flags=re.DOTALL)
|
||||
# Remove whitespace around : ; { } ,
|
||||
text = re.sub(r"\s*([:{};,])\s*", r"\1", text)
|
||||
# Collapse remaining whitespace
|
||||
text = re.sub(r"\s{2,}", " ", text)
|
||||
# Strip leading/trailing
|
||||
return text.strip()
|
||||
|
||||
|
||||
# def minify_js(text: str) -> str:
|
||||
# """
|
||||
# Light JS minifier – removes comments and collapses whitespace.
|
||||
# For heavy minification install terser and the script will use it
|
||||
# automatically (see _try_terser below).
|
||||
# """
|
||||
# # Remove single-line comments (careful with URLs – :// )
|
||||
# text = re.sub(r"(?<!:)//(?!/)[^\n]*", "", text)
|
||||
# # Remove multi-line comments
|
||||
# text = re.sub(r"/\*.*?\*/", "", text, flags=re.DOTALL)
|
||||
# # Collapse whitespace
|
||||
# text = re.sub(r"\s{2,}", " ", text)
|
||||
# lines = [line.strip() for line in text.splitlines() if line.strip()]
|
||||
# return "\n".join(lines)
|
||||
|
||||
|
||||
def minify_json(text: str) -> str:
|
||||
"""Compact JSON by removing unnecessary whitespace."""
|
||||
import json
|
||||
try:
|
||||
data = json.loads(text)
|
||||
return json.dumps(data, separators=(",", ":"), ensure_ascii=False)
|
||||
except json.JSONDecodeError:
|
||||
return text
|
||||
|
||||
|
||||
def minify_svg(text: str) -> str:
|
||||
"""Minimal SVG minifier – comments + whitespace."""
|
||||
text = re.sub(r"<!--.*?-->", "", text, flags=re.DOTALL)
|
||||
text = re.sub(r">\s+<", "><", text)
|
||||
text = re.sub(r"\s{2,}", " ", text)
|
||||
return text.strip()
|
||||
|
||||
|
||||
MINIFIERS = {
|
||||
# ".html": minify_html,
|
||||
# ".htm": minify_html,
|
||||
# ".css": minify_css,
|
||||
# ".js": minify_js,
|
||||
# ".json": minify_json,
|
||||
# ".svg": minify_svg,
|
||||
# ".xml": minify_svg, # same approach works for generic XML
|
||||
}
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Optional external tools (used when available)
|
||||
# ──────────────────────────────────────────────
|
||||
def _has_command(cmd: str) -> bool:
|
||||
return shutil.which(cmd) is not None
|
||||
|
||||
|
||||
def _try_terser(src_path: str, dst_path: str) -> bool:
|
||||
"""Use terser for JS if installed (npm i -g terser)."""
|
||||
if not _has_command("terser"):
|
||||
return False
|
||||
try:
|
||||
subprocess.run(
|
||||
["terser", src_path, "-o", dst_path, "--compress", "--mangle"],
|
||||
check=True, capture_output=True,
|
||||
)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
|
||||
def _try_optipng(path: str) -> None:
|
||||
"""Optimise PNG in-place if optipng is available."""
|
||||
if _has_command("optipng"):
|
||||
try:
|
||||
subprocess.run(
|
||||
["optipng", "-quiet", "-o2", path],
|
||||
check=True, capture_output=True,
|
||||
)
|
||||
except subprocess.CalledProcessError:
|
||||
pass
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Core logic
|
||||
# ──────────────────────────────────────────────
|
||||
def process_file(src_path: str, dst_path: str) -> dict:
|
||||
"""
|
||||
Process a single file: minify, gzip or copy.
|
||||
Returns a small stats dict.
|
||||
"""
|
||||
ext = os.path.splitext(src_path)[1].lower()
|
||||
original_size = os.path.getsize(src_path)
|
||||
stats = {"src": src_path, "original": original_size, "final": 0, "action": "copy"}
|
||||
|
||||
os.makedirs(os.path.dirname(dst_path), exist_ok=True)
|
||||
|
||||
# ── Text assets: minify + gzip ──────────
|
||||
if ext in MINIFY_AND_GZIP:
|
||||
# Try terser for JS first
|
||||
if ext == ".js" and _try_terser(src_path, dst_path + ".tmp"):
|
||||
with open(dst_path + ".tmp", "rb") as f:
|
||||
minified = f.read()
|
||||
os.remove(dst_path + ".tmp")
|
||||
stats["action"] = "terser+gzip"
|
||||
else:
|
||||
with open(src_path, "r", encoding="utf-8", errors="ignore") as f:
|
||||
content = f.read()
|
||||
|
||||
minifier = MINIFIERS.get(ext)
|
||||
if minifier:
|
||||
content = minifier(content)
|
||||
stats["action"] = "minify+gzip"
|
||||
else:
|
||||
stats["action"] = "gzip"
|
||||
|
||||
minified = content.encode("utf-8")
|
||||
|
||||
# Write gzipped version
|
||||
gz_path = dst_path + ".gz"
|
||||
with gzip.open(gz_path, "wb", compresslevel=9) as gz:
|
||||
gz.write(minified)
|
||||
|
||||
stats["final"] = os.path.getsize(gz_path)
|
||||
return stats
|
||||
|
||||
# ── PNG: copy + optional optipng ────────
|
||||
if ext in PNG_EXTENSIONS:
|
||||
shutil.copy2(src_path, dst_path)
|
||||
_try_optipng(dst_path)
|
||||
stats["final"] = os.path.getsize(dst_path)
|
||||
stats["action"] = "optipng" if _has_command("optipng") else "copy"
|
||||
return stats
|
||||
|
||||
# ── Everything else: plain copy ─────────
|
||||
shutil.copy2(src_path, dst_path)
|
||||
stats["final"] = os.path.getsize(dst_path)
|
||||
return stats
|
||||
|
||||
|
||||
def minify_all():
|
||||
src_dir = _src_dir()
|
||||
dst_dir = _dst_dir()
|
||||
|
||||
if not os.path.isdir(src_dir):
|
||||
print(f"[minify] WARNING: '{SRC_DIR_NAME}/' not found – skipping.")
|
||||
return
|
||||
|
||||
print(f"[minify] {SRC_DIR_NAME}/ → {DST_DIR_NAME}/")
|
||||
|
||||
# Clean destination
|
||||
if os.path.exists(dst_dir):
|
||||
shutil.rmtree(dst_dir)
|
||||
os.makedirs(dst_dir, exist_ok=True)
|
||||
|
||||
total_original = 0
|
||||
total_final = 0
|
||||
file_count = 0
|
||||
|
||||
for root, dirs, files in os.walk(src_dir):
|
||||
for fname in sorted(files):
|
||||
# Skip hidden files and editor temp files
|
||||
if fname.startswith(".") or fname.endswith("~"):
|
||||
continue
|
||||
|
||||
src_path = os.path.join(root, fname)
|
||||
rel_path = os.path.relpath(src_path, src_dir)
|
||||
dst_path = os.path.join(dst_dir, rel_path)
|
||||
|
||||
stats = process_file(src_path, dst_path)
|
||||
|
||||
pct = (1 - stats["final"] / stats["original"]) * 100 if stats["original"] > 0 else 0
|
||||
print(
|
||||
f" {rel_path:<40s} "
|
||||
f"{stats['original']:>8,d} → {stats['final']:>8,d} B "
|
||||
f"({pct:+.0f}%) [{stats['action']}]"
|
||||
)
|
||||
|
||||
total_original += stats["original"]
|
||||
total_final += stats["final"]
|
||||
file_count += 1
|
||||
|
||||
print(f"[minify] {file_count} files processed")
|
||||
print(
|
||||
f"[minify] Total: {total_original:,d} → {total_final:,d} bytes "
|
||||
f"(saved {total_original - total_final:,d} bytes, "
|
||||
f"{(1 - total_final / total_original) * 100:.0f}%)"
|
||||
)
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# PlatformIO hooks
|
||||
# ──────────────────────────────────────────────
|
||||
# Run at the start of every build
|
||||
minify_all()
|
||||
66
platformio.ini
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[platformio]
|
||||
default_envs = esp32devdbg
|
||||
|
||||
[env]
|
||||
platform = espressif32 @ 5.4.0
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
bblanchon/ArduinoJson@^7.2.2
|
||||
lsatan/SmartRC-CC1101-Driver-Lib@^2.5.7
|
||||
knolleary/PubSubClient@^2.8
|
||||
esp32async/ESPAsyncWebServer@^3.10.3
|
||||
esp32async/AsyncTCP@^3.4.10
|
||||
extra_scripts =
|
||||
pre:minify.py
|
||||
post:archive_elf.py
|
||||
|
||||
board_build.filesystem = littlefs
|
||||
build_flags =
|
||||
-DCORE_DEBUG_LEVEL=1
|
||||
-DCONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=1
|
||||
-DCONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=1
|
||||
-DCONFIG_ESP_COREDUMP_CHECKSUM_CRC32=1
|
||||
-DCONFIG_ESP_TASK_WDT_PANIC=1
|
||||
-DCONFIG_ESP_COREDUMP_DECODE_INFO=1
|
||||
monitor_speed = 115200
|
||||
monitor_filters =
|
||||
time
|
||||
esp32_exception_decoder
|
||||
log2file
|
||||
|
||||
[env:esp32dev]
|
||||
board = esp32dev
|
||||
board_build.partitions = esp32_3MB.csv
|
||||
|
||||
[env:esp32devdbg]
|
||||
board = esp32dev
|
||||
build_type = debug
|
||||
board_build.partitions = esp32_3MB.csv
|
||||
|
||||
[env:esp32c3]
|
||||
board = esp32-c3-devkitm-1
|
||||
board_build.partitions = esp32_3MB.csv
|
||||
|
||||
[env:esp32s3]
|
||||
board = esp32-s3-devkitc-1
|
||||
|
||||
[env:esp32c6]
|
||||
platform = https://github.com/mnowak32/platform-espressif32.git#boards/seeed_xiao_esp32c6
|
||||
platform_packages =
|
||||
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.4
|
||||
framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.4/esp32-arduino-libs-3.0.4.zip
|
||||
board = seeed_xiao_esp32c6
|
||||
board_build.partitions = esp32_3MB.csv
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-I${platformio.packages_dir}/framework-arduinoespressif32/libraries/Network/src
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
#include <Arduino.h>
|
||||
#include <LittleFS.h>
|
||||
#include <Preferences.h>
|
||||
#include "esp_log.h"
|
||||
#include "ConfigFile.h"
|
||||
#include "Utils.h"
|
||||
#include "ConfigSettings.h"
|
||||
|
||||
static const char *TAG = "ConfigFile";
|
||||
|
||||
extern Preferences pref;
|
||||
|
||||
#define SHADE_HDR_VER 24
|
||||
|
|
@ -22,10 +25,15 @@ bool ConfigFile::begin(const char* filename, bool readOnly) {
|
|||
this->_opened = true;
|
||||
return true;
|
||||
}
|
||||
bool ConfigFile::beginRAM(String *buf) {
|
||||
_ramBuf = buf;
|
||||
_opened = true;
|
||||
return true;
|
||||
}
|
||||
void ConfigFile::end() {
|
||||
if(this->isOpen()) {
|
||||
if(!this->readOnly) this->file.flush();
|
||||
this->file.close();
|
||||
if(_ramBuf) { _ramBuf = nullptr; }
|
||||
else { if(!this->readOnly) this->file.flush(); this->file.close(); }
|
||||
}
|
||||
this->_opened = false;
|
||||
}
|
||||
|
|
@ -63,7 +71,7 @@ bool ConfigFile::writeHeader(const config_header_t &hdr) {
|
|||
bool ConfigFile::readHeader() {
|
||||
if(!this->isOpen()) return false;
|
||||
//if(this->file.position() != 0) this->file.seek(0, SeekSet);
|
||||
Serial.printf("Reading header at %u\n", this->file.position());
|
||||
ESP_LOGD(TAG, "Reading header at %u", this->file.position());
|
||||
this->header.version = this->readUInt8(this->header.version);
|
||||
this->header.length = this->readUInt8(0);
|
||||
if(this->header.version >= 19) {
|
||||
|
|
@ -88,7 +96,7 @@ bool ConfigFile::readHeader() {
|
|||
this->header.transRecordSize = this->readUInt16(this->header.transRecordSize);
|
||||
this->readString(this->header.serverId, sizeof(this->header.serverId));
|
||||
}
|
||||
Serial.printf("version:%u len:%u roomSize:%u roomRecs:%u shadeSize:%u shadeRecs:%u groupSize:%u groupRecs: %u pos:%d\n", this->header.version, this->header.length, this->header.roomRecordSize, this->header.roomRecords, this->header.shadeRecordSize, this->header.shadeRecords, this->header.groupRecordSize, this->header.groupRecords, this->file.position());
|
||||
ESP_LOGD(TAG, "version:%u len:%u roomSize:%u roomRecs:%u shadeSize:%u shadeRecs:%u groupSize:%u groupRecs: %u pos:%d", this->header.version, this->header.length, this->header.roomRecordSize, this->header.roomRecords, this->header.shadeRecordSize, this->header.shadeRecords, this->header.groupRecordSize, this->header.groupRecords, this->file.position());
|
||||
return true;
|
||||
}
|
||||
/*
|
||||
|
|
@ -187,10 +195,16 @@ bool ConfigFile::readVarString(char *buff, size_t len) {
|
|||
bool ConfigFile::writeString(const char *val, size_t len, const char tok) {
|
||||
if(!this->isOpen()) return false;
|
||||
int slen = strlen(val);
|
||||
if(_ramBuf) {
|
||||
if(slen > 0) _ramBuf->concat(val);
|
||||
while(slen < (int)len - 1) { _ramBuf->concat(' '); slen++; }
|
||||
if(tok != CFG_TOK_NONE) _ramBuf->concat(tok);
|
||||
return true;
|
||||
}
|
||||
if(slen > 0)
|
||||
if(this->file.write((uint8_t *)val, slen) != slen) return false;
|
||||
// Now we need to pad the end of the string so that it is of a fixed length.
|
||||
while(slen < len - 1) {
|
||||
while(slen < (int)len - 1) {
|
||||
this->file.write(' ');
|
||||
slen++;
|
||||
}
|
||||
|
|
@ -202,6 +216,13 @@ bool ConfigFile::writeString(const char *val, size_t len, const char tok) {
|
|||
bool ConfigFile::writeVarString(const char *val, const char tok) {
|
||||
if(!this->isOpen()) return false;
|
||||
int slen = strlen(val);
|
||||
if(_ramBuf) {
|
||||
_ramBuf->concat((char)CFG_TOK_QUOTE);
|
||||
if(slen > 0) _ramBuf->concat(val);
|
||||
_ramBuf->concat((char)CFG_TOK_QUOTE);
|
||||
if(tok != CFG_TOK_NONE) _ramBuf->concat(tok);
|
||||
return true;
|
||||
}
|
||||
this->writeChar(CFG_TOK_QUOTE);
|
||||
if(slen > 0) if(this->file.write((uint8_t *)val, slen) != slen) return false;
|
||||
this->writeChar(CFG_TOK_QUOTE);
|
||||
|
|
@ -210,6 +231,7 @@ bool ConfigFile::writeVarString(const char *val, const char tok) {
|
|||
}
|
||||
bool ConfigFile::writeChar(const char val) {
|
||||
if(!this->isOpen()) return false;
|
||||
if(_ramBuf) { _ramBuf->concat(val); return true; }
|
||||
if(this->file.write(static_cast<uint8_t>(val)) == 1) return true;
|
||||
return false;
|
||||
}
|
||||
|
|
@ -384,39 +406,34 @@ bool ShadeConfigFile::backup(SomfyShadeController *s) {
|
|||
bool ShadeConfigFile::validate() {
|
||||
this->readHeader();
|
||||
if(this->header.version < 1) {
|
||||
Serial.print("Invalid Header Version:");
|
||||
Serial.println(this->header.version);
|
||||
ESP_LOGE(TAG, "Invalid Header Version:%u", this->header.version);
|
||||
return false;
|
||||
}
|
||||
if(this->header.shadeRecordSize < 100) {
|
||||
Serial.print("Invalid Shade Record Size:");
|
||||
Serial.println(this->header.shadeRecordSize);
|
||||
ESP_LOGE(TAG, "Invalid Shade Record Size:%u", this->header.shadeRecordSize);
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
if(this->header.shadeRecords != SOMFY_MAX_SHADES) {
|
||||
Serial.print("Invalid Shade Record Count:");
|
||||
Serial.println(this->header.shadeRecords);
|
||||
ESP_LOGE(TAG, "Invalid Shade Record Count:%u", this->header.shadeRecords);
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
if(this->header.version > 10) {
|
||||
if(this->header.groupRecordSize < 100) {
|
||||
Serial.print("Invalid Group Record Size:");
|
||||
Serial.println(this->header.groupRecordSize);
|
||||
ESP_LOGE(TAG, "Invalid Group Record Size:%u", this->header.groupRecordSize);
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
if(this->header.groupRecords != SOMFY_MAX_GROUPS) {
|
||||
Serial.print("Invalid Group Record Count:");
|
||||
Serial.println(this->header.groupRecords);
|
||||
ESP_LOGE(TAG, "Invalid Group Record Count:%u", this->header.groupRecords);
|
||||
return false;
|
||||
|
||||
}
|
||||
*/
|
||||
}
|
||||
if(this->file.position() != this->header.length) {
|
||||
Serial.printf("File not positioned at %u end of header: %d\n", this->header.length, this->file.position());
|
||||
ESP_LOGE(TAG, "File not positioned at %u end of header: %d", this->header.length, this->file.position());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -433,7 +450,7 @@ bool ShadeConfigFile::validate() {
|
|||
fsize += (this->header.repeaterRecordSize * this->header.repeaterRecords);
|
||||
}
|
||||
if(this->file.size() != fsize) {
|
||||
Serial.printf("File size is not correct should be %d and got %d\n", fsize, this->file.size());
|
||||
ESP_LOGE(TAG, "File size is not correct should be %d and got %d", fsize, this->file.size());
|
||||
}
|
||||
// Next check to see if the records match the header length.
|
||||
uint8_t recs = 0;
|
||||
|
|
@ -442,11 +459,11 @@ bool ShadeConfigFile::validate() {
|
|||
while(recs < this->header.roomRecords) {
|
||||
uint32_t pos = this->file.position();
|
||||
if(!this->seekChar(CFG_REC_END)) {
|
||||
Serial.printf("Failed to find the room record end %d\n", recs);
|
||||
ESP_LOGE(TAG, "Failed to find the room record end %d", recs);
|
||||
return false;
|
||||
}
|
||||
if(this->file.position() - pos != this->header.roomRecordSize) {
|
||||
Serial.printf("Room record length is %d and should be %d\n", this->file.position() - pos, this->header.roomRecordSize);
|
||||
ESP_LOGE(TAG, "Room record length is %d and should be %d", this->file.position() - pos, this->header.roomRecordSize);
|
||||
return false;
|
||||
}
|
||||
recs++;
|
||||
|
|
@ -456,11 +473,11 @@ bool ShadeConfigFile::validate() {
|
|||
while(recs < this->header.shadeRecords) {
|
||||
uint32_t pos = this->file.position();
|
||||
if(!this->seekChar(CFG_REC_END)) {
|
||||
Serial.printf("Failed to find the shade record end %d\n", recs);
|
||||
ESP_LOGE(TAG, "Failed to find the shade record end %d", recs);
|
||||
return false;
|
||||
}
|
||||
if(this->file.position() - pos != this->header.shadeRecordSize) {
|
||||
Serial.printf("Shade record length is %d and should be %d\n", this->file.position() - pos, this->header.shadeRecordSize);
|
||||
ESP_LOGE(TAG, "Shade record length is %d and should be %d", this->file.position() - pos, this->header.shadeRecordSize);
|
||||
return false;
|
||||
}
|
||||
recs++;
|
||||
|
|
@ -470,12 +487,12 @@ bool ShadeConfigFile::validate() {
|
|||
while(recs < this->header.groupRecords) {
|
||||
uint32_t pos = this->file.position();
|
||||
if(!this->seekChar(CFG_REC_END)) {
|
||||
Serial.printf("Failed to find the group record end %d\n", recs);
|
||||
ESP_LOGE(TAG, "Failed to find the group record end %d", recs);
|
||||
return false;
|
||||
}
|
||||
recs++;
|
||||
if(this->file.position() - pos != this->header.groupRecordSize) {
|
||||
Serial.printf("Group record length is %d and should be %d\n", this->file.position() - pos, this->header.groupRecordSize);
|
||||
ESP_LOGE(TAG, "Group record length is %d and should be %d", this->file.position() - pos, this->header.groupRecordSize);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -485,7 +502,7 @@ bool ShadeConfigFile::validate() {
|
|||
while(recs < this->header.repeaterRecords) {
|
||||
//uint32_t pos = this->file.position();
|
||||
if(!this->seekChar(CFG_REC_END)) {
|
||||
Serial.printf("Failed to find the repeater record end %d\n", recs);
|
||||
ESP_LOGE(TAG, "Failed to find the repeater record end %d", recs);
|
||||
}
|
||||
recs++;
|
||||
|
||||
|
|
@ -515,30 +532,27 @@ bool ShadeConfigFile::restore(SomfyShadeController *s, const char *filename, res
|
|||
bool ShadeConfigFile::restoreFile(SomfyShadeController *s, const char *filename, restore_options_t &opts) {
|
||||
bool opened = false;
|
||||
if(!this->isOpen()) {
|
||||
Serial.println("Opening shade restore file");
|
||||
ESP_LOGI(TAG, "Opening shade restore file");
|
||||
this->begin(filename, true);
|
||||
opened = true;
|
||||
}
|
||||
if(!this->validate()) {
|
||||
Serial.println("Shade restore file invalid!");
|
||||
ESP_LOGE(TAG, "Shade restore file invalid!");
|
||||
if(opened) this->end();
|
||||
return false;
|
||||
}
|
||||
if(opts.shades) {
|
||||
Serial.println("Restoring Rooms...");
|
||||
ESP_LOGI(TAG, "Restoring Rooms...");
|
||||
for(uint8_t i = 0; i < this->header.roomRecords; i++) {
|
||||
this->readRoomRecord(&s->rooms[i]);
|
||||
if(i > 0) Serial.print(",");
|
||||
Serial.print(s->rooms[i].roomId);
|
||||
ESP_LOGD(TAG, "Room %u", s->rooms[i].roomId);
|
||||
}
|
||||
Serial.println("Restoring Shades...");
|
||||
ESP_LOGI(TAG, "Restoring Shades...");
|
||||
// We should be valid so start reading.
|
||||
for(uint8_t i = 0; i < this->header.shadeRecords; i++) {
|
||||
this->readShadeRecord(&s->shades[i]);
|
||||
if(i > 0) Serial.print(",");
|
||||
Serial.print(s->shades[i].getShadeId());
|
||||
ESP_LOGD(TAG, "Shade %u", s->shades[i].getShadeId());
|
||||
}
|
||||
Serial.println("");
|
||||
if(this->header.shadeRecords < SOMFY_MAX_SHADES) {
|
||||
uint8_t ndx = this->header.shadeRecords;
|
||||
// Clear out any positions that are not in the shade file.
|
||||
|
|
@ -546,13 +560,11 @@ bool ShadeConfigFile::restoreFile(SomfyShadeController *s, const char *filename,
|
|||
((SomfyShade *)&s->shades[ndx++])->clear();
|
||||
}
|
||||
}
|
||||
Serial.println("Restoring Groups...");
|
||||
ESP_LOGI(TAG, "Restoring Groups...");
|
||||
for(uint8_t i = 0; i < this->header.groupRecords; i++) {
|
||||
if(i > 0) Serial.print(",");
|
||||
Serial.print(s->groups[i].getGroupId());
|
||||
ESP_LOGD(TAG, "Group %u", s->groups[i].getGroupId());
|
||||
this->readGroupRecord(&s->groups[i]);
|
||||
}
|
||||
Serial.println("");
|
||||
if(this->header.groupRecords < SOMFY_MAX_GROUPS) {
|
||||
uint8_t ndx = this->header.groupRecords;
|
||||
// Clear out any positions that are not in the shade file.
|
||||
|
|
@ -562,14 +574,14 @@ bool ShadeConfigFile::restoreFile(SomfyShadeController *s, const char *filename,
|
|||
}
|
||||
}
|
||||
else {
|
||||
Serial.println("Shade data ignored");
|
||||
ESP_LOGI(TAG, "Shade data ignored");
|
||||
// FF past the shades and groups.
|
||||
this->file.seek(this->file.position()
|
||||
+ (this->header.shadeRecords * this->header.shadeRecordSize)
|
||||
+ (this->header.groupRecords * this->header.groupRecordSize), SeekSet); // Start at the beginning of the file after the header.
|
||||
}
|
||||
if(opts.repeaters) {
|
||||
Serial.println("Restoring Repeaters...");
|
||||
ESP_LOGI(TAG, "Restoring Repeaters...");
|
||||
if(this->header.repeaterRecords > 0) {
|
||||
memset(s->repeaters, 0x00, sizeof(uint32_t) * SOMFY_MAX_REPEATERS);
|
||||
for(uint8_t i = 0; i < this->header.repeaterRecords; i++) {
|
||||
|
|
@ -604,7 +616,9 @@ bool ShadeConfigFile::restoreFile(SomfyShadeController *s, const char *filename,
|
|||
if(opts.network) {
|
||||
settings.IP.save();
|
||||
settings.WIFI.save();
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
settings.Ethernet.save();
|
||||
#endif
|
||||
}
|
||||
if(opts.mqtt) settings.MQTT.save();
|
||||
return true;
|
||||
|
|
@ -613,7 +627,7 @@ bool ShadeConfigFile::readNetRecord(restore_options_t &opts) {
|
|||
if(this->header.netRecordSize > 0) {
|
||||
uint32_t startPos = this->file.position();
|
||||
if(opts.network) {
|
||||
Serial.println("Reading network settings from file...");
|
||||
ESP_LOGD(TAG, "Reading network settings from file...");
|
||||
settings.connType = static_cast<conn_types_t>(this->readUInt8(static_cast<uint8_t>(conn_types_t::unset)));
|
||||
settings.IP.dhcp = this->readBool(true);
|
||||
char ip[24];
|
||||
|
|
@ -659,7 +673,8 @@ bool ShadeConfigFile::readNetRecord(restore_options_t &opts) {
|
|||
// the ethernet phy settings.
|
||||
if(opts.network) {
|
||||
if(strncmp(settings.serverId, this->header.serverId, sizeof(settings.serverId)) == 0) {
|
||||
Serial.println("Restoring Ethernet adapter settings");
|
||||
ESP_LOGI(TAG, "Restoring Ethernet adapter settings");
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
settings.Ethernet.boardType = this->readUInt8(1);
|
||||
settings.Ethernet.phyType = static_cast<eth_phy_type_t>(this->readUInt8(0));
|
||||
settings.Ethernet.CLKMode = static_cast<eth_clock_mode_t>(this->readUInt8(0));
|
||||
|
|
@ -667,10 +682,11 @@ bool ShadeConfigFile::readNetRecord(restore_options_t &opts) {
|
|||
settings.Ethernet.PWRPin = this->readInt8(1);
|
||||
settings.Ethernet.MDCPin = this->readInt8(16);
|
||||
settings.Ethernet.MDIOPin = this->readInt8(23);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if(this->file.position() != startPos + this->header.netRecordSize) {
|
||||
Serial.println("Reading to end of network record");
|
||||
ESP_LOGD(TAG, "Reading to end of network record");
|
||||
this->seekChar(CFG_REC_END);
|
||||
}
|
||||
}
|
||||
|
|
@ -679,7 +695,7 @@ bool ShadeConfigFile::readNetRecord(restore_options_t &opts) {
|
|||
bool ShadeConfigFile::readTransRecord(transceiver_config_t &cfg) {
|
||||
if(this->header.transRecordSize > 0) {
|
||||
uint32_t startPos = this->file.position();
|
||||
Serial.println("Reading Transceiver settings from file...");
|
||||
ESP_LOGD(TAG, "Reading Transceiver settings from file...");
|
||||
cfg.enabled = this->readBool(false);
|
||||
cfg.proto = static_cast<radio_proto>(this->readUInt8(0));
|
||||
cfg.type = this->readUInt8(56);
|
||||
|
|
@ -694,7 +710,7 @@ bool ShadeConfigFile::readTransRecord(transceiver_config_t &cfg) {
|
|||
cfg.deviation = this->readFloat(cfg.deviation);
|
||||
cfg.txPower = this->readInt8(cfg.txPower);
|
||||
if(this->file.position() != startPos + this->header.transRecordSize) {
|
||||
Serial.println("Reading to end of transceiver record");
|
||||
ESP_LOGD(TAG, "Reading to end of transceiver record");
|
||||
this->seekChar(CFG_REC_END);
|
||||
}
|
||||
|
||||
|
|
@ -704,7 +720,7 @@ bool ShadeConfigFile::readTransRecord(transceiver_config_t &cfg) {
|
|||
bool ShadeConfigFile::readSettingsRecord() {
|
||||
if(this->header.settingsRecordSize > 0) {
|
||||
uint32_t startPos = this->file.position();
|
||||
Serial.println("Reading settings from file...");
|
||||
ESP_LOGD(TAG, "Reading settings from file...");
|
||||
char ver[24];
|
||||
this->readVarString(ver, sizeof(ver));
|
||||
this->readVarString(settings.hostname, sizeof(settings.hostname));
|
||||
|
|
@ -713,7 +729,7 @@ bool ShadeConfigFile::readSettingsRecord() {
|
|||
settings.ssdpBroadcast = this->readBool(false);
|
||||
if(this->header.version >= 20) settings.checkForUpdate = this->readBool(true);
|
||||
if(this->file.position() != startPos + this->header.settingsRecordSize) {
|
||||
Serial.println("Reading to end of settings record");
|
||||
ESP_LOGD(TAG, "Reading to end of settings record");
|
||||
this->seekChar(CFG_REC_END);
|
||||
}
|
||||
}
|
||||
|
|
@ -753,7 +769,7 @@ bool ShadeConfigFile::readGroupRecord(SomfyGroup *group) {
|
|||
|
||||
pref.end();
|
||||
if(this->file.position() != startPos + this->header.groupRecordSize) {
|
||||
Serial.println("Reading to end of group record");
|
||||
ESP_LOGD(TAG, "Reading to end of group record");
|
||||
this->seekChar(CFG_REC_END);
|
||||
}
|
||||
return true;
|
||||
|
|
@ -765,7 +781,7 @@ bool ShadeConfigFile::readRepeaterRecord(SomfyShadeController *s) {
|
|||
s->linkRepeater(this->readUInt32(0));
|
||||
}
|
||||
if(this->file.position() != startPos + this->header.repeaterRecordSize) {
|
||||
Serial.println("Reading to end of repeater record");
|
||||
ESP_LOGD(TAG, "Reading to end of repeater record");
|
||||
this->seekChar(CFG_REC_END);
|
||||
}
|
||||
return true;
|
||||
|
|
@ -776,7 +792,7 @@ bool ShadeConfigFile::readRoomRecord(SomfyRoom *room) {
|
|||
this->readString(room->name, sizeof(room->name));
|
||||
room->sortOrder = this->readUInt8(room->roomId - 1);
|
||||
if(this->file.position() != startPos + this->header.roomRecordSize) {
|
||||
Serial.println("Reading to end of room record");
|
||||
ESP_LOGD(TAG, "Reading to end of room record");
|
||||
this->seekChar(CFG_REC_END);
|
||||
}
|
||||
return true;
|
||||
|
|
@ -862,7 +878,7 @@ bool ShadeConfigFile::readShadeRecord(SomfyShade *shade) {
|
|||
pinMode(shade->gpioMy, OUTPUT);
|
||||
if(this->header.version >= 19) shade->roomId = this->readUInt8(0);
|
||||
if(this->file.position() != startPos + this->header.shadeRecordSize) {
|
||||
Serial.println("Reading to end of shade record");
|
||||
ESP_LOGD(TAG, "Reading to end of shade record");
|
||||
this->seekChar(CFG_REC_END);
|
||||
}
|
||||
return true;
|
||||
|
|
@ -870,12 +886,12 @@ bool ShadeConfigFile::readShadeRecord(SomfyShade *shade) {
|
|||
bool ShadeConfigFile::loadFile(SomfyShadeController *s, const char *filename) {
|
||||
bool opened = false;
|
||||
if(!this->isOpen()) {
|
||||
Serial.println("Opening shade config file");
|
||||
ESP_LOGI(TAG, "Opening shade config file");
|
||||
this->begin(filename, true);
|
||||
opened = true;
|
||||
}
|
||||
if(!this->validate()) {
|
||||
Serial.println("Shade config file invalid!");
|
||||
ESP_LOGE(TAG, "Shade config file invalid!");
|
||||
if(opened) this->end();
|
||||
return false;
|
||||
}
|
||||
|
|
@ -917,7 +933,7 @@ bool ShadeConfigFile::loadFile(SomfyShadeController *s, const char *filename) {
|
|||
this->readRepeaterRecord(s);
|
||||
}
|
||||
if(opened) {
|
||||
Serial.println("Closing shade config file");
|
||||
ESP_LOGI(TAG, "Closing shade config file");
|
||||
this->end();
|
||||
}
|
||||
return true;
|
||||
|
|
@ -1023,6 +1039,7 @@ bool ShadeConfigFile::writeNetRecord() {
|
|||
this->writeBool(settings.MQTT.pubDisco);
|
||||
this->writeVarString(settings.MQTT.rootTopic);
|
||||
this->writeVarString(settings.MQTT.discoTopic);
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
this->writeUInt8(settings.Ethernet.boardType);
|
||||
this->writeUInt8(static_cast<uint8_t>(settings.Ethernet.phyType));
|
||||
this->writeUInt8(static_cast<uint8_t>(settings.Ethernet.CLKMode));
|
||||
|
|
@ -1030,6 +1047,9 @@ bool ShadeConfigFile::writeNetRecord() {
|
|||
this->writeInt8(settings.Ethernet.PWRPin);
|
||||
this->writeInt8(settings.Ethernet.MDCPin);
|
||||
this->writeInt8(settings.Ethernet.MDIOPin, CFG_REC_END);
|
||||
#else
|
||||
this->writeUInt8(0, CFG_REC_END);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
bool ShadeConfigFile::writeTransRecord(transceiver_config_t &cfg) {
|
||||
|
|
@ -31,12 +31,14 @@ struct config_header_t {
|
|||
class ConfigFile {
|
||||
protected:
|
||||
File file;
|
||||
String *_ramBuf = nullptr;
|
||||
bool readOnly = false;
|
||||
bool begin(const char *filename, bool readOnly = false);
|
||||
uint32_t startRecPos = 0;
|
||||
bool _opened = false;
|
||||
public:
|
||||
config_header_t header;
|
||||
bool beginRAM(String *buf);
|
||||
void end();
|
||||
bool isOpen();
|
||||
bool seekRecordByIndex(uint16_t ndx);
|
||||
|
|
@ -6,16 +6,19 @@
|
|||
#include "ConfigSettings.h"
|
||||
#include "Utils.h"
|
||||
#include "esp_chip_info.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG = "Config";
|
||||
|
||||
Preferences pref;
|
||||
|
||||
void restore_options_t::fromJSON(JsonObject &obj) {
|
||||
if(obj.containsKey("shades")) this->shades = obj["shades"];
|
||||
if(obj.containsKey("settings")) this->settings = obj["settings"];
|
||||
if(obj.containsKey("network")) this->network = obj["network"];
|
||||
if(obj.containsKey("transceiver")) this->transceiver = obj["transceiver"];
|
||||
if(obj.containsKey("repeaters")) this->repeaters = obj["repeaters"];
|
||||
if(obj.containsKey("mqtt")) this->mqtt = obj["mqtt"];
|
||||
if(!obj["shades"].isNull()) this->shades = obj["shades"];
|
||||
if(!obj["settings"].isNull()) this->settings = obj["settings"];
|
||||
if(!obj["network"].isNull()) this->network = obj["network"];
|
||||
if(!obj["transceiver"].isNull()) this->transceiver = obj["transceiver"];
|
||||
if(!obj["repeaters"].isNull()) this->repeaters = obj["repeaters"];
|
||||
if(!obj["mqtt"].isNull()) this->mqtt = obj["mqtt"];
|
||||
}
|
||||
int8_t appver_t::compare(appver_t &ver) {
|
||||
if(this->major == ver.major && this->minor == ver.minor && this->build == ver.build) return 0;
|
||||
|
|
@ -85,13 +88,6 @@ bool appver_t::toJSON(JsonObject &obj) {
|
|||
obj["suffix"] = this->suffix;
|
||||
return true;
|
||||
}
|
||||
void appver_t::toJSON(JsonResponse &json) {
|
||||
json.addElem("name", this->name);
|
||||
json.addElem("major", this->major);
|
||||
json.addElem("minor", this->minor);
|
||||
json.addElem("build", this->build);
|
||||
json.addElem("suffix", this->suffix);
|
||||
}
|
||||
void appver_t::toJSON(JsonSockEvent *json) {
|
||||
json->addElem("name", this->name);
|
||||
json->addElem("major", this->major);
|
||||
|
|
@ -111,7 +107,7 @@ bool BaseSettings::loadFile(const char *filename) {
|
|||
char c = file.read();
|
||||
data += c;
|
||||
}
|
||||
DynamicJsonDocument doc(filesize);
|
||||
JsonDocument doc;
|
||||
deserializeJson(doc, data);
|
||||
JsonObject obj = doc.as<JsonObject>();
|
||||
this->fromJSON(obj);
|
||||
|
|
@ -121,7 +117,7 @@ bool BaseSettings::loadFile(const char *filename) {
|
|||
}
|
||||
bool BaseSettings::saveFile(const char *filename) {
|
||||
File file = LittleFS.open(filename, "w");
|
||||
DynamicJsonDocument doc(2048);
|
||||
JsonDocument doc;
|
||||
JsonObject obj = doc.as<JsonObject>();
|
||||
this->toJSON(obj);
|
||||
serializeJson(doc, file);
|
||||
|
|
@ -129,11 +125,11 @@ bool BaseSettings::saveFile(const char *filename) {
|
|||
return true;
|
||||
}
|
||||
bool BaseSettings::parseValueString(JsonObject &obj, const char *prop, char *pdest, size_t size) {
|
||||
if(obj.containsKey(prop)) strlcpy(pdest, obj[prop], size);
|
||||
if(!obj[prop].isNull()) strlcpy(pdest, obj[prop], size);
|
||||
return true;
|
||||
}
|
||||
bool BaseSettings::parseIPAddress(JsonObject &obj, const char *prop, IPAddress *pdest) {
|
||||
if(obj.containsKey(prop)) {
|
||||
if(!obj[prop].isNull()) {
|
||||
char buff[16];
|
||||
strlcpy(buff, obj[prop], sizeof(buff));
|
||||
pdest->fromString(buff);
|
||||
|
|
@ -141,11 +137,11 @@ bool BaseSettings::parseIPAddress(JsonObject &obj, const char *prop, IPAddress *
|
|||
return true;
|
||||
}
|
||||
int BaseSettings::parseValueInt(JsonObject &obj, const char *prop, int defVal) {
|
||||
if(obj.containsKey(prop)) return obj[prop];
|
||||
if(!obj[prop].isNull()) return obj[prop];
|
||||
return defVal;
|
||||
}
|
||||
double BaseSettings::parseValueDouble(JsonObject &obj, const char *prop, double defVal) {
|
||||
if(obj.containsKey(prop)) return obj[prop];
|
||||
if(!obj[prop].isNull()) return obj[prop];
|
||||
return defVal;
|
||||
}
|
||||
bool ConfigSettings::begin() {
|
||||
|
|
@ -159,26 +155,19 @@ bool ConfigSettings::begin() {
|
|||
case esp_chip_model_t::CHIP_ESP32S3:
|
||||
strcpy(this->chipModel, "s3");
|
||||
break;
|
||||
case esp_chip_model_t::CHIP_ESP32S2:
|
||||
strcpy(this->chipModel, "s2");
|
||||
break;
|
||||
case esp_chip_model_t::CHIP_ESP32C3:
|
||||
strcpy(this->chipModel, "c3");
|
||||
break;
|
||||
// case esp_chip_model_t::CHIP_ESP32C2:
|
||||
// strcpy(this->chipModel, "c2");
|
||||
// break;
|
||||
// case esp_chip_model_t::CHIP_ESP32C6:
|
||||
// strcpy(this->chipModel, "c6");
|
||||
// break;
|
||||
case esp_chip_model_t::CHIP_ESP32H2:
|
||||
strcpy(this->chipModel, "h2");
|
||||
#ifdef CHIP_ESP32C6
|
||||
case esp_chip_model_t::CHIP_ESP32C6:
|
||||
strcpy(this->chipModel, "c6");
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
sprintf(this->chipModel, "UNK%d", static_cast<int>(ci.model));
|
||||
break;
|
||||
}
|
||||
Serial.printf("Chip Model ESP32-%s\n", this->chipModel);
|
||||
ESP_LOGD(TAG, "Chip Model ESP32-%s", this->chipModel);
|
||||
this->fwVersion.parse(FW_VERSION);
|
||||
uint64_t mac = ESP.getEfuseMac();
|
||||
for(int i=0; i<17; i=i+8) {
|
||||
|
|
@ -192,7 +181,9 @@ bool ConfigSettings::begin() {
|
|||
this->Security.begin();
|
||||
this->IP.begin();
|
||||
this->WIFI.begin();
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
this->Ethernet.begin();
|
||||
#endif
|
||||
this->NTP.begin();
|
||||
this->MQTT.begin();
|
||||
this->print();
|
||||
|
|
@ -249,28 +240,22 @@ bool ConfigSettings::toJSON(JsonObject &obj) {
|
|||
obj["checkForUpdate"] = this->checkForUpdate;
|
||||
return true;
|
||||
}
|
||||
void ConfigSettings::toJSON(JsonResponse &json) {
|
||||
json.addElem("ssdpBroadcast", this->ssdpBroadcast);
|
||||
json.addElem("hostname", this->hostname);
|
||||
json.addElem("connType", static_cast<uint8_t>(this->connType));
|
||||
json.addElem("chipModel", this->chipModel);
|
||||
json.addElem("checkForUpdate", this->checkForUpdate);
|
||||
}
|
||||
|
||||
bool ConfigSettings::requiresAuth() { return this->Security.type != security_types::None; }
|
||||
bool ConfigSettings::fromJSON(JsonObject &obj) {
|
||||
if(obj.containsKey("ssdpBroadcast")) this->ssdpBroadcast = obj["ssdpBroadcast"];
|
||||
if(obj.containsKey("hostname")) this->parseValueString(obj, "hostname", this->hostname, sizeof(this->hostname));
|
||||
if(obj.containsKey("connType")) this->connType = static_cast<conn_types_t>(obj["connType"].as<uint8_t>());
|
||||
if(obj.containsKey("checkForUpdate")) this->checkForUpdate = obj["checkForUpdate"];
|
||||
if(!obj["ssdpBroadcast"].isNull()) this->ssdpBroadcast = obj["ssdpBroadcast"];
|
||||
if(!obj["hostname"].isNull()) this->parseValueString(obj, "hostname", this->hostname, sizeof(this->hostname));
|
||||
if(!obj["connType"].isNull()) this->connType = static_cast<conn_types_t>(obj["connType"].as<uint8_t>());
|
||||
if(!obj["checkForUpdate"].isNull()) this->checkForUpdate = obj["checkForUpdate"];
|
||||
return true;
|
||||
}
|
||||
void ConfigSettings::print() {
|
||||
this->Security.print();
|
||||
Serial.printf("Connection Type: %u\n", (unsigned int) this->connType);
|
||||
ESP_LOGD(TAG, "Connection Type: %u", (unsigned int) this->connType);
|
||||
this->NTP.print();
|
||||
if(this->connType == conn_types_t::wifi || this->connType == conn_types_t::unset) this->WIFI.print();
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
if(this->connType == conn_types_t::ethernet || this->connType == conn_types_t::ethernetpref) this->Ethernet.print();
|
||||
#endif
|
||||
}
|
||||
void ConfigSettings::emitSockets() {}
|
||||
void ConfigSettings::emitSockets(uint8_t num) {}
|
||||
|
|
@ -308,18 +293,6 @@ bool MQTTSettings::begin() {
|
|||
this->load();
|
||||
return true;
|
||||
}
|
||||
void MQTTSettings::toJSON(JsonResponse &json) {
|
||||
json.addElem("enabled", this->enabled);
|
||||
json.addElem("pubDisco", this->pubDisco);
|
||||
json.addElem("protocol", this->protocol);
|
||||
json.addElem("hostname", this->hostname);
|
||||
json.addElem("port", (uint32_t)this->port);
|
||||
json.addElem("username", this->username);
|
||||
json.addElem("password", this->password);
|
||||
json.addElem("rootTopic", this->rootTopic);
|
||||
json.addElem("discoTopic", this->discoTopic);
|
||||
}
|
||||
|
||||
bool MQTTSettings::toJSON(JsonObject &obj) {
|
||||
obj["enabled"] = this->enabled;
|
||||
obj["pubDisco"] = this->pubDisco;
|
||||
|
|
@ -333,15 +306,15 @@ bool MQTTSettings::toJSON(JsonObject &obj) {
|
|||
return true;
|
||||
}
|
||||
bool MQTTSettings::fromJSON(JsonObject &obj) {
|
||||
if(obj.containsKey("enabled")) this->enabled = obj["enabled"];
|
||||
if(obj.containsKey("pubDisco")) this->pubDisco = obj["pubDisco"];
|
||||
if(!obj["enabled"].isNull()) this->enabled = obj["enabled"];
|
||||
if(!obj["pubDisco"].isNull()) this->pubDisco = obj["pubDisco"];
|
||||
this->parseValueString(obj, "protocol", this->protocol, sizeof(this->protocol));
|
||||
this->parseValueString(obj, "hostname", this->hostname, sizeof(this->hostname));
|
||||
this->parseValueString(obj, "username", this->username, sizeof(this->username));
|
||||
this->parseValueString(obj, "password", this->password, sizeof(this->password));
|
||||
this->parseValueString(obj, "rootTopic", this->rootTopic, sizeof(this->rootTopic));
|
||||
this->parseValueString(obj, "discoTopic", this->discoTopic, sizeof(this->discoTopic));
|
||||
if(obj.containsKey("port")) this->port = obj["port"];
|
||||
if(!obj["port"].isNull()) this->port = obj["port"];
|
||||
return true;
|
||||
}
|
||||
bool MQTTSettings::save() {
|
||||
|
|
@ -373,7 +346,7 @@ bool MQTTSettings::load() {
|
|||
pref.end();
|
||||
return true;
|
||||
}
|
||||
bool ConfigSettings::toJSON(DynamicJsonDocument &doc) {
|
||||
bool ConfigSettings::toJSON(JsonDocument &doc) {
|
||||
doc["fwVersion"] = this->fwVersion.name;
|
||||
JsonObject objWIFI = doc.createNestedObject("WIFI");
|
||||
this->WIFI.toJSON(objWIFI);
|
||||
|
|
@ -408,21 +381,13 @@ bool NTPSettings::load() {
|
|||
return true;
|
||||
}
|
||||
void NTPSettings::print() {
|
||||
Serial.println("NTP Settings ");
|
||||
Serial.print(this->ntpServer);
|
||||
Serial.print(" TZ:");
|
||||
Serial.println(this->posixZone);
|
||||
ESP_LOGD(TAG, "NTP Settings %s TZ:%s", this->ntpServer, this->posixZone);
|
||||
}
|
||||
bool NTPSettings::fromJSON(JsonObject &obj) {
|
||||
this->parseValueString(obj, "ntpServer", this->ntpServer, sizeof(this->ntpServer));
|
||||
this->parseValueString(obj, "posixZone", this->posixZone, sizeof(this->posixZone));
|
||||
return true;
|
||||
}
|
||||
void NTPSettings::toJSON(JsonResponse &json) {
|
||||
json.addElem("ntpServer", this->ntpServer);
|
||||
json.addElem("posixZone", this->posixZone);
|
||||
}
|
||||
|
||||
bool NTPSettings::toJSON(JsonObject &obj) {
|
||||
obj["ntpServer"] = this->ntpServer;
|
||||
obj["posixZone"] = this->posixZone;
|
||||
|
|
@ -441,7 +406,7 @@ bool IPSettings::begin() {
|
|||
return true;
|
||||
}
|
||||
bool IPSettings::fromJSON(JsonObject &obj) {
|
||||
if(obj.containsKey("dhcp")) this->dhcp = obj["dhcp"];
|
||||
if(!obj["dhcp"].isNull()) this->dhcp = obj["dhcp"];
|
||||
this->parseIPAddress(obj, "ip", &this->ip);
|
||||
this->parseIPAddress(obj, "gateway", &this->gateway);
|
||||
this->parseIPAddress(obj, "subnet", &this->subnet);
|
||||
|
|
@ -459,16 +424,6 @@ bool IPSettings::toJSON(JsonObject &obj) {
|
|||
obj["dns2"] = this->dns2 == ipEmpty ? "" : this->dns2.toString();
|
||||
return true;
|
||||
}
|
||||
void IPSettings::toJSON(JsonResponse &json) {
|
||||
IPAddress ipEmpty(0,0,0,0);
|
||||
json.addElem("dhcp", this->dhcp);
|
||||
json.addElem("ip", this->ip.toString().c_str());
|
||||
json.addElem("gateway", this->gateway.toString().c_str());
|
||||
json.addElem("subnet", this->subnet.toString().c_str());
|
||||
json.addElem("dns1", this->dns1.toString().c_str());
|
||||
json.addElem("dns2", this->dns2.toString().c_str());
|
||||
}
|
||||
|
||||
bool IPSettings::save() {
|
||||
pref.begin("IP");
|
||||
pref.clear();
|
||||
|
|
@ -505,7 +460,7 @@ bool IPSettings::load() {
|
|||
pref.getString("dns2", buff, sizeof(buff));
|
||||
this->dns2.fromString(buff);
|
||||
}
|
||||
Serial.printf("Preference IP Free Entries: %d\n", pref.freeEntries());
|
||||
ESP_LOGD(TAG, "Preference IP Free Entries: %d", pref.freeEntries());
|
||||
pref.end();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -514,11 +469,11 @@ bool SecuritySettings::begin() {
|
|||
return true;
|
||||
}
|
||||
bool SecuritySettings::fromJSON(JsonObject &obj) {
|
||||
if(obj.containsKey("type")) this->type = static_cast<security_types>(obj["type"].as<uint8_t>());
|
||||
if(!obj["type"].isNull()) this->type = static_cast<security_types>(obj["type"].as<uint8_t>());
|
||||
this->parseValueString(obj, "username", this->username, sizeof(this->username));
|
||||
this->parseValueString(obj, "password", this->password, sizeof(this->password));
|
||||
this->parseValueString(obj, "pin", this->pin, sizeof(this->pin));
|
||||
if(obj.containsKey("permissions")) this->permissions = obj["permissions"];
|
||||
if(!obj["permissions"].isNull()) this->permissions = obj["permissions"];
|
||||
return true;
|
||||
}
|
||||
bool SecuritySettings::toJSON(JsonObject &obj) {
|
||||
|
|
@ -529,14 +484,6 @@ bool SecuritySettings::toJSON(JsonObject &obj) {
|
|||
obj["permissions"] = this->permissions;
|
||||
return true;
|
||||
}
|
||||
void SecuritySettings::toJSON(JsonResponse &json) {
|
||||
json.addElem("type", static_cast<uint8_t>(this->type));
|
||||
json.addElem("username", this->username);
|
||||
json.addElem("password", this->password);
|
||||
json.addElem("pin", this->pin);
|
||||
json.addElem("permissions", this->permissions);
|
||||
}
|
||||
|
||||
bool SecuritySettings::save() {
|
||||
pref.begin("SEC");
|
||||
pref.clear();
|
||||
|
|
@ -559,16 +506,8 @@ bool SecuritySettings::load() {
|
|||
return true;
|
||||
}
|
||||
void SecuritySettings::print() {
|
||||
Serial.print("SECURITY Type:");
|
||||
Serial.print(static_cast<uint8_t>(this->type));
|
||||
Serial.print(" Username:[");
|
||||
Serial.print(this->username);
|
||||
Serial.print("] Password:[");
|
||||
Serial.print(this->password);
|
||||
Serial.print("] Pin:[");
|
||||
Serial.print(this->pin);
|
||||
Serial.print("] Permissions:");
|
||||
Serial.println(this->permissions);
|
||||
ESP_LOGD(TAG, "SECURITY Type:%u Username:[%s] Password:[%s] Pin:[%s] Permissions:%u",
|
||||
static_cast<uint8_t>(this->type), this->username, this->password, this->pin, this->permissions);
|
||||
}
|
||||
|
||||
WifiSettings::WifiSettings() {}
|
||||
|
|
@ -579,8 +518,8 @@ bool WifiSettings::begin() {
|
|||
bool WifiSettings::fromJSON(JsonObject &obj) {
|
||||
this->parseValueString(obj, "ssid", this->ssid, sizeof(this->ssid));
|
||||
this->parseValueString(obj, "passphrase", this->passphrase, sizeof(this->passphrase));
|
||||
if(obj.containsKey("roaming")) this->roaming = obj["roaming"];
|
||||
if(obj.containsKey("hidden")) this->hidden = obj["hidden"];
|
||||
if(!obj["roaming"].isNull()) this->roaming = obj["roaming"];
|
||||
if(!obj["hidden"].isNull()) this->hidden = obj["hidden"];
|
||||
return true;
|
||||
}
|
||||
bool WifiSettings::toJSON(JsonObject &obj) {
|
||||
|
|
@ -590,13 +529,6 @@ bool WifiSettings::toJSON(JsonObject &obj) {
|
|||
obj["hidden"] = this->hidden;
|
||||
return true;
|
||||
}
|
||||
void WifiSettings::toJSON(JsonResponse &json) {
|
||||
json.addElem("ssid", this->ssid);
|
||||
json.addElem("passphrase", this->passphrase);
|
||||
json.addElem("roaming", this->roaming);
|
||||
json.addElem("hidden", this->hidden);
|
||||
}
|
||||
|
||||
bool WifiSettings::save() {
|
||||
pref.begin("WIFI");
|
||||
pref.clear();
|
||||
|
|
@ -636,34 +568,17 @@ String WifiSettings::mapEncryptionType(int type) {
|
|||
return "Unknown";
|
||||
}
|
||||
void WifiSettings::print() {
|
||||
Serial.println("WIFI Settings");
|
||||
Serial.print(" SSID: [");
|
||||
Serial.print(this->ssid);
|
||||
Serial.print("] PassPhrase: [");
|
||||
Serial.print(this->passphrase);
|
||||
Serial.println("]");
|
||||
ESP_LOGD(TAG, "WIFI Settings SSID: [%s] PassPhrase: [%s]", this->ssid, this->passphrase);
|
||||
}
|
||||
void WifiSettings::printNetworks() {
|
||||
int n = WiFi.scanNetworks(false, false);
|
||||
Serial.print("Scanned ");
|
||||
Serial.print(n);
|
||||
Serial.println(" Networks...");
|
||||
ESP_LOGI(TAG, "Scanned %d Networks...", n);
|
||||
String network;
|
||||
for(int i = 0; i < n; i++) {
|
||||
if(WiFi.SSID(i).compareTo(this->ssid) == 0) Serial.print("*");
|
||||
else Serial.print(" ");
|
||||
Serial.print(i);
|
||||
Serial.print(": ");
|
||||
Serial.print(WiFi.SSID(i));
|
||||
Serial.print(" (");
|
||||
Serial.print(WiFi.RSSI(i));
|
||||
Serial.print("dBm) CH:");
|
||||
Serial.print(WiFi.channel(i));
|
||||
Serial.print(" MAC:");
|
||||
Serial.print(WiFi.BSSIDstr(i));
|
||||
Serial.println();
|
||||
ESP_LOGI(TAG, "%s%d: %s (%ddBm) CH:%d MAC:%s",
|
||||
(WiFi.SSID(i).compareTo(this->ssid) == 0) ? "*" : " ",
|
||||
i, WiFi.SSID(i).c_str(), WiFi.RSSI(i), WiFi.channel(i), WiFi.BSSIDstr(i).c_str());
|
||||
}
|
||||
|
||||
}
|
||||
bool WifiSettings::ssidExists(const char *ssid) {
|
||||
int n = WiFi.scanNetworks(false, true);
|
||||
|
|
@ -672,19 +587,20 @@ bool WifiSettings::ssidExists(const char *ssid) {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
EthernetSettings::EthernetSettings() {}
|
||||
bool EthernetSettings::begin() {
|
||||
this->load();
|
||||
return true;
|
||||
}
|
||||
bool EthernetSettings::fromJSON(JsonObject &obj) {
|
||||
if(obj.containsKey("boardType")) this->boardType = obj["boardType"];
|
||||
if(obj.containsKey("phyAddress")) this->phyAddress = obj["phyAddress"];
|
||||
if(obj.containsKey("CLKMode")) this->CLKMode = static_cast<eth_clock_mode_t>(obj["CLKMode"]);
|
||||
if(obj.containsKey("phyType")) this->phyType = static_cast<eth_phy_type_t>(obj["phyType"]);
|
||||
if(obj.containsKey("PWRPin")) this->PWRPin = obj["PWRPin"];
|
||||
if(obj.containsKey("MDCPin")) this->MDCPin = obj["MDCPin"];
|
||||
if(obj.containsKey("MDIOPin")) this->MDIOPin = obj["MDIOPin"];
|
||||
if(!obj["boardType"].isNull()) this->boardType = obj["boardType"];
|
||||
if(!obj["phyAddress"].isNull()) this->phyAddress = obj["phyAddress"];
|
||||
if(!obj["CLKMode"].isNull()) this->CLKMode = static_cast<eth_clock_mode_t>(obj["CLKMode"]);
|
||||
if(!obj["phyType"].isNull()) this->phyType = static_cast<eth_phy_type_t>(obj["phyType"]);
|
||||
if(!obj["PWRPin"].isNull()) this->PWRPin = obj["PWRPin"];
|
||||
if(!obj["MDCPin"].isNull()) this->MDCPin = obj["MDCPin"];
|
||||
if(!obj["MDIOPin"].isNull()) this->MDIOPin = obj["MDIOPin"];
|
||||
return true;
|
||||
}
|
||||
bool EthernetSettings::toJSON(JsonObject &obj) {
|
||||
|
|
@ -697,16 +613,6 @@ bool EthernetSettings::toJSON(JsonObject &obj) {
|
|||
obj["MDIOPin"] = this->MDIOPin;
|
||||
return true;
|
||||
}
|
||||
void EthernetSettings::toJSON(JsonResponse &json) {
|
||||
json.addElem("boardType", this->boardType);
|
||||
json.addElem("phyAddress", this->phyAddress);
|
||||
json.addElem("CLKMode", static_cast<uint8_t>(this->CLKMode));
|
||||
json.addElem("phyType", static_cast<uint8_t>(this->phyType));
|
||||
json.addElem("PWRPin", this->PWRPin);
|
||||
json.addElem("MDCPin", this->MDCPin);
|
||||
json.addElem("MDIOPin", this->MDIOPin);
|
||||
}
|
||||
|
||||
bool EthernetSettings::usesPin(uint8_t pin) {
|
||||
if((this->CLKMode == 0 || this->CLKMode == 1) && pin == 0) return true;
|
||||
else if(this->CLKMode == 2 && pin == 16) return true;
|
||||
|
|
@ -742,14 +648,12 @@ bool EthernetSettings::load() {
|
|||
return true;
|
||||
}
|
||||
void EthernetSettings::print() {
|
||||
Serial.println("Ethernet Settings");
|
||||
Serial.printf("Board:%d PHYType:%d CLK:%d ADDR:%d PWR:%d MDC:%d MDIO:%d\n", this->boardType, this->phyType, this->CLKMode, this->phyAddress, this->PWRPin, this->MDCPin, this->MDIOPin);
|
||||
ESP_LOGD(TAG, "Ethernet Settings Board:%d PHYType:%d CLK:%d ADDR:%d PWR:%d MDC:%d MDIO:%d",
|
||||
this->boardType, this->phyType, this->CLKMode, this->phyAddress, this->PWRPin, this->MDCPin, this->MDIOPin);
|
||||
}
|
||||
#endif // CONFIG_IDF_TARGET_ESP32C6
|
||||
void ConfigSettings::printAvailHeap() {
|
||||
Serial.print("Max Heap: ");
|
||||
Serial.println(ESP.getMaxAllocHeap());
|
||||
Serial.print("Free Heap: ");
|
||||
Serial.println(ESP.getFreeHeap());
|
||||
Serial.print("Min Heap: ");
|
||||
Serial.println(ESP.getMinFreeHeap());
|
||||
ESP_LOGD(TAG, "Max Heap: %u", (unsigned int)ESP.getMaxAllocHeap());
|
||||
ESP_LOGD(TAG, "Free Heap: %u", (unsigned int)ESP.getFreeHeap());
|
||||
ESP_LOGD(TAG, "Min Heap: %u", (unsigned int)ESP.getMinFreeHeap());
|
||||
}
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
#include <ArduinoJson.h>
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
#include <ETH.h>
|
||||
#endif
|
||||
#ifndef configsettings_h
|
||||
#define configsettings_h
|
||||
#include "WResp.h"
|
||||
#define FW_VERSION "v2.4.7"
|
||||
#define FW_VERSION "v2.4.8"
|
||||
enum class conn_types_t : byte {
|
||||
unset = 0x00,
|
||||
wifi = 0x01,
|
||||
|
|
@ -34,7 +36,7 @@ struct appver_t {
|
|||
char suffix[4] = "";
|
||||
void parse(const char *ver);
|
||||
bool toJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
|
||||
void toJSON(JsonSockEvent *json);
|
||||
int8_t compare(appver_t &ver);
|
||||
void copy(appver_t &ver);
|
||||
|
|
@ -46,7 +48,7 @@ class BaseSettings {
|
|||
bool loadFile(const char* filename);
|
||||
bool fromJSON(JsonObject &obj);
|
||||
bool toJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
|
||||
bool parseIPAddress(JsonObject &obj, const char *prop, IPAddress *);
|
||||
bool parseValueString(JsonObject &obj, const char *prop, char *dest, size_t size);
|
||||
int parseValueInt(JsonObject &obj, const char *prop, int defVal);
|
||||
|
|
@ -61,7 +63,7 @@ class NTPSettings: BaseSettings {
|
|||
char posixZone[64] = "";
|
||||
bool fromJSON(JsonObject &obj);
|
||||
bool toJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
|
||||
bool apply();
|
||||
bool begin();
|
||||
bool save();
|
||||
|
|
@ -79,7 +81,7 @@ class WifiSettings: BaseSettings {
|
|||
bool begin();
|
||||
bool fromJSON(JsonObject &obj);
|
||||
bool toJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
|
||||
String mapEncryptionType(int type);
|
||||
bool ssidExists(const char *ssid);
|
||||
void printNetworks();
|
||||
|
|
@ -88,6 +90,7 @@ class WifiSettings: BaseSettings {
|
|||
void print();
|
||||
|
||||
};
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
class EthernetSettings: BaseSettings {
|
||||
public:
|
||||
EthernetSettings();
|
||||
|
|
@ -98,16 +101,17 @@ class EthernetSettings: BaseSettings {
|
|||
int8_t PWRPin = ETH_PHY_POWER;
|
||||
int8_t MDCPin = ETH_PHY_MDC;
|
||||
int8_t MDIOPin = ETH_PHY_MDIO;
|
||||
|
||||
|
||||
bool begin();
|
||||
bool fromJSON(JsonObject &obj);
|
||||
bool toJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
|
||||
bool load();
|
||||
bool save();
|
||||
void print();
|
||||
bool usesPin(uint8_t pin);
|
||||
};
|
||||
#endif
|
||||
class IPSettings: BaseSettings {
|
||||
public:
|
||||
IPSettings();
|
||||
|
|
@ -120,7 +124,7 @@ class IPSettings: BaseSettings {
|
|||
bool begin();
|
||||
bool fromJSON(JsonObject &obj);
|
||||
bool toJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
|
||||
bool load();
|
||||
bool save();
|
||||
void print();
|
||||
|
|
@ -145,7 +149,7 @@ class SecuritySettings: BaseSettings {
|
|||
bool load();
|
||||
void print();
|
||||
bool toJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
|
||||
bool fromJSON(JsonObject &obj);
|
||||
};
|
||||
class MQTTSettings: BaseSettings {
|
||||
|
|
@ -163,7 +167,7 @@ class MQTTSettings: BaseSettings {
|
|||
bool save();
|
||||
bool load();
|
||||
bool toJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
|
||||
bool fromJSON(JsonObject &obj);
|
||||
};
|
||||
class ConfigSettings: BaseSettings {
|
||||
|
|
@ -180,21 +184,23 @@ class ConfigSettings: BaseSettings {
|
|||
uint8_t status;
|
||||
IPSettings IP;
|
||||
WifiSettings WIFI;
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
EthernetSettings Ethernet;
|
||||
#endif
|
||||
NTPSettings NTP;
|
||||
MQTTSettings MQTT;
|
||||
SecuritySettings Security;
|
||||
bool requiresAuth();
|
||||
bool fromJSON(JsonObject &obj);
|
||||
bool toJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
|
||||
bool begin();
|
||||
bool save();
|
||||
bool load();
|
||||
void print();
|
||||
void emitSockets();
|
||||
void emitSockets(uint8_t num);
|
||||
bool toJSON(DynamicJsonDocument &doc);
|
||||
bool toJSON(JsonDocument &doc);
|
||||
uint16_t calcSettingsRecSize();
|
||||
uint16_t calcNetRecSize();
|
||||
bool getAppVersion();
|
||||
|
|
@ -1,21 +1,26 @@
|
|||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
#include <ETH.h>
|
||||
#endif
|
||||
#include <WiFi.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include "esp_log.h"
|
||||
#include "ConfigSettings.h"
|
||||
#include "Network.h"
|
||||
#include "ESPNetwork.h"
|
||||
#include "Web.h"
|
||||
#include "Sockets.h"
|
||||
#include "Utils.h"
|
||||
#include "SSDP.h"
|
||||
#include "MQTT.h"
|
||||
|
||||
static const char *TAG = "Network";
|
||||
|
||||
extern ConfigSettings settings;
|
||||
extern Web webServer;
|
||||
extern SocketEmitter sockEmit;
|
||||
extern MQTTClass mqtt;
|
||||
extern rebootDelay_t rebootDelay;
|
||||
extern Network net;
|
||||
extern ESPNetwork net;
|
||||
extern SomfyShadeController somfy;
|
||||
|
||||
static unsigned long _lastHeapEmit = 0;
|
||||
|
|
@ -24,13 +29,13 @@ static bool _apScanning = false;
|
|||
static uint32_t _lastMaxHeap = 0;
|
||||
static uint32_t _lastHeap = 0;
|
||||
int connectRetries = 0;
|
||||
void Network::end() {
|
||||
void ESPNetwork::end() {
|
||||
SSDP.end();
|
||||
mqtt.end();
|
||||
sockEmit.end();
|
||||
delay(100);
|
||||
}
|
||||
bool Network::setup() {
|
||||
bool ESPNetwork::setup() {
|
||||
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
|
||||
WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
|
||||
WiFi.persistent(false);
|
||||
|
|
@ -41,29 +46,30 @@ bool Network::setup() {
|
|||
if(settings.connType == conn_types_t::wifi || settings.connType == conn_types_t::unset) {
|
||||
WiFi.persistent(false);
|
||||
if(settings.hostname[0] != '\0') WiFi.setHostname(settings.hostname);
|
||||
Serial.print("WiFi Mode: ");
|
||||
Serial.println(WiFi.getMode());
|
||||
ESP_LOGI(TAG, "WiFi Mode: %d", WiFi.getMode());
|
||||
WiFi.mode(WIFI_STA);
|
||||
}
|
||||
sockEmit.begin();
|
||||
return true;
|
||||
}
|
||||
conn_types_t Network::preferredConnType() {
|
||||
conn_types_t ESPNetwork::preferredConnType() {
|
||||
switch(settings.connType) {
|
||||
case conn_types_t::wifi:
|
||||
return settings.WIFI.ssid[0] != '\0' ? conn_types_t::wifi : conn_types_t::ap;
|
||||
case conn_types_t::unset:
|
||||
case conn_types_t::ap:
|
||||
return conn_types_t::ap;
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
case conn_types_t::ethernetpref:
|
||||
return settings.WIFI.ssid[0] != '\0' && (!ETH.linkUp() && this->ethStarted) ? conn_types_t::wifi : conn_types_t::ethernet;
|
||||
case conn_types_t::ethernet:
|
||||
return ETH.linkUp() || !this->ethStarted ? conn_types_t::ethernet : conn_types_t::ap;
|
||||
#endif
|
||||
default:
|
||||
return settings.connType;
|
||||
}
|
||||
}
|
||||
void Network::loop() {
|
||||
void ESPNetwork::loop() {
|
||||
// ORDER OF OPERATIONS:
|
||||
// ----------------------------------------------
|
||||
// 1. If we are in the middle of a connection process we need to simply bail after the connect method. The
|
||||
|
|
@ -87,7 +93,7 @@ void Network::loop() {
|
|||
(this->connected() && !settings.WIFI.roaming) || // We are already connected and should not be roaming.
|
||||
(this->softAPOpened && WiFi.softAPgetStationNum() != 0) || // The Soft AP is open and a user is connected.
|
||||
(ctype != conn_types_t::wifi)) { // The Ethernet link is up so we should ignore this scan.
|
||||
Serial.println("Cancelling WiFi STA Scan...");
|
||||
ESP_LOGI(TAG, "Cancelling WiFi STA Scan...");
|
||||
_apScanning = false;
|
||||
WiFi.scanDelete();
|
||||
}
|
||||
|
|
@ -99,11 +105,11 @@ void Network::loop() {
|
|||
if(this->getStrongestAP(settings.WIFI.ssid, bssid, &channel)) {
|
||||
if(!WiFi.BSSID() || memcmp(bssid, WiFi.BSSID(), sizeof(bssid)) != 0) {
|
||||
if(!this->connected()) {
|
||||
Serial.printf("Connecting to AP %02X:%02X:%02X:%02X:%02X:%02X CH: %d\n", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], channel);
|
||||
ESP_LOGD(TAG, "Connecting to AP %02X:%02X:%02X:%02X:%02X:%02X CH: %d", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], channel);
|
||||
this->connectWiFi(bssid, channel);
|
||||
}
|
||||
else {
|
||||
Serial.printf("Found stronger AP %02X:%02X:%02X:%02X:%02X:%02X CH: %d\n", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], channel);
|
||||
ESP_LOGD(TAG, "Found stronger AP %02X:%02X:%02X:%02X:%02X:%02X CH: %d", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], channel);
|
||||
this->changeAP(bssid, channel);
|
||||
}
|
||||
}
|
||||
|
|
@ -154,7 +160,7 @@ void Network::loop() {
|
|||
}
|
||||
else if(!settings.ssdpBroadcast && SSDP.isStarted) SSDP.end();
|
||||
}
|
||||
bool Network::changeAP(const uint8_t *bssid, const int32_t channel) {
|
||||
bool ESPNetwork::changeAP(const uint8_t *bssid, const int32_t channel) {
|
||||
esp_task_wdt_reset(); // Make sure we do not reboot here.
|
||||
if(SSDP.isStarted) SSDP.end();
|
||||
mqtt.disconnect();
|
||||
|
|
@ -167,7 +173,7 @@ bool Network::changeAP(const uint8_t *bssid, const int32_t channel) {
|
|||
this->connectStart = millis();
|
||||
return false;
|
||||
}
|
||||
void Network::emitSockets() {
|
||||
void ESPNetwork::emitSockets() {
|
||||
this->emitHeap();
|
||||
if(this->needsBroadcast ||
|
||||
(this->connType == conn_types_t::wifi && (abs(abs(WiFi.RSSI()) - abs(this->lastRSSI)) > 1 || WiFi.channel() != this->lastChannel))) {
|
||||
|
|
@ -176,7 +182,8 @@ void Network::emitSockets() {
|
|||
this->needsBroadcast = false;
|
||||
}
|
||||
}
|
||||
void Network::emitSockets(uint8_t num) {
|
||||
void ESPNetwork::emitSockets(uint8_t num) {
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
if(this->connType == conn_types_t::ethernet) {
|
||||
JsonSockEvent *json = sockEmit.beginEmit("ethernet");
|
||||
json->beginObject();
|
||||
|
|
@ -186,7 +193,9 @@ void Network::emitSockets(uint8_t num) {
|
|||
json->endObject();
|
||||
sockEmit.endEmit(num);
|
||||
}
|
||||
else {
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if(WiFi.status() == WL_CONNECTED) {
|
||||
JsonSockEvent *json = sockEmit.beginEmit("wifiStrength");
|
||||
json->beginObject();
|
||||
|
|
@ -220,12 +229,12 @@ void Network::emitSockets(uint8_t num) {
|
|||
}
|
||||
this->emitHeap(num);
|
||||
}
|
||||
void Network::setConnected(conn_types_t connType) {
|
||||
void ESPNetwork::setConnected(conn_types_t connType) {
|
||||
esp_task_wdt_reset();
|
||||
this->connType = connType;
|
||||
this->connectTime = millis();
|
||||
connectRetries = 0;
|
||||
Serial.println("Setting connected...");
|
||||
ESP_LOGI(TAG, "Setting connected...");
|
||||
if(this->connType == conn_types_t::wifi) {
|
||||
if(this->softAPOpened && WiFi.softAPgetStationNum() == 0) {
|
||||
WiFi.softAPdisconnect(true);
|
||||
|
|
@ -238,9 +247,10 @@ void Network::setConnected(conn_types_t connType) {
|
|||
this->channel = WiFi.channel();
|
||||
this->connectAttempts++;
|
||||
}
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
else if(this->connType == conn_types_t::ethernet) {
|
||||
if(this->softAPOpened) {
|
||||
Serial.println("Disonnecting from SoftAP");
|
||||
ESP_LOGI(TAG, "Disconnecting from SoftAP");
|
||||
WiFi.softAPdisconnect(true);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
}
|
||||
|
|
@ -248,18 +258,14 @@ void Network::setConnected(conn_types_t connType) {
|
|||
this->_connecting = false;
|
||||
this->wifiFallback = false;
|
||||
}
|
||||
#endif // CONFIG_IDF_TARGET_ESP32C6
|
||||
// NET: Begin this in the startup.
|
||||
//sockEmit.begin();
|
||||
esp_task_wdt_reset();
|
||||
|
||||
|
||||
if(this->connectAttempts == 1) {
|
||||
Serial.println();
|
||||
if(this->connType == conn_types_t::wifi) {
|
||||
Serial.print("Successfully Connected to WiFi!!!!");
|
||||
Serial.print(WiFi.localIP());
|
||||
Serial.print(" (");
|
||||
Serial.print(this->strength);
|
||||
Serial.println("dbm)");
|
||||
ESP_LOGI(TAG, "Successfully Connected to WiFi!!!! %s (%ddbm)", WiFi.localIP().toString().c_str(), this->strength);
|
||||
if(settings.IP.dhcp) {
|
||||
settings.IP.ip = WiFi.localIP();
|
||||
settings.IP.subnet = WiFi.subnetMask();
|
||||
|
|
@ -268,15 +274,9 @@ void Network::setConnected(conn_types_t connType) {
|
|||
settings.IP.dns2 = WiFi.dnsIP(1);
|
||||
}
|
||||
}
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
else {
|
||||
Serial.print("Successfully Connected to Ethernet!!! ");
|
||||
Serial.print(ETH.localIP());
|
||||
if(ETH.fullDuplex()) {
|
||||
Serial.print(" FULL DUPLEX");
|
||||
}
|
||||
Serial.print(" ");
|
||||
Serial.print(ETH.linkSpeed());
|
||||
Serial.println("Mbps");
|
||||
ESP_LOGI(TAG, "Successfully Connected to Ethernet!!! %s%s %dMbps", ETH.localIP().toString().c_str(), ETH.fullDuplex() ? " FULL DUPLEX" : "", ETH.linkSpeed());
|
||||
if(settings.IP.dhcp) {
|
||||
settings.IP.ip = ETH.localIP();
|
||||
settings.IP.subnet = ETH.subnetMask();
|
||||
|
|
@ -294,34 +294,17 @@ void Network::setConnected(conn_types_t connType) {
|
|||
sockEmit.endEmit();
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
Serial.println();
|
||||
Serial.print("Reconnected after ");
|
||||
Serial.print(1.0 * (millis() - this->connectStart)/1000);
|
||||
Serial.print("sec IP: ");
|
||||
if(this->connType == conn_types_t::wifi) {
|
||||
Serial.print(WiFi.localIP());
|
||||
Serial.print(" ");
|
||||
Serial.print(this->mac);
|
||||
Serial.print(" CH:");
|
||||
Serial.print(this->channel);
|
||||
Serial.print(" (");
|
||||
Serial.print(this->strength);
|
||||
Serial.print(" dBm)");
|
||||
ESP_LOGI(TAG, "Reconnected after %.3fsec IP: %s %s CH:%d (%d dBm) Disconnected %d times", 1.0 * (millis() - this->connectStart)/1000, WiFi.localIP().toString().c_str(), this->mac.c_str(), this->channel, this->strength, this->connectAttempts - 1);
|
||||
}
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
else {
|
||||
Serial.print(ETH.localIP());
|
||||
if(ETH.fullDuplex()) {
|
||||
Serial.print(" FULL DUPLEX");
|
||||
}
|
||||
Serial.print(" ");
|
||||
Serial.print(ETH.linkSpeed());
|
||||
Serial.print("Mbps");
|
||||
ESP_LOGI(TAG, "Reconnected after %.3fsec IP: %s%s %dMbps Disconnected %d times", 1.0 * (millis() - this->connectStart)/1000, ETH.localIP().toString().c_str(), ETH.fullDuplex() ? " FULL DUPLEX" : "", ETH.linkSpeed(), this->connectAttempts - 1);
|
||||
}
|
||||
Serial.print(" Disconnected ");
|
||||
Serial.print(this->connectAttempts - 1);
|
||||
Serial.println(" times");
|
||||
#endif
|
||||
}
|
||||
SSDP.setHTTPPort(80);
|
||||
SSDP.setSchemaURL(0, "upnp.xml");
|
||||
|
|
@ -345,7 +328,7 @@ void Network::setConnected(conn_types_t connType) {
|
|||
SSDP.setActive(0, true);
|
||||
esp_task_wdt_reset();
|
||||
if(MDNS.begin(settings.hostname)) {
|
||||
Serial.printf("MDNS Responder Started: serverId=%s\n", settings.serverId);
|
||||
ESP_LOGI(TAG, "MDNS Responder Started: serverId=%s", settings.serverId);
|
||||
MDNS.addService("http", "tcp", 80);
|
||||
//MDNS.addServiceTxt("http", "tcp", "board", "ESP32");
|
||||
//MDNS.addServiceTxt("http", "tcp", "model", "ESPSomfyRTS");
|
||||
|
|
@ -365,7 +348,8 @@ void Network::setConnected(conn_types_t connType) {
|
|||
settings.printAvailHeap();
|
||||
this->needsBroadcast = true;
|
||||
}
|
||||
bool Network::connectWired() {
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
bool ESPNetwork::connectWired() {
|
||||
if(ETH.linkUp()) {
|
||||
// If the ethernet link is re-established then we need to shut down wifi.
|
||||
if(WiFi.status() == WL_CONNECTED) {
|
||||
|
|
@ -382,11 +366,10 @@ bool Network::connectWired() {
|
|||
return this->connectWiFi();
|
||||
}
|
||||
if(this->connectAttempts > 0) {
|
||||
Serial.printf("Ethernet Connection Lost... %d Reconnecting ", this->connectAttempts);
|
||||
Serial.println(this->mac);
|
||||
ESP_LOGW(TAG, "Ethernet Connection Lost... %d Reconnecting %s", this->connectAttempts, this->mac.c_str());
|
||||
}
|
||||
else
|
||||
Serial.println("Connecting to Wired Ethernet");
|
||||
ESP_LOGI(TAG, "Connecting to Wired Ethernet");
|
||||
this->_connecting = true;
|
||||
this->connTarget = conn_types_t::ethernet;
|
||||
this->connType = conn_types_t::unset;
|
||||
|
|
@ -398,10 +381,9 @@ bool Network::connectWired() {
|
|||
ETH.setHostname(settings.hostname);
|
||||
else
|
||||
ETH.setHostname("ESPSomfy-RTS");
|
||||
Serial.print("Set hostname to:");
|
||||
Serial.println(ETH.getHostname());
|
||||
if(!ETH.begin(settings.Ethernet.phyAddress, settings.Ethernet.PWRPin, settings.Ethernet.MDCPin, settings.Ethernet.MDIOPin, settings.Ethernet.phyType, settings.Ethernet.CLKMode)) {
|
||||
Serial.println("Ethernet Begin failed");
|
||||
ESP_LOGD(TAG, "Set hostname to: %s", ETH.getHostname());
|
||||
if(!ETH.begin(settings.Ethernet.phyAddress, settings.Ethernet.PWRPin, settings.Ethernet.MDCPin, settings.Ethernet.MDIOPin, settings.Ethernet.phyType, settings.Ethernet.CLKMode)) {
|
||||
ESP_LOGE(TAG, "Ethernet Begin failed");
|
||||
this->ethStarted = false;
|
||||
if(settings.connType == conn_types_t::ethernetpref) {
|
||||
this->wifiFallback = true;
|
||||
|
|
@ -412,7 +394,7 @@ bool Network::connectWired() {
|
|||
else {
|
||||
if(!settings.IP.dhcp) {
|
||||
if(!ETH.config(settings.IP.ip, settings.IP.gateway, settings.IP.subnet, settings.IP.dns1, settings.IP.dns2)) {
|
||||
Serial.println("Unable to configure static IP address....");
|
||||
ESP_LOGE(TAG, "Unable to configure static IP address....");
|
||||
ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
||||
}
|
||||
}
|
||||
|
|
@ -423,24 +405,28 @@ bool Network::connectWired() {
|
|||
this->connectStart = millis();
|
||||
return true;
|
||||
}
|
||||
void Network::updateHostname() {
|
||||
#endif // CONFIG_IDF_TARGET_ESP32C6
|
||||
void ESPNetwork::updateHostname() {
|
||||
if(settings.hostname[0] != '\0' && this->connected()) {
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
if(this->connType == conn_types_t::ethernet &&
|
||||
strcmp(settings.hostname, ETH.getHostname()) != 0) {
|
||||
Serial.printf("Updating host name to %s...\n", settings.hostname);
|
||||
ESP_LOGD(TAG, "Updating host name to %s...", settings.hostname);
|
||||
ETH.setHostname(settings.hostname);
|
||||
MDNS.setInstanceName(settings.hostname);
|
||||
MDNS.setInstanceName(settings.hostname);
|
||||
SSDP.setName(0, settings.hostname);
|
||||
}
|
||||
else if(strcmp(settings.hostname, WiFi.getHostname()) != 0) {
|
||||
Serial.printf("Updating host name to %s...\n", settings.hostname);
|
||||
else
|
||||
#endif
|
||||
if(strcmp(settings.hostname, WiFi.getHostname()) != 0) {
|
||||
ESP_LOGD(TAG, "Updating host name to %s...", settings.hostname);
|
||||
WiFi.setHostname(settings.hostname);
|
||||
MDNS.setInstanceName(settings.hostname);
|
||||
SSDP.setName(0, settings.hostname);
|
||||
}
|
||||
}
|
||||
}
|
||||
bool Network::connectWiFi(const uint8_t *bssid, const int32_t channel) {
|
||||
bool ESPNetwork::connectWiFi(const uint8_t *bssid, const int32_t channel) {
|
||||
if(this->softAPOpened && WiFi.softAPgetStationNum() > 0) {
|
||||
// There is a client connected to the soft AP. We do not want to close out the connection. While both the
|
||||
// Soft AP and a wifi connection can coexist on ESP32 the performance is abysmal.
|
||||
|
|
@ -467,7 +453,7 @@ bool Network::connectWiFi(const uint8_t *bssid, const int32_t channel) {
|
|||
}
|
||||
this->connTarget = conn_types_t::wifi;
|
||||
this->connType = conn_types_t::unset;
|
||||
Serial.println("WiFi begin...");
|
||||
ESP_LOGI(TAG, "WiFi begin...");
|
||||
this->_connecting = true;
|
||||
WiFi.begin(settings.WIFI.ssid, settings.WIFI.passphrase, channel, bssid);
|
||||
this->connectStart = millis();
|
||||
|
|
@ -484,26 +470,19 @@ bool Network::connectWiFi(const uint8_t *bssid, const int32_t channel) {
|
|||
this->connTarget = conn_types_t::wifi;
|
||||
this->connType = conn_types_t::unset;
|
||||
if(this->connectAttempts > 0) {
|
||||
Serial.print("Connection Lost...");
|
||||
Serial.print(this->mac);
|
||||
Serial.print(" CH:");
|
||||
Serial.print(this->channel);
|
||||
Serial.print(" (");
|
||||
Serial.print(this->strength);
|
||||
Serial.println("dbm) ");
|
||||
ESP_LOGW(TAG, "Connection Lost... %s CH:%d (%ddbm)", this->mac.c_str(), this->channel, this->strength);
|
||||
}
|
||||
else Serial.println("Connecting to AP");
|
||||
else ESP_LOGI(TAG, "Connecting to AP");
|
||||
delay(100);
|
||||
// There is also another method simply called hostname() but this is legacy for esp8266.
|
||||
if(settings.hostname[0] != '\0') WiFi.setHostname(settings.hostname);
|
||||
Serial.print("Set hostname to:");
|
||||
Serial.println(WiFi.getHostname());
|
||||
ESP_LOGD(TAG, "Set hostname to: %s", WiFi.getHostname());
|
||||
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
|
||||
WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
|
||||
uint8_t _bssid[6];
|
||||
int32_t _channel = 0;
|
||||
if(!settings.WIFI.hidden && this->getStrongestAP(settings.WIFI.ssid, _bssid, &_channel)) {
|
||||
Serial.printf("Found strongest AP %02X:%02X:%02X:%02X:%02X:%02X CH:%d\n", _bssid[0], _bssid[1], _bssid[2], _bssid[3], _bssid[4], _bssid[5], _channel);
|
||||
ESP_LOGD(TAG, "Found strongest AP %02X:%02X:%02X:%02X:%02X:%02X CH:%d", _bssid[0], _bssid[1], _bssid[2], _bssid[3], _bssid[4], _bssid[5], _channel);
|
||||
WiFi.begin(settings.WIFI.ssid, settings.WIFI.passphrase, _channel, _bssid);
|
||||
}
|
||||
else
|
||||
|
|
@ -513,15 +492,18 @@ bool Network::connectWiFi(const uint8_t *bssid, const int32_t channel) {
|
|||
this->connectStart = millis();
|
||||
return true;
|
||||
}
|
||||
bool Network::connect(conn_types_t ctype) {
|
||||
bool ESPNetwork::connect(conn_types_t ctype) {
|
||||
esp_task_wdt_reset();
|
||||
if(this->connecting()) return true;
|
||||
if(this->disconnectTime == 0) this->disconnectTime = millis();
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
if(ctype == conn_types_t::ethernet && this->connType != conn_types_t::ethernet) {
|
||||
// Here we need to call the connect to ethernet.
|
||||
this->connectWired();
|
||||
}
|
||||
else if(ctype == conn_types_t::ap || (!this->connected() && millis() > this->disconnectTime + CONNECT_TIMEOUT)) {
|
||||
else
|
||||
#endif
|
||||
if(ctype == conn_types_t::ap || (!this->connected() && millis() > this->disconnectTime + CONNECT_TIMEOUT)) {
|
||||
if(!this->softAPOpened && !this->openingSoftAP) {
|
||||
this->disconnectTime = millis();
|
||||
this->openSoftAP();
|
||||
|
|
@ -538,7 +520,7 @@ bool Network::connect(conn_types_t ctype) {
|
|||
|
||||
return true;
|
||||
}
|
||||
uint32_t Network::getChipId() {
|
||||
uint32_t ESPNetwork::getChipId() {
|
||||
uint32_t chipId = 0;
|
||||
uint64_t mac = ESP.getEfuseMac();
|
||||
for(int i=0; i<17; i=i+8) {
|
||||
|
|
@ -546,7 +528,7 @@ uint32_t Network::getChipId() {
|
|||
}
|
||||
return chipId;
|
||||
}
|
||||
bool Network::getStrongestAP(const char *ssid, uint8_t *bssid, int32_t *channel) {
|
||||
bool ESPNetwork::getStrongestAP(const char *ssid, uint8_t *bssid, int32_t *channel) {
|
||||
// The new AP must be at least 10dbm greater.
|
||||
int32_t strength = this->connected() ? WiFi.RSSI() + 10 : -127;
|
||||
int32_t chan = -1;
|
||||
|
|
@ -567,64 +549,64 @@ bool Network::getStrongestAP(const char *ssid, uint8_t *bssid, int32_t *channel)
|
|||
WiFi.scanDelete();
|
||||
return chan > 0;
|
||||
}
|
||||
bool Network::openSoftAP() {
|
||||
bool ESPNetwork::openSoftAP() {
|
||||
if(this->softAPOpened || this->openingSoftAP) return true;
|
||||
if(this->connected()) WiFi.disconnect(false);
|
||||
this->openingSoftAP = true;
|
||||
Serial.println();
|
||||
Serial.println("Turning the HotSpot On");
|
||||
ESP_LOGI(TAG, "Turning the HotSpot On");
|
||||
esp_task_wdt_reset(); // Make sure we do not reboot here.
|
||||
WiFi.softAP(strlen(settings.hostname) > 0 ? settings.hostname : "ESPSomfy RTS", "");
|
||||
delay(200);
|
||||
return true;
|
||||
}
|
||||
bool Network::connected() {
|
||||
bool ESPNetwork::connected() {
|
||||
if(this->connecting()) return false;
|
||||
else if(this->connType == conn_types_t::unset) return false;
|
||||
else if(this->connType == conn_types_t::wifi) return WiFi.status() == WL_CONNECTED;
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
else if(this->connType == conn_types_t::ethernet) return ETH.linkUp();
|
||||
#endif
|
||||
else return this->connType != conn_types_t::unset;
|
||||
return false;
|
||||
}
|
||||
bool Network::connecting() {
|
||||
bool ESPNetwork::connecting() {
|
||||
if(this->_connecting && millis() > this->connectStart + CONNECT_TIMEOUT) this->_connecting = false;
|
||||
return this->_connecting;
|
||||
}
|
||||
void Network::clearConnecting() { this->_connecting = false; }
|
||||
void Network::networkEvent(WiFiEvent_t event) {
|
||||
void ESPNetwork::clearConnecting() { this->_connecting = false; }
|
||||
void ESPNetwork::networkEvent(WiFiEvent_t event) {
|
||||
switch(event) {
|
||||
case ARDUINO_EVENT_WIFI_READY: Serial.println("(evt) WiFi interface ready"); break;
|
||||
case ARDUINO_EVENT_WIFI_READY: ESP_LOGI(TAG, "(evt) WiFi interface ready"); break;
|
||||
case ARDUINO_EVENT_WIFI_SCAN_DONE:
|
||||
Serial.printf("(evt) Completed scan for access points (%d)\n", WiFi.scanComplete());
|
||||
ESP_LOGI(TAG, "(evt) Completed scan for access points (%d)", WiFi.scanComplete());
|
||||
//Serial.println("(evt) Completed scan for access points");
|
||||
net.lastWifiScan = millis();
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_START:
|
||||
Serial.println("WiFi station mode started");
|
||||
ESP_LOGI(TAG, "WiFi station mode started");
|
||||
if(settings.hostname[0] != '\0') WiFi.setHostname(settings.hostname);
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_STOP: Serial.println("(evt) WiFi clients stopped"); break;
|
||||
case ARDUINO_EVENT_WIFI_STA_CONNECTED: Serial.println("(evt) Connected to WiFi STA access point"); break;
|
||||
case ARDUINO_EVENT_WIFI_STA_STOP: ESP_LOGI(TAG, "(evt) WiFi clients stopped"); break;
|
||||
case ARDUINO_EVENT_WIFI_STA_CONNECTED: ESP_LOGI(TAG, "(evt) Connected to WiFi STA access point"); break;
|
||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||
Serial.printf("(evt) Disconnected from WiFi STA access point. Connecting: %d\n", net.connecting());
|
||||
ESP_LOGI(TAG, "(evt) Disconnected from WiFi STA access point. Connecting: %d", net.connecting());
|
||||
net.connType = conn_types_t::unset;
|
||||
net.disconnectTime = millis();
|
||||
net.clearConnecting();
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: Serial.println("(evt) Authentication mode of STA access point has changed"); break;
|
||||
case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: ESP_LOGI(TAG, "(evt) Authentication mode of STA access point has changed"); break;
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||
Serial.print("(evt) Got WiFi STA IP: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
ESP_LOGI(TAG, "(evt) Got WiFi STA IP: %s", WiFi.localIP().toString().c_str());
|
||||
net.connType = conn_types_t::wifi;
|
||||
net.connectTime = millis();
|
||||
net.setConnected(conn_types_t::wifi);
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_LOST_IP: Serial.println("Lost IP address and IP address is reset to 0"); break;
|
||||
case ARDUINO_EVENT_WIFI_STA_LOST_IP: ESP_LOGW(TAG, "Lost IP address and IP address is reset to 0"); break;
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||
// If the Wifi is connected then drop that connection
|
||||
if(WiFi.status() == WL_CONNECTED) WiFi.disconnect(true);
|
||||
Serial.print("Got Ethernet IP ");
|
||||
Serial.println(ETH.localIP());
|
||||
ESP_LOGI(TAG, "Got Ethernet IP %s", ETH.localIP().toString().c_str());
|
||||
net.connectTime = millis();
|
||||
net.connType = conn_types_t::ethernet;
|
||||
if(settings.IP.dhcp) {
|
||||
|
|
@ -633,44 +615,46 @@ void Network::networkEvent(WiFiEvent_t event) {
|
|||
settings.IP.gateway = ETH.gatewayIP();
|
||||
settings.IP.dns1 = ETH.dnsIP(0);
|
||||
settings.IP.dns2 = ETH.dnsIP(1);
|
||||
}
|
||||
}
|
||||
net.setConnected(conn_types_t::ethernet);
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_CONNECTED:
|
||||
Serial.print("(evt) Ethernet Connected ");
|
||||
ESP_LOGI(TAG, "(evt) Ethernet Connected");
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||
Serial.println("(evt) Ethernet Disconnected");
|
||||
ESP_LOGI(TAG, "(evt) Ethernet Disconnected");
|
||||
net.connType = conn_types_t::unset;
|
||||
net.disconnectTime = millis();
|
||||
net.clearConnecting();
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_START:
|
||||
Serial.println("(evt) Ethernet Started");
|
||||
case ARDUINO_EVENT_ETH_START:
|
||||
ESP_LOGI(TAG, "(evt) Ethernet Started");
|
||||
net.ethStarted = true;
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_STOP:
|
||||
Serial.println("(evt) Ethernet Stopped");
|
||||
ESP_LOGI(TAG, "(evt) Ethernet Stopped");
|
||||
net.connType = conn_types_t::unset;
|
||||
net.ethStarted = false;
|
||||
break;
|
||||
#endif
|
||||
case ARDUINO_EVENT_WIFI_AP_START:
|
||||
Serial.print("(evt) WiFi SoftAP Started IP:");
|
||||
Serial.println(WiFi.softAPIP());
|
||||
ESP_LOGI(TAG, "(evt) WiFi SoftAP Started IP: %s", WiFi.softAPIP().toString().c_str());
|
||||
net.openingSoftAP = false;
|
||||
net.softAPOpened = true;
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_AP_STOP:
|
||||
if(!net.openingSoftAP) Serial.println("(evt) WiFi SoftAP Stopped");
|
||||
if(!net.openingSoftAP) ESP_LOGI(TAG, "(evt) WiFi SoftAP Stopped");
|
||||
net.softAPOpened = false;
|
||||
break;
|
||||
default:
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C6
|
||||
if(event > ARDUINO_EVENT_ETH_START)
|
||||
Serial.printf("(evt) Unknown Ethernet Event %d\n", event);
|
||||
ESP_LOGW(TAG, "(evt) Unknown Ethernet Event %d", event);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
void Network::emitHeap(uint8_t num) {
|
||||
void ESPNetwork::emitHeap(uint8_t num) {
|
||||
bool bEmit = false;
|
||||
bool bTimeEmit = millis() - _lastHeapEmit > 15000;
|
||||
bool bRoomEmit = false;
|
||||
|
|
@ -691,6 +675,7 @@ void Network::emitHeap(uint8_t num) {
|
|||
json->addElem("free", freeHeap);
|
||||
json->addElem("min", minHeap);
|
||||
json->addElem("total", ESP.getHeapSize());
|
||||
json->addElem("uptime", (uint64_t)millis());
|
||||
json->endObject();
|
||||
if(num == 255 && bTimeEmit && bValEmit) {
|
||||
sockEmit.endEmit(num);
|
||||
|
|
@ -1,13 +1,14 @@
|
|||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#ifndef Network_h
|
||||
#define Network_h
|
||||
#ifndef ESPNetwork_h
|
||||
#define ESPNetwork_h
|
||||
|
||||
//enum class conn_types_t : byte;
|
||||
|
||||
#define CONNECT_TIMEOUT 20000
|
||||
#define SSID_SCAN_INTERVAL 60000
|
||||
class Network {
|
||||
class ESPNetwork {
|
||||
protected:
|
||||
unsigned long lastEmit = 0;
|
||||
unsigned long lastMDNS = 0;
|
||||
|
|
@ -3,6 +3,8 @@
|
|||
#include <Update.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include <esp_chip_info.h>
|
||||
#include "esp_log.h"
|
||||
#include "ConfigSettings.h"
|
||||
#include "GitOTA.h"
|
||||
#include "Utils.h"
|
||||
|
|
@ -10,7 +12,7 @@
|
|||
#include "Somfy.h"
|
||||
#include "Web.h"
|
||||
#include "WResp.h"
|
||||
#include "Network.h"
|
||||
#include "ESPNetwork.h"
|
||||
|
||||
|
||||
|
||||
|
|
@ -20,9 +22,9 @@ extern SocketEmitter sockEmit;
|
|||
extern SomfyShadeController somfy;
|
||||
extern rebootDelay_t rebootDelay;
|
||||
extern Web webServer;
|
||||
extern Network net;
|
||||
|
||||
extern ESPNetwork net;
|
||||
|
||||
static const char *TAG = "OTA";
|
||||
|
||||
#define MAX_BUFF_SIZE 4096
|
||||
void GitRelease::setReleaseProperty(const char *key, const char *val) {
|
||||
|
|
@ -72,22 +74,6 @@ void GitRelease::setAssetProperty(const char *key, const char *val) {
|
|||
}
|
||||
}
|
||||
}
|
||||
void GitRelease::toJSON(JsonResponse &json) {
|
||||
Timestamp ts;
|
||||
char buff[20];
|
||||
sprintf(buff, "%llu", this->id);
|
||||
json.addElem("id", buff);
|
||||
json.addElem("name", this->name);
|
||||
json.addElem("date", ts.getISOTime(this->releaseDate));
|
||||
json.addElem("draft", this->draft);
|
||||
json.addElem("preRelease", this->preRelease);
|
||||
json.addElem("main", this->main);
|
||||
json.addElem("hasFS", this->hasFS);
|
||||
json.addElem("hwVersions", this->hwVersions);
|
||||
json.beginObject("version");
|
||||
this->version.toJSON(json);
|
||||
json.endObject();
|
||||
}
|
||||
#define ERR_CLIENT_OFFSET -50
|
||||
|
||||
int16_t GitRepo::getReleases(uint8_t num) {
|
||||
|
|
@ -111,10 +97,10 @@ int16_t GitRepo::getReleases(uint8_t num) {
|
|||
if(https.begin(sclient, url)) {
|
||||
esp_task_wdt_reset();
|
||||
int httpCode = https.GET();
|
||||
Serial.printf("[HTTPS] GET... code: %d\n", httpCode);
|
||||
ESP_LOGD(TAG, "[HTTPS] GET... code: %d", httpCode);
|
||||
if(httpCode > 0) {
|
||||
int len = https.getSize();
|
||||
Serial.printf("[HTTPS] GET... code: %d - %d\n", httpCode, len);
|
||||
ESP_LOGD(TAG, "[HTTPS] GET... code: %d - %d", httpCode, len);
|
||||
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
|
||||
WiFiClient *stream = https.getStreamPtr();
|
||||
uint8_t buff[128] = {0};
|
||||
|
|
@ -230,22 +216,6 @@ int16_t GitRepo::getReleases(uint8_t num) {
|
|||
settings.printAvailHeap();
|
||||
return 0;
|
||||
}
|
||||
void GitRepo::toJSON(JsonResponse &json) {
|
||||
json.beginObject("fwVersion");
|
||||
settings.fwVersion.toJSON(json);
|
||||
json.endObject();
|
||||
json.beginObject("appVersion");
|
||||
settings.appVersion.toJSON(json);
|
||||
json.endObject();
|
||||
json.beginArray("releases");
|
||||
for(uint8_t i = 0; i < GIT_MAX_RELEASES + 1; i++) {
|
||||
if(this->releases[i].id == 0) continue;
|
||||
json.beginObject();
|
||||
this->releases[i].toJSON(json);
|
||||
json.endObject();
|
||||
}
|
||||
json.endArray();
|
||||
}
|
||||
#define UPDATE_ERR_OFFSET 20
|
||||
#define ERR_DOWNLOAD_HTTP -40
|
||||
#define ERR_DOWNLOAD_BUFFER -41
|
||||
|
|
@ -261,14 +231,14 @@ void GitUpdater::loop() {
|
|||
}
|
||||
}
|
||||
else if(this->status == GIT_AWAITING_UPDATE) {
|
||||
Serial.println("Starting update process.........");
|
||||
ESP_LOGI(TAG, "Starting update process.........");
|
||||
this->status = GIT_UPDATING;
|
||||
this->beginUpdate(this->targetRelease);
|
||||
this->status = GIT_STATUS_READY;
|
||||
this->emitUpdateCheck();
|
||||
}
|
||||
else if(this->status == GIT_UPDATE_CANCELLING) {
|
||||
Serial.println("Cancelling update process..........");
|
||||
ESP_LOGI(TAG, "Cancelling update process..........");
|
||||
if(!this->lockFS) {
|
||||
this->status = GIT_UPDATE_CANCELLED;
|
||||
this->cancelled = true;
|
||||
|
|
@ -278,8 +248,8 @@ void GitUpdater::loop() {
|
|||
}
|
||||
void GitUpdater::checkForUpdate() {
|
||||
if(this->status != 0) return; // If we are already checking.
|
||||
Serial.println("Check github for updates...");
|
||||
|
||||
ESP_LOGI(TAG, "Check github for updates...");
|
||||
|
||||
this->status = GIT_STATUS_CHECK;
|
||||
settings.printAvailHeap();
|
||||
this->lastCheck = millis();
|
||||
|
|
@ -310,23 +280,6 @@ void GitUpdater::setCurrentRelease(GitRepo &repo) {
|
|||
}
|
||||
this->emitUpdateCheck();
|
||||
}
|
||||
void GitUpdater::toJSON(JsonResponse &json) {
|
||||
json.addElem("available", this->updateAvailable);
|
||||
json.addElem("status", this->status);
|
||||
json.addElem("error", (int32_t)this->error);
|
||||
json.addElem("cancelled", this->cancelled);
|
||||
json.addElem("checkForUpdate", settings.checkForUpdate);
|
||||
json.addElem("inetAvailable", this->inetAvailable);
|
||||
json.beginObject("fwVersion");
|
||||
settings.fwVersion.toJSON(json);
|
||||
json.endObject();
|
||||
json.beginObject("appVersion");
|
||||
settings.appVersion.toJSON(json);
|
||||
json.endObject();
|
||||
json.beginObject("latest");
|
||||
this->latest.toJSON(json);
|
||||
json.endObject();
|
||||
}
|
||||
void GitUpdater::emitUpdateCheck(uint8_t num) {
|
||||
JsonSockEvent *json = sockEmit.beginEmit("fwStatus");
|
||||
json->beginObject();
|
||||
|
|
@ -365,12 +318,12 @@ int GitUpdater::checkInternet() {
|
|||
esp_task_wdt_reset();
|
||||
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY || httpCode == HTTP_CODE_FOUND) {
|
||||
err = 0;
|
||||
Serial.printf("Internet is Available: %ldms\n", millis() - t);
|
||||
ESP_LOGI(TAG, "Internet is Available: %ldms", millis() - t);
|
||||
this->inetAvailable = true;
|
||||
}
|
||||
else {
|
||||
err = httpCode;
|
||||
Serial.printf("Internet is Unavailable: %d: %ldms\n", err, millis() - t);
|
||||
ESP_LOGE(TAG, "Internet is Unavailable: %d: %ldms", err, millis() - t);
|
||||
this->inetAvailable = false;
|
||||
}
|
||||
https.end();
|
||||
|
|
@ -420,7 +373,7 @@ void GitUpdater::setFirmwareFile() {
|
|||
}
|
||||
|
||||
bool GitUpdater::beginUpdate(const char *version) {
|
||||
Serial.println("Begin update called...");
|
||||
ESP_LOGI(TAG, "Begin update called...");
|
||||
if(strcmp(version, "Main") == 0) strcpy(this->baseUrl, "https://raw.githubusercontent.com/rstrouse/ESPSomfy-RTS/master/");
|
||||
else sprintf(this->baseUrl, "https://github.com/rstrouse/ESPSomfy-RTS/releases/download/%s/", version);
|
||||
|
||||
|
|
@ -441,7 +394,7 @@ bool GitUpdater::beginUpdate(const char *version) {
|
|||
if(this->error == 0) {
|
||||
settings.fwVersion.parse(version);
|
||||
delay(100);
|
||||
Serial.println("Committing Configuration...");
|
||||
ESP_LOGI(TAG, "Committing Configuration...");
|
||||
somfy.commit();
|
||||
}
|
||||
rebootDelay.reboot = true;
|
||||
|
|
@ -461,7 +414,7 @@ bool GitUpdater::recoverFilesystem() {
|
|||
this->lockFS = false;
|
||||
if(this->error == 0) {
|
||||
delay(100);
|
||||
Serial.println("Committing Configuration...");
|
||||
ESP_LOGI(TAG, "Committing Configuration...");
|
||||
somfy.commit();
|
||||
}
|
||||
this->status = GIT_UPDATE_COMPLETE;
|
||||
|
|
@ -471,28 +424,27 @@ bool GitUpdater::recoverFilesystem() {
|
|||
}
|
||||
bool GitUpdater::endUpdate() { return true; }
|
||||
int8_t GitUpdater::downloadFile() {
|
||||
Serial.printf("Begin update %s\n", this->currentFile);
|
||||
ESP_LOGI(TAG, "Begin update %s", this->currentFile);
|
||||
WiFiClientSecure sclient;
|
||||
sclient.setInsecure();
|
||||
HTTPClient https;
|
||||
char url[196];
|
||||
sprintf(url, "%s%s", this->baseUrl, this->currentFile);
|
||||
Serial.println(url);
|
||||
ESP_LOGD(TAG, "%s", url);
|
||||
esp_task_wdt_reset();
|
||||
if(https.begin(sclient, url)) {
|
||||
https.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
|
||||
Serial.print("[HTTPS] GET...\n");
|
||||
ESP_LOGD(TAG, "[HTTPS] GET...");
|
||||
int httpCode = https.GET();
|
||||
if(httpCode > 0) {
|
||||
size_t len = https.getSize();
|
||||
size_t total = 0;
|
||||
uint8_t pct = 0;
|
||||
Serial.printf("[HTTPS] GET... code: %d - %d\n", httpCode, len);
|
||||
ESP_LOGD(TAG, "[HTTPS] GET... code: %d - %d", httpCode, len);
|
||||
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY || httpCode == HTTP_CODE_FOUND) {
|
||||
WiFiClient *stream = https.getStreamPtr();
|
||||
if(!Update.begin(len, this->partition)) {
|
||||
Serial.println("Update Error detected!!!!!");
|
||||
Update.printError(Serial);
|
||||
ESP_LOGE(TAG, "Update Error detected!!!!!");
|
||||
https.end();
|
||||
return -(Update.getError() + UPDATE_ERR_OFFSET);
|
||||
}
|
||||
|
|
@ -515,8 +467,7 @@ int8_t GitUpdater::downloadFile() {
|
|||
total += c;
|
||||
//Serial.println(total);
|
||||
if (Update.write(buff, c) != c) {
|
||||
Update.printError(Serial);
|
||||
Serial.printf("Upload of %s aborted invalid size %d\n", url, c);
|
||||
ESP_LOGE(TAG, "Upload of %s aborted invalid size %d", url, c);
|
||||
free(buff);
|
||||
https.end();
|
||||
sclient.stop();
|
||||
|
|
@ -526,17 +477,16 @@ int8_t GitUpdater::downloadFile() {
|
|||
uint8_t p = (uint8_t)floor(((float)total / (float)len) * 100.0f);
|
||||
if(p != pct) {
|
||||
pct = p;
|
||||
Serial.printf("LEN:%d TOTAL:%d %d%%\n", len, total, pct);
|
||||
ESP_LOGD(TAG, "LEN:%d TOTAL:%d %d%%", len, total, pct);
|
||||
this->emitDownloadProgress(len, total);
|
||||
}
|
||||
delay(1);
|
||||
if(total >= len) {
|
||||
if(!Update.end(true)) {
|
||||
Serial.println("Error downloading update...");
|
||||
Update.printError(Serial);
|
||||
ESP_LOGE(TAG, "Error downloading update...");
|
||||
}
|
||||
else {
|
||||
Serial.println("Update.end Called...");
|
||||
ESP_LOGI(TAG, "Update.end Called...");
|
||||
}
|
||||
https.end();
|
||||
sclient.stop();
|
||||
|
|
@ -548,7 +498,7 @@ int8_t GitUpdater::downloadFile() {
|
|||
Update.abort();
|
||||
https.end();
|
||||
free(buff);
|
||||
Serial.println("Stream timeout!!!");
|
||||
ESP_LOGE(TAG, "Stream timeout!!!");
|
||||
return -43;
|
||||
}
|
||||
sockEmit.loop();
|
||||
|
|
@ -560,28 +510,28 @@ int8_t GitUpdater::downloadFile() {
|
|||
if(len > total) {
|
||||
Update.abort();
|
||||
somfy.commit();
|
||||
Serial.println("Error downloading file!!!");
|
||||
ESP_LOGE(TAG, "Error downloading file!!!");
|
||||
return -42;
|
||||
}
|
||||
else
|
||||
Serial.printf("Update %s complete\n", this->currentFile);
|
||||
ESP_LOGI(TAG, "Update %s complete", this->currentFile);
|
||||
}
|
||||
else {
|
||||
// TODO: memory allocation error.
|
||||
Serial.println("Unable to allocate memory for update!!!");
|
||||
ESP_LOGE(TAG, "Unable to allocate memory for update!!!");
|
||||
}
|
||||
}
|
||||
else {
|
||||
Serial.printf("Invalid HTTP Code... %d", httpCode);
|
||||
ESP_LOGE(TAG, "Invalid HTTP Code... %d", httpCode);
|
||||
return httpCode;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Serial.printf("Invalid HTTP Code: %d\n", httpCode);
|
||||
ESP_LOGE(TAG, "Invalid HTTP Code: %d", httpCode);
|
||||
}
|
||||
https.end();
|
||||
sclient.stop();
|
||||
Serial.printf("End update %s\n", this->currentFile);
|
||||
ESP_LOGI(TAG, "End update %s", this->currentFile);
|
||||
}
|
||||
esp_task_wdt_reset();
|
||||
return 0;
|
||||
|
|
@ -28,13 +28,13 @@ class GitRelease {
|
|||
appver_t version;
|
||||
void setReleaseProperty(const char *key, const char *val);
|
||||
void setAssetProperty(const char *key, const char *val);
|
||||
void toJSON(JsonResponse &json);
|
||||
|
||||
};
|
||||
class GitRepo {
|
||||
public:
|
||||
int16_t getReleases(uint8_t num = GIT_MAX_RELEASES);
|
||||
GitRelease releases[GIT_MAX_RELEASES + 1];
|
||||
void toJSON(JsonResponse &json);
|
||||
|
||||
};
|
||||
class GitUpdater {
|
||||
public:
|
||||
|
|
@ -58,7 +58,7 @@ class GitUpdater {
|
|||
void setFirmwareFile();
|
||||
void setCurrentRelease(GitRepo &repo);
|
||||
void loop();
|
||||
void toJSON(JsonResponse &json);
|
||||
|
||||
bool recoverFilesystem();
|
||||
int checkInternet();
|
||||
void emitUpdateCheck(uint8_t num=255);
|
||||
|
|
@ -2,12 +2,15 @@
|
|||
#include <PubSubClient.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include "esp_log.h"
|
||||
#include "ConfigSettings.h"
|
||||
#include "MQTT.h"
|
||||
#include "Somfy.h"
|
||||
#include "Network.h"
|
||||
#include "ESPNetwork.h"
|
||||
#include "Utils.h"
|
||||
|
||||
static const char *TAG = "MQTT";
|
||||
|
||||
WiFiClient tcpClient;
|
||||
PubSubClient mqttClient(tcpClient);
|
||||
|
||||
|
|
@ -16,7 +19,7 @@ static char g_content[MQTT_MAX_RESPONSE];
|
|||
|
||||
extern ConfigSettings settings;
|
||||
extern SomfyShadeController somfy;
|
||||
extern Network net;
|
||||
extern ESPNetwork net;
|
||||
extern rebootDelay_t rebootDelay;
|
||||
|
||||
|
||||
|
|
@ -45,12 +48,7 @@ bool MQTTClass::loop() {
|
|||
}
|
||||
void MQTTClass::receive(const char *topic, byte*payload, uint32_t length) {
|
||||
esp_task_wdt_reset(); // Make sure we do not reboot here.
|
||||
Serial.print("MQTT Topic:");
|
||||
Serial.print(topic);
|
||||
Serial.print(" payload:");
|
||||
for(uint32_t i=0; i<length; i++)
|
||||
Serial.print((char)payload[i]);
|
||||
Serial.println();
|
||||
ESP_LOGD(TAG, "MQTT Topic:%s payload:%.*s", topic, (int)length, (const char *)payload);
|
||||
|
||||
// We need to start at the last slash in the data
|
||||
uint8_t len = strlen(topic);
|
||||
|
|
@ -97,21 +95,16 @@ void MQTTClass::receive(const char *topic, byte*payload, uint32_t length) {
|
|||
for(uint8_t j = 0; j < length && j < sizeof(value); j++)
|
||||
value[j] = payload[j];
|
||||
|
||||
Serial.print("MQTT type:[");
|
||||
Serial.print(entityType);
|
||||
Serial.print("] command:[");
|
||||
Serial.print(command);
|
||||
Serial.print("] entityId:");
|
||||
Serial.print(entityId);
|
||||
Serial.print(" value:");
|
||||
Serial.println(value);
|
||||
ESP_LOGD(TAG, "MQTT type:[%s] command:[%s] entityId:%s value:%s", entityType, command, entityId, value);
|
||||
if(strncmp(entityType, "shades", sizeof(entityType)) == 0) {
|
||||
SomfyShade* shade = somfy.getShadeById(atoi(entityId));
|
||||
if (shade) {
|
||||
int val = atoi(value);
|
||||
if(strncmp(command, "target", sizeof(command)) == 0) {
|
||||
if(val >= 0 && val <= 100)
|
||||
if(val >= 0 && val <= 100) {
|
||||
ESP_LOGI(TAG, "MQTT shade %s target=%d", entityId, val);
|
||||
shade->moveToTarget(shade->transformPosition(atoi(value)));
|
||||
}
|
||||
}
|
||||
if(strncmp(command, "tiltTarget", sizeof(command)) == 0) {
|
||||
if(val >= 0 && val <= 100)
|
||||
|
|
@ -161,6 +154,7 @@ void MQTTClass::receive(const char *topic, byte*payload, uint32_t length) {
|
|||
SomfyGroup* group = somfy.getGroupById(atoi(entityId));
|
||||
if (group) {
|
||||
int val = atoi(value);
|
||||
ESP_LOGI(TAG, "MQTT group %s command=%s value=%d", entityId, command, val);
|
||||
if(strncmp(command, "direction", sizeof(command)) == 0) {
|
||||
if(val < 0)
|
||||
group->sendCommand(somfy_commands::Up);
|
||||
|
|
@ -204,8 +198,7 @@ bool MQTTClass::connect() {
|
|||
snprintf(lwtTopic, sizeof(lwtTopic), "%s/status", settings.MQTT.rootTopic);
|
||||
esp_task_wdt_reset();
|
||||
if(mqttClient.connect(this->clientId, settings.MQTT.username, settings.MQTT.password, lwtTopic, 0, true, "offline")) {
|
||||
Serial.print("Successfully connected MQTT client ");
|
||||
Serial.println(this->clientId);
|
||||
ESP_LOGI(TAG, "Successfully connected MQTT client %s", this->clientId);
|
||||
this->publish("status", "online", true);
|
||||
this->publish("ipAddress", settings.IP.ip.toString().c_str(), true);
|
||||
this->publish("host", settings.hostname, true);
|
||||
|
|
@ -228,14 +221,13 @@ bool MQTTClass::connect() {
|
|||
this->subscribe("groups/+/sunny/set");
|
||||
this->subscribe("groups/+/windy/set");
|
||||
mqttClient.setCallback(MQTTClass::receive);
|
||||
Serial.println("MQTT Startup Completed");
|
||||
ESP_LOGI(TAG, "MQTT Startup Completed");
|
||||
esp_task_wdt_reset();
|
||||
this->lastConnect = millis();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
Serial.print("MQTT Connection failed for: ");
|
||||
Serial.println(mqttClient.state());
|
||||
ESP_LOGE(TAG, "MQTT Connection failed for: %d", mqttClient.state());
|
||||
this->lastConnect = millis();
|
||||
return false;
|
||||
}
|
||||
|
|
@ -273,8 +265,7 @@ bool MQTTClass::unsubscribe(const char *topic) {
|
|||
snprintf(top, sizeof(top), "%s/%s", settings.MQTT.rootTopic, topic);
|
||||
else
|
||||
strlcpy(top, topic, sizeof(top));
|
||||
Serial.print("MQTT Unsubscribed from:");
|
||||
Serial.println(top);
|
||||
ESP_LOGD(TAG, "MQTT Unsubscribed from:%s", top);
|
||||
return mqttClient.unsubscribe(top);
|
||||
}
|
||||
return true;
|
||||
|
|
@ -287,8 +278,7 @@ bool MQTTClass::subscribe(const char *topic) {
|
|||
snprintf(top, sizeof(top), "%s/%s", settings.MQTT.rootTopic, topic);
|
||||
else
|
||||
strlcpy(top, topic, sizeof(top));
|
||||
Serial.print("MQTT Subscribed to:");
|
||||
Serial.println(top);
|
||||
ESP_LOGD(TAG, "MQTT Subscribed to:%s", top);
|
||||
return mqttClient.subscribe(top);
|
||||
}
|
||||
return true;
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
#include <functional>
|
||||
#include <AsyncUDP.h>
|
||||
#include "esp_log.h"
|
||||
#include "Utils.h"
|
||||
#include "ConfigSettings.h"
|
||||
#include "SSDP.h"
|
||||
|
|
@ -12,6 +13,7 @@
|
|||
#define SSDP_MULTICAST_ADDR 239, 255, 255, 250
|
||||
//#define DEBUG_SSDP Serial
|
||||
//#define DEBUG_SSDP_PACKET Serial
|
||||
static const char *TAG = "SSDP";
|
||||
extern ConfigSettings settings;
|
||||
|
||||
static const char _ssdp_uuid_template[] PROGMEM = "C2496952-5610-47E6-A968-2FC1%02X%02X%02X%02X";
|
||||
|
|
@ -193,12 +195,12 @@ bool SSDPClass::begin() {
|
|||
return false;
|
||||
}
|
||||
for(uint8_t i = 0; i < this->m_cdeviceTypes; i++) {
|
||||
Serial.printf("SSDP: %s - %s\n", this->deviceTypes[i].deviceType, this->deviceTypes[i].isActive ? "true" : "false");
|
||||
ESP_LOGI(TAG, "SSDP: %s - %s", this->deviceTypes[i].deviceType, this->deviceTypes[i].isActive ? "true" : "false");
|
||||
}
|
||||
this->isStarted = true;
|
||||
this->_sendByeBye();
|
||||
this->_sendNotify();
|
||||
Serial.println("Connected to SSDP...");
|
||||
ESP_LOGI(TAG, "Connected to SSDP...");
|
||||
return true;
|
||||
}
|
||||
void SSDPClass::end() {
|
||||
|
|
@ -209,7 +211,7 @@ void SSDPClass::end() {
|
|||
if(this->_server.connected()) {
|
||||
this->_sendByeBye();
|
||||
this->_server.close();
|
||||
Serial.println("Disconnected from SSDP...");
|
||||
ESP_LOGI(TAG, "Disconnected from SSDP...");
|
||||
}
|
||||
this->isStarted = false;
|
||||
// Clear out the last notified so if the user starts us up again it will notify
|
||||
|
|
@ -381,6 +383,9 @@ void SSDPClass::_parsePacket(ssdp_packet_t *pkt, AsyncUDPPacket &p) {
|
|||
IPAddress SSDPClass::localIP()
|
||||
{
|
||||
// Make sure we don't get a null IPAddress.
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C6
|
||||
return WiFi.localIP();
|
||||
#else
|
||||
tcpip_adapter_ip_info_t ip;
|
||||
if (WiFi.getMode() == WIFI_STA) {
|
||||
if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip)) {
|
||||
|
|
@ -392,6 +397,7 @@ IPAddress SSDPClass::localIP()
|
|||
}
|
||||
}
|
||||
return IPAddress(ip.ip.addr);
|
||||
#endif
|
||||
}
|
||||
void SSDPClass::_sendResponse(IPAddress addr, uint16_t port, UPNPDeviceType *d, const char *st, response_types_t responseType) {
|
||||
char buffer[1460];
|
||||
|
|
@ -432,7 +438,7 @@ void SSDPClass::_sendResponse(IPAddress addr, uint16_t port, const char *buff) {
|
|||
void SSDPClass::_sendNotify() {
|
||||
for(uint8_t i = 0; i < this->m_cdeviceTypes; i++) {
|
||||
UPNPDeviceType *dev = &this->deviceTypes[i];
|
||||
if(i == 0 && (strlen(dev->deviceType) == 0 || !dev->isActive)) Serial.printf("The device type is empty: %s\n", dev->isActive ? "true" : "false");
|
||||
if(i == 0 && (strlen(dev->deviceType) == 0 || !dev->isActive)) ESP_LOGD(TAG, "The device type is empty: %s", dev->isActive ? "true" : "false");
|
||||
if(strlen(dev->deviceType) > 0 && dev->isActive) {
|
||||
unsigned long elapsed = (millis() - dev->lastNotified);
|
||||
if(!dev->lastNotified || (elapsed * 5) > (this->_interval * 1000)) {
|
||||
|
|
@ -653,28 +659,23 @@ void SSDPClass::_sendQueuedResponses() {
|
|||
}
|
||||
}
|
||||
void SSDPClass::_printPacket(ssdp_packet_t *pkt) {
|
||||
Serial.printf("Rec: %lu\n", pkt->recvd);
|
||||
ESP_LOGD(TAG, "Rec: %lu", pkt->recvd);
|
||||
switch(pkt->method) {
|
||||
case NONE:
|
||||
Serial.println("Method: NONE");
|
||||
ESP_LOGD(TAG, "Method: NONE");
|
||||
break;
|
||||
case SEARCH:
|
||||
Serial.println("Method: SEARCH");
|
||||
ESP_LOGD(TAG, "Method: SEARCH");
|
||||
break;
|
||||
case NOTIFY:
|
||||
Serial.println("Method: NOTIFY");
|
||||
ESP_LOGD(TAG, "Method: NOTIFY");
|
||||
break;
|
||||
default:
|
||||
Serial.println("Method: UNKOWN");
|
||||
ESP_LOGD(TAG, "Method: UNKNOWN");
|
||||
break;
|
||||
}
|
||||
Serial.printf("ST: %s\n", pkt->st);
|
||||
Serial.printf("MAN: %s\n", pkt->man);
|
||||
Serial.printf("AGENT: %s\n", pkt->agent);
|
||||
Serial.printf("HOST: %s\n", pkt->host);
|
||||
Serial.printf("MX: %d\n", pkt->mx);
|
||||
Serial.printf("type: %d\n", pkt->type);
|
||||
Serial.printf("valid: %d\n", pkt->valid);
|
||||
ESP_LOGD(TAG, "ST: %s MAN: %s AGENT: %s HOST: %s MX: %d type: %d valid: %d",
|
||||
pkt->st, pkt->man, pkt->agent, pkt->host, pkt->mx, pkt->type, pkt->valid);
|
||||
}
|
||||
void SSDPClass::_processRequest(AsyncUDPPacket &p) {
|
||||
// This pending BS should probably be for unicast request only but we will play along for now.
|
||||
224
src/Sockets.cpp
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include "esp_log.h"
|
||||
#include "Sockets.h"
|
||||
#include "ConfigSettings.h"
|
||||
#include "Somfy.h"
|
||||
#include "ESPNetwork.h"
|
||||
#include "GitOTA.h"
|
||||
|
||||
static const char *TAG = "Sockets";
|
||||
|
||||
extern ConfigSettings settings;
|
||||
extern ESPNetwork net;
|
||||
extern SomfyShadeController somfy;
|
||||
extern SocketEmitter sockEmit;
|
||||
extern GitUpdater git;
|
||||
|
||||
AsyncWebServer wsServer(8080);
|
||||
AsyncWebSocket ws("/");
|
||||
|
||||
#define MAX_WS_CLIENTS 5
|
||||
static uint32_t clientMap[MAX_WS_CLIENTS] = {0,0,0,0,0};
|
||||
|
||||
static uint8_t mapClientId(uint32_t asyncId) {
|
||||
for(uint8_t i = 0; i < MAX_WS_CLIENTS; i++)
|
||||
if(clientMap[i] == asyncId) return i;
|
||||
return 255;
|
||||
}
|
||||
static uint32_t getAsyncId(uint8_t slot) {
|
||||
if(slot < MAX_WS_CLIENTS) return clientMap[slot];
|
||||
return 0;
|
||||
}
|
||||
static uint8_t addClient(uint32_t asyncId) {
|
||||
for(uint8_t i = 0; i < MAX_WS_CLIENTS; i++) {
|
||||
if(clientMap[i] == 0) { clientMap[i] = asyncId; return i; }
|
||||
}
|
||||
return 255;
|
||||
}
|
||||
static void removeClient(uint32_t asyncId) {
|
||||
for(uint8_t i = 0; i < MAX_WS_CLIENTS; i++)
|
||||
if(clientMap[i] == asyncId) clientMap[i] = 0;
|
||||
}
|
||||
|
||||
#define MAX_SOCK_RESPONSE 2048
|
||||
static char g_response[MAX_SOCK_RESPONSE];
|
||||
|
||||
bool room_t::isJoined(uint8_t num) {
|
||||
for(uint8_t i = 0; i < sizeof(this->clients); i++) {
|
||||
if(this->clients[i] == num) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool room_t::join(uint8_t num) {
|
||||
if(this->isJoined(num)) return true;
|
||||
for(uint8_t i = 0; i < sizeof(this->clients); i++) {
|
||||
if(this->clients[i] == 255) {
|
||||
this->clients[i] = num;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool room_t::leave(uint8_t num) {
|
||||
if(!this->isJoined(num)) return false;
|
||||
for(uint8_t i = 0; i < sizeof(this->clients); i++) {
|
||||
if(this->clients[i] == num) this->clients[i] = 255;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void room_t::clear() {
|
||||
memset(this->clients, 255, sizeof(this->clients));
|
||||
}
|
||||
uint8_t room_t::activeClients() {
|
||||
uint8_t n = 0;
|
||||
for(uint8_t i = 0; i < sizeof(this->clients); i++) {
|
||||
if(this->clients[i] != 255) n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/*********************************************************************
|
||||
* SocketEmitter class members
|
||||
********************************************************************/
|
||||
void SocketEmitter::startup() {
|
||||
|
||||
}
|
||||
void SocketEmitter::begin() {
|
||||
ws.onEvent(SocketEmitter::wsEvent);
|
||||
wsServer.addHandler(&ws);
|
||||
wsServer.begin();
|
||||
ESP_LOGI(TAG, "Socket Server Started...");
|
||||
}
|
||||
void SocketEmitter::loop() {
|
||||
ws.cleanupClients();
|
||||
this->initClients();
|
||||
}
|
||||
JsonSockEvent *SocketEmitter::beginEmit(const char *evt) {
|
||||
this->json.beginEvent(&ws, evt, g_response, sizeof(g_response));
|
||||
return &this->json;
|
||||
}
|
||||
void SocketEmitter::endEmit(uint8_t num) {
|
||||
if(num == 255) {
|
||||
this->json.endEvent(0);
|
||||
} else {
|
||||
uint32_t asyncId = getAsyncId(num);
|
||||
this->json.endEvent(asyncId);
|
||||
}
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
void SocketEmitter::endEmitRoom(uint8_t room) {
|
||||
if(room < SOCK_MAX_ROOMS) {
|
||||
room_t *r = &this->rooms[room];
|
||||
for(uint8_t i = 0; i < sizeof(r->clients); i++) {
|
||||
if(r->clients[i] != 255) {
|
||||
uint32_t asyncId = getAsyncId(r->clients[i]);
|
||||
if(asyncId != 0) this->json.endEvent(asyncId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
uint8_t SocketEmitter::activeClients(uint8_t room) {
|
||||
if(room < SOCK_MAX_ROOMS) return this->rooms[room].activeClients();
|
||||
return 0;
|
||||
}
|
||||
void SocketEmitter::initClients() {
|
||||
for(uint8_t i = 0; i < sizeof(this->newClients); i++) {
|
||||
uint8_t slot = this->newClients[i];
|
||||
if(slot != 255) {
|
||||
uint32_t asyncId = getAsyncId(slot);
|
||||
if(asyncId != 0 && ws.hasClient(asyncId)) {
|
||||
ESP_LOGD(TAG, "Initializing Socket Client %u (asyncId=%lu)", slot, asyncId);
|
||||
esp_task_wdt_reset();
|
||||
settings.emitSockets(slot);
|
||||
if(!ws.hasClient(asyncId)) { this->newClients[i] = 255; continue; }
|
||||
somfy.emitState(slot);
|
||||
if(!ws.hasClient(asyncId)) { this->newClients[i] = 255; continue; }
|
||||
git.emitUpdateCheck(slot);
|
||||
if(!ws.hasClient(asyncId)) { this->newClients[i] = 255; continue; }
|
||||
net.emitSockets(slot);
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
this->newClients[i] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
void SocketEmitter::delayInit(uint8_t num) {
|
||||
for(uint8_t i=0; i < sizeof(this->newClients); i++) {
|
||||
if(this->newClients[i] == num) break;
|
||||
else if(this->newClients[i] == 255) {
|
||||
this->newClients[i] = num;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void SocketEmitter::end() {
|
||||
ws.closeAll();
|
||||
wsServer.end();
|
||||
for(uint8_t i = 0; i < SOCK_MAX_ROOMS; i++)
|
||||
this->rooms[i].clear();
|
||||
memset(clientMap, 0, sizeof(clientMap));
|
||||
}
|
||||
void SocketEmitter::disconnect() {
|
||||
ws.closeAll();
|
||||
memset(clientMap, 0, sizeof(clientMap));
|
||||
}
|
||||
void SocketEmitter::wsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
|
||||
uint32_t asyncId = client->id();
|
||||
switch(type) {
|
||||
case WS_EVT_CONNECT:
|
||||
{
|
||||
uint8_t slot = addClient(asyncId);
|
||||
if(slot == 255) {
|
||||
ESP_LOGE(TAG, "Socket: No free client slots, closing %lu", asyncId);
|
||||
client->close();
|
||||
return;
|
||||
}
|
||||
IPAddress ip = client->remoteIP();
|
||||
ESP_LOGD(TAG, "Socket [%lu] Connected from %d.%d.%d.%d (slot %u)", asyncId, ip[0], ip[1], ip[2], ip[3], slot);
|
||||
client->text("Connected");
|
||||
client->setCloseClientOnQueueFull(false);
|
||||
sockEmit.delayInit(slot);
|
||||
}
|
||||
break;
|
||||
case WS_EVT_DISCONNECT:
|
||||
{
|
||||
uint8_t slot = mapClientId(asyncId);
|
||||
ESP_LOGD(TAG, "Socket [%lu] Disconnected (slot %u)", asyncId, slot);
|
||||
if(slot != 255) {
|
||||
for(uint8_t i = 0; i < SOCK_MAX_ROOMS; i++)
|
||||
sockEmit.rooms[i].leave(slot);
|
||||
}
|
||||
removeClient(asyncId);
|
||||
}
|
||||
break;
|
||||
case WS_EVT_DATA:
|
||||
{
|
||||
AwsFrameInfo *info = (AwsFrameInfo*)arg;
|
||||
if(info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
|
||||
uint8_t slot = mapClientId(asyncId);
|
||||
data[len] = 0;
|
||||
if(strncmp((char*)data, "join:", 5) == 0) {
|
||||
uint8_t roomNum = atoi((char*)&data[5]);
|
||||
ESP_LOGD(TAG, "Client %u joining room %u", slot, roomNum);
|
||||
if(roomNum < SOCK_MAX_ROOMS && slot != 255) sockEmit.rooms[roomNum].join(slot);
|
||||
}
|
||||
else if(strncmp((char*)data, "leave:", 6) == 0) {
|
||||
uint8_t roomNum = atoi((char*)&data[6]);
|
||||
ESP_LOGD(TAG, "Client %u leaving room %u", slot, roomNum);
|
||||
if(roomNum < SOCK_MAX_ROOMS && slot != 255) sockEmit.rooms[roomNum].leave(slot);
|
||||
}
|
||||
else {
|
||||
ESP_LOGD(TAG, "Socket [%lu] text: %s", asyncId, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WS_EVT_ERROR:
|
||||
ESP_LOGE(TAG, "Socket [%lu] Error", asyncId);
|
||||
break;
|
||||
case WS_EVT_PONG:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#include <WebSocketsServer.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include "WResp.h"
|
||||
#ifndef sockets_h
|
||||
#define sockets_h
|
||||
|
|
@ -21,7 +21,6 @@ class SocketEmitter {
|
|||
void delayInit(uint8_t num);
|
||||
public:
|
||||
JsonSockEvent json;
|
||||
//ClientSocketEvent evt;
|
||||
room_t rooms[SOCK_MAX_ROOMS];
|
||||
uint8_t activeClients(uint8_t room);
|
||||
void initClients();
|
||||
|
|
@ -33,6 +32,6 @@ class SocketEmitter {
|
|||
JsonSockEvent * beginEmit(const char *evt);
|
||||
void endEmit(uint8_t num = 255);
|
||||
void endEmitRoom(uint8_t num);
|
||||
static void wsEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length);
|
||||
static void wsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len);
|
||||
};
|
||||
#endif
|
||||
|
|
@ -209,7 +209,6 @@ class SomfyRoom {
|
|||
void clear();
|
||||
bool save();
|
||||
bool fromJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
void emitState(const char *evt = "roomState");
|
||||
void emitState(uint8_t num, const char *evt = "roomState");
|
||||
void publish();
|
||||
|
|
@ -239,7 +238,6 @@ class SomfyRemote {
|
|||
uint8_t repeats = 1;
|
||||
virtual bool isLastCommand(somfy_commands cmd);
|
||||
char *getRemotePrefId() {return m_remotePrefId;}
|
||||
virtual void toJSON(JsonResponse &json);
|
||||
virtual void setRemoteAddress(uint32_t address);
|
||||
virtual uint32_t getRemoteAddress();
|
||||
virtual uint16_t getNextRollingCode();
|
||||
|
|
@ -304,9 +302,7 @@ class SomfyShade : public SomfyRemote {
|
|||
SomfyLinkedRemote linkedRemotes[SOMFY_MAX_LINKED_REMOTES];
|
||||
bool paired = false;
|
||||
int8_t validateJSON(JsonObject &obj);
|
||||
void toJSONRef(JsonResponse &json);
|
||||
int8_t fromJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json) override;
|
||||
|
||||
char name[21] = "";
|
||||
void setShadeId(uint8_t id) { shadeId = id; }
|
||||
|
|
@ -393,9 +389,6 @@ class SomfyGroup : public SomfyRemote {
|
|||
void clear();
|
||||
bool fromJSON(JsonObject &obj);
|
||||
//bool toJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
void toJSONRef(JsonResponse &json);
|
||||
|
||||
bool linkShade(uint8_t shadeId);
|
||||
bool unlinkShade(uint8_t shadeId);
|
||||
bool hasShadeId(uint8_t shadeId);
|
||||
|
|
@ -485,7 +478,6 @@ struct transceiver_config_t {
|
|||
*/
|
||||
void fromJSON(JsonObject& obj);
|
||||
//void toJSON(JsonObject& obj);
|
||||
void toJSON(JsonResponse& json);
|
||||
void save();
|
||||
void load();
|
||||
void apply();
|
||||
|
|
@ -500,7 +492,6 @@ class Transceiver {
|
|||
transceiver_config_t config;
|
||||
bool printBuffer = false;
|
||||
//bool toJSON(JsonObject& obj);
|
||||
void toJSON(JsonResponse& json);
|
||||
bool fromJSON(JsonObject& obj);
|
||||
bool save();
|
||||
bool begin();
|
||||
|
|
@ -557,10 +548,6 @@ class SomfyShadeController {
|
|||
SomfyGroup groups[SOMFY_MAX_GROUPS];
|
||||
bool linkRepeater(uint32_t address);
|
||||
bool unlinkRepeater(uint32_t address);
|
||||
void toJSONShades(JsonResponse &json);
|
||||
void toJSONRooms(JsonResponse &json);
|
||||
void toJSONGroups(JsonResponse &json);
|
||||
void toJSONRepeaters(JsonResponse &json);
|
||||
uint8_t repeaterCount();
|
||||
uint8_t roomCount();
|
||||
uint8_t shadeCount();
|
||||
|
|
@ -578,6 +565,7 @@ class SomfyShadeController {
|
|||
void processWaitingFrame();
|
||||
void commit();
|
||||
void writeBackup();
|
||||
String backupData;
|
||||
bool loadShadesFile(const char *filename);
|
||||
#ifdef USE_NVS
|
||||
bool loadLegacy();
|
||||
141
src/SomfyController.ino
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
#include "esp_log.h"
|
||||
#include <WiFi.h>
|
||||
#include <LittleFS.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include "ConfigSettings.h"
|
||||
#include "ESPNetwork.h"
|
||||
#include "Web.h"
|
||||
#include "Sockets.h"
|
||||
#include "Utils.h"
|
||||
#include "Somfy.h"
|
||||
#include "MQTT.h"
|
||||
#include "GitOTA.h"
|
||||
#include "esp_core_dump.h"
|
||||
|
||||
static const char *TAG = "Main";
|
||||
|
||||
ConfigSettings settings;
|
||||
Web webServer;
|
||||
SocketEmitter sockEmit;
|
||||
ESPNetwork net;
|
||||
rebootDelay_t rebootDelay;
|
||||
SomfyShadeController somfy;
|
||||
MQTTClass mqtt;
|
||||
GitUpdater git;
|
||||
|
||||
uint32_t oldheap = 0;
|
||||
|
||||
void listDir(const char *dirname, uint8_t levels) {
|
||||
ESP_LOGI(TAG, "Listing: %s", dirname);
|
||||
File root = LittleFS.open(dirname);
|
||||
if (!root || !root.isDirectory()) {
|
||||
ESP_LOGE(TAG, "Failed to open directory");
|
||||
return;
|
||||
}
|
||||
File file = root.openNextFile();
|
||||
while (file) {
|
||||
if (file.isDirectory()) {
|
||||
ESP_LOGI(TAG, " DIR : %s", file.name());
|
||||
if (levels) listDir(file.path(), levels - 1);
|
||||
} else {
|
||||
ESP_LOGI(TAG, " FILE: %-30s %d bytes", file.name(), file.size());
|
||||
}
|
||||
file = root.openNextFile();
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
ESP_LOGI(TAG, "Startup/Boot....");
|
||||
esp_core_dump_summary_t summary;
|
||||
if (esp_core_dump_get_summary(&summary) == ESP_OK) {
|
||||
ESP_LOGW(TAG, "*** Previous crash coredump found ***");
|
||||
ESP_LOGW(TAG, " Task: %s", summary.exc_task);
|
||||
ESP_LOGW(TAG, " PC: 0x%08x", summary.exc_pc);
|
||||
#ifdef CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||
ESP_LOGW(TAG, " Cause: %d", summary.ex_info.exc_cause);
|
||||
char bt_buf[256] = {0};
|
||||
int pos = 0;
|
||||
for (int i = 0; i < summary.exc_bt_info.depth; i++) {
|
||||
pos += snprintf(bt_buf + pos, sizeof(bt_buf) - pos, " 0x%08x", summary.exc_bt_info.bt[i]);
|
||||
}
|
||||
ESP_LOGW(TAG, " Backtrace:%s", bt_buf);
|
||||
#elif CONFIG_IDF_TARGET_ARCH_RISCV
|
||||
ESP_LOGW(TAG, " Cause: %d", summary.ex_info.mcause);
|
||||
ESP_LOGW(TAG, " MTVAL: 0x%08x RA: 0x%08x SP: 0x%08x",
|
||||
summary.ex_info.mtval, summary.ex_info.ra, summary.ex_info.sp);
|
||||
#endif
|
||||
}
|
||||
ESP_LOGI(TAG, "Mounting File System...");
|
||||
|
||||
|
||||
if (LittleFS.begin()) {
|
||||
ESP_LOGI(TAG, "Total: %d bytes", LittleFS.totalBytes());
|
||||
ESP_LOGI(TAG, "Used: %d bytes", LittleFS.usedBytes());
|
||||
ESP_LOGI(TAG, "Free: %d bytes", LittleFS.totalBytes() - LittleFS.usedBytes());
|
||||
listDir("/", 3);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "LittleFS mount failed!");
|
||||
}
|
||||
|
||||
if(LittleFS.begin()) ESP_LOGI(TAG, "File system mounted successfully");
|
||||
else ESP_LOGE(TAG, "Error mounting file system");
|
||||
settings.begin();
|
||||
if(WiFi.status() == WL_CONNECTED) WiFi.disconnect(true);
|
||||
delay(10);
|
||||
webServer.startup();
|
||||
webServer.begin();
|
||||
delay(1000);
|
||||
net.setup();
|
||||
somfy.begin();
|
||||
//git.checkForUpdate();
|
||||
#if ESP_ARDUINO_VERSION_MAJOR >= 3
|
||||
const esp_task_wdt_config_t wdt_config = { .timeout_ms = 15000, .idle_core_mask = 1, .trigger_panic = true };
|
||||
esp_task_wdt_init(&wdt_config);
|
||||
#else
|
||||
esp_task_wdt_init(15, true); //enable panic so ESP32 restarts
|
||||
#endif
|
||||
esp_task_wdt_add(NULL); //add current thread to WDT watch
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// put your main code here, to run repeatedly:
|
||||
//uint32_t heap = ESP.getFreeHeap();
|
||||
if(rebootDelay.reboot && millis() > rebootDelay.rebootTime) {
|
||||
ESP_LOGI(TAG, "Rebooting after %lums", rebootDelay.rebootTime);
|
||||
net.end();
|
||||
ESP.restart();
|
||||
return;
|
||||
}
|
||||
uint32_t timing = millis();
|
||||
|
||||
net.loop();
|
||||
if(millis() - timing > 100) ESP_LOGD(TAG, "Timing Net: %ldms", millis() - timing);
|
||||
timing = millis();
|
||||
esp_task_wdt_reset();
|
||||
somfy.loop();
|
||||
if(millis() - timing > 100) ESP_LOGD(TAG, "Timing Somfy: %ldms", millis() - timing);
|
||||
timing = millis();
|
||||
esp_task_wdt_reset();
|
||||
if(net.connected() || net.softAPOpened) {
|
||||
if(!rebootDelay.reboot && net.connected() && !net.softAPOpened) {
|
||||
git.loop();
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
webServer.loop();
|
||||
esp_task_wdt_reset();
|
||||
if(millis() - timing > 100) ESP_LOGD(TAG, "Timing WebServer: %ldms", millis() - timing);
|
||||
esp_task_wdt_reset();
|
||||
timing = millis();
|
||||
sockEmit.loop();
|
||||
if(millis() - timing > 100) ESP_LOGD(TAG, "Timing Socket: %ldms", millis() - timing);
|
||||
esp_task_wdt_reset();
|
||||
timing = millis();
|
||||
}
|
||||
if(rebootDelay.reboot && millis() > rebootDelay.rebootTime) {
|
||||
net.end();
|
||||
ESP.restart();
|
||||
}
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
#include "WResp.h"
|
||||
void JsonSockEvent::beginEvent(WebSocketsServer *server, const char *evt, char *buff, size_t buffSize) {
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG = "WResp";
|
||||
void JsonSockEvent::beginEvent(AsyncWebSocket *server, const char *evt, char *buff, size_t buffSize) {
|
||||
this->server = server;
|
||||
this->buff = buff;
|
||||
this->buffSize = buffSize;
|
||||
|
|
@ -15,17 +18,16 @@ void JsonSockEvent::closeEvent() {
|
|||
this->_nocomma = true;
|
||||
this->_closed = true;
|
||||
}
|
||||
void JsonSockEvent::endEvent(uint8_t num) {
|
||||
void JsonSockEvent::endEvent(uint32_t clientId) {
|
||||
this->closeEvent();
|
||||
if(num == 255) this->server->broadcastTXT(this->buff);
|
||||
else this->server->sendTXT(num, this->buff);
|
||||
if(clientId == 0) this->server->textAll(this->buff);
|
||||
else this->server->text(clientId, this->buff);
|
||||
}
|
||||
void JsonSockEvent::_safecat(const char *val, bool escape) {
|
||||
size_t len = (escape ? this->calcEscapedLength(val) : strlen(val)) + strlen(this->buff);
|
||||
if(escape) len += 2;
|
||||
if(len >= this->buffSize) {
|
||||
Serial.printf("Socket exceeded buffer size %d - %d\n", this->buffSize, len);
|
||||
Serial.println(this->buff);
|
||||
ESP_LOGE(TAG, "Socket exceeded buffer size %d - %d: %s", this->buffSize, len, this->buff);
|
||||
return;
|
||||
}
|
||||
if(escape) strcat(this->buff, "\"");
|
||||
|
|
@ -33,30 +35,32 @@ void JsonSockEvent::_safecat(const char *val, bool escape) {
|
|||
else strcat(this->buff, val);
|
||||
if(escape) strcat(this->buff, "\"");
|
||||
}
|
||||
void JsonResponse::beginResponse(WebServer *server, char *buff, size_t buffSize) {
|
||||
this->server = server;
|
||||
void AsyncJsonResp::beginResponse(AsyncWebServerRequest *request, char *buff, size_t buffSize) {
|
||||
this->_request = request;
|
||||
this->buff = buff;
|
||||
this->buffSize = buffSize;
|
||||
this->buff[0] = 0x00;
|
||||
this->_nocomma = true;
|
||||
server->setContentLength(CONTENT_LENGTH_UNKNOWN);
|
||||
this->_headersSent = false;
|
||||
this->_stream = request->beginResponseStream("application/json");
|
||||
}
|
||||
void JsonResponse::endResponse() {
|
||||
if(strlen(buff)) this->send();
|
||||
server->sendContent("", 0);
|
||||
void AsyncJsonResp::endResponse() {
|
||||
if(strlen(this->buff)) this->flush();
|
||||
if(this->_request && this->_stream) {
|
||||
this->_request->send(this->_stream);
|
||||
}
|
||||
}
|
||||
void JsonResponse::send() {
|
||||
if(!this->_headersSent) server->send_P(200, "application/json", this->buff);
|
||||
else server->sendContent(this->buff);
|
||||
//Serial.printf("Sent %d bytes %d\n", strlen(this->buff), this->buffSize);
|
||||
void AsyncJsonResp::flush() {
|
||||
if(this->_stream && strlen(this->buff) > 0) {
|
||||
this->_stream->print(this->buff);
|
||||
this->buff[0] = 0x00;
|
||||
this->_headersSent = true;
|
||||
}
|
||||
}
|
||||
void JsonResponse::_safecat(const char *val, bool escape) {
|
||||
void AsyncJsonResp::_safecat(const char *val, bool escape) {
|
||||
size_t len = (escape ? this->calcEscapedLength(val) : strlen(val)) + strlen(this->buff);
|
||||
if(escape) len += 2;
|
||||
if(len >= this->buffSize) {
|
||||
this->send();
|
||||
this->flush();
|
||||
}
|
||||
if(escape) strcat(this->buff, "\"");
|
||||
if(escape) this->escapeString(val, &this->buff[strlen(this->buff)]);
|
||||
|
|
@ -130,8 +134,9 @@ void JsonFormatter::addElem(const char *name, uint32_t nval) { sprintf(this->_nu
|
|||
void JsonFormatter::addElem(const char *name, int16_t nval) { sprintf(this->_numbuff, "%d", nval); this->_appendNumber(name); }
|
||||
void JsonFormatter::addElem(const char *name, uint16_t nval) { sprintf(this->_numbuff, "%u", nval); this->_appendNumber(name); }
|
||||
void JsonFormatter::addElem(const char *name, int64_t lval) { sprintf(this->_numbuff, "%lld", (long long)lval); this->_appendNumber(name); }
|
||||
void JsonFormatter::addElem(const char *name, uint64_t lval) { sprintf(this->_numbuff, "%llu", (unsigned long long)lval); this->_appendNumber(name); }
|
||||
*/
|
||||
void JsonFormatter::addElem(const char *name, uint64_t lval) { sprintf(this->_numbuff, "%llu", (unsigned long long)lval); this->_appendNumber(name); }
|
||||
|
||||
void JsonFormatter::addElem(const char *name, bool bval) { strcpy(this->_numbuff, bval ? "true" : "false"); this->_appendNumber(name); }
|
||||
|
||||
void JsonFormatter::_safecat(const char *val, bool escape) {
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
#include <WebServer.h>
|
||||
#include <WebSocketsServer.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include "Somfy.h"
|
||||
#ifndef wresp_h
|
||||
#define wresp_h
|
||||
|
|
@ -51,24 +50,26 @@ class JsonFormatter {
|
|||
void addElem(const char* name, uint32_t lval);
|
||||
void addElem(const char* name, bool bval);
|
||||
void addElem(const char *name, const char *val);
|
||||
void addElem(const char* name, uint64_t lval);
|
||||
};
|
||||
class JsonResponse : public JsonFormatter {
|
||||
class AsyncJsonResp : public JsonFormatter {
|
||||
protected:
|
||||
void _safecat(const char *val, bool escape = false) override;
|
||||
AsyncWebServerRequest *_request = nullptr;
|
||||
AsyncResponseStream *_stream = nullptr;
|
||||
public:
|
||||
WebServer *server;
|
||||
void beginResponse(WebServer *server, char *buff, size_t buffSize);
|
||||
void beginResponse(AsyncWebServerRequest *request, char *buff, size_t buffSize);
|
||||
void endResponse();
|
||||
void send();
|
||||
void flush();
|
||||
};
|
||||
class JsonSockEvent : public JsonFormatter {
|
||||
protected:
|
||||
bool _closed = false;
|
||||
void _safecat(const char *val, bool escape = false) override;
|
||||
public:
|
||||
WebSocketsServer *server = nullptr;
|
||||
void beginEvent(WebSocketsServer *server, const char *evt, char *buff, size_t buffSize);
|
||||
void endEvent(uint8_t clientNum = 255);
|
||||
AsyncWebSocket *server = nullptr;
|
||||
void beginEvent(AsyncWebSocket *server, const char *evt, char *buff, size_t buffSize);
|
||||
void endEvent(uint32_t clientId = 0);
|
||||
void closeEvent();
|
||||
};
|
||||
#endif
|
||||
2445
src/Web.cpp
Normal file
74
src/Web.h
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#include <ESPAsyncWebServer.h>
|
||||
#include <AsyncJson.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include "Somfy.h"
|
||||
#ifndef webserver_h
|
||||
#define webserver_h
|
||||
|
||||
#define WEB_CMD_QUEUE_SIZE 8
|
||||
#define WEB_CMD_TIMEOUT_MS 3000
|
||||
|
||||
enum class web_cmd_t : uint8_t {
|
||||
shade_command, // moveToTarget or sendCommand
|
||||
group_command, // group sendCommand
|
||||
tilt_command, // moveToTiltTarget or sendTiltCommand
|
||||
shade_repeat, // shade sendCommand/repeatFrame
|
||||
group_repeat, // group sendCommand/repeatFrame
|
||||
set_positions, // set shade position directly
|
||||
shade_sensor, // shade sensor command
|
||||
group_sensor, // group sensor command
|
||||
};
|
||||
|
||||
struct web_command_t {
|
||||
web_cmd_t type;
|
||||
uint8_t shadeId;
|
||||
uint8_t groupId;
|
||||
uint8_t target; // 0-100 or 255 (none)
|
||||
somfy_commands command;
|
||||
int8_t repeat;
|
||||
uint8_t stepSize;
|
||||
int8_t position; // for setPositions
|
||||
int8_t tiltPosition; // for setPositions/tilt
|
||||
int8_t sunny; // for sensor
|
||||
int8_t windy; // for sensor
|
||||
};
|
||||
|
||||
class Web {
|
||||
public:
|
||||
bool uploadSuccess = false;
|
||||
void startup();
|
||||
void begin();
|
||||
void loop();
|
||||
// Auth helpers
|
||||
bool createAPIToken(const IPAddress ipAddress, char *token);
|
||||
bool createAPIToken(const char *payload, char *token);
|
||||
bool createAPIPinToken(const IPAddress ipAddress, const char *pin, char *token);
|
||||
bool createAPIPasswordToken(const IPAddress ipAddress, const char *username, const char *password, char *token);
|
||||
bool isAuthenticated(AsyncWebServerRequest *request, bool cfg = false);
|
||||
|
||||
// Async API handlers
|
||||
void handleDiscovery(AsyncWebServerRequest *request);
|
||||
void handleGetRooms(AsyncWebServerRequest *request);
|
||||
void handleGetShades(AsyncWebServerRequest *request);
|
||||
void handleGetGroups(AsyncWebServerRequest *request);
|
||||
void handleController(AsyncWebServerRequest *request);
|
||||
void handleRoom(AsyncWebServerRequest *request);
|
||||
void handleShade(AsyncWebServerRequest *request);
|
||||
void handleGroup(AsyncWebServerRequest *request);
|
||||
void handleLogin(AsyncWebServerRequest *request, JsonVariant &json);
|
||||
void handleShadeCommand(AsyncWebServerRequest *request, JsonVariant &json);
|
||||
void handleGroupCommand(AsyncWebServerRequest *request, JsonVariant &json);
|
||||
void handleTiltCommand(AsyncWebServerRequest *request, JsonVariant &json);
|
||||
void handleRepeatCommand(AsyncWebServerRequest *request, JsonVariant &json);
|
||||
void handleSetPositions(AsyncWebServerRequest *request, JsonVariant &json);
|
||||
void handleSetSensor(AsyncWebServerRequest *request, JsonVariant &json);
|
||||
void handleDownloadFirmware(AsyncWebServerRequest *request);
|
||||
void handleBackup(AsyncWebServerRequest *request);
|
||||
void handleReboot(AsyncWebServerRequest *request);
|
||||
void handleNotFound(AsyncWebServerRequest *request);
|
||||
private:
|
||||
void processQueue();
|
||||
bool queueCommand(const web_command_t &cmd);
|
||||
};
|
||||
#endif
|
||||
11
test/README
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
This directory is intended for PlatformIO Test Runner and project tests.
|
||||
|
||||
Unit Testing is a software testing method by which individual units of
|
||||
source code, sets of one or more MCU program modules together with associated
|
||||
control data, usage procedures, and operating procedures, are tested to
|
||||
determine whether they are fit for use. Unit testing finds problems early
|
||||
in the development cycle.
|
||||
|
||||
More information about PlatformIO Unit Testing:
|
||||
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
|
||||