73 Commits

Author SHA1 Message Date
Kuba Szczodrzyński
a21c07fa03 [release] v0.5.0
Some checks failed
Lint check / Lint with clang-format (push) Has been cancelled
Lint check / Lint with black (push) Has been cancelled
PlatformIO Publish / publish (push) Has been cancelled
2022-06-02 23:06:11 +02:00
Kuba Szczodrzyński
bdffa7ef53 [core] Add UF2-based uploader 2022-06-02 23:05:35 +02:00
Kuba Szczodrzyński
22d40825bb [tools] Extract common utilities to separate modules 2022-06-02 22:22:23 +02:00
Kuba Szczodrzyński
50f26f546c [core] Use term "family" instead of "platform" 2022-06-02 22:22:19 +02:00
Kuba Szczodrzyński
9c7ea46ec3 [core] Build UF2 OTA image after linking 2022-06-02 22:21:51 +02:00
Kuba Szczodrzyński
5b4cf53d8a [tools] uf2ota: embed build date in extension tags 2022-06-02 14:14:16 +02:00
Kuba Szczodrzyński
79a701a4d4 [tools] uf2ota: fix for Python 3.7, fix Windows path compatibility 2022-06-02 14:12:03 +02:00
Kuba Szczodrzyński
81897e634c [realtek-ambz] Export both OTA images after linking 2022-06-01 21:41:04 +02:00
Kuba Szczodrzyński
3e11da4dd4 [docs] Add resources page 2022-05-31 12:06:24 +02:00
Kuba Szczodrzyński
12aa7fef04 [docs] Move sections from README to docs, add uf2families.json 2022-05-31 11:54:05 +02:00
Kuba Szczodrzyński
1ea6420bbc [core] Add Update library 2022-05-30 22:31:04 +02:00
Kuba Szczodrzyński
f3f1f36525 [core] Add uf2ota library source 2022-05-30 22:23:40 +02:00
Kuba Szczodrzyński
dee9a98cc3 [core] Update OTA API 2022-05-30 21:59:22 +02:00
Kuba Szczodrzyński
3345ce3fb9 [docs] Add missing documents to SUMMARY.md 2022-05-28 20:13:01 +02:00
Kuba Szczodrzyński
de70583838 [core] Put full UF2 Family ID in ChipFamily 2022-05-28 20:09:10 +02:00
Kuba Szczodrzyński
a43a737004 [realtek-ambz] Implement API class methods, fix CPU clock 2022-05-28 19:41:44 +02:00
Kuba Szczodrzyński
1f6899354f [core] Add LT class API methods 2022-05-28 19:39:43 +02:00
Kuba Szczodrzyński
9110a0c47e [docs] Document the UF2 OTA format 2022-05-28 14:48:07 +02:00
Kuba Szczodrzyński
b549790798 [tools] Implement UF2 binary patching, add dumping images 2022-05-28 14:47:45 +02:00
Kuba Szczodrzyński
5df430f3be [tools] Add UF2 OTA writer tool 2022-05-27 20:53:08 +02:00
Kuba Szczodrzyński
f3e8bcd74a [realtek-ambz] Add KVS partition, update boardgen 2022-05-27 15:29:46 +02:00
Kuba Szczodrzyński
4b050e11cf [core] Add dynamic FAL_PART_TABLE generation 2022-05-27 15:29:21 +02:00
Kuba Szczodrzyński
7ddbc09564 [core] Workaround LwIPmDNS compilation 2022-05-26 14:24:51 +02:00
Kuba Szczodrzyński
d3d62f80fd [realtek-ambz] Fix WiFi encryption type conversion 2022-05-26 14:24:29 +02:00
Kuba Szczodrzyński
bc7dbe6eec [api] Fix hexdump() default parameters 2022-05-25 12:27:52 +02:00
Kuba Szczodrzyński
e625f55353 [api] Add hexdump() utility 2022-05-24 17:55:12 +02:00
Kuba Szczodrzyński
aeebff9d5d [realtek-ambz] Fix WiFiEvents linking when not needed 2022-05-24 17:47:45 +02:00
Kuba Szczodrzyński
9a3c077ef1 [core] Add FlashDB KVS library 2022-05-24 17:43:30 +02:00
Kuba Szczodrzyński
91ae692058 [core] Fix gathering external library dependencies 2022-05-24 17:30:14 +02:00
Kuba Szczodrzyński
ad590d1eb2 [release] v0.4.0
Some checks failed
Lint check / Lint with clang-format (push) Has been cancelled
Lint check / Lint with black (push) Has been cancelled
PlatformIO Publish / publish (push) Has been cancelled
2022-05-23 14:21:19 +02:00
Kuba Szczodrzyński
8b00358901 [core] Add lwIP-based mDNS responder library 2022-05-23 14:20:41 +02:00
Kuba Szczodrzyński
bf4d8bb9be [realtek-ambz] Update to external lwIP v2.0.0 2022-05-23 12:49:18 +02:00
Kuba Szczodrzyński
157dd2f407 [realtek-ambz] Move to external lwIP v1.4.1 2022-05-23 12:49:18 +02:00
Kuba Szczodrzyński
1dc47878d4 [core] Allow using external framework parts 2022-05-18 20:57:09 +02:00
Kuba Szczodrzyński
36e4b6fd45 [core] Write manifests when installing packages 2022-05-18 20:47:40 +02:00
Kuba Szczodrzyński
36d9a6e7e4 [realtek-ambz] Support WiFi events 2022-05-17 19:27:35 +02:00
Kuba Szczodrzyński
b7cceed4d3 [core] Create POSIX utils module 2022-05-16 15:54:52 +02:00
Kuba Szczodrzyński
694c06e7f5 [core] Allow using "arduino" as framework 2022-05-16 15:53:51 +02:00
Kuba Szczodrzyński
12338747bf [realtek-ambz] Add Wire library 2022-05-14 21:47:56 +02:00
Kuba Szczodrzyński
f4f9788f68 [github] Add board readme generated with boardgen 2022-05-14 20:20:39 +02:00
Kuba Szczodrzyński
b9cdcad262 [core] Add boardgen submodule 2022-05-14 14:21:15 +02:00
Kuba Szczodrzyński
e4cd2ddec8 [core] Split boards as base files 2022-05-10 17:48:41 +02:00
Kuba Szczodrzyński
1477649a50 [core] Move LibreTuyaAPI to core directory 2022-05-09 15:41:22 +02:00
Kuba Szczodrzyński
2e80469ab3 [core] Fix and enable SSL client support 2022-05-08 18:43:10 +02:00
Kuba Szczodrzyński
ccf63a4cdb [realtek-ambz] Add platform configs to fixups 2022-05-08 18:42:19 +02:00
Kuba Szczodrzyński
b464871f73 [realtek-ambz] Remove PolarSSL completely, fix errno 2022-05-08 18:41:56 +02:00
Kuba Szczodrzyński
e44834a95e [docs] Fix mkdocs deployment, again, again 2022-05-07 20:47:34 +02:00
Kuba Szczodrzyński
4efefc5c6c [docs] Fix mkdocs deployment, again 2022-05-07 20:38:13 +02:00
Kuba Szczodrzyński
dfd852b2db [docs] Fix mkdocs deployment 2022-05-07 20:36:41 +02:00
Kuba Szczodrzyński
afa7141ceb [docs] Add Doxygen 2022-05-07 20:35:03 +02:00
Kuba Szczodrzyński
ed76b23c69 [release] v0.3.0
Some checks failed
Lint check / Lint with clang-format (push) Has been cancelled
Lint check / Lint with black (push) Has been cancelled
PlatformIO Publish / publish (push) Has been cancelled
2022-05-06 20:28:10 +02:00
Kuba Szczodrzyński
a469a466ff [github] Update README.md badges 2022-05-06 20:27:43 +02:00
Kuba Szczodrzyński
589d3ef4d8 [core] Refactor realtek-ambz, cleanup, reformat, add missing copyrights 2022-05-06 20:27:06 +02:00
Kuba Szczodrzyński
5aba2eb4a1 [core] Reformat filter_rtl_hard_fault.py with updated black 2022-05-06 15:47:46 +02:00
Kuba Szczodrzyński
b7df8d7b37 [github] Add lint check workflow 2022-05-06 15:42:54 +02:00
Kuba Szczodrzyński
61554e6c7e [realtek-ambz] Describe C library functions 2022-05-06 15:42:27 +02:00
Kuba Szczodrzyński
d8cc61bc0e [realtek-ambz] Remove old WiFi library 2022-05-06 12:14:48 +02:00
Kuba Szczodrzyński
4b6e3956d6 [realtek-ambz] Implement SSL client, cleanup compilation 2022-05-05 21:25:19 +02:00
Kuba Szczodrzyński
9659ff8afa [core] Add MbedTLSClient 2022-05-05 20:56:37 +02:00
Kuba Szczodrzyński
783955cc5d [core] Refactor build system, cleanup unused files 2022-05-04 21:45:30 +02:00
Kuba Szczodrzyński
048556803b [core] Move non-interface classes to libraries 2022-04-30 21:08:09 +02:00
Kuba Szczodrzyński
5c46939556 [release] v0.2.0
Some checks failed
PlatformIO Publish / publish (push) Has been cancelled
2022-04-30 19:11:29 +02:00
Kuba Szczodrzyński
222d58e973 [core] Add WebServer library from ESP32 2022-04-30 19:04:46 +02:00
Kuba Szczodrzyński
38343112a5 [core] Add WiFiServer(addr, port) constructor 2022-04-30 19:03:37 +02:00
Kuba Szczodrzyński
770a7bc4fa [core] Fix GDB init commands 2022-04-30 19:03:02 +02:00
Kuba Szczodrzyński
d6695f127d [core] Add FS API from ESP32 2022-04-30 19:02:23 +02:00
Kuba Szczodrzyński
35e5fc5173 [realtek-ambz] Fix WiFiClient copy assignment 2022-04-30 19:00:17 +02:00
Kuba Szczodrzyński
b58ea46c22 [docs] Fix debugging.md case 2022-04-29 23:19:42 +02:00
Kuba Szczodrzyński
470eb64051 [docs] Migrate to mkdocs 2022-04-29 23:16:32 +02:00
Kuba Szczodrzyński
6192e9be72 [docs] Describe libraries 2022-04-29 21:32:00 +02:00
Kuba Szczodrzyński
b518451888 [realtek-ambz] Fix enabling WiFi before scanning 2022-04-29 21:31:47 +02:00
Kuba Szczodrzyński
7b5dcdf07e [core] Add HTTPClient and WiFiMulti libs 2022-04-29 21:31:29 +02:00
Kuba Szczodrzyński
515d47f055 [core] Add MQTT library compatibility 2022-04-29 19:19:49 +02:00
272 changed files with 17082 additions and 14482 deletions

29
.clang-format Normal file
View File

@@ -0,0 +1,29 @@
Language: Cpp
BasedOnStyle: LLVM
AlignAfterOpenBracket: BlockIndent
AlignArrayOfStructures: Left
AlignConsecutiveAssignments: true
AlignConsecutiveMacros: AcrossComments
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: false
AllowShortBlocksOnASingleLine: Empty
AllowShortFunctionsOnASingleLine: Empty
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BreakBeforeTernaryOperators: true
ColumnLimit: 120
ContinuationIndentWidth: 4
EmptyLineBeforeAccessModifier: Always
FixNamespaceComments: true
IndentAccessModifiers: false
IndentCaseLabels: true
IndentWidth: 4
LambdaBodyIndentation: Signature
MaxEmptyLinesToKeep: 1
# PointerAlignment: Left # TODO enable this and reformat project
QualifierAlignment: Left
ReflowComments: true
SeparateDefinitionBlocks: Always
TabWidth: 4
UseTab: Always

22
.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: Deploy docs on GitHub Pages
on:
push:
branches:
- master
jobs:
docs:
name: Deploy docs
runs-on: ubuntu-latest
steps:
- name: Checkout main
uses: actions/checkout@v2
- name: Deploy docs
uses: mhausenblas/mkdocs-deploy-gh-pages@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CONFIG_FILE: mkdocs.yml
EXTRA_PACKAGES: build-base doxygen
REQUIREMENTS: docs/requirements.txt

35
.github/workflows/lint.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: Lint check
on: [push, pull_request]
jobs:
lint-clang-format:
name: Lint with clang-format
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Check code with clang-format
uses: jidicula/clang-format-action@v4.5.0
with:
clang-format-version: "14"
lint-black:
name: Lint with black
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: "3.9"
- name: Install test dependencies
uses: BSFishy/pip-action@v1
with:
packages: |
black
isort
- name: Check code with black
run: black --check .
- name: Check code with isort
run: isort --profile black . --check-only

6
.gitignore vendored
View File

@@ -252,3 +252,9 @@ cython_debug/
# End of https://www.toptal.com/developers/gitignore/api/c,c++,visualstudiocode,python
.vscode/settings.json
# mkdocs
xml/
ltapi/
ltambz/
hashChanges.yaml

2
.gitmodules vendored Normal file
View File

@@ -0,0 +1,2 @@
[submodule "tools/boardgen"]
url = https://github.com/kuba2k2/boardgen

121
README.md
View File

@@ -1,10 +1,15 @@
# LibreTuya
<div align="center">
<div align="center" markdown>
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/kuba2k2/libretuya/Deploy%20docs%20on%20GitHub%20Pages?label=docs&logo=markdown)](https://kuba2k2.github.io/libretuya/)
![GitHub last commit](https://img.shields.io/github/last-commit/kuba2k2/libretuya?logo=github)
[![Code style: clang-format](https://img.shields.io/badge/code%20style-clang--format-purple.svg)](.clang-format)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Discord](https://img.shields.io/discord/967863521511608370?color=%235865F2&label=Discord&logo=discord&logoColor=white)](https://discord.gg/SyGCB9Xwtf)
[![PlatformIO Registry](https://badges.registry.platformio.org/packages/kuba2k2/platform/libretuya.svg)](https://registry.platformio.org/platforms/kuba2k2/libretuya)
![GitHub last commit](https://img.shields.io/github/last-commit/kuba2k2/libretuya)
![RTL8710BN](https://img.shields.io/badge/-rtl8710bn-blue)
@@ -13,25 +18,25 @@
PlatformIO development platform for IoT modules manufactured by Tuya Inc.
The main goal of this project is to provide a usable build environment for IoT developers. While also providing vendor SDKs as PlatformIO cores,
the project focuses on developing working Arduino-compatible cores for supported platforms. The cores are inspired by Espressif's official core for ESP32,
which should make it easier to port/run existing ESP apps on Tuya IoT (and 3-rd party) platforms.
the project focuses on developing working Arduino-compatible cores for supported families. The cores are inspired by Espressif's official core for ESP32,
which should make it easier to port/run existing ESP apps on Tuya IoT (and 3-rd party) modules.
LibreTuya also provides a common interface for all platform implementations. The interface is based on ESP32 official libraries.
LibreTuya also provides a common interface for all family implementations. The interface is based on ESP32 official libraries.
**Note:** this project is work-in-progress.
## Usage
1. [Install PlatformIO](https://platformio.org/platformio-ide)
2. `platformio platform install libretuya`
2. `platformio platform install https://github.com/kuba2k2/libretuya`
3. Create a project, build it and upload!
4. See the [docs](docs/README.md) for any questions/problems.
4. See the [docs](https://kuba2k2.github.io/libretuya/) for any questions/problems.
## Board List
A (mostly) complete* list of Tuya wireless module boards.
&nbsp; | Module Name | MCU | Flash | RAM | Pins** | Wi-Fi | BLE | Platform name
&nbsp; | Module Name | MCU | Flash | RAM | Pins** | Wi-Fi | BLE | Family name
------------------------------|------------------------------------------------------------------------------------------------|-------------------------|-------|----------|-------------|-------|-----|---------------
❌ | [WB1S](https://developer.tuya.com/en/docs/iot/wb1s?id=K9duevbj3ol4x) | BK7231T @ 120 MHz | 2 MiB | 256 KiB | 18 (11 I/O) | ✔️ | ✔️ | -
❌ | [WB2L](https://developer.tuya.com/en/docs/iot/wb2l-datasheet?id=K9duegc9bualu) | BK7231T @ 120 MHz | 2 MiB | 256 KiB | 7 (5 I/O) | ✔️ | ✔️ | -
@@ -87,74 +92,6 @@ A (mostly) complete* list of Tuya wireless module boards.
** I/O count includes GPIOs, ADCs, PWM outputs and UART, but doesn't count CEN/RST and power pins.
## Project structure
```
arduino/
├─ <platform name>/ Arduino Core for specific platform
│ ├─ cores/ Core files
│ ├─ libraries/ Supported built-in libraries
├─ libretuya/
│ ├─ api/ LibreTuya API for Arduino frameworks
│ ├─ compat/ Fixes for compatibility with ESP32 framework
│ ├─ libraries/ Built-in platform-independent libraries
boards/
├─ <board name>/ Board-specific code
│ ├─ variant.cpp Arduino variant initialization
│ ├─ variant.h Arduino variant pin configs
├─ <board name>.json PlatformIO board description
builder/
├─ frameworks/ Framework builders for PlatformIO
│ ├─ <platform name>-sdk.py Vanilla SDK build system
│ ├─ <platform name>-arduino.py Arduino Core build system
├─ arduino-common.py Builder to provide ArduinoCore-API and LibreTuya APIs
├─ main.py Main PlatformIO builder
docs/ Project documentation, guides, tips, etc.
platform/
├─ <platform name>/ Platform-specific configurations
│ ├─ bin/ Binary blobs (bootloaders, etc.)
│ ├─ fixups/ Code fix-ups to replace SDK parts
│ ├─ ld/ Linker scripts
│ ├─ openocd/ OpenOCD configuration files
tools/
├─ <tool name>/ Tools used during the build
platform.json PlatformIO manifest
platform.py Custom PlatformIO script
```
## Platforms
A list of platforms currently available in this project.
Platform name | Supported MCU(s) | Arduino Core | Source SDK (PIO framework)
---------------|------------------------------------------------------------------------|--------------|--------------------------------------------------------------------------
`realtek-ambz` | Realtek [AmebaZ](https://www.amebaiot.com/en/amebaz/) SoC (`RTL87xxB`) | ✔️ | `framework-realtek-amb1` ([amb1_sdk](https://github.com/ambiot/amb1_sdk))
### Realtek Ameba
The logic behind naming of Realtek chips and their series took me some time to figure out:
- RTL8xxxA - Ameba1/Ameba Series
- RTL8xxxB - AmebaZ Series
- RTL8xxxC - AmebaZ2/ZII Series
- RTL8xxxD - AmebaD Series
As such, there are numerous CPUs with the same numbers but different series, which makes them require different code and SDKs.
- [RTL8195AM](https://www.realtek.com/en/products/communications-network-ics/item/rtl8195am)
- RTL8710AF (found in amb1_arduino)
- [RTL8711AM](https://www.realtek.com/en/products/communications-network-ics/item/rtl8711am)
- [RTL8710BN](https://www.realtek.com/en/products/communications-network-ics/item/rtl8710bn)
- RTL8710BX (found in Tuya product pages)
- RTL8710B? (found in amb1_sdk)
- RTL8711B? (found in amb1_sdk)
- [RTL8710CM](https://www.realtek.com/en/products/communications-network-ics/item/rtl8710cm)
- RTL8722CSM (found in ambd_arduino)
- RTL8720DN (found in ambd_arduino)
- [RTL8721DM](https://www.realtek.com/en/products/communications-network-ics/item/rtl8721dm)
- RTL8722DM (found in ambd_arduino)
- and probably many more
Different Ameba series are not compatible with each other. Apparently, there isn't an official public SDK for AmebaZ that can support C++ properly.
## Arduino Core support status
Note: this list will probably change with each functionality update.
@@ -169,29 +106,39 @@ Flash I/O | ❓
**CORE LIBRARIES** |
SoftwareSerial | ❌
SPI | ❌
Wire |
Wire |
**OTHER LIBRARIES** |
Wi-Fi Station | ✔️
Wi-Fi Access Point | ✔️
Wi-Fi Events | ❌
Wi-Fi Client (SSL) | ✔️ (❌)
Wi-Fi STA/AP/Mixed | ✔️
Wi-Fi Client (SSL) | ✔️ (✔️)
Wi-Fi Server | ✔️
Wi-Fi Events | ✔️
IPv6 | ❌
HTTP Client (SSL) | ✔️ (✔️)
HTTP Server | ✔️
NVS / Preferences | ✔️
SPIFFS | ❌
BLE | -
HTTP | ❌
NTP | ❌
OTA |
MDNS |
MQTT |
OTA |
MDNS | ✔️
MQTT |
SD | ❌
Legend:
Symbols:
- ✔️ working
- ❗ broken
- ✅ tested, external library
- ❓ untested
- ❗ broken
- ❌ not implemented (yet?)
- \- not applicable
Names:
- Core functions - stuff like delay(), millis(), yield(), etc.
- **CORE LIBRARIES** - included normally in all Arduino cores
- **OTHER LIBRARIES** - included in ESP32 core or downloadable
## License
See [LICENSE](LICENSE). Project is licensed under MIT License.

55
SUMMARY.md Normal file
View File

@@ -0,0 +1,55 @@
* [Home](README.md)
* [💻 Family list](docs/families.md)
* [✔️ Implementation status](docs/implementation-status.md)
* [🔧 Configuration](docs/config.md)
* [📁 Project structure](docs/project-structure.md)
* 🔖 Code reference
* [LibreTuya API](docs/reference/lt-api.md)
* [Class reference](ltapi/class_libre_tuya.md)
* [Static functions](ltapi/_libre_tuya_a_p_i_8cpp.md)
* [Logger](ltapi/lt__logger_8h.md)
* [Chip types & UF2 families](ltapi/_chip_type_8h.md)
* [POSIX utilities](ltapi/lt__posix__api_8h.md)
* Common API
* [Flash](ltapi/class_i_flash_class.md)
* [FS](ltapi/classfs_1_1_f_s.md)
* [Preferences](ltapi/class_i_preferences.md)
* [WiFi API](ltapi/class_i_wi_fi_generic_class.md)
* [Station](ltapi/class_i_wi_fi_s_t_a_class.md)
* [Access Point](ltapi/class_i_wi_fi_a_p_class.md)
* [Scanning](ltapi/class_i_wi_fi_scan_class.md)
* [TCP Client](ltapi/class_i_wi_fi_client.md)
* [SSL Client](ltapi/class_i_wi_fi_client_secure.md)
* [TCP Server](ltapi/class_i_wi_fi_server.md)
* [LibreTuya libraries](docs/libs-built-in.md)
* [base64](ltapi/classbase64.md)
* [HTTPClient](ltapi/class_h_t_t_p_client.md)
* [mDNS](ltapi/classm_d_n_s.md)
* NetUtils
* [ssl/MbedTLSClient](ltapi/class_mbed_t_l_s_client.md)
* [IPv6Address](ltapi/classarduino_1_1_i_pv6_address.md)
* [LwIPRxBuffer](ltapi/class_lw_i_p_rx_buffer.md)
* [Update](ltapi/class_update_class.md)
* [WebServer](ltapi/class_web_server.md)
* [WiFiMulti](ltapi/class_wi_fi_multi.md)
* [Third party libraries](docs/libs-3rd-party.md)
* Full documentation
* [Classes](ltapi/classes.md)
* [Functions](ltapi/functions.md)
* [Macros](ltapi/macros.md)
* [File list](ltapi/files.md)
* [✈️ OTA format](docs/ota/README.md)
* [uf2ota.py tool](docs/ota/uf2ota.md)
* [uf2ota.h library](docs/ota/library.md)
* [uf2ota.h reference](ltapi/uf2ota_8h.md)
* Families
* [Realtek - notes](docs/platform/realtek/README.md)
* Realtek AmebaZ Series
* Boards
* [WR3](boards/wr3/README.md)
* C library
* [Built-in functions](docs/platform/realtek-ambz/stdlib.md)
* [Memory management](docs/platform/realtek-ambz/memory-management.md)
* [Debugging](docs/platform/realtek/debugging.md)
* [Exception decoder](docs/platform/realtek/exception-decoder.md)
* [🔗 Resources](docs/resources.md)

View File

@@ -0,0 +1,5 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-17. */
#include "Events.h"
uint16_t EventHandler_s::lastId = 1;

View File

@@ -0,0 +1,117 @@
/*
ESP8266WiFiGeneric.h - esp8266 Wifi support.
Based on WiFi.h from Ardiono WiFi shield library.
Copyright (c) 2011-2014 Arduino. All right reserved.
Modified by Ivan Grokhotkov, December 2014
Reworked by Markus Sattler, December 2015
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include <functional>
#include "WiFiEvents.h"
typedef enum {
ARDUINO_EVENT_WIFI_READY = 0, /**< ESP32 WiFi ready */
ARDUINO_EVENT_WIFI_SCAN_DONE, /**< ESP32 finish scanning AP */
ARDUINO_EVENT_WIFI_STA_START, /**< ESP32 station start */
ARDUINO_EVENT_WIFI_STA_STOP, /**< ESP32 station stop */
ARDUINO_EVENT_WIFI_STA_CONNECTED, /**< ESP32 station connected to AP */
ARDUINO_EVENT_WIFI_STA_DISCONNECTED, /**< ESP32 station disconnected from AP */
ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE, /**< the auth mode of AP connected by ESP32 station changed */
ARDUINO_EVENT_WIFI_STA_GOT_IP,
ARDUINO_EVENT_WIFI_STA_GOT_IP6,
ARDUINO_EVENT_WIFI_STA_LOST_IP,
ARDUINO_EVENT_WIFI_AP_START, /**< ESP32 soft-AP start */
ARDUINO_EVENT_WIFI_AP_STOP, /**< ESP32 soft-AP stop */
ARDUINO_EVENT_WIFI_AP_STACONNECTED, /**< a station connected to ESP32 soft-AP */
ARDUINO_EVENT_WIFI_AP_STADISCONNECTED, /**< a station disconnected from ESP32 soft-AP */
ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED,
ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED, /**< Receive probe request packet in soft-AP interface */
ARDUINO_EVENT_WIFI_AP_GOT_IP6,
ARDUINO_EVENT_WIFI_FTM_REPORT, /**< Receive report of FTM procedure */
ARDUINO_EVENT_ETH_START,
ARDUINO_EVENT_ETH_STOP,
ARDUINO_EVENT_ETH_CONNECTED,
ARDUINO_EVENT_ETH_DISCONNECTED,
ARDUINO_EVENT_ETH_GOT_IP,
ARDUINO_EVENT_ETH_GOT_IP6,
ARDUINO_EVENT_WPS_ER_SUCCESS, /**< ESP32 station wps succeeds in enrollee mode */
ARDUINO_EVENT_WPS_ER_FAILED, /**< ESP32 station wps fails in enrollee mode */
ARDUINO_EVENT_WPS_ER_TIMEOUT, /**< ESP32 station wps timeout in enrollee mode */
ARDUINO_EVENT_WPS_ER_PIN, /**< ESP32 station wps pin code in enrollee mode */
ARDUINO_EVENT_WPS_ER_PBC_OVERLAP, /**< ESP32 station wps overlap in enrollee mode */
ARDUINO_EVENT_SC_SCAN_DONE,
ARDUINO_EVENT_SC_FOUND_CHANNEL,
ARDUINO_EVENT_SC_GOT_SSID_PSWD,
ARDUINO_EVENT_SC_SEND_ACK_DONE,
ARDUINO_EVENT_PROV_INIT,
ARDUINO_EVENT_PROV_DEINIT,
ARDUINO_EVENT_PROV_START,
ARDUINO_EVENT_PROV_END,
ARDUINO_EVENT_PROV_CRED_RECV,
ARDUINO_EVENT_PROV_CRED_FAIL,
ARDUINO_EVENT_PROV_CRED_SUCCESS,
ARDUINO_EVENT_MAX
} arduino_event_id_t;
typedef union {
wifi_event_sta_scan_done_t wifi_scan_done;
wifi_event_sta_authmode_change_t wifi_sta_authmode_change;
wifi_event_sta_connected_t wifi_sta_connected;
wifi_event_sta_disconnected_t wifi_sta_disconnected;
wifi_event_sta_wps_er_pin_t wps_er_pin;
wifi_event_sta_wps_fail_reason_t wps_fail_reason;
wifi_event_ap_probe_req_rx_t wifi_ap_probereqrecved;
wifi_event_ap_staconnected_t wifi_ap_staconnected;
wifi_event_ap_stadisconnected_t wifi_ap_stadisconnected;
wifi_event_ftm_report_t wifi_ftm_report;
ip_event_ap_staipassigned_t wifi_ap_staipassigned;
ip_event_got_ip_t got_ip;
ip_event_got_ip6_t got_ip6;
// smartconfig_event_got_ssid_pswd_t sc_got_ssid_pswd;
// esp_eth_handle_t eth_connected;
// wifi_sta_config_t prov_cred_recv;
// wifi_prov_sta_fail_reason_t prov_fail_reason;
} arduino_event_info_t;
typedef struct {
arduino_event_id_t event_id;
arduino_event_info_t event_info;
} arduino_event_t;
#define EventId arduino_event_id_t
#define EventId_t arduino_event_id_t
#define EventInfo arduino_event_info_t
#define EventInfo_t arduino_event_info_t
#define Event_t arduino_event_t
typedef void (*EventCb)(EventId event);
typedef std::function<void(EventId event, EventInfo info)> EventFuncCb;
typedef void (*EventSysCb)(Event_t *event);
typedef struct EventHandler_s {
static uint16_t lastId;
uint16_t id;
EventCb cb;
EventFuncCb fcb;
EventSysCb scb;
EventId eventId;
EventHandler_s() : id(lastId++), cb(NULL), fcb(NULL), scb(NULL) {}
} EventHandler;

View File

@@ -0,0 +1,228 @@
/*
FS.cpp - file system wrapper
Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "FS.h"
using namespace fs;
size_t File::write(uint8_t c) {
if (!*this) {
return 0;
}
return _p->write(&c, 1);
}
time_t File::getLastWrite() {
if (!*this) {
return 0;
}
return _p->getLastWrite();
}
size_t File::write(const uint8_t *buf, size_t size) {
if (!*this) {
return 0;
}
return _p->write(buf, size);
}
int File::available() {
if (!*this) {
return false;
}
return _p->size() - _p->position();
}
int File::read() {
if (!*this) {
return -1;
}
uint8_t result;
if (_p->read(&result, 1) != 1) {
return -1;
}
return result;
}
size_t File::read(uint8_t *buf, size_t size) {
if (!*this) {
return -1;
}
return _p->read(buf, size);
}
int File::peek() {
if (!*this) {
return -1;
}
size_t curPos = _p->position();
int result = read();
seek(curPos, SeekSet);
return result;
}
void File::flush() {
if (!*this) {
return;
}
_p->flush();
}
bool File::seek(uint32_t pos, SeekMode mode) {
if (!*this) {
return false;
}
return _p->seek(pos, mode);
}
size_t File::position() const {
if (!*this) {
return 0;
}
return _p->position();
}
size_t File::size() const {
if (!*this) {
return 0;
}
return _p->size();
}
bool File::setBufferSize(size_t size) {
if (!*this) {
return 0;
}
return _p->setBufferSize(size);
}
void File::close() {
if (_p) {
_p->close();
_p = nullptr;
}
}
File::operator bool() const {
return _p != nullptr && *_p != false;
}
const char *File::path() const {
if (!*this) {
return nullptr;
}
return _p->path();
}
const char *File::name() const {
if (!*this) {
return nullptr;
}
return _p->name();
}
// to implement
boolean File::isDirectory(void) {
if (!*this) {
return false;
}
return _p->isDirectory();
}
File File::openNextFile(const char *mode) {
if (!*this) {
return File();
}
return _p->openNextFile(mode);
}
void File::rewindDirectory(void) {
if (!*this) {
return;
}
_p->rewindDirectory();
}
File FS::open(const String &path, const char *mode, const bool create) {
return open(path.c_str(), mode, create);
}
File FS::open(const char *path, const char *mode, const bool create) {
if (!_impl) {
return File();
}
return File(_impl->open(path, mode, create));
}
bool FS::exists(const char *path) {
if (!_impl) {
return false;
}
return _impl->exists(path);
}
bool FS::exists(const String &path) {
return exists(path.c_str());
}
bool FS::remove(const char *path) {
if (!_impl) {
return false;
}
return _impl->remove(path);
}
bool FS::remove(const String &path) {
return remove(path.c_str());
}
bool FS::rename(const char *pathFrom, const char *pathTo) {
if (!_impl) {
return false;
}
return _impl->rename(pathFrom, pathTo);
}
bool FS::rename(const String &pathFrom, const String &pathTo) {
return rename(pathFrom.c_str(), pathTo.c_str());
}
bool FS::mkdir(const char *path) {
if (!_impl) {
return false;
}
return _impl->mkdir(path);
}
bool FS::mkdir(const String &path) {
return mkdir(path.c_str());
}
bool FS::rmdir(const char *path) {
if (!_impl) {
return false;
}
return _impl->rmdir(path);
}
bool FS::rmdir(const String &path) {
return rmdir(path.c_str());
}

152
arduino/libretuya/api/FS.h Normal file
View File

@@ -0,0 +1,152 @@
/*
FS.h - file system wrapper
Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include <Arduino.h>
#include <memory>
namespace fs {
#define FILE_READ "r"
#define FILE_WRITE "w"
#define FILE_APPEND "a"
class File;
class FileImpl;
typedef std::shared_ptr<FileImpl> FileImplPtr;
class FSImpl;
typedef std::shared_ptr<FSImpl> FSImplPtr;
enum SeekMode { SeekSet = 0, SeekCur = 1, SeekEnd = 2 };
class File : public Stream {
public:
File(FileImplPtr p = FileImplPtr()) : _p(p) {
_timeout = 0;
}
size_t write(uint8_t) override;
size_t write(const uint8_t *buf, size_t size) override;
int available() override;
int read() override;
int peek() override;
void flush() override;
size_t read(uint8_t *buf, size_t size);
size_t readBytes(char *buffer, size_t length) {
return read((uint8_t *)buffer, length);
}
bool seek(uint32_t pos, SeekMode mode);
bool seek(uint32_t pos) {
return seek(pos, SeekSet);
}
size_t position() const;
size_t size() const;
bool setBufferSize(size_t size);
void close();
operator bool() const;
time_t getLastWrite();
const char *path() const;
const char *name() const;
boolean isDirectory(void);
File openNextFile(const char *mode = FILE_READ);
void rewindDirectory(void);
protected:
FileImplPtr _p;
};
class FileImpl {
public:
virtual ~FileImpl() {}
virtual size_t write(const uint8_t *buf, size_t size) = 0;
virtual size_t read(uint8_t *buf, size_t size) = 0;
virtual void flush() = 0;
virtual bool seek(uint32_t pos, SeekMode mode) = 0;
virtual size_t position() const = 0;
virtual size_t size() const = 0;
virtual bool setBufferSize(size_t size) = 0;
virtual void close() = 0;
virtual time_t getLastWrite() = 0;
virtual const char *path() const = 0;
virtual const char *name() const = 0;
virtual boolean isDirectory(void) = 0;
virtual FileImplPtr openNextFile(const char *mode) = 0;
virtual void rewindDirectory(void) = 0;
virtual operator bool() = 0;
};
class FS {
public:
FS(FSImplPtr impl) : _impl(impl) {}
File open(const char *path, const char *mode = FILE_READ, const bool create = false);
File open(const String &path, const char *mode = FILE_READ, const bool create = false);
bool exists(const char *path);
bool exists(const String &path);
bool remove(const char *path);
bool remove(const String &path);
bool rename(const char *pathFrom, const char *pathTo);
bool rename(const String &pathFrom, const String &pathTo);
bool mkdir(const char *path);
bool mkdir(const String &path);
bool rmdir(const char *path);
bool rmdir(const String &path);
protected:
FSImplPtr _impl;
};
class FSImpl {
public:
FSImpl() {}
virtual ~FSImpl() {}
virtual FileImplPtr open(const char *path, const char *mode, const bool create) = 0;
virtual bool exists(const char *path) = 0;
virtual bool rename(const char *pathFrom, const char *pathTo) = 0;
virtual bool remove(const char *path) = 0;
virtual bool mkdir(const char *path) = 0;
virtual bool rmdir(const char *path) = 0;
};
} // namespace fs
#ifndef FS_NO_GLOBALS
using fs::File;
using fs::FS;
using fs::SeekCur;
using fs::SeekEnd;
using fs::SeekMode;
using fs::SeekSet;
#endif // FS_NO_GLOBALS

View File

@@ -1,3 +1,5 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-24. */
#pragma once
#include <stdbool.h>

View File

@@ -1,91 +0,0 @@
/*
IPv6Address.cpp - Base class that provides IPv6Address
Copyright (c) 2011 Adrian McEwen. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <Arduino.h>
#include <api/Print.h>
#include "IPv6Address.h"
IPv6Address::IPv6Address()
{
memset(_address.bytes, 0, sizeof(_address.bytes));
}
IPv6Address::IPv6Address(const uint8_t *address)
{
memcpy(_address.bytes, address, sizeof(_address.bytes));
}
IPv6Address::IPv6Address(const uint32_t *address)
{
memcpy(_address.bytes, (const uint8_t *)address, sizeof(_address.bytes));
}
IPv6Address& IPv6Address::operator=(const uint8_t *address)
{
memcpy(_address.bytes, address, sizeof(_address.bytes));
return *this;
}
bool IPv6Address::operator==(const uint8_t* addr) const
{
return memcmp(addr, _address.bytes, sizeof(_address.bytes)) == 0;
}
size_t IPv6Address::printTo(Print& p) const
{
/* size_t n = 0;
for(int i = 0; i < 16; i+=2) {
if(i){
n += p.print(':');
}
n += p.printf("%02x", _address.bytes[i]);
n += p.printf("%02x", _address.bytes[i+1]);
}
return n; */
}
String IPv6Address::toString() const
{
char szRet[40];
sprintf(szRet,"%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
_address.bytes[0], _address.bytes[1], _address.bytes[2], _address.bytes[3],
_address.bytes[4], _address.bytes[5], _address.bytes[6], _address.bytes[7],
_address.bytes[8], _address.bytes[9], _address.bytes[10], _address.bytes[11],
_address.bytes[12], _address.bytes[13], _address.bytes[14], _address.bytes[15]);
return String(szRet);
}
bool IPv6Address::fromString(const char *address)
{
//format 0011:2233:4455:6677:8899:aabb:ccdd:eeff
if(strlen(address) != 39){
return false;
}
char * pos = (char *)address;
size_t i = 0;
for(i = 0; i < 16; i+=2) {
if(!sscanf(pos, "%2hhx", &_address.bytes[i]) || !sscanf(pos+2, "%2hhx", &_address.bytes[i+1])){
return false;
}
pos += 5;
}
return true;
}

View File

@@ -1,98 +0,0 @@
/*
IPv6Address.h - Base class that provides IPv6Address
Copyright (c) 2011 Adrian McEwen. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include <stdint.h>
#include <api/String.h>
#include <api/Print.h>
// A class to make it easier to handle and pass around IP addresses
namespace arduino {
class IPv6Address: public Printable
{
private:
union {
uint8_t bytes[16]; // IPv4 address
uint32_t dword[4];
} _address;
// Access the raw byte array containing the address. Because this returns a pointer
// to the internal structure rather than a copy of the address this function should only
// be used when you know that the usage of the returned uint8_t* will be transient and not
// stored.
uint8_t* raw_address()
{
return _address.bytes;
}
public:
// Constructors
IPv6Address();
IPv6Address(const uint8_t *address);
IPv6Address(const uint32_t *address);
virtual ~IPv6Address() {}
bool fromString(const char *address);
bool fromString(const String &address) { return fromString(address.c_str()); }
operator const uint8_t*() const
{
return _address.bytes;
}
operator const uint32_t*() const
{
return _address.dword;
}
bool operator==(const IPv6Address& addr) const
{
return (_address.dword[0] == addr._address.dword[0])
&& (_address.dword[1] == addr._address.dword[1])
&& (_address.dword[2] == addr._address.dword[2])
&& (_address.dword[3] == addr._address.dword[3]);
}
bool operator==(const uint8_t* addr) const;
// Overloaded index operator to allow getting and setting individual octets of the address
uint8_t operator[](int index) const
{
return _address.bytes[index];
}
uint8_t& operator[](int index)
{
return _address.bytes[index];
}
// Overloaded copy operators to allow initialisation of IPv6Address objects from other types
IPv6Address& operator=(const uint8_t *address);
// TODO implement printTo()
virtual size_t printTo(Print& p) const;
String toString() const;
friend class UDP;
friend class Client;
friend class Server;
};
}
using arduino::IPv6Address;

View File

@@ -1,36 +0,0 @@
#pragma once
#ifndef LT_VERSION
#define LT_VERSION 1.0.0
#endif
#ifndef LT_BOARD
#define LT_BOARD unknown
#endif
#define STRINGIFY(x) #x
#define STRINGIFY_MACRO(x) STRINGIFY(x)
#define LT_VERSION_STR STRINGIFY_MACRO(LT_VERSION)
#define LT_BOARD_STR STRINGIFY_MACRO(LT_BOARD)
#include <Arduino.h>
#include "LibreTuyaConfig.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
#include "lt_logger.h"
#ifdef __cplusplus
} // extern "C"
#endif
#define LT_BANNER() \
LT_LOG( \
LT_LEVEL_INFO, \
"main.cpp", \
__LINE__, \
"LibreTuya v" LT_VERSION_STR " on " LT_BOARD_STR ", compiled at " __DATE__ " " __TIME__ \
)

View File

@@ -17,7 +17,7 @@
#include <stdint.h>
#include <stdlib.h>
#include "api/String.h"
#include <api/String.h>
typedef enum {
PT_I8,

View File

@@ -0,0 +1,84 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-17. */
#include "WiFi.h"
std::vector<EventHandler> IWiFiGenericClass::handlers;
uint16_t IWiFiGenericClass::onEvent(EventCb callback, EventId eventId) {
if (!callback)
return 0;
EventHandler handler;
handler.cb = callback;
handler.eventId = eventId;
handlers.push_back(handler);
return handler.id;
}
uint16_t IWiFiGenericClass::onEvent(EventFuncCb callback, EventId eventId) {
if (!callback)
return 0;
EventHandler handler;
handler.fcb = callback;
handler.eventId = eventId;
handlers.push_back(handler);
return handler.id;
}
uint16_t IWiFiGenericClass::onEvent(EventSysCb callback, EventId eventId) {
if (!callback)
return 0;
EventHandler handler;
handler.scb = callback;
handler.eventId = eventId;
handlers.push_back(handler);
return handler.id;
}
void IWiFiGenericClass::removeEvent(EventCb callback, EventId eventId) {
if (!callback)
return;
for (uint16_t i = 0; i < handlers.size(); i++) {
EventHandler handler = handlers[i];
if (handler.cb == callback && handler.eventId == eventId) {
handlers.erase(handlers.begin() + i);
}
}
}
void IWiFiGenericClass::removeEvent(EventSysCb callback, EventId eventId) {
if (!callback)
return;
for (uint16_t i = 0; i < handlers.size(); i++) {
EventHandler handler = handlers[i];
if (handler.scb == callback && handler.eventId == eventId) {
handlers.erase(handlers.begin() + i);
}
}
}
void IWiFiGenericClass::removeEvent(uint16_t id) {
for (uint16_t i = 0; i < handlers.size(); i++) {
EventHandler handler = handlers[i];
if (handler.id == id) {
handlers.erase(handlers.begin() + i);
}
}
}
void IWiFiGenericClass::postEvent(EventId eventId, EventInfo eventInfo) {
for (auto handler : handlers) {
if (handler.eventId != ARDUINO_EVENT_MAX && handler.eventId != eventId)
continue;
if (handler.cb) {
handler.cb(eventId);
} else if (handler.fcb) {
handler.fcb(eventId, eventInfo);
} else if (handler.scb) {
Event_t event = {
.event_id = eventId,
.event_info = eventInfo,
};
handler.scb(&event);
}
}
}

View File

@@ -26,12 +26,11 @@
#include <api/IPAddress.h>
#include <api/IPv6Address.h>
#include <api/Print.h>
#include <vector>
#include "Events.h"
#include "WiFiType.h"
// TODO wifi events
// TODO WiFiMulti library
class IWiFiClass {
public:
virtual void printDiag(Print &dest) = 0;
@@ -64,6 +63,20 @@ class IWiFiGenericClass {
static IPAddress calculateBroadcast(IPAddress ip, IPAddress subnet);
static uint8_t calculateSubnetCIDR(IPAddress subnetMask);
static String macToString(uint8_t *mac);
protected:
static std::vector<EventHandler> handlers;
public:
uint16_t onEvent(EventCb callback, EventId eventId = ARDUINO_EVENT_MAX);
uint16_t onEvent(EventFuncCb callback, EventId eventId = ARDUINO_EVENT_MAX);
uint16_t onEvent(EventSysCb callback, EventId eventId = ARDUINO_EVENT_MAX);
void removeEvent(EventCb callback, EventId eventId);
void removeEvent(EventSysCb callback, EventId eventId);
void removeEvent(uint16_t id);
protected:
static void postEvent(EventId eventId, EventInfo eventInfo);
};
class IWiFiSTAClass {

View File

@@ -35,12 +35,15 @@ class IWiFiClient : public Client {
virtual size_t write(Stream &stream) = 0;
size_t write_P(PGM_P buffer, size_t size) {
return write((const uint8_t *)buffer, size);
}
virtual int fd() const = 0;
virtual int socket() = 0;
virtual int setTimeout(uint32_t seconds) = 0;
virtual IWiFiClient &operator=(const IWiFiClient &other) = 0;
virtual bool operator==(const IWiFiClient &other) const = 0;
bool operator==(const IWiFiClient &other) const;
operator bool() {
return connected();

View File

@@ -22,42 +22,28 @@
#include <Arduino.h>
#include "WiFi.h"
#include "WiFiClient.h"
class IWiFiClientSecure : public IWiFiClient {
class IWiFiClientSecure {
public:
int connect(IPAddress ip, uint16_t port, const char *rootCABuff, const char *cli_cert, const char *cli_key);
int connect(const char *host, uint16_t port, const char *rootCABuff, const char *cli_cert, const char *cli_key);
int connect(IPAddress ip, uint16_t port, const char *pskIdent, const char *psKey);
int connect(const char *host, uint16_t port, const char *pskIdent, const char *psKey);
virtual int
connect(IPAddress ip, uint16_t port, const char *rootCABuf, const char *clientCert, const char *clientKey) = 0;
virtual int
connect(const char *host, uint16_t port, const char *rootCABuf, const char *clientCert, const char *clientKey) = 0;
virtual int connect(IPAddress ip, uint16_t port, const char *pskIdent, const char *psk) = 0;
virtual int connect(const char *host, uint16_t port, const char *pskIdent, const char *psk) = 0;
int lastError(char *buf, const size_t size);
void setInsecure(); // Don't validate the chain, just accept whatever is given. VERY INSECURE!
void setPreSharedKey(const char *pskIdent, const char *psKey); // psKey in Hex
void setCACert(const char *rootCA);
void setCertificate(const char *client_ca);
void setPrivateKey(const char *private_key);
bool loadCACert(Stream &stream, size_t size);
bool loadCertificate(Stream &stream, size_t size);
bool loadPrivateKey(Stream &stream, size_t size);
bool verify(const char *fingerprint, const char *domain_name);
void setHandshakeTimeout(unsigned long handshake_timeout);
WiFiClientSecure &operator=(const WiFiClientSecure &other);
bool operator==(const bool value) {
return bool() == value;
}
bool operator!=(const bool value) {
return bool() != value;
}
bool operator==(const WiFiClientSecure &);
bool operator!=(const WiFiClientSecure &rhs) {
return !this->operator==(rhs);
};
using Print::write;
virtual int lastError(char *buf, const size_t size) = 0;
virtual void setInsecure() = 0; // Don't validate the chain, just accept whatever is given. VERY INSECURE!
virtual void setPreSharedKey(const char *pskIdent, const char *psk) = 0; // psk in hex
virtual void setCACert(const char *rootCA) = 0;
virtual void setCertificate(const char *clientCA) = 0;
virtual void setPrivateKey(const char *privateKey) = 0;
virtual bool loadCACert(Stream &stream, size_t size) = 0;
virtual bool loadCertificate(Stream &stream, size_t size) = 0;
virtual bool loadPrivateKey(Stream &stream, size_t size) = 0;
virtual bool verify(const char *fingerprint, const char *domainName) = 0;
virtual void setHandshakeTimeout(unsigned long handshakeTimeout) = 0;
virtual void setAlpnProtocols(const char **alpnProtocols) = 0;
virtual bool getFingerprintSHA256(uint8_t result[32]) = 0;
};

View File

@@ -0,0 +1,173 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "WiFiType.h"
/** Argument structure for WIFI_EVENT_SCAN_DONE event */
typedef struct {
uint32_t status; /**< status of scanning APs: 0 - success, 1 - failure */
uint8_t number; /**< number of scan results */
uint8_t scan_id; /**< scan sequence number, used for block scan */
} wifi_event_sta_scan_done_t;
/** Argument structure for WIFI_EVENT_STA_CONNECTED event */
typedef struct {
uint8_t ssid[32]; /**< SSID of connected AP */
uint8_t ssid_len; /**< SSID length of connected AP */
uint8_t bssid[6]; /**< BSSID of connected AP*/
uint8_t channel; /**< channel of connected AP*/
wifi_auth_mode_t authmode; /**< authentication mode used by AP*/
} wifi_event_sta_connected_t;
/** Argument structure for WIFI_EVENT_STA_DISCONNECTED event */
typedef struct {
uint8_t ssid[32]; /**< SSID of disconnected AP */
uint8_t ssid_len; /**< SSID length of disconnected AP */
uint8_t bssid[6]; /**< BSSID of disconnected AP */
uint8_t reason; /**< reason of disconnection */
} wifi_event_sta_disconnected_t;
/** Argument structure for WIFI_EVENT_STA_AUTHMODE_CHANGE event */
typedef struct {
wifi_auth_mode_t old_mode; /**< the old auth mode of AP */
wifi_auth_mode_t new_mode; /**< the new auth mode of AP */
} wifi_event_sta_authmode_change_t;
/** Argument structure for WIFI_EVENT_STA_WPS_ER_PIN event */
typedef struct {
uint8_t pin_code[8]; /**< PIN code of station in enrollee mode */
} wifi_event_sta_wps_er_pin_t;
/** Argument structure for WIFI_EVENT_STA_WPS_ER_FAILED event */
typedef enum {
WPS_FAIL_REASON_NORMAL = 0, /**< ESP32 WPS normal fail reason */
WPS_FAIL_REASON_RECV_M2D, /**< ESP32 WPS receive M2D frame */
WPS_FAIL_REASON_MAX
} wifi_event_sta_wps_fail_reason_t;
#define MAX_SSID_LEN 32
#define MAX_PASSPHRASE_LEN 64
#define MAX_WPS_AP_CRED 3
/** Argument structure for WIFI_EVENT_STA_WPS_ER_SUCCESS event */
typedef struct {
uint8_t ap_cred_cnt; /**< Number of AP credentials received */
struct {
uint8_t ssid[MAX_SSID_LEN]; /**< SSID of AP */
uint8_t passphrase[MAX_PASSPHRASE_LEN]; /**< Passphrase for the AP */
} ap_cred[MAX_WPS_AP_CRED]; /**< All AP credentials received from WPS handshake */
} wifi_event_sta_wps_er_success_t;
/** Argument structure for WIFI_EVENT_AP_STACONNECTED event */
typedef struct {
uint8_t mac[6]; /**< MAC address of the station connected to ESP32 soft-AP */
uint8_t aid; /**< the aid that ESP32 soft-AP gives to the station connected to */
bool is_mesh_child; /**< flag to identify mesh child */
} wifi_event_ap_staconnected_t;
/** Argument structure for WIFI_EVENT_AP_STADISCONNECTED event */
typedef struct {
uint8_t mac[6]; /**< MAC address of the station disconnects to ESP32 soft-AP */
uint8_t aid; /**< the aid that ESP32 soft-AP gave to the station disconnects to */
bool is_mesh_child; /**< flag to identify mesh child */
} wifi_event_ap_stadisconnected_t;
/** Argument structure for WIFI_EVENT_AP_PROBEREQRECVED event */
typedef struct {
int rssi; /**< Received probe request signal strength */
uint8_t mac[6]; /**< MAC address of the station which send probe request */
} wifi_event_ap_probe_req_rx_t;
/**
* @brief FTM operation status types
*
*/
typedef enum {
FTM_STATUS_SUCCESS = 0, /**< FTM exchange is successful */
FTM_STATUS_UNSUPPORTED, /**< Peer does not support FTM */
FTM_STATUS_CONF_REJECTED, /**< Peer rejected FTM configuration in FTM Request */
FTM_STATUS_NO_RESPONSE, /**< Peer did not respond to FTM Requests */
FTM_STATUS_FAIL, /**< Unknown error during FTM exchange */
} wifi_ftm_status_t;
/** Argument structure for */
typedef struct {
uint8_t dlog_token; /**< Dialog Token of the FTM frame */
int8_t rssi; /**< RSSI of the FTM frame received */
uint32_t rtt; /**< Round Trip Time in pSec with a peer */
uint64_t t1; /**< Time of departure of FTM frame from FTM Responder in pSec */
uint64_t t2; /**< Time of arrival of FTM frame at FTM Initiator in pSec */
uint64_t t3; /**< Time of departure of ACK from FTM Initiator in pSec */
uint64_t t4; /**< Time of arrival of ACK at FTM Responder in pSec */
} wifi_ftm_report_entry_t;
/** Argument structure for WIFI_EVENT_FTM_REPORT event */
typedef struct {
uint8_t peer_mac[6]; /**< MAC address of the FTM Peer */
wifi_ftm_status_t status; /**< Status of the FTM operation */
uint32_t rtt_raw; /**< Raw average Round-Trip-Time with peer in Nano-Seconds */
uint32_t rtt_est; /**< Estimated Round-Trip-Time with peer in Nano-Seconds */
uint32_t dist_est; /**< Estimated one-way distance in Centi-Meters */
wifi_ftm_report_entry_t
*ftm_report_data; /**< Pointer to FTM Report with multiple entries, should be freed after use */
uint8_t ftm_report_num_entries; /**< Number of entries in the FTM Report data */
} wifi_event_ftm_report_t;
#define WIFI_STATIS_BUFFER (1 << 0)
#define WIFI_STATIS_RXTX (1 << 1)
#define WIFI_STATIS_HW (1 << 2)
#define WIFI_STATIS_DIAG (1 << 3)
#define WIFI_STATIS_PS (1 << 4)
#define WIFI_STATIS_ALL (-1)
/** Argument structure for WIFI_EVENT_ACTION_TX_STATUS event */
typedef struct {
int ifx; /**< WiFi interface to send request to */
uint32_t context; /**< Context to identify the request */
uint8_t da[6]; /**< Destination MAC address */
uint8_t status; /**< Status of the operation */
} wifi_event_action_tx_status_t;
/** Argument structure for WIFI_EVENT_ROC_DONE event */
typedef struct {
uint32_t context; /**< Context to identify the request */
} wifi_event_roc_done_t;
/** Event structure for IP_EVENT_STA_GOT_IP, IP_EVENT_ETH_GOT_IP events */
typedef struct {
esp_ip4_addr_t ip; /**< Interface IPV4 address */
esp_ip4_addr_t netmask; /**< Interface IPV4 netmask */
esp_ip4_addr_t gw; /**< Interface IPV4 gateway address */
} esp_netif_ip_info_t;
/** @brief IPV6 IP address information
*/
typedef struct {
esp_ip6_addr_t ip; /**< Interface IPV6 address */
} esp_netif_ip6_info_t;
typedef struct {
int if_index; /*!< Interface index for which the event is received (left for legacy compilation) */
void *esp_netif; /*!< Pointer to corresponding esp-netif object */
esp_netif_ip_info_t ip_info; /*!< IP address, netmask, gatway IP address */
bool ip_changed; /*!< Whether the assigned IP has changed or not */
} ip_event_got_ip_t;
/** Event structure for IP_EVENT_GOT_IP6 event */
typedef struct {
int if_index; /*!< Interface index for which the event is received (left for legacy compilation) */
void *esp_netif; /*!< Pointer to corresponding esp-netif object */
esp_netif_ip6_info_t ip6_info; /*!< IPv6 address of the interface */
int ip_index; /*!< IPv6 address index */
} ip_event_got_ip6_t;
/** Event structure for IP_EVENT_AP_STAIPASSIGNED event */
typedef struct {
esp_ip4_addr_t ip; /*!< IP address which was assigned to the station */
} ip_event_ap_staipassigned_t;

View File

@@ -34,6 +34,8 @@ class IWiFiServer : public Print { // arduino::Server is useless anyway
IWiFiServer(uint16_t port = 80, uint8_t maxClients = 4) {}
IWiFiServer(const IPAddress &addr, uint16_t port = 80, uint8_t maxClients = 4) {}
~IWiFiServer() {
stop();
}

View File

@@ -34,12 +34,28 @@
#define WIFI_AP WIFI_MODE_AP
#define WIFI_AP_STA WIFI_MODE_APSTA
#define WiFiEvent_t arduino_event_id_t
#define WiFiEventInfo_t arduino_event_info_t
#define WiFiEventId_t uint16_t
struct esp_ip6_addr {
uint32_t addr[4];
uint8_t zone;
};
struct esp_ip4_addr {
uint32_t addr;
};
typedef struct esp_ip4_addr esp_ip4_addr_t;
typedef struct esp_ip6_addr esp_ip6_addr_t;
typedef enum {
WIFI_MODE_NULL = 0,
WIFI_MODE_STA,
WIFI_MODE_AP,
WIFI_MODE_APSTA,
WIFI_MODE_MAX,
WIFI_MODE_NULL = 0, /**< null mode */
WIFI_MODE_STA, /**< WiFi station mode */
WIFI_MODE_AP, /**< WiFi soft-AP mode */
WIFI_MODE_APSTA, /**< WiFi station + soft-AP mode */
WIFI_MODE_MAX
} wifi_mode_t;
typedef enum {
@@ -54,13 +70,54 @@ typedef enum {
} wl_status_t;
typedef enum {
WIFI_AUTH_INVALID = 255,
WIFI_AUTH_AUTO = 200,
WIFI_AUTH_OPEN = 0,
WIFI_AUTH_WEP = 1,
WIFI_AUTH_WPA = 5,
WIFI_AUTH_WPA2 = 6,
WIFI_AUTH_WPA_PSK = 2,
WIFI_AUTH_WPA2_PSK = 3,
WIFI_AUTH_WPA_WPA2_PSK = 4,
WIFI_AUTH_OPEN = 0, /**< authenticate mode : open */
WIFI_AUTH_WEP, /**< authenticate mode : WEP */
WIFI_AUTH_WPA_PSK, /**< authenticate mode : WPA_PSK */
WIFI_AUTH_WPA2_PSK, /**< authenticate mode : WPA2_PSK */
WIFI_AUTH_WPA_WPA2_PSK, /**< authenticate mode : WPA_WPA2_PSK */
WIFI_AUTH_WPA2_ENTERPRISE, /**< authenticate mode : WPA2_ENTERPRISE */
WIFI_AUTH_WPA3_PSK, /**< authenticate mode : WPA3_PSK */
WIFI_AUTH_WPA2_WPA3_PSK, /**< authenticate mode : WPA2_WPA3_PSK */
WIFI_AUTH_WAPI_PSK, /**< authenticate mode : WAPI_PSK */
WIFI_AUTH_WPA,
WIFI_AUTH_WPA2,
WIFI_AUTH_AUTO = 200,
WIFI_AUTH_INVALID = 255,
WIFI_AUTH_MAX
} wifi_auth_mode_t;
typedef enum {
WIFI_REASON_UNSPECIFIED = 1,
WIFI_REASON_AUTH_EXPIRE = 2,
WIFI_REASON_AUTH_LEAVE = 3,
WIFI_REASON_ASSOC_EXPIRE = 4,
WIFI_REASON_ASSOC_TOOMANY = 5,
WIFI_REASON_NOT_AUTHED = 6,
WIFI_REASON_NOT_ASSOCED = 7,
WIFI_REASON_ASSOC_LEAVE = 8,
WIFI_REASON_ASSOC_NOT_AUTHED = 9,
WIFI_REASON_DISASSOC_PWRCAP_BAD = 10,
WIFI_REASON_DISASSOC_SUPCHAN_BAD = 11,
WIFI_REASON_BSS_TRANSITION_DISASSOC = 12,
WIFI_REASON_IE_INVALID = 13,
WIFI_REASON_MIC_FAILURE = 14,
WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT = 15,
WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT = 16,
WIFI_REASON_IE_IN_4WAY_DIFFERS = 17,
WIFI_REASON_GROUP_CIPHER_INVALID = 18,
WIFI_REASON_PAIRWISE_CIPHER_INVALID = 19,
WIFI_REASON_AKMP_INVALID = 20,
WIFI_REASON_UNSUPP_RSN_IE_VERSION = 21,
WIFI_REASON_INVALID_RSN_IE_CAP = 22,
WIFI_REASON_802_1X_AUTH_FAILED = 23,
WIFI_REASON_CIPHER_SUITE_REJECTED = 24,
WIFI_REASON_INVALID_PMKID = 53,
WIFI_REASON_BEACON_TIMEOUT = 200,
WIFI_REASON_NO_AP_FOUND = 201,
WIFI_REASON_AUTH_FAIL = 202,
WIFI_REASON_ASSOC_FAIL = 203,
WIFI_REASON_HANDSHAKE_TIMEOUT = 204,
WIFI_REASON_CONNECTION_FAIL = 205,
WIFI_REASON_AP_TSF_RESET = 206,
WIFI_REASON_ROAMING = 207,
} wifi_err_reason_t;

View File

@@ -0,0 +1,66 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-09. */
#include <api/Stream.h>
class ITwoWire : public Stream {
protected:
int8_t _sda = -1;
int8_t _scl = -1;
uint32_t _freq = 0;
void (*onRequestCallback)(void);
void (*onReceiveCallback)(int);
public:
bool begin() {
return begin(_sda, _scl, _freq);
}
bool begin(uint8_t address) {
return begin(address, _sda, _scl, _freq);
}
virtual bool setPins(int8_t sda, int8_t scl) = 0;
virtual bool begin(int8_t sda, int8_t scl, uint32_t frequency = 0) = 0;
virtual bool begin(uint8_t address, int8_t sda, int8_t scl, uint32_t frequency = 0) = 0;
virtual bool end() = 0;
virtual bool setClock(uint32_t freq) = 0;
virtual void beginTransmission(uint8_t address) = 0;
virtual uint8_t endTransmission(bool stopBit) = 0;
virtual size_t requestFrom(uint8_t address, size_t len, bool stopBit) = 0;
virtual size_t write(const uint8_t *data, size_t len) = 0;
virtual int available() = 0;
virtual int read() = 0;
virtual int peek() = 0;
virtual void flush() = 0;
uint32_t getClock() {
return _freq;
}
uint8_t endTransmission() {
return endTransmission(true);
}
size_t requestFrom(uint8_t address, size_t len) {
return requestFrom(address, len, true);
}
virtual size_t write(uint8_t data) {
return write(&data, 1);
}
void onReceive(void (*cb)(int)) {
onReceiveCallback = cb;
}
void onRequest(void (*cb)(void)) {
onRequestCallback = cb;
}
};

View File

@@ -1,89 +0,0 @@
#pragma once
#include "LibreTuyaConfig.h"
#include <stdint.h>
#if LT_LOGGER_FILE
#define LT_LOG(level, file, line, ...) lt_log(level, file, line, __VA_ARGS__)
void lt_log(const uint8_t level, const char *filename, const unsigned short line, const char *format, ...);
#else
#define LT_LOG(level, file, line, ...) lt_log(level, __VA_ARGS__)
void lt_log(const uint8_t level, const char *format, ...);
#endif
#if LT_LEVEL_TRACE >= LT_LOGLEVEL
#define LT_T(...) LT_LOG(LT_LEVEL_TRACE, __FILE__, __LINE__, __VA_ARGS__)
#define LT_V(...) LT_LOG(LT_LEVEL_TRACE, __FILE__, __LINE__, __VA_ARGS__)
#else
#define LT_T(...)
#define LT_V(...)
#endif
#if LT_LEVEL_DEBUG >= LT_LOGLEVEL
#define LT_D(...) LT_LOG(LT_LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
#else
#define LT_D(...)
#endif
#if LT_LEVEL_INFO >= LT_LOGLEVEL
#define LT_I(...) LT_LOG(LT_LEVEL_INFO, __FILE__, __LINE__, __VA_ARGS__)
#else
#define LT_I(...)
#endif
#if LT_LEVEL_WARN >= LT_LOGLEVEL
#define LT_W(...) LT_LOG(LT_LEVEL_WARN, __FILE__, __LINE__, __VA_ARGS__)
#else
#define LT_W(...)
#endif
#if LT_LEVEL_ERROR >= LT_LOGLEVEL
#define LT_E(...) LT_LOG(LT_LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__)
#else
#define LT_E(...)
#endif
#if LT_LEVEL_FATAL >= LT_LOGLEVEL
#define LT_F(...) LT_LOG(LT_LEVEL_FATAL, __FILE__, __LINE__, __VA_ARGS__)
#else
#define LT_F(...)
#endif
#define LT_T_MOD(module, ...) \
do { \
if (module) { \
LT_T(__VA_ARGS__) \
} \
} while (0)
#define LT_D_MOD(module, ...) \
do { \
if (module) { \
LT_D(__VA_ARGS__) \
} \
} while (0)
// WiFi.cpp
#define LT_T_WG(...) LT_T_MOD(LT_DEBUG_WIFI, __VA_ARGS__)
#define LT_V_WG(...) LT_T_MOD(LT_DEBUG_WIFI, __VA_ARGS__)
#define LT_D_WG(...) LT_D_MOD(LT_DEBUG_WIFI, __VA_ARGS__)
// WiFiClient.cpp
#define LT_T_WC(...) LT_T_MOD(LT_DEBUG_WIFI_CLIENT, __VA_ARGS__)
#define LT_V_WC(...) LT_T_MOD(LT_DEBUG_WIFI_CLIENT, __VA_ARGS__)
#define LT_D_WC(...) LT_D_MOD(LT_DEBUG_WIFI_CLIENT, __VA_ARGS__)
// WiFiServer.cpp
#define LT_T_WS(...) LT_T_MOD(LT_DEBUG_WIFI_SERVER, __VA_ARGS__)
#define LT_V_WS(...) LT_T_MOD(LT_DEBUG_WIFI_SERVER, __VA_ARGS__)
#define LT_D_WS(...) LT_D_MOD(LT_DEBUG_WIFI_SERVER, __VA_ARGS__)
// WiFiSTA.cpp
#define LT_T_WSTA(...) LT_T_MOD(LT_DEBUG_WIFI_STA, __VA_ARGS__)
#define LT_V_WSTA(...) LT_T_MOD(LT_DEBUG_WIFI_STA, __VA_ARGS__)
#define LT_D_WSTA(...) LT_D_MOD(LT_DEBUG_WIFI_STA, __VA_ARGS__)
// WiFiAP.cpp
#define LT_T_WAP(...) LT_T_MOD(LT_DEBUG_WIFI_AP, __VA_ARGS__)
#define LT_V_WAP(...) LT_T_MOD(LT_DEBUG_WIFI_AP, __VA_ARGS__)
#define LT_D_WAP(...) LT_D_MOD(LT_DEBUG_WIFI_AP, __VA_ARGS__)

View File

@@ -0,0 +1,55 @@
/*
Copyright (c) 2014 Arduino. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <Arduino.h>
void randomSeed(uint32_t dwSeed) {
if (dwSeed != 0) {
srand(dwSeed);
}
}
long random(long howbig) {
if (howbig == 0) {
return 0;
}
return rand() % howbig;
}
long random(long howsmall, long howbig) {
if (howsmall >= howbig) {
return howsmall;
}
long diff = howbig - howsmall;
return random(diff) + howsmall;
}
extern long map(long x, long in_min, long in_max, long out_min, long out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
extern uint16_t makeWord(uint16_t w) {
return w;
}
extern uint16_t makeWord(uint8_t h, uint8_t l) {
return (h << 8) | l;
}

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012 Arduino. All right reserved.
Copyright (c) 2014 Arduino LLC. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
@@ -16,19 +16,21 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef RESET_H
#define RESET_H
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
extern "C" void __cxa_pure_virtual(void) __attribute__((__noreturn__));
extern "C" void __cxa_deleted_virtual(void) __attribute__((__noreturn__));
void initiateReset(int ms);
void tickReset();
void cancelReset();
#ifdef __cplusplus
void __cxa_pure_virtual(void) {
// We might want to write some diagnostics to uart in this case
// std::terminate();
while (1)
;
}
#endif
#endif
void __cxa_deleted_virtual(void) {
// We might want to write some diagnostics to uart in this case
// std::terminate();
while (1)
;
}

View File

@@ -18,16 +18,11 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef ARDUINO_AMEBA
#include <Arduino.h>
#else
#include <stdio.h>
#endif
char *dtostrf (double val, signed char width, unsigned char prec, char *sout) {
char fmt[20];
sprintf(fmt, "%%%d.%df", width, prec);
sprintf(sout, fmt, val);
return sout;
char *dtostrf(double val, signed char width, unsigned char prec, char *sout) {
char fmt[20];
sprintf(fmt, "%%%d.%df", width, prec);
sprintf(sout, fmt, val);
return sout;
}

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2012 Arduino. All right reserved.
Copyright (c) 2015 Arduino LLC. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
@@ -29,12 +29,7 @@ static void __empty() {
// Empty
}
#include "cmsis_os.h"
void yield(void) {
vTaskDelay(1);
taskYIELD();
}
void yield(void) __attribute__((weak, alias("__empty")));
/**
* SysTick hook
@@ -46,7 +41,8 @@ static int __false() {
// Return false
return 0;
}
int sysTickHook(void) __attribute__ ((weak, alias("__false")));
int sysTickHook(void) __attribute__((weak, alias("__false")));
/**
* SVC hook
@@ -60,5 +56,6 @@ static void __halt() {
while (1)
;
}
void svcHook(void) __attribute__ ((weak, alias("__halt")));
void pendSVHook(void) __attribute__ ((weak, alias("__halt")));
void svcHook(void) __attribute__((weak, alias("__halt")));
void pendSVHook(void) __attribute__((weak, alias("__halt")));

View File

@@ -0,0 +1,111 @@
/*
Copyright (c) 2014 Arduino LLC. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <string.h>
#ifdef __cplusplus
extern "C" {
#endif
char *ltoa(long value, char *string, int radix) {
char tmp[33];
char *tp = tmp;
long i;
unsigned long v;
int sign;
char *sp;
if (string == NULL) {
return 0;
}
if (radix > 36 || radix <= 1) {
return 0;
}
sign = (radix == 10 && value < 0);
if (sign) {
v = -value;
} else {
v = (unsigned long)value;
}
while (v || tp == tmp) {
i = v % radix;
v = v / radix;
if (i < 10)
*tp++ = i + '0';
else
*tp++ = i + 'a' - 10;
}
sp = string;
if (sign)
*sp++ = '-';
while (tp > tmp)
*sp++ = *--tp;
*sp = 0;
return string;
}
char *ultoa(unsigned long value, char *string, int radix) {
char tmp[33];
char *tp = tmp;
long i;
unsigned long v = value;
char *sp;
if (string == NULL) {
return 0;
}
if (radix > 36 || radix <= 1) {
return 0;
}
while (v || tp == tmp) {
i = v % radix;
v = v / radix;
if (i < 10)
*tp++ = i + '0';
else
*tp++ = i + 'a' - 10;
}
sp = string;
while (tp > tmp)
*sp++ = *--tp;
*sp = 0;
return string;
}
char *itoa(int value, char *string, int radix) {
return ltoa(value, string, radix);
}
char *utoa(unsigned int value, char *string, int radix) {
return ultoa(value, string, radix);
}
#ifdef __cplusplus
} // extern "C"
#endif

View File

@@ -0,0 +1,54 @@
/*
Copyright (c) 2014 Arduino. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <Arduino.h>
#include <stdint.h>
uint8_t shiftIn(pin_size_t ulDataPin, pin_size_t ulClockPin, BitOrder ulBitOrder) {
uint8_t value = 0;
uint8_t i;
for (i = 0; i < 8; ++i) {
digitalWrite(ulClockPin, HIGH);
if (ulBitOrder == LSBFIRST) {
value |= digitalRead(ulDataPin) << i;
} else {
value |= digitalRead(ulDataPin) << (7 - i);
}
digitalWrite(ulClockPin, LOW);
}
return value;
}
void shiftOut(pin_size_t ulDataPin, pin_size_t ulClockPin, BitOrder ulBitOrder, uint8_t ulVal) {
uint8_t i;
for (i = 0; i < 8; i++) {
if (ulBitOrder == LSBFIRST) {
digitalWrite(ulDataPin, !!(ulVal & (1 << i)));
} else {
digitalWrite(ulDataPin, !!(ulVal & (1 << (7 - i))));
}
digitalWrite(ulClockPin, HIGH);
digitalWrite(ulClockPin, LOW);
}
}

View File

@@ -0,0 +1,5 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-30. */
#pragma once
#include <api/FS.h>

View File

@@ -0,0 +1,3 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-30. */
// nop

View File

@@ -1,3 +1,5 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-26. */
#pragma once
// ESP32 WiFi examples use WiFiAP.h include

View File

@@ -0,0 +1,5 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-30. */
#pragma once
#include <api/deprecated-avr-comp/avr/pgmspace.h>

View File

@@ -0,0 +1,3 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-30. */
// nop

View File

@@ -0,0 +1,23 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-28. */
enum ChipFamily {
// used in UF2 Family ID
RTL8710A = 0x9FFFD543, // Realtek Ameba1
RTL8710B = 0x22E0D6FC, // Realtek AmebaZ (realtek-ambz)
RTL8720C = 0xE08F7564, // Realtek AmebaZ2
RTL8720D = 0x3379CFE2, // Realtek AmebaD
BK7231T = 0x675A40B0, // Beken 7231T
BK7231N = 0x7B3EF230, // Beken 7231N
BL602 = 0xDE1270B7, // Boufallo 602
XR809 = 0x51E903A8, // Xradiotech 809
};
enum ChipType {
// IDs copied from rtl8710b_efuse.h
RTL8710BL = ((RTL8710B >> 24) << 8) | 0xE0, // ???
RTL8710BN = ((RTL8710B >> 24) << 8) | 0xFF, // CHIPID_8710BN / QFN32
RTL8710BU = ((RTL8710B >> 24) << 8) | 0xFE, // CHIPID_8710BU / QFN48
RTL8710BX = ((RTL8710B >> 24) << 8) | 0xFB, // CHIPID_8710BN_L0 / QFN32
RTL8711BN = ((RTL8710B >> 24) << 8) | 0xFD, // CHIPID_8711BN / QFN48
RTL8711BU = ((RTL8710B >> 24) << 8) | 0xFC, // CHIPID_8711BG / QFN68
};

View File

@@ -0,0 +1,162 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-29. */
#include "LibreTuyaAPI.h"
String ipToString(const IPAddress &ip) {
char szRet[16];
sprintf(szRet, "%hhu.%hhu.%hhu.%hhu", ip[0], ip[1], ip[2], ip[3]);
return String(szRet);
}
/**
* @brief Generate random bytes using rand().
*
* @param buf destination pointer
* @param len how many bytes to generate
*/
void lt_rand_bytes(uint8_t *buf, size_t len) {
int *data = (int *)buf;
size_t i;
for (i = 0; len >= sizeof(int); len -= sizeof(int)) {
data[i++] = rand();
}
if (len) {
int rem = rand();
unsigned char *pRem = (unsigned char *)&rem;
memcpy(buf + i * sizeof(int), pRem, len);
}
}
/**
* @brief Print data pointed to by buf in hexdump-like format (hex+ASCII).
*
* @param buf source pointer
* @param len how many bytes to print
* @param offset increment printed offset by this value
* @param width how many bytes on a line
*/
void hexdump(uint8_t *buf, size_t len, uint32_t offset, uint8_t width) {
uint16_t pos = 0;
while (pos < len) {
// print hex offset
printf("%06x ", offset + pos);
// calculate current line width
uint8_t lineWidth = min(width, len - pos);
// print hexadecimal representation
for (uint8_t i = 0; i < lineWidth; i++) {
if (i % 8 == 0) {
printf(" ");
}
printf("%02x ", buf[pos + i]);
}
// print ascii representation
printf(" |");
for (uint8_t i = 0; i < lineWidth; i++) {
char c = buf[pos + i];
printf("%c", isprint(c) ? c : '.');
}
printf("|\n");
pos += lineWidth;
}
}
/**
* @brief Get LibreTuya version string.
*/
const char *LibreTuya::getVersion() {
return LT_VERSION_STR;
}
/**
* @brief Get board name.
*/
const char *LibreTuya::getBoard() {
return LT_BOARD_STR;
}
/**
* @brief Get CPU family ID.
*/
ChipFamily LibreTuya::getChipFamily() {
return FAMILY;
}
/**
* @brief Get CPU family name as string.
*/
const char *LibreTuya::getChipFamilyName() {
return STRINGIFY_MACRO(FAMILY);
}
static char *deviceName = NULL;
/**
* @brief Get device friendly name in format "LT-<board>-<chip id>".
* Can be used as hostname.
*/
const char *LibreTuya::getDeviceName() {
if (deviceName)
return deviceName;
uint32_t chipId = getChipId();
uint8_t *id = (uint8_t *)&chipId;
const char *board = getBoard();
uint8_t boardLen = strlen(board);
deviceName = (char *)malloc(3 + boardLen + 1 + 6 + 1);
sprintf(deviceName, "LT-%s-%02x%02x%02x", board, id[0], id[1], id[2]);
return deviceName;
}
static uint8_t otaRunningIndex = 0;
/**
* @brief Get the currently running firmware OTA index.
*/
uint8_t LibreTuya::otaGetRunning() {
if (otaRunningIndex)
return otaRunningIndex;
// otaRunningIndex will be correct even after switchOta()
return otaRunningIndex = otaGetStoredIndex();
}
/**
* @brief Get the OTA index for updated firmware.
*
* Note: returns 1 for chips without dual-OTA.
*/
uint8_t LibreTuya::otaGetTarget() {
if (!otaSupportsDual())
return 1;
return otaGetRunning() ^ 0b11;
}
/**
* @brief Perform OTA rollback.
*
* @return false if no second image to run, writing failed or dual-OTA not supported
*/
bool LibreTuya::otaRollback() {
if (!otaCanRollback())
return false;
if (otaGetRunning() != otaGetStoredIndex())
// force switching back to current image
return otaSwitch(true);
return true;
}
/**
* @brief Check if OTA rollback is supported and available (there is another image to run).
* @return false if no second image to run or dual-OTA not supported
*/
bool LibreTuya::otaCanRollback() {
if (!otaSupportsDual())
return false;
if (otaGetRunning() == otaGetStoredIndex())
return true;
if (otaGetRunning() == 1 && otaHasImage1())
return true;
if (otaGetRunning() == 2 && otaHasImage2())
return true;
return false;
}

View File

@@ -0,0 +1,181 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-28. */
#pragma once
// LibreTuya version macros
#ifndef LT_VERSION
#define LT_VERSION 1.0.0
#endif
#ifndef LT_BOARD
#define LT_BOARD unknown
#endif
#define STRINGIFY(x) #x
#define STRINGIFY_MACRO(x) STRINGIFY(x)
#define LT_VERSION_STR STRINGIFY_MACRO(LT_VERSION)
#define LT_BOARD_STR STRINGIFY_MACRO(LT_BOARD)
// Includes
#include "LibreTuyaConfig.h"
#include <Arduino.h>
// C includes
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
#include "lt_logger.h"
#include "lt_posix_api.h"
#ifdef __cplusplus
} // extern "C"
#endif
// Functional macros
#define LT_BANNER() \
LT_LOG( \
LT_LEVEL_INFO, \
__FUNCTION__, \
__LINE__, \
"LibreTuya v" LT_VERSION_STR " on " LT_BOARD_STR ", compiled at " __DATE__ " " __TIME__ \
)
// ArduinoCore-API doesn't define these anymore
#define FPSTR(pstr_pointer) (reinterpret_cast<const __FlashStringHelper *>(pstr_pointer))
#define PGM_VOID_P const void *
void lt_rand_bytes(uint8_t *buf, size_t len);
#ifdef __cplusplus
String ipToString(const IPAddress &ip);
void hexdump(uint8_t *buf, size_t len, uint32_t offset = 0, uint8_t width = 16);
#else
void hexdump(uint8_t *buf, size_t len, uint32_t offset, uint8_t width);
#endif
// Main class
#ifdef __cplusplus
#include <Flash.h> // for flash inline methods
#include <core/ChipType.h>
/**
* @brief Main LibreTuya API class.
*
* This class contains all functions common amongst all families.
* Implementations of these methods may vary between families.
*
* The class is accessible using the `LT` global object (defined by the family).
*/
class LibreTuya {
public: /* Common methods - note: these are documented in LibreTuyaAPI.cpp */
const char *getVersion();
const char *getBoard();
ChipFamily getChipFamily();
const char *getChipFamilyName();
const char *getDeviceName();
uint8_t otaGetRunning();
uint8_t otaGetTarget();
bool otaRollback();
bool otaCanRollback();
public: /* Inline methods */
inline uint32_t getFlashChipSize() {
return Flash.getSize();
}
// inline bool flashEraseSector(uint32_t sector) {}
// inline bool flashWrite(uint32_t offset, uint32_t *data, size_t size) {}
// inline bool flashRead(uint32_t offset, uint32_t *data, size_t size) {}
// inline bool partitionEraseRange(const esp_partition_t *partition, uint32_t offset, size_t size) {}
// inline bool partitionWrite(const esp_partition_t *partition, uint32_t offset, uint32_t *data, size_t size) {}
// inline bool partitionRead(const esp_partition_t *partition, uint32_t offset, uint32_t *data, size_t size) {}
public: /* Family-defined methods */
/**
* @brief Reboot the CPU.
*/
void restart();
public: /* CPU-related */
/**
* @brief Get CPU model ID.
*/
ChipType getChipType();
/**
* @brief Get CPU model name as string.
*/
const char *getChipModel();
/**
* @brief Get CPU unique ID. This may be based on MAC, eFuse, etc.
*/
uint32_t getChipId();
/**
* @brief Get CPU core count.
*/
uint8_t getChipCores();
/**
* @brief Get CPU core type name as string.
*/
const char *getChipCoreType();
/**
* @brief Get CPU frequency in MHz.
*/
uint32_t getCpuFreqMHz();
/**
* @brief Get CPU cycle count.
*/
inline uint32_t getCycleCount() __attribute__((always_inline));
public: /* Memory management */
/**
* @brief Get total RAM size.
*/
uint32_t getRamSize();
/**
* @brief Get total heap size.
*/
uint32_t getHeapSize();
/**
* @brief Get free heap size.
*/
uint32_t getFreeHeap();
/**
* @brief Get lowest level of free heap memory.
*/
uint32_t getMinFreeHeap();
/**
* @brief Get largest block of heap that can be allocated at once.
*/
uint32_t getMaxAllocHeap();
public: /* OTA-related */
/**
* @brief Read the currently active OTA index, i.e. the one that will boot upon restart.
*/
uint8_t otaGetStoredIndex();
/**
* @brief Check if the chip supports dual-OTA.
*/
bool otaSupportsDual();
/**
* @brief Check if OTA1 image is valid.
*/
bool otaHasImage1();
/**
* @brief Check if OTA2 image is valid.
*/
bool otaHasImage2();
/**
* @brief Try to switch OTA index to the other image.
*
* Note: should return true for chips without dual-OTA. Should return false if one of two images is not valid.
*
* @param force switch even if other image already marked as active
* @return false if writing failed; true otherwise
*/
bool otaSwitch(bool force = false);
};
extern LibreTuya LT;
extern LibreTuya ESP;
#endif

View File

@@ -1,3 +1,5 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-28. */
#pragma once
// see docs/API Configuration
@@ -21,8 +23,8 @@
#define LT_LOGGER_TIMESTAMP 1
#endif
#ifndef LT_LOGGER_FILE
#define LT_LOGGER_FILE 0
#ifndef LT_LOGGER_CALLER
#define LT_LOGGER_CALLER 1
#endif
#ifndef LT_LOGGER_TASK
@@ -42,6 +44,11 @@
#define LT_LOGLEVEL LT_LEVEL_INFO
#endif
// Free heap size debugging
#ifndef LT_LOG_HEAP
#define LT_LOG_HEAP 0
#endif
// Per-module debugging
#ifndef LT_DEBUG_WIFI
#define LT_DEBUG_WIFI 0
@@ -62,3 +69,7 @@
#ifndef LT_DEBUG_WIFI_AP
#define LT_DEBUG_WIFI_AP 0
#endif
#ifndef LT_DEBUG_SSL
#define LT_DEBUG_SSL 0
#endif

View File

@@ -1,3 +1,5 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-28. */
#include "lt_logger.h"
#include <Arduino.h>
@@ -40,8 +42,8 @@ const uint8_t colors[] = {
unsigned long millis(void);
#if LT_LOGGER_FILE
void lt_log(const uint8_t level, const char *filename, const unsigned short line, const char *format, ...) {
#if LT_LOGGER_CALLER
void lt_log(const uint8_t level, const char *caller, const unsigned short line, const char *format, ...) {
#else
void lt_log(const uint8_t level, const char *format, ...) {
#endif
@@ -85,8 +87,8 @@ void lt_log(const uint8_t level, const char *format, ...) {
#if LT_LOGGER_COLOR
"\e[0m"
#endif
#if LT_LOGGER_FILE
"%s:%hu: "
#if LT_LOGGER_CALLER
"%s():%hu: "
#endif
#if LT_LOGGER_TASK
"%s%c "
@@ -106,9 +108,9 @@ void lt_log(const uint8_t level, const char *format, ...) {
zero // append missing zeroes if printf "%11.3f" prints "0."
#endif
#endif
#if LT_LOGGER_FILE
#if LT_LOGGER_CALLER
,
filename,
caller,
line
#endif
#if LT_LOGGER_TASK

View File

@@ -0,0 +1,163 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-28. */
#pragma once
#include "LibreTuyaConfig.h"
#include <stdint.h>
#if LT_LOGGER_CALLER
#define LT_LOG(level, caller, line, ...) lt_log(level, caller, line, __VA_ARGS__)
void lt_log(const uint8_t level, const char *caller, const unsigned short line, const char *format, ...);
#else
#define LT_LOG(level, caller, line, ...) lt_log(level, __VA_ARGS__)
void lt_log(const uint8_t level, const char *format, ...);
#endif
#if LT_LEVEL_TRACE >= LT_LOGLEVEL
#define LT_T(...) LT_LOG(LT_LEVEL_TRACE, __FUNCTION__, __LINE__, __VA_ARGS__)
#define LT_V(...) LT_LOG(LT_LEVEL_TRACE, __FUNCTION__, __LINE__, __VA_ARGS__)
#else
#define LT_T(...)
#define LT_V(...)
#endif
#if LT_LEVEL_DEBUG >= LT_LOGLEVEL
#define LT_D(...) LT_LOG(LT_LEVEL_DEBUG, __FUNCTION__, __LINE__, __VA_ARGS__)
#else
#define LT_D(...)
#endif
#if LT_LEVEL_INFO >= LT_LOGLEVEL
#define LT_I(...) LT_LOG(LT_LEVEL_INFO, __FUNCTION__, __LINE__, __VA_ARGS__)
#else
#define LT_I(...)
#endif
#if LT_LEVEL_WARN >= LT_LOGLEVEL
#define LT_W(...) LT_LOG(LT_LEVEL_WARN, __FUNCTION__, __LINE__, __VA_ARGS__)
#else
#define LT_W(...)
#endif
#if LT_LEVEL_ERROR >= LT_LOGLEVEL
#define LT_E(...) LT_LOG(LT_LEVEL_ERROR, __FUNCTION__, __LINE__, __VA_ARGS__)
#else
#define LT_E(...)
#endif
#if LT_LEVEL_FATAL >= LT_LOGLEVEL
#define LT_F(...) LT_LOG(LT_LEVEL_FATAL, __FUNCTION__, __LINE__, __VA_ARGS__)
#else
#define LT_F(...)
#endif
#if LT_LOG_HEAP
#define LT_HEAP_I() LT_I("Free heap: %u", LT_HEAP_FUNC());
#else
#define LT_HEAP_I()
#endif
// ESP32 compat
#define log_printf(...) LT_I(__VA_ARGS__)
#define log_v(...) LT_V(__VA_ARGS__)
#define log_d(...) LT_D(__VA_ARGS__)
#define log_i(...) LT_I(__VA_ARGS__)
#define log_w(...) LT_W(__VA_ARGS__)
#define log_e(...) LT_E(__VA_ARGS__)
#define log_n(...) LT_E(__VA_ARGS__)
#define isr_log_v(...) LT_V(__VA_ARGS__)
#define isr_log_d(...) LT_D(__VA_ARGS__)
#define isr_log_i(...) LT_I(__VA_ARGS__)
#define isr_log_w(...) LT_W(__VA_ARGS__)
#define isr_log_e(...) LT_E(__VA_ARGS__)
#define isr_log_n(...) LT_E(__VA_ARGS__)
#define ESP_LOGV(...) LT_V(__VA_ARGS__)
#define ESP_LOGD(...) LT_D(__VA_ARGS__)
#define ESP_LOGI(...) LT_I(__VA_ARGS__)
#define ESP_LOGW(...) LT_W(__VA_ARGS__)
#define ESP_LOGE(...) LT_E(__VA_ARGS__)
#define ESP_EARLY_LOGV(...) LT_V(__VA_ARGS__)
#define ESP_EARLY_LOGD(...) LT_D(__VA_ARGS__)
#define ESP_EARLY_LOGI(...) LT_I(__VA_ARGS__)
#define ESP_EARLY_LOGW(...) LT_W(__VA_ARGS__)
#define ESP_EARLY_LOGE(...) LT_E(__VA_ARGS__)
#define LT_T_MOD(module, ...) \
do { \
if (module) { \
LT_T(__VA_ARGS__); \
} \
} while (0)
#define LT_D_MOD(module, ...) \
do { \
if (module) { \
LT_D(__VA_ARGS__); \
} \
} while (0)
#define LT_RET(ret) \
LT_E("ret=%d", ret); \
return ret;
#define LT_RET_NZ(ret) \
if (ret) { \
LT_E("ret=%d", ret); \
return ret; \
}
#define LT_RET_LZ(ret) \
if (ret < 0) { \
LT_E("ret=%d", ret); \
return ret; \
}
#define LT_RET_LEZ(ret) \
if (ret <= 0) { \
LT_E("ret=%d", ret); \
return ret; \
}
#define LT_ERRNO_NZ(ret) \
if (ret) { \
LT_E("errno=%d, ret=%d", errno, ret); \
return ret; \
}
#define LT_ERRNO_LZ(ret) \
if (ret < 0) { \
LT_E("errno=%d, ret=%d", errno, ret); \
return ret; \
}
#define LT_ERRNO_LEZ(ret) \
if (ret <= 0) { \
LT_E("errno=%d, ret=%d", errno, ret); \
return ret; \
}
// WiFi.cpp
#define LT_T_WG(...) LT_T_MOD(LT_DEBUG_WIFI, __VA_ARGS__)
#define LT_V_WG(...) LT_T_MOD(LT_DEBUG_WIFI, __VA_ARGS__)
#define LT_D_WG(...) LT_D_MOD(LT_DEBUG_WIFI, __VA_ARGS__)
// WiFiClient.cpp
#define LT_T_WC(...) LT_T_MOD(LT_DEBUG_WIFI_CLIENT, __VA_ARGS__)
#define LT_V_WC(...) LT_T_MOD(LT_DEBUG_WIFI_CLIENT, __VA_ARGS__)
#define LT_D_WC(...) LT_D_MOD(LT_DEBUG_WIFI_CLIENT, __VA_ARGS__)
// WiFiServer.cpp
#define LT_T_WS(...) LT_T_MOD(LT_DEBUG_WIFI_SERVER, __VA_ARGS__)
#define LT_V_WS(...) LT_T_MOD(LT_DEBUG_WIFI_SERVER, __VA_ARGS__)
#define LT_D_WS(...) LT_D_MOD(LT_DEBUG_WIFI_SERVER, __VA_ARGS__)
// WiFiSTA.cpp
#define LT_T_WSTA(...) LT_T_MOD(LT_DEBUG_WIFI_STA, __VA_ARGS__)
#define LT_V_WSTA(...) LT_T_MOD(LT_DEBUG_WIFI_STA, __VA_ARGS__)
#define LT_D_WSTA(...) LT_D_MOD(LT_DEBUG_WIFI_STA, __VA_ARGS__)
// WiFiAP.cpp
#define LT_T_WAP(...) LT_T_MOD(LT_DEBUG_WIFI_AP, __VA_ARGS__)
#define LT_V_WAP(...) LT_T_MOD(LT_DEBUG_WIFI_AP, __VA_ARGS__)
#define LT_D_WAP(...) LT_D_MOD(LT_DEBUG_WIFI_AP, __VA_ARGS__)
// WiFiClientSecure.cpp & implementations
#define LT_T_SSL(...) LT_T_MOD(LT_DEBUG_SSL, __VA_ARGS__)
#define LT_V_SSL(...) LT_T_MOD(LT_DEBUG_SSL, __VA_ARGS__)
#define LT_D_SSL(...) LT_D_MOD(LT_DEBUG_SSL, __VA_ARGS__)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,305 @@
/**
* HTTPClient.h
*
* Created on: 02.11.2015
*
* Copyright (c) 2015 Markus Sattler. All rights reserved.
* This file is part of the HTTPClient for Arduino.
* Port to ESP32 by Evandro Luis Copercini (2017),
* changed fingerprints to CA verification.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef HTTPClient_H_
#define HTTPClient_H_
#ifndef HTTPCLIENT_1_1_COMPATIBLE
#define HTTPCLIENT_1_1_COMPATIBLE
#endif
#include <Arduino.h>
#include <WiFiClient.h>
#include <WiFiClientSecure.h>
#include <memory>
/// Cookie jar support
#include <vector>
#define HTTPCLIENT_DEFAULT_TCP_TIMEOUT (5000)
/// HTTP client errors
#define HTTPC_ERROR_CONNECTION_REFUSED (-1)
#define HTTPC_ERROR_SEND_HEADER_FAILED (-2)
#define HTTPC_ERROR_SEND_PAYLOAD_FAILED (-3)
#define HTTPC_ERROR_NOT_CONNECTED (-4)
#define HTTPC_ERROR_CONNECTION_LOST (-5)
#define HTTPC_ERROR_NO_STREAM (-6)
#define HTTPC_ERROR_NO_HTTP_SERVER (-7)
#define HTTPC_ERROR_TOO_LESS_RAM (-8)
#define HTTPC_ERROR_ENCODING (-9)
#define HTTPC_ERROR_STREAM_WRITE (-10)
#define HTTPC_ERROR_READ_TIMEOUT (-11)
/// size for the stream handling
#define HTTP_TCP_BUFFER_SIZE (1460)
/// HTTP codes see RFC7231
typedef enum {
HTTP_CODE_CONTINUE = 100,
HTTP_CODE_SWITCHING_PROTOCOLS = 101,
HTTP_CODE_PROCESSING = 102,
HTTP_CODE_OK = 200,
HTTP_CODE_CREATED = 201,
HTTP_CODE_ACCEPTED = 202,
HTTP_CODE_NON_AUTHORITATIVE_INFORMATION = 203,
HTTP_CODE_NO_CONTENT = 204,
HTTP_CODE_RESET_CONTENT = 205,
HTTP_CODE_PARTIAL_CONTENT = 206,
HTTP_CODE_MULTI_STATUS = 207,
HTTP_CODE_ALREADY_REPORTED = 208,
HTTP_CODE_IM_USED = 226,
HTTP_CODE_MULTIPLE_CHOICES = 300,
HTTP_CODE_MOVED_PERMANENTLY = 301,
HTTP_CODE_FOUND = 302,
HTTP_CODE_SEE_OTHER = 303,
HTTP_CODE_NOT_MODIFIED = 304,
HTTP_CODE_USE_PROXY = 305,
HTTP_CODE_TEMPORARY_REDIRECT = 307,
HTTP_CODE_PERMANENT_REDIRECT = 308,
HTTP_CODE_BAD_REQUEST = 400,
HTTP_CODE_UNAUTHORIZED = 401,
HTTP_CODE_PAYMENT_REQUIRED = 402,
HTTP_CODE_FORBIDDEN = 403,
HTTP_CODE_NOT_FOUND = 404,
HTTP_CODE_METHOD_NOT_ALLOWED = 405,
HTTP_CODE_NOT_ACCEPTABLE = 406,
HTTP_CODE_PROXY_AUTHENTICATION_REQUIRED = 407,
HTTP_CODE_REQUEST_TIMEOUT = 408,
HTTP_CODE_CONFLICT = 409,
HTTP_CODE_GONE = 410,
HTTP_CODE_LENGTH_REQUIRED = 411,
HTTP_CODE_PRECONDITION_FAILED = 412,
HTTP_CODE_PAYLOAD_TOO_LARGE = 413,
HTTP_CODE_URI_TOO_LONG = 414,
HTTP_CODE_UNSUPPORTED_MEDIA_TYPE = 415,
HTTP_CODE_RANGE_NOT_SATISFIABLE = 416,
HTTP_CODE_EXPECTATION_FAILED = 417,
HTTP_CODE_MISDIRECTED_REQUEST = 421,
HTTP_CODE_UNPROCESSABLE_ENTITY = 422,
HTTP_CODE_LOCKED = 423,
HTTP_CODE_FAILED_DEPENDENCY = 424,
HTTP_CODE_UPGRADE_REQUIRED = 426,
HTTP_CODE_PRECONDITION_REQUIRED = 428,
HTTP_CODE_TOO_MANY_REQUESTS = 429,
HTTP_CODE_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
HTTP_CODE_INTERNAL_SERVER_ERROR = 500,
HTTP_CODE_NOT_IMPLEMENTED = 501,
HTTP_CODE_BAD_GATEWAY = 502,
HTTP_CODE_SERVICE_UNAVAILABLE = 503,
HTTP_CODE_GATEWAY_TIMEOUT = 504,
HTTP_CODE_HTTP_VERSION_NOT_SUPPORTED = 505,
HTTP_CODE_VARIANT_ALSO_NEGOTIATES = 506,
HTTP_CODE_INSUFFICIENT_STORAGE = 507,
HTTP_CODE_LOOP_DETECTED = 508,
HTTP_CODE_NOT_EXTENDED = 510,
HTTP_CODE_NETWORK_AUTHENTICATION_REQUIRED = 511
} t_http_codes;
typedef enum { HTTPC_TE_IDENTITY, HTTPC_TE_CHUNKED } transferEncoding_t;
/**
* redirection follow mode.
* + `HTTPC_DISABLE_FOLLOW_REDIRECTS` - no redirection will be followed.
* + `HTTPC_STRICT_FOLLOW_REDIRECTS` - strict RFC2616, only requests using
* GET or HEAD methods will be redirected (using the same method),
* since the RFC requires end-user confirmation in other cases.
* + `HTTPC_FORCE_FOLLOW_REDIRECTS` - all redirections will be followed,
* regardless of a used method. New request will use the same method,
* and they will include the same body data and the same headers.
* In the sense of the RFC, it's just like every redirection is confirmed.
*/
typedef enum {
HTTPC_DISABLE_FOLLOW_REDIRECTS,
HTTPC_STRICT_FOLLOW_REDIRECTS,
HTTPC_FORCE_FOLLOW_REDIRECTS
} followRedirects_t;
#ifdef HTTPCLIENT_1_1_COMPATIBLE
class TransportTraits;
typedef std::unique_ptr<TransportTraits> TransportTraitsPtr;
#endif
// cookie jar support
typedef struct {
String host; // host which tries to set the cookie
time_t date; // timestamp of the response that set the cookie
String name;
String value;
String domain;
String path = "";
struct {
time_t date = 0;
bool valid = false;
} expires;
struct {
time_t duration = 0;
bool valid = false;
} max_age;
bool http_only = false;
bool secure = false;
} Cookie;
typedef std::vector<Cookie> CookieJar;
class HTTPClient {
public:
HTTPClient();
~HTTPClient();
/*
* Since both begin() functions take a reference to client as a parameter, you need to
* ensure the client object lives the entire time of the HTTPClient
*/
bool begin(WiFiClient &client, String url);
bool begin(WiFiClient &client, String host, uint16_t port, String uri = "/", bool https = false);
#ifdef HTTPCLIENT_1_1_COMPATIBLE
bool begin(String url);
bool begin(String url, const char *CAcert);
bool begin(String host, uint16_t port, String uri = "/");
bool begin(String host, uint16_t port, String uri, const char *CAcert);
bool begin(String host, uint16_t port, String uri, const char *CAcert, const char *cli_cert, const char *cli_key);
#endif
void end(void);
bool connected(void);
void setReuse(bool reuse); /// keep-alive
void setUserAgent(const String &userAgent);
void setAuthorization(const char *user, const char *password);
void setAuthorization(const char *auth);
void setAuthorizationType(const char *authType);
void setConnectTimeout(int32_t connectTimeout);
void setTimeout(uint16_t timeout);
// Redirections
void setFollowRedirects(followRedirects_t follow);
void setRedirectLimit(uint16_t limit); // max redirects to follow for a single request
bool setURL(const String &url);
void useHTTP10(bool usehttp10 = true);
/// request handling
int GET();
int PATCH(uint8_t *payload, size_t size);
int PATCH(String payload);
int POST(uint8_t *payload, size_t size);
int POST(String payload);
int PUT(uint8_t *payload, size_t size);
int PUT(String payload);
int sendRequest(const char *type, String payload);
int sendRequest(const char *type, uint8_t *payload = NULL, size_t size = 0);
int sendRequest(const char *type, Stream *stream, size_t size = 0);
void addHeader(const String &name, const String &value, bool first = false, bool replace = true);
/// Response handling
void collectHeaders(const char *headerKeys[], const size_t headerKeysCount);
String header(const char *name); // get request header value by name
String header(size_t i); // get request header value by number
String headerName(size_t i); // get request header name by number
int headers(); // get header count
bool hasHeader(const char *name); // check if header exists
int getSize(void);
const String &getLocation(void);
WiFiClient &getStream(void);
WiFiClient *getStreamPtr(void);
int writeToStream(Stream *stream);
// String getString(void);
static String errorToString(int error);
/// Cookie jar support
void setCookieJar(CookieJar *cookieJar);
void resetCookieJar();
void clearAllCookies();
protected:
struct RequestArgument {
String key;
String value;
};
bool beginInternal(String url, const char *expectedProtocol);
void disconnect(bool preserveClient = false);
void clear();
int returnError(int error);
bool connect(void);
bool sendHeader(const char *type);
int handleHeaderResponse();
int writeToStreamDataBlock(Stream *stream, int len);
/// Cookie jar support
void setCookie(String date, String headerValue);
bool generateCookieString(String *cookieString);
#ifdef HTTPCLIENT_1_1_COMPATIBLE
TransportTraitsPtr _transportTraits;
std::unique_ptr<WiFiClient> _tcpDeprecated;
#endif
WiFiClient *_client = nullptr;
/// request handling
String _host;
uint16_t _port = 0;
int32_t _connectTimeout = -1;
bool _reuse = true;
uint16_t _tcpTimeout = HTTPCLIENT_DEFAULT_TCP_TIMEOUT;
bool _useHTTP10 = false;
bool _secure = false;
String _uri;
String _protocol;
String _headers;
String _userAgent = "ESP32HTTPClient";
String _base64Authorization;
String _authorizationType = "Basic";
/// Response handling
RequestArgument *_currentHeaders = nullptr;
size_t _headerKeysCount = 0;
int _returnCode = 0;
int _size = -1;
bool _canReuse = false;
followRedirects_t _followRedirects = HTTPC_DISABLE_FOLLOW_REDIRECTS;
uint16_t _redirectLimit = 10;
String _location;
transferEncoding_t _transferEncoding = HTTPC_TE_IDENTITY;
/// Cookie jar support
CookieJar *_cookieJar = nullptr;
};
#endif /* HTTPClient_H_ */

View File

@@ -0,0 +1,7 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-29. */
#pragma once
#include <time.h>
extern char *strptime(const char *buf, const char *fmt, struct tm *tm);

View File

@@ -0,0 +1,2 @@
DisableFormat: true
SortIncludes: Never

View File

@@ -0,0 +1,197 @@
#include <stdlib.h>
#include <langinfo.h>
#include <time.h>
#include <ctype.h>
#include <stddef.h>
#include <string.h>
#include <strings.h>
char *strptime(const char *restrict s, const char *restrict f, struct tm *restrict tm)
{
int i, w, neg, adj, min, range, *dest, dummy;
const char *ex;
size_t len;
int want_century = 0, century = 0;
while (*f) {
if (*f != '%') {
if (isspace(*f)) for (; *s && isspace(*s); s++);
else if (*s != *f) return 0;
else s++;
f++;
continue;
}
f++;
if (*f == '+') f++;
if (isdigit(*f)) w=strtoul(f, (void *)&f, 10);
else w=-1;
adj=0;
switch (*f++) {
case 'a': case 'A':
dest = &tm->tm_wday;
min = ABDAY_1;
range = 7;
goto symbolic_range;
case 'b': case 'B': case 'h':
dest = &tm->tm_mon;
min = ABMON_1;
range = 12;
goto symbolic_range;
case 'c':
s = strptime(s, nl_langinfo(D_T_FMT), tm);
if (!s) return 0;
break;
case 'C':
dest = &century;
if (w<0) w=2;
want_century |= 2;
goto numeric_digits;
case 'd': case 'e':
dest = &tm->tm_mday;
min = 1;
range = 31;
goto numeric_range;
case 'D':
s = strptime(s, "%m/%d/%y", tm);
if (!s) return 0;
break;
case 'H':
dest = &tm->tm_hour;
min = 0;
range = 24;
goto numeric_range;
case 'I':
dest = &tm->tm_hour;
min = 1;
range = 12;
goto numeric_range;
case 'j':
dest = &tm->tm_yday;
min = 1;
range = 366;
goto numeric_range;
case 'm':
dest = &tm->tm_mon;
min = 1;
range = 12;
adj = 1;
goto numeric_range;
case 'M':
dest = &tm->tm_min;
min = 0;
range = 60;
goto numeric_range;
case 'n': case 't':
for (; *s && isspace(*s); s++);
break;
case 'p':
ex = nl_langinfo(AM_STR);
len = strlen(ex);
if (!strncasecmp(s, ex, len)) {
tm->tm_hour %= 12;
break;
}
ex = nl_langinfo(PM_STR);
len = strlen(ex);
if (!strncasecmp(s, ex, len)) {
tm->tm_hour %= 12;
tm->tm_hour += 12;
break;
}
return 0;
case 'r':
s = strptime(s, nl_langinfo(T_FMT_AMPM), tm);
if (!s) return 0;
break;
case 'R':
s = strptime(s, "%H:%M", tm);
if (!s) return 0;
break;
case 'S':
dest = &tm->tm_sec;
min = 0;
range = 61;
goto numeric_range;
case 'T':
s = strptime(s, "%H:%M:%S", tm);
if (!s) return 0;
break;
case 'U':
case 'W':
/* Throw away result, for now. (FIXME?) */
dest = &dummy;
min = 0;
range = 54;
goto numeric_range;
case 'w':
dest = &tm->tm_wday;
min = 0;
range = 7;
goto numeric_range;
case 'x':
s = strptime(s, nl_langinfo(D_FMT), tm);
if (!s) return 0;
break;
case 'X':
s = strptime(s, nl_langinfo(T_FMT), tm);
if (!s) return 0;
break;
case 'y':
dest = &tm->tm_year;
w = 2;
want_century |= 1;
goto numeric_digits;
case 'Y':
dest = &tm->tm_year;
if (w<0) w=4;
adj = 1900;
want_century = 0;
goto numeric_digits;
case '%':
if (*s++ != '%') return 0;
break;
default:
return 0;
numeric_range:
if (!isdigit(*s)) return 0;
*dest = 0;
for (i=1; i<=min+range && isdigit(*s); i*=10)
*dest = *dest * 10 + *s++ - '0';
if (*dest - min >= (unsigned)range) return 0;
*dest -= adj;
switch((char *)dest - (char *)tm) {
case offsetof(struct tm, tm_yday):
;
}
goto update;
numeric_digits:
neg = 0;
if (*s == '+') s++;
else if (*s == '-') neg=1, s++;
if (!isdigit(*s)) return 0;
for (*dest=i=0; i<w && isdigit(*s); i++)
*dest = *dest * 10 + *s++ - '0';
if (neg) *dest = -*dest;
*dest -= adj;
goto update;
symbolic_range:
for (i=2*range-1; i>=0; i--) {
ex = nl_langinfo(min+i);
len = strlen(ex);
if (strncasecmp(s, ex, len)) continue;
s += len;
*dest = i % range;
break;
}
if (i<0) return 0;
goto update;
update:
//FIXME
;
}
}
if (want_century) {
if (want_century & 2) tm->tm_year += century * 100 - 1900;
else if (tm->tm_year <= 68) tm->tm_year += 100;
}
return (char *)s;
}

View File

@@ -0,0 +1,98 @@
/*
IPv6Address.cpp - Base class that provides IPv6Address
Copyright (c) 2011 Adrian McEwen. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "IPv6Address.h"
#include <Arduino.h>
#include <api/Print.h>
IPv6Address::IPv6Address() {
memset(_address.bytes, 0, sizeof(_address.bytes));
}
IPv6Address::IPv6Address(const uint8_t *address) {
memcpy(_address.bytes, address, sizeof(_address.bytes));
}
IPv6Address::IPv6Address(const uint32_t *address) {
memcpy(_address.bytes, (const uint8_t *)address, sizeof(_address.bytes));
}
IPv6Address &IPv6Address::operator=(const uint8_t *address) {
memcpy(_address.bytes, address, sizeof(_address.bytes));
return *this;
}
bool IPv6Address::operator==(const uint8_t *addr) const {
return memcmp(addr, _address.bytes, sizeof(_address.bytes)) == 0;
}
size_t IPv6Address::printTo(Print &p) const {
/* size_t n = 0;
for(int i = 0; i < 16; i+=2) {
if(i){
n += p.print(':');
}
n += p.printf("%02x", _address.bytes[i]);
n += p.printf("%02x", _address.bytes[i+1]);
}
return n; */
}
String IPv6Address::toString() const {
char szRet[40];
sprintf(
szRet,
"%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
_address.bytes[0],
_address.bytes[1],
_address.bytes[2],
_address.bytes[3],
_address.bytes[4],
_address.bytes[5],
_address.bytes[6],
_address.bytes[7],
_address.bytes[8],
_address.bytes[9],
_address.bytes[10],
_address.bytes[11],
_address.bytes[12],
_address.bytes[13],
_address.bytes[14],
_address.bytes[15]
);
return String(szRet);
}
bool IPv6Address::fromString(const char *address) {
// format 0011:2233:4455:6677:8899:aabb:ccdd:eeff
if (strlen(address) != 39) {
return false;
}
char *pos = (char *)address;
size_t i = 0;
for (i = 0; i < 16; i += 2) {
if (!sscanf(pos, "%2hhx", &_address.bytes[i]) || !sscanf(pos + 2, "%2hhx", &_address.bytes[i + 1])) {
return false;
}
pos += 5;
}
return true;
}

View File

@@ -0,0 +1,97 @@
/*
IPv6Address.h - Base class that provides IPv6Address
Copyright (c) 2011 Adrian McEwen. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include <api/Print.h>
#include <api/String.h>
#include <stdint.h>
// A class to make it easier to handle and pass around IP addresses
namespace arduino {
class IPv6Address : public Printable {
private:
union {
uint8_t bytes[16]; // IPv4 address
uint32_t dword[4];
} _address;
// Access the raw byte array containing the address. Because this returns a pointer
// to the internal structure rather than a copy of the address this function should only
// be used when you know that the usage of the returned uint8_t* will be transient and not
// stored.
uint8_t *raw_address() {
return _address.bytes;
}
public:
// Constructors
IPv6Address();
IPv6Address(const uint8_t *address);
IPv6Address(const uint32_t *address);
virtual ~IPv6Address() {}
bool fromString(const char *address);
bool fromString(const String &address) {
return fromString(address.c_str());
}
operator const uint8_t *() const {
return _address.bytes;
}
operator const uint32_t *() const {
return _address.dword;
}
bool operator==(const IPv6Address &addr) const {
return (_address.dword[0] == addr._address.dword[0]) && (_address.dword[1] == addr._address.dword[1]) &&
(_address.dword[2] == addr._address.dword[2]) && (_address.dword[3] == addr._address.dword[3]);
}
bool operator==(const uint8_t *addr) const;
// Overloaded index operator to allow getting and setting individual octets of the address
uint8_t operator[](int index) const {
return _address.bytes[index];
}
uint8_t &operator[](int index) {
return _address.bytes[index];
}
// Overloaded copy operators to allow initialisation of IPv6Address objects from other types
IPv6Address &operator=(const uint8_t *address);
// TODO implement printTo()
virtual size_t printTo(Print &p) const;
String toString() const;
friend class UDP;
friend class Client;
friend class Server;
};
} // namespace arduino
using arduino::IPv6Address;

View File

@@ -19,7 +19,7 @@ size_t LwIPRxBuffer::r_available() {
return 0;
}
uint16_t count = 0;
int res = lwip_ioctl(_sock, FIONREAD, &count);
int res = lwip_ioctl(_sock, FIONREAD, &count);
if (res < 0) {
_failed = true;
return 0;

View File

@@ -0,0 +1,5 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-30. */
#pragma once
#include "../IPv6Address.h"

View File

@@ -0,0 +1,442 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-30. */
#include "MbedTLSClient.h"
#include <IPAddress.h>
#include <WiFi.h>
#include <WiFiClient.h>
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
#include <mbedtls/debug.h>
#include <mbedtls/platform.h>
#include <mbedtls/sha256.h>
#include <mbedtls/ssl.h>
#ifdef __cplusplus
} // extern "C"
#endif
MbedTLSClient::MbedTLSClient() : WiFiClient() {
init(); // ensure the context is zero filled
}
MbedTLSClient::MbedTLSClient(int sock) : WiFiClient(sock) {
init(); // ensure the context is zero filled
}
void MbedTLSClient::stop() {
WiFiClient::stop();
LT_V_SSL("Closing SSL connection");
if (_sslCfg.ca_chain) {
mbedtls_x509_crt_free(&_caCert);
}
if (_sslCfg.key_cert) {
mbedtls_x509_crt_free(&_clientCert);
mbedtls_pk_free(&_clientKey);
}
mbedtls_ssl_free(&_sslCtx);
mbedtls_ssl_config_free(&_sslCfg);
}
void MbedTLSClient::init() {
// Realtek AmbZ: init platform here to ensure HW crypto is initialized in ssl_init
mbedtls_platform_set_calloc_free(calloc, free);
mbedtls_ssl_init(&_sslCtx);
mbedtls_ssl_config_init(&_sslCfg);
}
int MbedTLSClient::connect(IPAddress ip, uint16_t port, int32_t timeout) {
return connect(ipToString(ip).c_str(), port, timeout);
}
int MbedTLSClient::connect(const char *host, uint16_t port, int32_t timeout) {
if (_pskIdentStr && _pskStr)
return connect(host, port, timeout, NULL, NULL, NULL, _pskIdentStr, _pskStr) == 0;
return connect(host, port, timeout, _caCertStr, _clientCertStr, _clientKeyStr, NULL, NULL) == 0;
}
int MbedTLSClient::connect(
IPAddress ip, uint16_t port, const char *rootCABuf, const char *clientCert, const char *clientKey
) {
return connect(ipToString(ip).c_str(), port, 0, rootCABuf, clientCert, clientKey, NULL, NULL) == 0;
}
int MbedTLSClient::connect(
const char *host, uint16_t port, const char *rootCABuf, const char *clientCert, const char *clientKey
) {
return connect(host, port, 0, rootCABuf, clientCert, clientKey, NULL, NULL) == 0;
}
int MbedTLSClient::connect(IPAddress ip, uint16_t port, const char *pskIdent, const char *psk) {
return connect(ipToString(ip).c_str(), port, 0, NULL, NULL, NULL, pskIdent, psk) == 0;
}
int MbedTLSClient::connect(const char *host, uint16_t port, const char *pskIdent, const char *psk) {
return connect(host, port, 0, NULL, NULL, NULL, pskIdent, psk) == 0;
}
static int ssl_random(void *data, unsigned char *output, size_t len) {
lt_rand_bytes((uint8_t *)output, len);
return 0;
}
void debug_cb(void *ctx, int level, const char *file, int line, const char *str) {
LT_I("%04d: |%d| %s", line, level, str);
}
int MbedTLSClient::connect(
const char *host,
uint16_t port,
int32_t timeout,
const char *rootCABuf,
const char *clientCert,
const char *clientKey,
const char *pskIdent,
const char *psk
) {
LT_D_SSL("Free heap before TLS: TODO");
if (!rootCABuf && !pskIdent && !psk && !_insecure && !_useRootCA)
return -1;
if (timeout <= 0)
timeout = _timeout; // use default when -1 passed as timeout
IPAddress addr = WiFi.hostByName(host);
if (!(uint32_t)addr)
return -1;
int ret = WiFiClient::connect(addr, port, timeout);
if (ret < 0) {
LT_E("SSL socket failed");
return ret;
}
char *uid = "lt-ssl"; // TODO
LT_V_SSL("Init SSL");
init();
// mbedtls_debug_set_threshold(4);
// mbedtls_ssl_conf_dbg(&_sslCfg, debug_cb, NULL);
ret = mbedtls_ssl_config_defaults(
&_sslCfg,
MBEDTLS_SSL_IS_CLIENT,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT
);
LT_RET_NZ(ret);
#ifdef MBEDTLS_SSL_ALPN
if (_alpnProtocols) {
ret = mbedtls_ssl_conf_alpn_protocols(&_sslCfg, _alpnProtocols);
LT_RET_NZ(ret);
}
#endif
if (_insecure) {
mbedtls_ssl_conf_authmode(&_sslCfg, MBEDTLS_SSL_VERIFY_NONE);
} else if (rootCABuf) {
mbedtls_x509_crt_init(&_caCert);
mbedtls_ssl_conf_authmode(&_sslCfg, MBEDTLS_SSL_VERIFY_REQUIRED);
ret = mbedtls_x509_crt_parse(&_caCert, (const unsigned char *)rootCABuf, strlen(rootCABuf) + 1);
mbedtls_ssl_conf_ca_chain(&_sslCfg, &_caCert, NULL);
if (ret < 0) {
mbedtls_x509_crt_free(&_caCert);
LT_RET(ret);
}
} else if (_useRootCA) {
return -1; // not implemented
} else if (pskIdent && psk) {
#ifdef MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED
uint16_t len = strlen(psk);
if ((len & 1) != 0 || len > 2 * MBEDTLS_PSK_MAX_LEN) {
LT_E("PSK length invalid");
return -1;
}
unsigned char pskBin[MBEDTLS_PSK_MAX_LEN] = {};
for (uint8_t i = 0; i < len; i++) {
uint8_t c = psk[i];
c |= 0b00100000; // make lowercase
c -= '0' * (c >= '0' && c <= '9');
c -= ('a' - 10) * (c >= 'a' && c <= 'z');
if (c > 0xf)
return -1;
pskBin[i / 2] |= c << (4 * ((i & 1) ^ 1));
}
ret = mbedtls_ssl_conf_psk(&_sslCfg, pskBin, len / 2, (const unsigned char *)pskIdent, strlen(pskIdent));
LT_RET_NZ(ret);
#else
return -1;
#endif
} else {
return -1;
}
if (!_insecure && clientCert && clientKey) {
mbedtls_x509_crt_init(&_clientCert);
mbedtls_pk_init(&_clientKey);
LT_V_SSL("Loading client cert");
ret = mbedtls_x509_crt_parse(&_clientCert, (const unsigned char *)clientCert, strlen(clientCert) + 1);
if (ret < 0) {
mbedtls_x509_crt_free(&_clientCert);
LT_RET(ret);
}
LT_V_SSL("Loading private key");
ret = mbedtls_pk_parse_key(&_clientKey, (const unsigned char *)clientKey, strlen(clientKey) + 1, NULL, 0);
if (ret < 0) {
mbedtls_x509_crt_free(&_clientCert);
LT_RET(ret);
}
mbedtls_ssl_conf_own_cert(&_sslCfg, &_clientCert, &_clientKey);
}
LT_V_SSL("Setting TLS hostname");
ret = mbedtls_ssl_set_hostname(&_sslCtx, host);
LT_RET_NZ(ret);
mbedtls_ssl_conf_rng(&_sslCfg, ssl_random, NULL);
ret = mbedtls_ssl_setup(&_sslCtx, &_sslCfg);
LT_RET_NZ(ret);
_sockTls = fd();
mbedtls_ssl_set_bio(&_sslCtx, &_sockTls, mbedtls_net_send, mbedtls_net_recv, NULL);
mbedtls_net_set_nonblock((mbedtls_net_context *)&_sockTls);
LT_V_SSL("SSL handshake");
if (_handshakeTimeout == 0)
_handshakeTimeout = timeout;
unsigned long start = millis();
while (ret = mbedtls_ssl_handshake(&_sslCtx)) {
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
LT_RET(ret);
}
if ((millis() - start) > _handshakeTimeout) {
LT_E("SSL handshake timeout");
return -1;
}
delay(2);
}
if (clientCert && clientKey) {
LT_D_SSL(
"Protocol %s, ciphersuite %s",
mbedtls_ssl_get_version(&_sslCtx),
mbedtls_ssl_get_ciphersuite(&_sslCtx)
);
ret = mbedtls_ssl_get_record_expansion(&_sslCtx);
if (ret >= 0)
LT_D_SSL("Record expansion: %d", ret);
else {
LT_W("Record expansion unknown");
}
}
LT_V_SSL("Verifying certificate");
ret = mbedtls_ssl_get_verify_result(&_sslCtx);
if (ret) {
char buf[512];
memset(buf, 0, sizeof(buf));
mbedtls_x509_crt_verify_info(buf, sizeof(buf), " ! ", ret);
LT_E("Failed to verify peer certificate! Verification info: %s", buf);
return ret;
}
if (rootCABuf)
mbedtls_x509_crt_free(&_caCert);
if (clientCert)
mbedtls_x509_crt_free(&_clientCert);
if (clientKey != NULL)
mbedtls_pk_free(&_clientKey);
return 0; // OK
}
size_t MbedTLSClient::write(const uint8_t *buf, size_t size) {
int ret = -1;
while ((ret = mbedtls_ssl_write(&_sslCtx, buf, size)) <= 0) {
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) {
LT_RET(ret);
}
delay(2);
}
return ret;
}
int MbedTLSClient::available() {
bool peeked = _peeked >= 0;
if (!connected())
return peeked;
int ret = mbedtls_ssl_read(&_sslCtx, NULL, 0);
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) {
stop();
return peeked ? peeked : ret;
}
return mbedtls_ssl_get_bytes_avail(&_sslCtx) + peeked;
}
int MbedTLSClient::read(uint8_t *buf, size_t size) {
bool peeked = false;
int toRead = available();
if ((!buf && size) || toRead <= 0)
return -1;
if (!size)
return 0;
if (_peeked >= 0) {
buf[0] = _peeked;
_peeked = -1;
size--;
toRead--;
if (!size || !toRead)
return 1;
buf++;
peeked = true;
}
int ret = mbedtls_ssl_read(&_sslCtx, buf, size);
if (ret < 0) {
stop();
return peeked ? peeked : ret;
}
return ret + peeked;
}
int MbedTLSClient::peek() {
if (_peeked >= 0)
return _peeked;
_peeked = timedRead();
return _peeked;
}
void MbedTLSClient::flush() {}
int MbedTLSClient::lastError(char *buf, const size_t size) {
return 0; // TODO (?)
}
void MbedTLSClient::setInsecure() {
_caCertStr = NULL;
_clientCertStr = NULL;
_clientKeyStr = NULL;
_pskIdentStr = NULL;
_pskStr = NULL;
_insecure = true;
}
void MbedTLSClient::setPreSharedKey(const char *pskIdent, const char *psk) {
_pskIdentStr = pskIdent;
_pskStr = psk;
}
void MbedTLSClient::setCACert(const char *rootCA) {
_caCertStr = rootCA;
}
void MbedTLSClient::setCertificate(const char *clientCA) {
_clientCertStr = clientCA;
}
void MbedTLSClient::setPrivateKey(const char *privateKey) {
_clientKeyStr = privateKey;
}
char *streamToStr(Stream &stream, size_t size) {
char *buf = (char *)malloc(size + 1);
if (!buf)
return NULL;
if (size != stream.readBytes(buf, size)) {
free(buf);
return NULL;
}
buf[size] = '\0';
return buf;
}
bool MbedTLSClient::loadCACert(Stream &stream, size_t size) {
char *str = streamToStr(stream, size);
if (str) {
_caCertStr = str;
return true;
}
return false;
}
bool MbedTLSClient::loadCertificate(Stream &stream, size_t size) {
char *str = streamToStr(stream, size);
if (str) {
_clientCertStr = str;
return true;
}
return false;
}
bool MbedTLSClient::loadPrivateKey(Stream &stream, size_t size) {
char *str = streamToStr(stream, size);
if (str) {
_clientKeyStr = str;
return true;
}
return false;
}
bool MbedTLSClient::verify(const char *fingerprint, const char *domainName) {
uint8_t fpLocal[32] = {};
uint16_t len = strlen(fingerprint);
uint8_t byte = 0;
for (uint8_t i = 0; i < len; i++) {
uint8_t c = fingerprint[i];
while ((c == ' ' || c == ':') && i < len) {
c = fingerprint[++i];
}
c |= 0b00100000; // make lowercase
c -= '0' * (c >= '0' && c <= '9');
c -= ('a' - 10) * (c >= 'a' && c <= 'z');
if (c > 0xf)
return -1;
fpLocal[byte / 2] |= c << (4 * ((byte & 1) ^ 1));
byte++;
if (byte >= 64)
break;
}
uint8_t fpRemote[32];
if (!getFingerprintSHA256(fpRemote))
return false;
if (memcmp(fpLocal, fpRemote, 32)) {
LT_D_SSL("Fingerprints don't match");
return false;
}
if (!domainName)
return true;
// TODO domain name verification
return true;
}
void MbedTLSClient::setHandshakeTimeout(unsigned long handshakeTimeout) {
_handshakeTimeout = handshakeTimeout * 1000;
}
void MbedTLSClient::setAlpnProtocols(const char **alpnProtocols) {
_alpnProtocols = alpnProtocols;
}
bool MbedTLSClient::getFingerprintSHA256(uint8_t result[32]) {
const mbedtls_x509_crt *cert = mbedtls_ssl_get_peer_cert(&_sslCtx);
if (!cert) {
LT_E("Failed to get peer certificate");
return false;
}
mbedtls_sha256_context shaCtx;
mbedtls_sha256_init(&shaCtx);
mbedtls_sha256_starts(&shaCtx, false);
mbedtls_sha256_update(&shaCtx, cert->raw.p, cert->raw.len);
mbedtls_sha256_finish(&shaCtx, result);
return true;
}

View File

@@ -0,0 +1,90 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-30. */
#pragma once
#include <api/WiFiClient.h>
#include <api/WiFiClientSecure.h>
#include <WiFiClient.h> // extend family's WiFiClient impl
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
#include <mbedtls/net.h>
#ifdef __cplusplus
} // extern "C"
#endif
class MbedTLSClient : public WiFiClient, public IWiFiClientSecure {
private:
mbedtls_ssl_context _sslCtx;
mbedtls_ssl_config _sslCfg;
mbedtls_x509_crt _caCert;
mbedtls_x509_crt _clientCert;
mbedtls_pk_context _clientKey;
uint32_t _handshakeTimeout = 0;
void init();
int _sockTls = -1;
bool _insecure = false;
bool _useRootCA = false;
int _peeked = -1;
const char *_caCertStr;
const char *_clientCertStr;
const char *_clientKeyStr;
const char *_pskIdentStr;
const char *_pskStr;
const char **_alpnProtocols;
int connect(
const char *host,
uint16_t port,
int32_t timeout,
const char *rootCABuf,
const char *clientCert,
const char *clientKey,
const char *pskIdent,
const char *psk
);
public:
MbedTLSClient();
MbedTLSClient(int sock);
int connect(IPAddress ip, uint16_t port, int32_t timeout);
int connect(const char *host, uint16_t port, int32_t timeout);
int connect(IPAddress ip, uint16_t port, const char *rootCABuf, const char *clientCert, const char *clientKey);
int connect(const char *host, uint16_t port, const char *rootCABuf, const char *clientCert, const char *clientKey);
int connect(IPAddress ip, uint16_t port, const char *pskIdent, const char *psk);
int connect(const char *host, uint16_t port, const char *pskIdent, const char *psk);
size_t write(const uint8_t *buf, size_t size);
int available();
int read(uint8_t *buf, size_t size);
int peek();
void flush();
void stop();
int lastError(char *buf, const size_t size);
void setInsecure(); // Don't validate the chain, just accept whatever is given. VERY INSECURE!
void setPreSharedKey(const char *pskIdent, const char *psk); // psk in hex
void setCACert(const char *rootCA);
void setCertificate(const char *clientCA);
void setPrivateKey(const char *privateKey);
bool loadCACert(Stream &stream, size_t size);
bool loadCertificate(Stream &stream, size_t size);
bool loadPrivateKey(Stream &stream, size_t size);
bool verify(const char *fingerprint, const char *domainName);
void setHandshakeTimeout(unsigned long handshakeTimeout);
void setAlpnProtocols(const char **alpnProtocols);
bool getFingerprintSHA256(uint8_t result[32]);
using WiFiClient::connect;
using WiFiClient::read;
};

View File

@@ -0,0 +1,199 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-29. */
#include "Update.h"
UpdateClass::UpdateClass() : ctx(NULL), info(NULL), buf(NULL) {
cleanup();
}
/**
* @brief Initialize the update process.
*
* @param size total UF2 file size
* @param command must be U_FLASH
* @return false if parameters are invalid or update is running, true otherwise
*/
bool UpdateClass::begin(size_t size, int command, int unused2, uint8_t unused3, const char *unused4) {
if (ctx)
return false;
cleanup();
ctx = uf2_ctx_init(LT.otaGetTarget(), FAMILY);
info = uf2_info_init();
if (!size)
return errorArd(UPDATE_ERROR_SIZE);
if (command != U_FLASH)
return errorArd(UPDATE_ERROR_BAD_ARGUMENT);
bytesTotal = size;
return true;
}
/**
* @brief Finalize the update process. Check for errors and update completion, then activate the new firmware image.
*
* @param evenIfRemaining no idea
* @return false in case of errors or no update running, true otherwise
*/
bool UpdateClass::end(bool evenIfRemaining) {
if (hasError() || !ctx)
// false if not running
return false;
if (!isFinished() && !evenIfRemaining) {
// abort if not finished
return errorArd(UPDATE_ERROR_ABORT);
}
// TODO what is evenIfRemaining for?
if (!LT.otaSwitch())
// try to activate the second OTA
return errorArd(UPDATE_ERROR_ACTIVATE);
cleanup();
return true;
}
/**
* @brief Write a chunk of data to the buffer or flash memory.
*
* It's advised to write in 512-byte chunks (or its multiples).
*
* @param data
* @param len
* @return size_t
*/
size_t UpdateClass::write(uint8_t *data, size_t len) {
size_t written = 0;
if (hasError() || !ctx)
// 0 if not running
return 0;
/* while (buf == bufPos && len >= UF2_BLOCK_SIZE) {
// buffer empty and entire block is in data
if (!tryWriteData(data, UF2_BLOCK_SIZE)) {
// returns 0 if data contains an invalid block
return written;
}
data += UF2_BLOCK_SIZE;
len -= UF2_BLOCK_SIZE;
written += UF2_BLOCK_SIZE;
} */
// write until buffer space is available
uint16_t toWrite;
while (len && (toWrite = min(len, bufLeft()))) {
tryWriteData(data, toWrite);
if (hasError())
// return on errors
return written;
data += toWrite;
len -= toWrite;
written += toWrite;
}
return written;
}
size_t UpdateClass::writeStream(Stream &data) {
size_t written = 0;
if (hasError() || !ctx)
// 0 if not running
return 0;
uint32_t lastData = millis();
// loop until the update is complete
while (remaining()) {
// check stream availability
int available = data.available();
if (available <= 0) {
if (millis() - lastData > UPDATE_TIMEOUT_MS) {
// waited for data too long; abort with error
errorArd(UPDATE_ERROR_STREAM);
return written;
}
continue;
}
// available > 0
lastData = millis();
// read data to fit in the remaining buffer space
bufAlloc();
uint16_t read = data.readBytes(bufPos, bufLeft());
bufPos += read;
written += read;
tryWriteData();
if (hasError())
// return on errors
return written;
}
}
/**
* @brief Try to use the buffer as a block to write. In case of UF2 errors,
* error codes are set, the update is aborted and 0 is returned
*
* @param data received data to copy to buffer or NULL if already in buffer
* @param len received data length - must be at most bufLeft()
* @return size_t "used" data size - 0 or 512
*/
size_t UpdateClass::tryWriteData(uint8_t *data, size_t len) {
uf2_block_t *block = NULL;
if (len == UF2_BLOCK_SIZE) {
// data has a complete block
block = (uf2_block_t *)data;
} else if (data && len) {
// data has a part of a block, copy it to buffer
bufAlloc();
memcpy(bufPos, data, len);
bufPos += len;
}
if (!block && bufSize() == UF2_BLOCK_SIZE) {
// use buffer as block (only if not found above)
block = (uf2_block_t *)buf;
}
// a complete block has been found
if (block) {
if (errorUf2(uf2_check_block(ctx, block)))
// block is invalid
return 0;
if (errUf2 == UF2_ERR_IGNORE)
// treat ignored blocks as valid
return UF2_BLOCK_SIZE;
if (!bytesWritten) {
// parse header block to allow retrieving firmware info
if (errorUf2(uf2_parse_header(ctx, block, info)))
// header is invalid
return 0;
if (bytesTotal == UPDATE_SIZE_UNKNOWN) {
// set total update size from block count info
bytesTotal = block->block_count * UF2_BLOCK_SIZE;
} else if (bytesTotal != block->block_count * UF2_BLOCK_SIZE) {
// given update size does not match the block count
return errorArd(UPDATE_ERROR_SIZE);
}
} else {
// write data blocks normally
if (errorUf2(uf2_write(ctx, block)))
// block writing failed
return 0;
}
// increment total writing progress
bytesWritten += UF2_BLOCK_SIZE;
// call progress callback
if (callback)
callback(bytesWritten, bytesTotal);
return UF2_BLOCK_SIZE;
}
return 0;
}
UpdateClass Update;

View File

@@ -0,0 +1,150 @@
#pragma once
#include <Arduino.h>
#include <functional>
#include "uf2ota/uf2ota.h"
// No Error
#define UPDATE_ERROR_OK (0)
// Flash Write Failed
#define UPDATE_ERROR_WRITE (1)
// Flash Erase Failed
#define UPDATE_ERROR_ERASE (2)
// Flash Read Failed
#define UPDATE_ERROR_READ (3)
// Not Enough Space
#define UPDATE_ERROR_SPACE (4)
// Bad Size Given
#define UPDATE_ERROR_SIZE (5)
// Stream Read Timeout
#define UPDATE_ERROR_STREAM (6)
// MD5 Check Failed
#define UPDATE_ERROR_MD5 (7)
// Wrong Magic Byte
#define UPDATE_ERROR_MAGIC_BYTE (8)
// Could Not Activate The Firmware
#define UPDATE_ERROR_ACTIVATE (9)
// Partition Could Not be Found
#define UPDATE_ERROR_NO_PARTITION (10)
// Bad Argument
#define UPDATE_ERROR_BAD_ARGUMENT (11)
// Aborted
#define UPDATE_ERROR_ABORT (12)
#define UPDATE_SIZE_UNKNOWN 0xFFFFFFFF
#define U_FLASH 0
#define U_SPIFFS 100
#define U_AUTH 200
#define ENCRYPTED_BLOCK_SIZE 16
#define UPDATE_TIMEOUT_MS 30 * 1000
class UpdateClass {
public:
typedef std::function<void(size_t, size_t)> THandlerFunction_Progress;
public: /* Update.cpp */
UpdateClass();
bool begin(
size_t size = UPDATE_SIZE_UNKNOWN,
int command = U_FLASH,
int unused2 = -1,
uint8_t unused3 = LOW,
const char *unused4 = NULL // this is for SPIFFS
);
bool end(bool evenIfRemaining = false);
size_t write(uint8_t *data, size_t len);
size_t writeStream(Stream &data);
bool canRollBack();
bool rollBack();
// bool setMD5(const char *expected_md5);
private: /* Update.cpp */
size_t tryWriteData(uint8_t *data = NULL, size_t len = 0);
public: /* UpdateUtil.cpp */
UpdateClass &onProgress(THandlerFunction_Progress callback);
void abort();
void printError(Print &out);
const char *errorString();
const char *getFirmwareName();
const char *getFirmwareVersion();
const char *getLibreTuyaVersion();
const char *getBoardName();
private: /* UpdateUtil.cpp */
void cleanup();
bool errorUf2(uf2_err_t err);
bool errorArd(uint8_t err);
void bufAlloc();
uint16_t bufLeft();
uint16_t bufSize();
private:
// uf2ota context
uf2_ota_t *ctx;
uf2_info_t *info;
// block buffer
uint8_t *buf;
uint8_t *bufPos;
// update progress - multiplies of 512 bytes
uint32_t bytesWritten;
uint32_t bytesTotal;
// errors
uf2_err_t errUf2;
uint8_t errArd;
// progress callback
THandlerFunction_Progress callback;
// String _target_md5;
// MD5Builder _md5;
public:
String md5String(void) {
// return _md5.toString();
}
void md5(uint8_t *result) {
// return _md5.getBytes(result);
}
uint8_t getError() {
return errArd;
}
uf2_err_t getUF2Error() {
return errUf2;
}
void clearError() {
errorUf2(UF2_ERR_OK);
}
bool hasError() {
return errArd != UPDATE_ERROR_OK;
}
bool isRunning() {
return ctx != NULL;
}
bool isFinished() {
return bytesWritten == bytesTotal;
}
size_t size() {
return bytesTotal;
}
size_t progress() {
return bytesWritten;
}
size_t remaining() {
return bytesTotal - bytesWritten;
}
};
extern UpdateClass Update;

View File

@@ -0,0 +1,162 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-30. */
#include "Update.h"
static const uint8_t errorMap[] = {
UPDATE_ERROR_OK, /* UF2_ERR_OK - no error */
UPDATE_ERROR_OK, /* UF2_ERR_IGNORE - block should be ignored */
UPDATE_ERROR_MAGIC_BYTE, /* UF2_ERR_MAGIC - wrong magic numbers */
UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_FAMILY - family ID mismatched */
UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_NOT_HEADER - block is not a header */
UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_OTA_VER - unknown/invalid OTA format version */
UPDATE_ERROR_MAGIC_BYTE, /* UF2_ERR_OTA_WRONG - no data for current OTA index */
UPDATE_ERROR_NO_PARTITION, /* UF2_ERR_PART_404 - no partition with that name */
UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_PART_ONE - only one partition tag in a block */
UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_PART_UNSET - attempted to write without target partition */
UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_DATA_TOO_LONG - data too long - tags won't fit */
UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_SEQ_MISMATCH - sequence number mismatched */
UPDATE_ERROR_ERASE, /* UF2_ERR_ERASE_FAILED - erasing flash failed */
UPDATE_ERROR_WRITE, /* UF2_ERR_WRITE_FAILED - writing to flash failed */
UPDATE_ERROR_WRITE /* UF2_ERR_WRITE_LENGTH - wrote fewer data than requested */
};
static char errorStr[14];
/**
* @brief Set the callback invoked after writing data to flash.
*/
UpdateClass &UpdateClass::onProgress(THandlerFunction_Progress callback) {
this->callback = callback;
return *this;
}
void UpdateClass::cleanup() {
free(ctx); // NULL in constructor
ctx = NULL;
uf2_info_free(info); // NULL in constructor
info = NULL;
free(buf); // NULL in constructor
buf = bufPos = NULL;
bytesWritten = 0;
bytesTotal = 0;
errUf2 = UF2_ERR_OK;
errArd = UPDATE_ERROR_OK;
}
/**
* @brief Check for UF2 errors. Set errArd and errUf2 in case of errors.
* Ignored blocks are not reported as errors.
* Abort the update.
* Use like: "if (errorUf2(...)) return false;"
* @return true if err is not OK, false otherwise
*/
bool UpdateClass::errorUf2(uf2_err_t err) {
if (err <= UF2_ERR_IGNORE)
return false;
cleanup();
errUf2 = err;
errArd = errorMap[err];
return true;
}
/**
* @brief Set errUf2 and errArd according to given Arduino error code.
* Abort the update.
* Use like: "return errorArd(...);"
* @return false - always
*/
bool UpdateClass::errorArd(uint8_t err) {
cleanup();
errUf2 = UF2_ERR_OK;
errArd = err;
return false;
}
/**
* @brief Abort the update with UPDATE_ERROR_ABORT reason.
*/
void UpdateClass::abort() {
errorArd(UPDATE_ERROR_ABORT);
}
void UpdateClass::bufAlloc() {
if (!buf)
buf = bufPos = (uint8_t *)malloc(UF2_BLOCK_SIZE);
}
uint16_t UpdateClass::bufLeft() {
return buf + UF2_BLOCK_SIZE - bufPos;
}
uint16_t UpdateClass::bufSize() {
return bufPos - buf;
}
/**
* @brief Print string error info to the stream.
*/
void UpdateClass::printError(Print &out) {
out.println(errorString());
}
/**
* @brief Get string representation of the error in format
* "ard=..,uf2=..". Returns "" if no error.
*/
const char *UpdateClass::errorString() {
if (!errArd)
return "";
sprintf(errorStr, "ard=%u,uf2=%u", errArd, errUf2);
return errorStr;
}
/**
* @brief Get firmware name from UF2 info.
*/
const char *UpdateClass::getFirmwareName() {
if (info)
return info->fw_name;
return NULL;
}
/**
* @brief Get firmware version from UF2 info.
*/
const char *UpdateClass::getFirmwareVersion() {
if (info)
return info->fw_version;
return NULL;
}
/**
* @brief Get LibreTuya version from UF2 info.
*/
const char *UpdateClass::getLibreTuyaVersion() {
if (info)
return info->lt_version;
return NULL;
}
/**
* @brief Get target board name from UF2 info.
*/
const char *UpdateClass::getBoardName() {
if (info)
return info->board;
return NULL;
}
/**
* @brief See LT.otaCanRollback() for more info.
*/
bool UpdateClass::canRollBack() {
return LT.otaCanRollback();
}
/**
* @brief See LT.otaRollback() for more info.
*/
bool UpdateClass::rollBack() {
return LT.otaRollback();
}

View File

@@ -0,0 +1,32 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-29. */
#include "uf2priv.h"
uf2_err_t uf2_binpatch(uint8_t *data, const uint8_t *binpatch, uint8_t binpatch_len) {
const uint8_t *binpatch_end = binpatch + binpatch_len;
// +2 to make sure opcode and length is present
while ((binpatch + 2) < binpatch_end) {
uf2_opcode_t opcode = binpatch[0];
uint8_t len = binpatch[1];
switch (opcode) {
case UF2_OPC_DIFF32:
uf2_binpatch_diff32(data, binpatch + 1);
break;
}
// advance by opcode + length + data
binpatch += len + 2;
}
return UF2_ERR_OK;
}
void uf2_binpatch_diff32(uint8_t *data, const uint8_t *patch) {
uint8_t num_offs = patch[0] - 4; // read offset count
uint32_t diff = *((uint32_t *)(patch + 1)); // read diff value
patch += 5; // skip num_offs and diff value
for (uint8_t i = 0; i < num_offs; i++) {
// patch the data
uint8_t offs = patch[i];
uint32_t *value = (uint32_t *)(data + offs);
*(value) += diff;
}
}

View File

@@ -0,0 +1,26 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-29. */
#pragma once
#include "uf2types.h"
/**
* @brief Apply binary patch to data.
*
* @param data input data
* @param data_len input data length
* @param binpatch binary patch data
* @param binpatch_len binary patch data length
* @return uf2_err_t error code
*/
uf2_err_t uf2_binpatch(uint8_t *data, const uint8_t *binpatch, uint8_t binpatch_len);
/**
* Apply DIFF32 binary patch.
*
* @param data input data
* @param len input data length
* @param patch patch data, incl. length byte
* @return uf2_err_t error code
*/
void uf2_binpatch_diff32(uint8_t *data, const uint8_t *patch);

View File

@@ -0,0 +1,100 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-29. */
#include "uf2priv.h"
uf2_ota_t *uf2_ctx_init(uint8_t ota_idx, uint32_t family_id) {
uf2_ota_t *ctx = (uf2_ota_t *)zalloc(sizeof(uf2_ota_t));
ctx->ota_idx = ota_idx;
ctx->family_id = family_id;
return ctx;
}
uf2_info_t *uf2_info_init() {
uf2_info_t *info = (uf2_info_t *)zalloc(sizeof(uf2_info_t));
return info;
}
void uf2_info_free(uf2_info_t *info) {
if (!info)
return;
free(info->fw_name);
free(info->fw_version);
free(info->lt_version);
free(info->board);
free(info);
}
uf2_err_t uf2_check_block(uf2_ota_t *ctx, uf2_block_t *block) {
if (block->magic1 != UF2_MAGIC_1)
return UF2_ERR_MAGIC;
if (block->magic2 != UF2_MAGIC_2)
return UF2_ERR_MAGIC;
if (block->magic3 != UF2_MAGIC_3)
return UF2_ERR_MAGIC;
if (block->file_container)
// ignore file containers, for now
return UF2_ERR_IGNORE;
if (!block->has_family_id || block->file_size != ctx->family_id)
// require family_id
return UF2_ERR_FAMILY;
return UF2_ERR_OK;
}
uf2_err_t uf2_parse_header(uf2_ota_t *ctx, uf2_block_t *block, uf2_info_t *info) {
if (!block->has_tags || block->file_container || block->len)
// header must have tags and no data
return UF2_ERR_NOT_HEADER;
uf2_err_t err = uf2_parse_block(ctx, block, info);
if (err)
return err;
if ((ctx->ota_idx == 1 && !ctx->has_ota1) || !ctx->has_ota2)
return UF2_ERR_OTA_WRONG;
return UF2_ERR_OK;
}
uf2_err_t uf2_write(uf2_ota_t *ctx, uf2_block_t *block) {
if (ctx->seq == 0)
return uf2_parse_header(ctx, block, NULL);
if (block->not_main_flash || !block->len)
// ignore blocks not meant for flashing
return UF2_ERR_IGNORE;
uf2_err_t err = uf2_parse_block(ctx, block, NULL);
if (err)
return err;
if (!ctx->part1 && !ctx->part2)
// no partitions set at all
return UF2_ERR_PART_UNSET;
fal_partition_t part = uf2_get_target_part(ctx);
if (!part)
// image is not for current OTA scheme
return UF2_ERR_IGNORE;
if (ctx->ota_idx == 2 && ctx->binpatch_len) {
// apply binpatch
err = uf2_binpatch(block->data, ctx->binpatch, ctx->binpatch_len);
if (err)
return err;
}
int ret;
// erase sectors if needed
if (!uf2_is_erased(ctx, block->addr, block->len)) {
ret = fal_partition_erase(part, block->addr, block->len);
if (ret < 0)
return UF2_ERR_ERASE_FAILED;
ctx->erased_offset = block->addr;
ctx->erased_length = ret;
}
// write data to flash
ret = fal_partition_write(part, block->addr, block->data, block->len);
if (ret < 0)
return UF2_ERR_WRITE_FAILED;
if (ret != block->len)
return UF2_ERR_WRITE_LENGTH;
return UF2_ERR_OK;
}

View File

@@ -0,0 +1,68 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-28. */
#pragma once
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
#include "uf2types.h"
/**
* @brief Create an UF2 OTA context.
*
* @param ota_idx target OTA index
* @param family_id expected family ID
* @return uf2_ota_t* heap-allocated structure
*/
uf2_ota_t *uf2_ctx_init(uint8_t ota_idx, uint32_t family_id);
/**
* @brief Create an UF2 Info structure.
*
* @return uf2_info_t* heap-allocated structure
*/
uf2_info_t *uf2_info_init();
/**
* @brief Free values in the info structure AND the structure itself.
*
* @param info structure to free; may be NULL
*/
void uf2_info_free(uf2_info_t *info);
/**
* @brief Check if block is valid.
*
* @param ctx context
* @param block block to check
* @return uf2_err_t error code; UF2_ERR_OK and UF2_ERR_IGNORE denote valid blocks
*/
uf2_err_t uf2_check_block(uf2_ota_t *ctx, uf2_block_t *block);
/**
* @brief Parse header block (LibreTuya UF2 first block).
*
* Note: caller should call uf2_check_block() first.
*
* @param ctx context
* @param block block to parse
* @param info structure to write firmware info, NULL if not used
* @return uf2_err_t error code
*/
uf2_err_t uf2_parse_header(uf2_ota_t *ctx, uf2_block_t *block, uf2_info_t *info);
/**
* @brief Write the block to flash memory.
*
* Note: caller should call uf2_check_block() first.
*
* @param ctx context
* @param block block to write
* @return uf2_err_t error code
*/
uf2_err_t uf2_write(uf2_ota_t *ctx, uf2_block_t *block);
#ifdef __cplusplus
} // extern "C"
#endif

View File

@@ -0,0 +1,146 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-29. */
#include "uf2priv.h"
uf2_err_t uf2_parse_block(uf2_ota_t *ctx, uf2_block_t *block, uf2_info_t *info) {
if (block->block_seq != ctx->seq)
// sequence number must match
return UF2_ERR_SEQ_MISMATCH;
ctx->seq++; // increment sequence number after checking it
if (!block->has_tags)
// no tags in this block, no further processing needed
return UF2_ERR_OK;
if (block->len > (476 - 4 - 4))
// at least one tag + last tag must fit
return UF2_ERR_DATA_TOO_LONG;
uint8_t *tags_start = block->data + block->len;
uint8_t tags_len = 476 - block->len;
uint8_t tags_pos = 0;
if (block->has_md5)
tags_len -= 24;
ctx->binpatch_len = 0; // binpatch applies to one block only
char *part1 = NULL;
char *part2 = NULL;
uf2_tag_type_t type;
while (tags_pos < tags_len) {
uint8_t len = uf2_read_tag(tags_start + tags_pos, &type);
if (!len)
break;
tags_pos += 4; // skip tag header
uint8_t *tag = tags_start + tags_pos;
char **str_dest = NULL; // char* to copy the tag into
switch (type) {
case UF2_TAG_OTA_VERSION:
if (tag[0] != 1)
return UF2_ERR_OTA_VER;
break;
case UF2_TAG_FIRMWARE:
if (info)
str_dest = &(info->fw_name);
break;
case UF2_TAG_VERSION:
if (info)
str_dest = &(info->fw_version);
break;
case UF2_TAG_LT_VERSION:
if (info)
str_dest = &(info->lt_version);
break;
case UF2_TAG_BOARD:
if (info)
str_dest = &(info->board);
break;
case UF2_TAG_HAS_OTA1:
ctx->has_ota1 = tag[0];
break;
case UF2_TAG_HAS_OTA2:
ctx->has_ota2 = tag[0];
break;
case UF2_TAG_PART_1:
str_dest = &(part1);
break;
case UF2_TAG_PART_2:
str_dest = &(part2);
break;
case UF2_TAG_BINPATCH:
ctx->binpatch = tag;
ctx->binpatch_len = len;
break;
default:
break;
}
if (str_dest) {
*str_dest = (char *)zalloc(len + 1);
memcpy(*str_dest, tag, len);
}
// align position to 4 bytes
tags_pos += (((len - 1) / 4) + 1) * 4;
}
if (part1 && part2) {
// update current target partition
uf2_err_t err = uf2_update_parts(ctx, part1, part2);
if (err)
return err;
} else if (part1 || part2) {
// only none or both partitions can be specified
return UF2_ERR_PART_ONE;
}
return UF2_ERR_OK;
}
uint8_t uf2_read_tag(const uint8_t *data, uf2_tag_type_t *type) {
uint8_t len = data[0];
if (!len)
return 0;
uint32_t tag_type = *((uint32_t *)data);
if (!tag_type)
return 0;
*type = tag_type >> 8; // remove tag length byte
return len - 4;
}
uf2_err_t uf2_update_parts(uf2_ota_t *ctx, char *part1, char *part2) {
// reset both target partitions
ctx->part1 = NULL;
ctx->part2 = NULL;
// reset offsets as they probably don't apply to this partition
ctx->erased_offset = 0;
ctx->erased_length = 0;
if (part1[0]) {
ctx->part1 = fal_partition_find(part1);
if (!ctx->part1)
return UF2_ERR_PART_404;
}
if (part2[0]) {
ctx->part2 = fal_partition_find(part2);
if (!ctx->part2)
return UF2_ERR_PART_404;
}
return UF2_ERR_OK;
}
fal_partition_t uf2_get_target_part(uf2_ota_t *ctx) {
if (ctx->ota_idx == 1)
return ctx->part1;
if (ctx->ota_idx == 2)
return ctx->part2;
return NULL;
}
bool uf2_is_erased(uf2_ota_t *ctx, uint32_t offset, uint32_t length) {
uint32_t erased_end = ctx->erased_offset + ctx->erased_length;
uint32_t end = offset + length;
return (offset >= ctx->erased_offset) && (end <= erased_end);
}

View File

@@ -0,0 +1,61 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-28. */
#pragma once
// include family stdlib APIs
#include <WVariant.h>
#include "uf2binpatch.h"
#include "uf2types.h"
/**
* @brief Parse a block and extract information from tags.
*
* @param ctx context
* @param block block to parse
* @param info structure to write firmware info, NULL if not used
* @return uf2_err_t error code
*/
uf2_err_t uf2_parse_block(uf2_ota_t *ctx, uf2_block_t *block, uf2_info_t *info);
/**
* @brief Parse a tag.
*
* @param data pointer to tag header beginning
* @param type [out] parsed tag type
* @return uint8_t parsed tag data length (excl. header); 0 if invalid/last tag
*/
uint8_t uf2_read_tag(const uint8_t *data, uf2_tag_type_t *type);
/**
* @brief Update destination partitions in context.
*
* Partition names cannot be NULL.
*
* Returns UF2_ERR_IGNORE if specified partitions don't match the
* current OTA index.
*
* @param ctx context
* @param part1 partition 1 name or empty string
* @param part2 partition 2 name or empty string
* @return uf2_err_t error code
*/
uf2_err_t uf2_update_parts(uf2_ota_t *ctx, char *part1, char *part2);
/**
* @brief Get target flashing partition, depending on OTA index.
*
* @param ctx context
* @return fal_partition_t target partition or NULL if not set
*/
fal_partition_t uf2_get_target_part(uf2_ota_t *ctx);
/**
* Check if specified flash memory region was already erased during update.
*
* @param ctx context
* @param offset offset to check
* @param length length to check
* @return bool true/false
*/
bool uf2_is_erased(uf2_ota_t *ctx, uint32_t offset, uint32_t length);

View File

@@ -0,0 +1,104 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-28. */
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <fal.h>
#define UF2_MAGIC_1 0x0A324655
#define UF2_MAGIC_2 0x9E5D5157
#define UF2_MAGIC_3 0x0AB16F30
#define UF2_BLOCK_SIZE sizeof(uf2_block_t)
typedef struct __attribute__((packed)) {
// 32 byte header
uint32_t magic1;
uint32_t magic2;
// flags split as bitfields
bool not_main_flash : 1;
uint16_t dummy1 : 11;
bool file_container : 1;
bool has_family_id : 1;
bool has_md5 : 1;
bool has_tags : 1;
uint16_t dummy2 : 16;
uint32_t addr;
uint32_t len;
uint32_t block_seq;
uint32_t block_count;
uint32_t file_size; // or familyID;
uint8_t data[476];
uint32_t magic3;
} uf2_block_t;
typedef struct {
uint32_t seq; // current block sequence number
uint8_t *binpatch; // current block's binpatch (if any) -> pointer inside block->data
uint8_t binpatch_len; // binpatch length
bool has_ota1; // image has any data for OTA1
bool has_ota2; // image has any data for OTA2
uint8_t ota_idx; // target OTA index
uint32_t family_id; // expected family ID
uint32_t erased_offset; // offset of region erased during update
uint32_t erased_length; // length of erased region
fal_partition_t part1; // OTA1 target partition
fal_partition_t part2; // OTA2 target partition
} uf2_ota_t;
typedef struct {
char *fw_name;
char *fw_version;
char *lt_version;
char *board;
} uf2_info_t;
typedef enum {
UF2_TAG_VERSION = 0x9FC7BC, // version of firmware file - UTF8 semver string
UF2_TAG_PAGE_SIZE = 0x0BE9F7, // page size of target device (32 bit unsigned number)
UF2_TAG_SHA2 = 0xB46DB0, // SHA-2 checksum of firmware (can be of various size)
UF2_TAG_DEVICE = 0x650D9D, // description of device (UTF8)
UF2_TAG_DEVICE_ID = 0xC8A729, // device type identifier
// LibreTuya custom, tags
UF2_TAG_OTA_VERSION = 0x5D57D0, // format version
UF2_TAG_BOARD = 0xCA25C8, // board name (lowercase code)
UF2_TAG_FIRMWARE = 0x00DE43, // firmware description / name
UF2_TAG_BUILD_DATE = 0x822F30, // build date/time as Unix timestamp
UF2_TAG_LT_VERSION = 0x59563D, // LT version (semver)
UF2_TAG_PART_1 = 0x805946, // OTA1 partition name
UF2_TAG_PART_2 = 0xA1E4D7, // OTA2 partition name
UF2_TAG_HAS_OTA1 = 0xBBD965, // image has any data for OTA1
UF2_TAG_HAS_OTA2 = 0x92280E, // image has any data for OTA2
UF2_TAG_BINPATCH = 0xB948DE, // binary patch to convert OTA1->OTA2
} uf2_tag_type_t;
typedef enum {
UF2_OPC_DIFF32 = 0xFE,
} uf2_opcode_t;
typedef enum {
UF2_ERR_OK = 0,
UF2_ERR_IGNORE, // block should be ignored
UF2_ERR_MAGIC, // wrong magic numbers
UF2_ERR_FAMILY, // family ID mismatched
UF2_ERR_NOT_HEADER, // block is not a header
UF2_ERR_OTA_VER, // unknown/invalid OTA format version
UF2_ERR_OTA_WRONG, // no data for current OTA index
UF2_ERR_PART_404, // no partition with that name
UF2_ERR_PART_ONE, // only one partition tag in a block
UF2_ERR_PART_UNSET, // image broken - attempted to write without target partition
UF2_ERR_DATA_TOO_LONG, // data too long - tags won't fit
UF2_ERR_SEQ_MISMATCH, // sequence number mismatched
UF2_ERR_ERASE_FAILED, // erasing flash failed
UF2_ERR_WRITE_FAILED, // writing to flash failed
UF2_ERR_WRITE_LENGTH, // wrote fewer data than requested
} uf2_err_t;

View File

@@ -0,0 +1,56 @@
#pragma once
/* Request Methods */
#define HTTP_METHOD_MAP(XX) \
XX(0, DELETE, DELETE) \
XX(1, GET, GET) \
XX(2, HEAD, HEAD) \
XX(3, POST, POST) \
XX(4, PUT, PUT) \
/* pathological */ \
XX(5, CONNECT, CONNECT) \
XX(6, OPTIONS, OPTIONS) \
XX(7, TRACE, TRACE) \
/* WebDAV */ \
XX(8, COPY, COPY) \
XX(9, LOCK, LOCK) \
XX(10, MKCOL, MKCOL) \
XX(11, MOVE, MOVE) \
XX(12, PROPFIND, PROPFIND) \
XX(13, PROPPATCH, PROPPATCH) \
XX(14, SEARCH, SEARCH) \
XX(15, UNLOCK, UNLOCK) \
XX(16, BIND, BIND) \
XX(17, REBIND, REBIND) \
XX(18, UNBIND, UNBIND) \
XX(19, ACL, ACL) \
/* subversion */ \
XX(20, REPORT, REPORT) \
XX(21, MKACTIVITY, MKACTIVITY) \
XX(22, CHECKOUT, CHECKOUT) \
XX(23, MERGE, MERGE) \
/* upnp */ \
XX(24, MSEARCH, M - SEARCH) \
XX(25, NOTIFY, NOTIFY) \
XX(26, SUBSCRIBE, SUBSCRIBE) \
XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
/* RFC-5789 */ \
XX(28, PATCH, PATCH) \
XX(29, PURGE, PURGE) \
/* CalDAV */ \
XX(30, MKCALENDAR, MKCALENDAR) \
/* RFC-2068, section 19.6.1.2 */ \
XX(31, LINK, LINK) \
XX(32, UNLINK, UNLINK) \
/* icecast */ \
XX(33, SOURCE, SOURCE)
enum http_method {
#define XX(num, name, string) HTTP_##name = num,
HTTP_METHOD_MAP(XX)
#undef XX
};
typedef enum http_method HTTPMethod;
#define HTTP_ANY (HTTPMethod)(255)

View File

@@ -0,0 +1,605 @@
/*
Parsing.cpp - HTTP request parsing.
Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/
#include <Arduino.h>
#include "WebServer.h"
#include "WiFiClient.h"
#include "WiFiServer.h"
#include "detail/mimetable.h"
#ifndef WEBSERVER_MAX_POST_ARGS
#define WEBSERVER_MAX_POST_ARGS 32
#endif
#define __STR(a) #a
#define _STR(a) __STR(a)
const char *_http_method_str[] = {
#define XX(num, name, string) _STR(name),
HTTP_METHOD_MAP(XX)
#undef XX
};
static const char Content_Type[] PROGMEM = "Content-Type";
static const char filename[] PROGMEM = "filename";
static char *readBytesWithTimeout(WiFiClient &client, size_t maxLength, size_t &dataLength, int timeout_ms) {
char *buf = nullptr;
dataLength = 0;
while (dataLength < maxLength) {
int tries = timeout_ms;
size_t newLength;
while (!(newLength = client.available()) && tries--)
delay(1);
if (!newLength) {
break;
}
if (!buf) {
buf = (char *)malloc(newLength + 1);
if (!buf) {
return nullptr;
}
} else {
char *newBuf = (char *)realloc(buf, dataLength + newLength + 1);
if (!newBuf) {
free(buf);
return nullptr;
}
buf = newBuf;
}
client.readBytes(buf + dataLength, newLength);
dataLength += newLength;
buf[dataLength] = '\0';
}
return buf;
}
bool WebServer::_parseRequest(WiFiClient &client) {
// Read the first line of HTTP request
String req = client.readStringUntil('\r');
client.readStringUntil('\n');
// reset header value
for (int i = 0; i < _headerKeysCount; ++i) {
_currentHeaders[i].value = String();
}
// First line of HTTP request looks like "GET /path HTTP/1.1"
// Retrieve the "/path" part by finding the spaces
int addr_start = req.indexOf(' ');
int addr_end = req.indexOf(' ', addr_start + 1);
if (addr_start == -1 || addr_end == -1) {
log_e("Invalid request: %s", req.c_str());
return false;
}
String methodStr = req.substring(0, addr_start);
String url = req.substring(addr_start + 1, addr_end);
String versionEnd = req.substring(addr_end + 8);
_currentVersion = atoi(versionEnd.c_str());
String searchStr = "";
int hasSearch = url.indexOf('?');
if (hasSearch != -1) {
searchStr = url.substring(hasSearch + 1);
url = url.substring(0, hasSearch);
}
_currentUri = url;
_chunked = false;
HTTPMethod method = HTTP_ANY;
size_t num_methods = sizeof(_http_method_str) / sizeof(const char *);
for (size_t i = 0; i < num_methods; i++) {
if (methodStr == _http_method_str[i]) {
method = (HTTPMethod)i;
break;
}
}
if (method == HTTP_ANY) {
log_e("Unknown HTTP Method: %s", methodStr.c_str());
return false;
}
_currentMethod = method;
log_v("method: %s url: %s search: %s", methodStr.c_str(), url.c_str(), searchStr.c_str());
// attach handler
RequestHandler *handler;
for (handler = _firstHandler; handler; handler = handler->next()) {
if (handler->canHandle(_currentMethod, _currentUri))
break;
}
_currentHandler = handler;
String formData;
// below is needed only when POST type request
if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE) {
String boundaryStr;
String headerName;
String headerValue;
bool isForm = false;
bool isEncoded = false;
uint32_t contentLength = 0;
// parse headers
while (1) {
req = client.readStringUntil('\r');
client.readStringUntil('\n');
if (req == "")
break; // no moar headers
int headerDiv = req.indexOf(':');
if (headerDiv == -1) {
break;
}
headerName = req.substring(0, headerDiv);
headerValue = req.substring(headerDiv + 1);
headerValue.trim();
_collectHeader(headerName.c_str(), headerValue.c_str());
log_v("headerName: %s", headerName.c_str());
log_v("headerValue: %s", headerValue.c_str());
if (headerName.equalsIgnoreCase(FPSTR(Content_Type))) {
using namespace mime;
if (headerValue.startsWith(FPSTR(mimeTable[txt].mimeType))) {
isForm = false;
} else if (headerValue.startsWith(F("application/x-www-form-urlencoded"))) {
isForm = false;
isEncoded = true;
} else if (headerValue.startsWith(F("multipart/"))) {
boundaryStr = headerValue.substring(headerValue.indexOf('=') + 1);
boundaryStr.replace("\"", "");
isForm = true;
}
} else if (headerName.equalsIgnoreCase(F("Content-Length"))) {
contentLength = headerValue.toInt();
} else if (headerName.equalsIgnoreCase(F("Host"))) {
_hostHeader = headerValue;
}
}
if (!isForm) {
size_t plainLength;
char *plainBuf = readBytesWithTimeout(client, contentLength, plainLength, HTTP_MAX_POST_WAIT);
if (plainLength < contentLength) {
free(plainBuf);
return false;
}
if (contentLength > 0) {
if (isEncoded) {
// url encoded form
if (searchStr != "")
searchStr += '&';
searchStr += plainBuf;
}
_parseArguments(searchStr);
if (!isEncoded) {
// plain post json or other data
RequestArgument &arg = _currentArgs[_currentArgCount++];
arg.key = F("plain");
arg.value = String(plainBuf);
}
log_v("Plain: %s", plainBuf);
free(plainBuf);
} else {
// No content - but we can still have arguments in the URL.
_parseArguments(searchStr);
}
}
if (isForm) {
_parseArguments(searchStr);
if (!_parseForm(client, boundaryStr, contentLength)) {
return false;
}
}
} else {
String headerName;
String headerValue;
// parse headers
while (1) {
req = client.readStringUntil('\r');
client.readStringUntil('\n');
if (req == "")
break; // no moar headers
int headerDiv = req.indexOf(':');
if (headerDiv == -1) {
break;
}
headerName = req.substring(0, headerDiv);
headerValue = req.substring(headerDiv + 2);
_collectHeader(headerName.c_str(), headerValue.c_str());
log_v("headerName: %s", headerName.c_str());
log_v("headerValue: %s", headerValue.c_str());
if (headerName.equalsIgnoreCase("Host")) {
_hostHeader = headerValue;
}
}
_parseArguments(searchStr);
}
client.flush();
log_v("Request: %s", url.c_str());
log_v(" Arguments: %s", searchStr.c_str());
return true;
}
bool WebServer::_collectHeader(const char *headerName, const char *headerValue) {
for (int i = 0; i < _headerKeysCount; i++) {
if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
_currentHeaders[i].value = headerValue;
return true;
}
}
return false;
}
void WebServer::_parseArguments(String data) {
log_v("args: %s", data.c_str());
if (_currentArgs)
delete[] _currentArgs;
_currentArgs = 0;
if (data.length() == 0) {
_currentArgCount = 0;
_currentArgs = new RequestArgument[1];
return;
}
_currentArgCount = 1;
for (int i = 0; i < (int)data.length();) {
i = data.indexOf('&', i);
if (i == -1)
break;
++i;
++_currentArgCount;
}
log_v("args count: %d", _currentArgCount);
_currentArgs = new RequestArgument[_currentArgCount + 1];
int pos = 0;
int iarg;
for (iarg = 0; iarg < _currentArgCount;) {
int equal_sign_index = data.indexOf('=', pos);
int next_arg_index = data.indexOf('&', pos);
log_v("pos %d =@%d &@%d", pos, equal_sign_index, next_arg_index);
if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) {
log_e("arg missing value: %d", iarg);
if (next_arg_index == -1)
break;
pos = next_arg_index + 1;
continue;
}
RequestArgument &arg = _currentArgs[iarg];
arg.key = urlDecode(data.substring(pos, equal_sign_index));
arg.value = urlDecode(data.substring(equal_sign_index + 1, next_arg_index));
log_v("arg %d key: %s value: %s", iarg, arg.key.c_str(), arg.value.c_str());
++iarg;
if (next_arg_index == -1)
break;
pos = next_arg_index + 1;
}
_currentArgCount = iarg;
log_v("args count: %d", _currentArgCount);
}
void WebServer::_uploadWriteByte(uint8_t b) {
if (_currentUpload->currentSize == HTTP_UPLOAD_BUFLEN) {
if (_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, *_currentUpload);
_currentUpload->totalSize += _currentUpload->currentSize;
_currentUpload->currentSize = 0;
}
_currentUpload->buf[_currentUpload->currentSize++] = b;
}
int WebServer::_uploadReadByte(WiFiClient &client) {
int res = client.read();
if (res < 0) {
// keep trying until you either read a valid byte or timeout
unsigned long startMillis = millis();
long timeoutIntervalMillis = client.getTimeout();
boolean timedOut = false;
for (;;) {
if (!client.connected())
return -1;
// loosely modeled after blinkWithoutDelay pattern
while (!timedOut && !client.available() && client.connected()) {
delay(2);
timedOut = millis() - startMillis >= timeoutIntervalMillis;
}
res = client.read();
if (res >= 0) {
return res; // exit on a valid read
}
// NOTE: it is possible to get here and have all of the following
// assertions hold true
//
// -- client.available() > 0
// -- client.connected == true
// -- res == -1
//
// a simple retry strategy overcomes this which is to say the
// assertion is not permanent, but the reason that this works
// is elusive, and possibly indicative of a more subtle underlying
// issue
timedOut = millis() - startMillis >= timeoutIntervalMillis;
if (timedOut) {
return res; // exit on a timeout
}
}
}
return res;
}
bool WebServer::_parseForm(WiFiClient &client, String boundary, uint32_t len) {
(void)len;
log_v("Parse Form: Boundary: %s Length: %d", boundary.c_str(), len);
String line;
int retry = 0;
do {
line = client.readStringUntil('\r');
++retry;
} while (line.length() == 0 && retry < 3);
client.readStringUntil('\n');
// start reading the form
if (line == ("--" + boundary)) {
if (_postArgs)
delete[] _postArgs;
_postArgs = new RequestArgument[WEBSERVER_MAX_POST_ARGS];
_postArgsLen = 0;
while (1) {
String argName;
String argValue;
String argType;
String argFilename;
bool argIsFile = false;
line = client.readStringUntil('\r');
client.readStringUntil('\n');
if (line.length() > 19 && line.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))) {
int nameStart = line.indexOf('=');
if (nameStart != -1) {
argName = line.substring(nameStart + 2);
nameStart = argName.indexOf('=');
if (nameStart == -1) {
argName = argName.substring(0, argName.length() - 1);
} else {
argFilename = argName.substring(nameStart + 2, argName.length() - 1);
argName = argName.substring(0, argName.indexOf('"'));
argIsFile = true;
log_v("PostArg FileName: %s", argFilename.c_str());
// use GET to set the filename if uploading using blob
if (argFilename == F("blob") && hasArg(FPSTR(filename)))
argFilename = arg(FPSTR(filename));
}
log_v("PostArg Name: %s", argName.c_str());
using namespace mime;
argType = FPSTR(mimeTable[txt].mimeType);
line = client.readStringUntil('\r');
client.readStringUntil('\n');
if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase(FPSTR(Content_Type))) {
argType = line.substring(line.indexOf(':') + 2);
// skip next line
client.readStringUntil('\r');
client.readStringUntil('\n');
}
log_v("PostArg Type: %s", argType.c_str());
if (!argIsFile) {
while (1) {
line = client.readStringUntil('\r');
client.readStringUntil('\n');
if (line.startsWith("--" + boundary))
break;
if (argValue.length() > 0)
argValue += "\n";
argValue += line;
}
log_v("PostArg Value: %s", argValue.c_str());
RequestArgument &arg = _postArgs[_postArgsLen++];
arg.key = argName;
arg.value = argValue;
if (line == ("--" + boundary + "--")) {
log_v("Done Parsing POST");
break;
} else if (_postArgsLen >= WEBSERVER_MAX_POST_ARGS) {
log_e("Too many PostArgs (max: %d) in request.", WEBSERVER_MAX_POST_ARGS);
return false;
}
} else {
_currentUpload.reset(new HTTPUpload());
_currentUpload->status = UPLOAD_FILE_START;
_currentUpload->name = argName;
_currentUpload->filename = argFilename;
_currentUpload->type = argType;
_currentUpload->totalSize = 0;
_currentUpload->currentSize = 0;
log_v(
"Start File: %s Type: %s",
_currentUpload->filename.c_str(),
_currentUpload->type.c_str()
);
if (_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, *_currentUpload);
_currentUpload->status = UPLOAD_FILE_WRITE;
int argByte = _uploadReadByte(client);
readfile:
while (argByte != 0x0D) {
if (argByte < 0)
return _parseFormUploadAborted();
_uploadWriteByte(argByte);
argByte = _uploadReadByte(client);
}
argByte = _uploadReadByte(client);
if (argByte < 0)
return _parseFormUploadAborted();
if (argByte == 0x0A) {
argByte = _uploadReadByte(client);
if (argByte < 0)
return _parseFormUploadAborted();
if ((char)argByte != '-') {
// continue reading the file
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
goto readfile;
} else {
argByte = _uploadReadByte(client);
if (argByte < 0)
return _parseFormUploadAborted();
if ((char)argByte != '-') {
// continue reading the file
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
_uploadWriteByte((uint8_t)('-'));
goto readfile;
}
}
uint8_t endBuf[boundary.length()];
uint32_t i = 0;
while (i < boundary.length()) {
argByte = _uploadReadByte(client);
if (argByte < 0)
return _parseFormUploadAborted();
if ((char)argByte == 0x0D) {
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
_uploadWriteByte((uint8_t)('-'));
_uploadWriteByte((uint8_t)('-'));
uint32_t j = 0;
while (j < i) {
_uploadWriteByte(endBuf[j++]);
}
goto readfile;
}
endBuf[i++] = (uint8_t)argByte;
}
if (strstr((const char *)endBuf, boundary.c_str()) != NULL) {
if (_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, *_currentUpload);
_currentUpload->totalSize += _currentUpload->currentSize;
_currentUpload->status = UPLOAD_FILE_END;
if (_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, *_currentUpload);
log_v(
"End File: %s Type: %s Size: %d",
_currentUpload->filename.c_str(),
_currentUpload->type.c_str(),
_currentUpload->totalSize
);
line = client.readStringUntil(0x0D);
client.readStringUntil(0x0A);
if (line == "--") {
log_v("Done Parsing POST");
break;
}
continue;
} else {
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
_uploadWriteByte((uint8_t)('-'));
_uploadWriteByte((uint8_t)('-'));
uint32_t i = 0;
while (i < boundary.length()) {
_uploadWriteByte(endBuf[i++]);
}
argByte = _uploadReadByte(client);
goto readfile;
}
} else {
_uploadWriteByte(0x0D);
goto readfile;
}
break;
}
}
}
}
int iarg;
int totalArgs = ((WEBSERVER_MAX_POST_ARGS - _postArgsLen) < _currentArgCount)
? (WEBSERVER_MAX_POST_ARGS - _postArgsLen)
: _currentArgCount;
for (iarg = 0; iarg < totalArgs; iarg++) {
RequestArgument &arg = _postArgs[_postArgsLen++];
arg.key = _currentArgs[iarg].key;
arg.value = _currentArgs[iarg].value;
}
if (_currentArgs)
delete[] _currentArgs;
_currentArgs = new RequestArgument[_postArgsLen];
for (iarg = 0; iarg < _postArgsLen; iarg++) {
RequestArgument &arg = _currentArgs[iarg];
arg.key = _postArgs[iarg].key;
arg.value = _postArgs[iarg].value;
}
_currentArgCount = iarg;
if (_postArgs) {
delete[] _postArgs;
_postArgs = nullptr;
_postArgsLen = 0;
}
return true;
}
log_e("Error: line: %s", line.c_str());
return false;
}
String WebServer::urlDecode(const String &text) {
String decoded = "";
char temp[] = "0x00";
unsigned int len = text.length();
unsigned int i = 0;
while (i < len) {
char decodedChar;
char encodedChar = text.charAt(i++);
if ((encodedChar == '%') && (i + 1 < len)) {
temp[2] = text.charAt(i++);
temp[3] = text.charAt(i++);
decodedChar = strtol(temp, NULL, 16);
} else {
if (encodedChar == '+') {
decodedChar = ' ';
} else {
decodedChar = encodedChar; // normal ascii char
}
}
decoded += decodedChar;
}
return decoded;
}
bool WebServer::_parseFormUploadAborted() {
_currentUpload->status = UPLOAD_FILE_ABORTED;
if (_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, *_currentUpload);
return false;
}

View File

@@ -0,0 +1,29 @@
#pragma once
#include <Arduino.h>
#include <vector>
class Uri {
protected:
const String _uri;
public:
Uri(const char *uri) : _uri(uri) {}
Uri(const String &uri) : _uri(uri) {}
Uri(const __FlashStringHelper *uri) : _uri(String(uri)) {}
virtual ~Uri() {}
virtual Uri *clone() const {
return new Uri(_uri);
};
virtual void initPathArgs(__attribute__((unused)) std::vector<String> &pathArgs) {}
virtual bool canHandle(const String &requestUri, __attribute__((unused)) std::vector<String> &pathArgs) {
return _uri == requestUri;
}
};

View File

@@ -0,0 +1,717 @@
/*
WebServer.cpp - Dead simple web-server.
Supports only one simultaneous client, knows how to handle GET and POST.
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/
#include <Arduino.h>
#include "FS.h"
#include "WebServer.h"
#include "WiFiClient.h"
#include "WiFiServer.h"
#include "detail/RequestHandlersImpl.h"
// #include "mbedtls/md5.h"
#include <libb64/cencode.h>
static const char AUTHORIZATION_HEADER[] = "Authorization";
static const char qop_auth[] PROGMEM = "qop=auth";
static const char qop_auth_quoted[] PROGMEM = "qop=\"auth\"";
static const char WWW_Authenticate[] = "WWW-Authenticate";
static const char Content_Length[] = "Content-Length";
WebServer::WebServer(IPAddress addr, int port)
: _corsEnabled(false), _server(addr, port), _currentMethod(HTTP_ANY), _currentVersion(0), _currentStatus(HC_NONE),
_statusChange(0), _nullDelay(true), _currentHandler(nullptr), _firstHandler(nullptr), _lastHandler(nullptr),
_currentArgCount(0), _currentArgs(nullptr), _postArgsLen(0), _postArgs(nullptr), _headerKeysCount(0),
_currentHeaders(nullptr), _contentLength(0), _chunked(false) {
log_v("WebServer::Webserver(addr=%s, port=%d)", addr.toString().c_str(), port);
}
WebServer::WebServer(int port)
: _corsEnabled(false), _server(port), _currentMethod(HTTP_ANY), _currentVersion(0), _currentStatus(HC_NONE),
_statusChange(0), _nullDelay(true), _currentHandler(nullptr), _firstHandler(nullptr), _lastHandler(nullptr),
_currentArgCount(0), _currentArgs(nullptr), _postArgsLen(0), _postArgs(nullptr), _headerKeysCount(0),
_currentHeaders(nullptr), _contentLength(0), _chunked(false) {
log_v("WebServer::Webserver(port=%d)", port);
}
WebServer::~WebServer() {
_server.close();
if (_currentHeaders)
delete[] _currentHeaders;
RequestHandler *handler = _firstHandler;
while (handler) {
RequestHandler *next = handler->next();
delete handler;
handler = next;
}
}
void WebServer::begin() {
close();
_server.begin();
_server.setNoDelay(true);
}
void WebServer::begin(uint16_t port) {
close();
_server.begin(port);
_server.setNoDelay(true);
}
String WebServer::_extractParam(String &authReq, const String &param, const char delimit) {
int _begin = authReq.indexOf(param);
if (_begin == -1)
return "";
return authReq.substring(_begin + param.length(), authReq.indexOf(delimit, _begin + param.length()));
}
static String md5str(String &in) {
/* char out[33] = {0};
mbedtls_md5_context _ctx;
uint8_t i;
uint8_t *_buf = (uint8_t *)malloc(16);
if (_buf == NULL)
return String(out);
memset(_buf, 0x00, 16);
mbedtls_md5_init(&_ctx);
mbedtls_md5_starts_ret(&_ctx);
mbedtls_md5_update_ret(&_ctx, (const uint8_t *)in.c_str(), in.length());
mbedtls_md5_finish_ret(&_ctx, _buf);
for (i = 0; i < 16; i++) {
sprintf(out + (i * 2), "%02x", _buf[i]);
}
out[32] = 0;
free(_buf);
return String(out); */
return "";
}
bool WebServer::authenticate(const char *username, const char *password) {
if (hasHeader(FPSTR(AUTHORIZATION_HEADER))) {
String authReq = header(FPSTR(AUTHORIZATION_HEADER));
if (authReq.startsWith(F("Basic"))) {
authReq = authReq.substring(6);
authReq.trim();
char toencodeLen = strlen(username) + strlen(password) + 1;
char *toencode = new char[toencodeLen + 1];
if (toencode == NULL) {
authReq = "";
return false;
}
char *encoded = new char[base64_encode_expected_len(toencodeLen) + 1];
if (encoded == NULL) {
authReq = "";
delete[] toencode;
return false;
}
sprintf(toencode, "%s:%s", username, password);
if (base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equals(encoded)) {
authReq = "";
delete[] toencode;
delete[] encoded;
return true;
}
delete[] toencode;
delete[] encoded;
} else if (authReq.startsWith(F("Digest"))) {
authReq = authReq.substring(7);
log_v("%s", authReq.c_str());
String _username = _extractParam(authReq, F("username=\""), '\"');
if (!_username.length() || _username != String(username)) {
authReq = "";
return false;
}
// extracting required parameters for RFC 2069 simpler Digest
String _realm = _extractParam(authReq, F("realm=\""), '\"');
String _nonce = _extractParam(authReq, F("nonce=\""), '\"');
String _uri = _extractParam(authReq, F("uri=\""), '\"');
String _response = _extractParam(authReq, F("response=\""), '\"');
String _opaque = _extractParam(authReq, F("opaque=\""), '\"');
if ((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_response.length()) ||
(!_opaque.length())) {
authReq = "";
return false;
}
if ((_opaque != _sopaque) || (_nonce != _snonce) || (_realm != _srealm)) {
authReq = "";
return false;
}
// parameters for the RFC 2617 newer Digest
String _nc, _cnonce;
if (authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) {
_nc = _extractParam(authReq, F("nc="), ',');
_cnonce = _extractParam(authReq, F("cnonce=\""), '\"');
}
String _H1 = md5str(String(username) + ':' + _realm + ':' + String(password));
log_v("Hash of user:realm:pass=%s", _H1.c_str());
String _H2 = "";
if (_currentMethod == HTTP_GET) {
_H2 = md5str(String(F("GET:")) + _uri);
} else if (_currentMethod == HTTP_POST) {
_H2 = md5str(String(F("POST:")) + _uri);
} else if (_currentMethod == HTTP_PUT) {
_H2 = md5str(String(F("PUT:")) + _uri);
} else if (_currentMethod == HTTP_DELETE) {
_H2 = md5str(String(F("DELETE:")) + _uri);
} else {
_H2 = md5str(String(F("GET:")) + _uri);
}
log_v("Hash of GET:uri=%s", _H2.c_str());
String _responsecheck = "";
if (authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) {
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2);
} else {
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2);
}
log_v("The Proper response=%s", _responsecheck.c_str());
if (_response == _responsecheck) {
authReq = "";
return true;
}
}
authReq = "";
}
return false;
}
String WebServer::_getRandomHexString() {
char buffer[33]; // buffer to hold 32 Hex Digit + /0
int i;
for (i = 0; i < 4; i++) {
sprintf(buffer + (i * 8), "%08x", rand());
}
return String(buffer);
}
void WebServer::requestAuthentication(HTTPAuthMethod mode, const char *realm, const String &authFailMsg) {
if (realm == NULL) {
_srealm = String(F("Login Required"));
} else {
_srealm = String(realm);
}
if (mode == BASIC_AUTH) {
sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Basic realm=\"")) + _srealm + String(F("\"")));
} else {
_snonce = _getRandomHexString();
_sopaque = _getRandomHexString();
sendHeader(
String(FPSTR(WWW_Authenticate)),
String(F("Digest realm=\"")) + _srealm + String(F("\", qop=\"auth\", nonce=\"")) + _snonce +
String(F("\", opaque=\"")) + _sopaque + String(F("\""))
);
}
using namespace mime;
send(401, String(FPSTR(mimeTable[html].mimeType)), authFailMsg);
}
void WebServer::on(const Uri &uri, WebServer::THandlerFunction handler) {
on(uri, HTTP_ANY, handler);
}
void WebServer::on(const Uri &uri, HTTPMethod method, WebServer::THandlerFunction fn) {
on(uri, method, fn, _fileUploadHandler);
}
void WebServer::on(const Uri &uri, HTTPMethod method, WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn) {
_addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method));
}
void WebServer::addHandler(RequestHandler *handler) {
_addRequestHandler(handler);
}
void WebServer::_addRequestHandler(RequestHandler *handler) {
if (!_lastHandler) {
_firstHandler = handler;
_lastHandler = handler;
} else {
_lastHandler->next(handler);
_lastHandler = handler;
}
}
void WebServer::serveStatic(const char *uri, FS &fs, const char *path, const char *cache_header) {
_addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header));
}
void WebServer::handleClient() {
if (_currentStatus == HC_NONE) {
WiFiClient client = _server.available();
if (!client) {
if (_nullDelay) {
delay(1);
}
return;
}
log_v("New client: client.localIP()=%s", client.localIP().toString().c_str());
_currentClient = client;
_currentStatus = HC_WAIT_READ;
_statusChange = millis();
}
bool keepCurrentClient = false;
bool callYield = false;
if (_currentClient.connected()) {
switch (_currentStatus) {
case HC_NONE:
// No-op to avoid C++ compiler warning
break;
case HC_WAIT_READ:
// Wait for data from client to become available
if (_currentClient.available()) {
if (_parseRequest(_currentClient)) {
// because HTTP_MAX_SEND_WAIT is expressed in milliseconds,
// it must be divided by 1000
_currentClient.setTimeout(HTTP_MAX_SEND_WAIT / 1000);
_contentLength = CONTENT_LENGTH_NOT_SET;
_handleRequest();
// Fix for issue with Chrome based browsers:
// https://github.com/espressif/arduino-esp32/issues/3652
// if (_currentClient.connected()) {
// _currentStatus = HC_WAIT_CLOSE;
// _statusChange = millis();
// keepCurrentClient = true;
// }
}
} else { // !_currentClient.available()
if (millis() - _statusChange <= HTTP_MAX_DATA_WAIT) {
keepCurrentClient = true;
}
callYield = true;
}
break;
case HC_WAIT_CLOSE:
// Wait for client to close the connection
if (millis() - _statusChange <= HTTP_MAX_CLOSE_WAIT) {
keepCurrentClient = true;
callYield = true;
}
}
}
if (!keepCurrentClient) {
_currentClient = WiFiClient();
_currentStatus = HC_NONE;
_currentUpload.reset();
}
if (callYield) {
yield();
}
}
void WebServer::close() {
_server.close();
_currentStatus = HC_NONE;
if (!_headerKeysCount)
collectHeaders(0, 0);
}
void WebServer::stop() {
close();
}
void WebServer::sendHeader(const String &name, const String &value, bool first) {
String headerLine = name;
headerLine += F(": ");
headerLine += value;
headerLine += "\r\n";
if (first) {
_responseHeaders = headerLine + _responseHeaders;
} else {
_responseHeaders += headerLine;
}
}
void WebServer::setContentLength(const size_t contentLength) {
_contentLength = contentLength;
}
void WebServer::enableDelay(boolean value) {
_nullDelay = value;
}
void WebServer::enableCORS(boolean value) {
_corsEnabled = value;
}
void WebServer::enableCrossOrigin(boolean value) {
enableCORS(value);
}
void WebServer::_prepareHeader(String &response, int code, const char *content_type, size_t contentLength) {
response = String(F("HTTP/1.")) + String(_currentVersion) + ' ';
response += String(code);
response += ' ';
response += _responseCodeToString(code);
response += "\r\n";
using namespace mime;
if (!content_type)
content_type = mimeTable[html].mimeType;
sendHeader(String(F("Content-Type")), String(FPSTR(content_type)), true);
if (_contentLength == CONTENT_LENGTH_NOT_SET) {
sendHeader(String(FPSTR(Content_Length)), String(contentLength));
} else if (_contentLength != CONTENT_LENGTH_UNKNOWN) {
sendHeader(String(FPSTR(Content_Length)), String(_contentLength));
} else if (_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion) { // HTTP/1.1 or above client
// let's do chunked
_chunked = true;
sendHeader(String(F("Accept-Ranges")), String(F("none")));
sendHeader(String(F("Transfer-Encoding")), String(F("chunked")));
}
if (_corsEnabled) {
sendHeader(String(FPSTR("Access-Control-Allow-Origin")), String("*"));
sendHeader(String(FPSTR("Access-Control-Allow-Methods")), String("*"));
sendHeader(String(FPSTR("Access-Control-Allow-Headers")), String("*"));
}
sendHeader(String(F("Connection")), String(F("close")));
response += _responseHeaders;
response += "\r\n";
_responseHeaders = "";
}
void WebServer::send(int code, const char *content_type, const String &content) {
String header;
// Can we asume the following?
// if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET)
// _contentLength = CONTENT_LENGTH_UNKNOWN;
_prepareHeader(header, code, content_type, content.length());
_currentClientWrite(header.c_str(), header.length());
if (content.length())
sendContent(content);
}
void WebServer::send_P(int code, PGM_P content_type, PGM_P content) {
size_t contentLength = 0;
if (content != NULL) {
contentLength = strlen_P(content);
}
String header;
char type[64];
strncpy_P(type, (PGM_P)content_type, sizeof(type));
_prepareHeader(header, code, (const char *)type, contentLength);
_currentClientWrite(header.c_str(), header.length());
sendContent_P(content);
}
void WebServer::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) {
String header;
char type[64];
strncpy_P(type, (PGM_P)content_type, sizeof(type));
_prepareHeader(header, code, (const char *)type, contentLength);
sendContent(header);
sendContent_P(content, contentLength);
}
void WebServer::send(int code, char *content_type, const String &content) {
send(code, (const char *)content_type, content);
}
void WebServer::send(int code, const String &content_type, const String &content) {
send(code, (const char *)content_type.c_str(), content);
}
void WebServer::sendContent(const String &content) {
sendContent(content.c_str(), content.length());
}
void WebServer::sendContent(const char *content, size_t contentLength) {
const char *footer = "\r\n";
if (_chunked) {
char *chunkSize = (char *)malloc(11);
if (chunkSize) {
sprintf(chunkSize, "%x%s", contentLength, footer);
_currentClientWrite(chunkSize, strlen(chunkSize));
free(chunkSize);
}
}
_currentClientWrite(content, contentLength);
if (_chunked) {
_currentClient.write(footer, 2);
if (contentLength == 0) {
_chunked = false;
}
}
}
void WebServer::sendContent_P(PGM_P content) {
sendContent_P(content, strlen_P(content));
}
void WebServer::sendContent_P(PGM_P content, size_t size) {
const char *footer = "\r\n";
if (_chunked) {
char *chunkSize = (char *)malloc(11);
if (chunkSize) {
sprintf(chunkSize, "%x%s", size, footer);
_currentClientWrite(chunkSize, strlen(chunkSize));
free(chunkSize);
}
}
_currentClientWrite_P(content, size);
if (_chunked) {
_currentClient.write(footer, 2);
if (size == 0) {
_chunked = false;
}
}
}
void WebServer::_streamFileCore(const size_t fileSize, const String &fileName, const String &contentType) {
using namespace mime;
setContentLength(fileSize);
if (fileName.endsWith(String(FPSTR(mimeTable[gz].endsWith))) &&
contentType != String(FPSTR(mimeTable[gz].mimeType)) &&
contentType != String(FPSTR(mimeTable[none].mimeType))) {
sendHeader(F("Content-Encoding"), F("gzip"));
}
send(200, contentType, "");
}
String WebServer::pathArg(unsigned int i) {
if (_currentHandler != nullptr)
return _currentHandler->pathArg(i);
return "";
}
String WebServer::arg(String name) {
for (int j = 0; j < _postArgsLen; ++j) {
if (_postArgs[j].key == name)
return _postArgs[j].value;
}
for (int i = 0; i < _currentArgCount; ++i) {
if (_currentArgs[i].key == name)
return _currentArgs[i].value;
}
return "";
}
String WebServer::arg(int i) {
if (i < _currentArgCount)
return _currentArgs[i].value;
return "";
}
String WebServer::argName(int i) {
if (i < _currentArgCount)
return _currentArgs[i].key;
return "";
}
int WebServer::args() {
return _currentArgCount;
}
bool WebServer::hasArg(String name) {
for (int j = 0; j < _postArgsLen; ++j) {
if (_postArgs[j].key == name)
return true;
}
for (int i = 0; i < _currentArgCount; ++i) {
if (_currentArgs[i].key == name)
return true;
}
return false;
}
String WebServer::header(String name) {
for (int i = 0; i < _headerKeysCount; ++i) {
if (_currentHeaders[i].key.equalsIgnoreCase(name))
return _currentHeaders[i].value;
}
return "";
}
void WebServer::collectHeaders(const char *headerKeys[], const size_t headerKeysCount) {
_headerKeysCount = headerKeysCount + 1;
if (_currentHeaders)
delete[] _currentHeaders;
_currentHeaders = new RequestArgument[_headerKeysCount];
_currentHeaders[0].key = FPSTR(AUTHORIZATION_HEADER);
for (int i = 1; i < _headerKeysCount; i++) {
_currentHeaders[i].key = headerKeys[i - 1];
}
}
String WebServer::header(int i) {
if (i < _headerKeysCount)
return _currentHeaders[i].value;
return "";
}
String WebServer::headerName(int i) {
if (i < _headerKeysCount)
return _currentHeaders[i].key;
return "";
}
int WebServer::headers() {
return _headerKeysCount;
}
bool WebServer::hasHeader(String name) {
for (int i = 0; i < _headerKeysCount; ++i) {
if ((_currentHeaders[i].key.equalsIgnoreCase(name)) && (_currentHeaders[i].value.length() > 0))
return true;
}
return false;
}
String WebServer::hostHeader() {
return _hostHeader;
}
void WebServer::onFileUpload(THandlerFunction fn) {
_fileUploadHandler = fn;
}
void WebServer::onNotFound(THandlerFunction fn) {
_notFoundHandler = fn;
}
void WebServer::_handleRequest() {
bool handled = false;
if (!_currentHandler) {
log_e("request handler not found");
} else {
handled = _currentHandler->handle(*this, _currentMethod, _currentUri);
if (!handled) {
log_e("request handler failed to handle request");
}
}
if (!handled && _notFoundHandler) {
_notFoundHandler();
handled = true;
}
if (!handled) {
using namespace mime;
send(404, String(FPSTR(mimeTable[html].mimeType)), String(F("Not found: ")) + _currentUri);
handled = true;
}
if (handled) {
_finalizeResponse();
}
_currentUri = "";
}
void WebServer::_finalizeResponse() {
if (_chunked) {
sendContent("");
}
}
String WebServer::_responseCodeToString(int code) {
switch (code) {
case 100:
return F("Continue");
case 101:
return F("Switching Protocols");
case 200:
return F("OK");
case 201:
return F("Created");
case 202:
return F("Accepted");
case 203:
return F("Non-Authoritative Information");
case 204:
return F("No Content");
case 205:
return F("Reset Content");
case 206:
return F("Partial Content");
case 300:
return F("Multiple Choices");
case 301:
return F("Moved Permanently");
case 302:
return F("Found");
case 303:
return F("See Other");
case 304:
return F("Not Modified");
case 305:
return F("Use Proxy");
case 307:
return F("Temporary Redirect");
case 400:
return F("Bad Request");
case 401:
return F("Unauthorized");
case 402:
return F("Payment Required");
case 403:
return F("Forbidden");
case 404:
return F("Not Found");
case 405:
return F("Method Not Allowed");
case 406:
return F("Not Acceptable");
case 407:
return F("Proxy Authentication Required");
case 408:
return F("Request Time-out");
case 409:
return F("Conflict");
case 410:
return F("Gone");
case 411:
return F("Length Required");
case 412:
return F("Precondition Failed");
case 413:
return F("Request Entity Too Large");
case 414:
return F("Request-URI Too Large");
case 415:
return F("Unsupported Media Type");
case 416:
return F("Requested range not satisfiable");
case 417:
return F("Expectation Failed");
case 500:
return F("Internal Server Error");
case 501:
return F("Not Implemented");
case 502:
return F("Bad Gateway");
case 503:
return F("Service Unavailable");
case 504:
return F("Gateway Time-out");
case 505:
return F("HTTP Version not supported");
default:
return F("");
}
}

View File

@@ -0,0 +1,224 @@
/*
WebServer.h - Dead simple web-server.
Supports only one simultaneous client, knows how to handle GET and POST.
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/
#pragma once
#include "HTTP_Method.h"
#include "Uri.h"
#include <WiFi.h>
#include <functional>
#include <memory>
enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END, UPLOAD_FILE_ABORTED };
enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE };
enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH };
#define HTTP_DOWNLOAD_UNIT_SIZE 1436
#ifndef HTTP_UPLOAD_BUFLEN
#define HTTP_UPLOAD_BUFLEN 1436
#endif
#define HTTP_MAX_DATA_WAIT 5000 // ms to wait for the client to send the request
#define HTTP_MAX_POST_WAIT 5000 // ms to wait for POST data to arrive
#define HTTP_MAX_SEND_WAIT 5000 // ms to wait for data chunk to be ACKed
#define HTTP_MAX_CLOSE_WAIT 2000 // ms to wait for the client to close the connection
#define CONTENT_LENGTH_UNKNOWN ((size_t)-1)
#define CONTENT_LENGTH_NOT_SET ((size_t)-2)
class WebServer;
typedef struct {
HTTPUploadStatus status;
String filename;
String name;
String type;
size_t totalSize; // file size
size_t currentSize; // size of data currently in buf
uint8_t buf[HTTP_UPLOAD_BUFLEN];
} HTTPUpload;
#include "detail/RequestHandler.h"
namespace fs {
class FS;
}
class WebServer {
public:
WebServer(IPAddress addr, int port = 80);
WebServer(int port = 80);
virtual ~WebServer();
virtual void begin();
virtual void begin(uint16_t port);
virtual void handleClient();
virtual void close();
void stop();
bool authenticate(const char *username, const char *password);
void requestAuthentication(
HTTPAuthMethod mode = BASIC_AUTH, const char *realm = NULL, const String &authFailMsg = String("")
);
typedef std::function<void(void)> THandlerFunction;
void on(const Uri &uri, THandlerFunction fn);
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn);
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn); // ufn handles file uploads
void addHandler(RequestHandler *handler);
void serveStatic(const char *uri, fs::FS &fs, const char *path, const char *cache_header = NULL);
void onNotFound(THandlerFunction fn); // called when handler is not assigned
void onFileUpload(THandlerFunction ufn); // handle file uploads
String uri() {
return _currentUri;
}
HTTPMethod method() {
return _currentMethod;
}
virtual WiFiClient client() {
return _currentClient;
}
HTTPUpload &upload() {
return *_currentUpload;
}
String pathArg(unsigned int i); // get request path argument by number
String arg(String name); // get request argument value by name
String arg(int i); // get request argument value by number
String argName(int i); // get request argument name by number
int args(); // get arguments count
bool hasArg(String name); // check if argument exists
void collectHeaders(const char *headerKeys[], const size_t headerKeysCount); // set the request headers to collect
String header(String name); // get request header value by name
String header(int i); // get request header value by number
String headerName(int i); // get request header name by number
int headers(); // get header count
bool hasHeader(String name); // check if header exists
String hostHeader(); // get request host header if available or empty String if not
// send response to the client
// code - HTTP response code, can be 200 or 404
// content_type - HTTP content type, like "text/plain" or "image/png"
// content - actual content body
void send(int code, const char *content_type = NULL, const String &content = String(""));
void send(int code, char *content_type, const String &content);
void send(int code, const String &content_type, const String &content);
void send_P(int code, PGM_P content_type, PGM_P content);
void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength);
void enableDelay(boolean value);
void enableCORS(boolean value = true);
void enableCrossOrigin(boolean value = true);
void setContentLength(const size_t contentLength);
void sendHeader(const String &name, const String &value, bool first = false);
void sendContent(const String &content);
void sendContent(const char *content, size_t contentLength);
void sendContent_P(PGM_P content);
void sendContent_P(PGM_P content, size_t size);
static String urlDecode(const String &text);
template <typename T>
size_t streamFile(T &file, const String &contentType) {
_streamFileCore(file.size(), file.name(), contentType);
return _currentClient.write(file);
}
protected:
virtual size_t _currentClientWrite(const char *b, size_t l) {
return _currentClient.write(b, l);
}
virtual size_t _currentClientWrite_P(PGM_P b, size_t l) {
return _currentClient.write_P(b, l);
}
void _addRequestHandler(RequestHandler *handler);
void _handleRequest();
void _finalizeResponse();
bool _parseRequest(WiFiClient &client);
void _parseArguments(String data);
static String _responseCodeToString(int code);
bool _parseForm(WiFiClient &client, String boundary, uint32_t len);
bool _parseFormUploadAborted();
void _uploadWriteByte(uint8_t b);
int _uploadReadByte(WiFiClient &client);
void _prepareHeader(String &response, int code, const char *content_type, size_t contentLength);
bool _collectHeader(const char *headerName, const char *headerValue);
void _streamFileCore(const size_t fileSize, const String &fileName, const String &contentType);
String _getRandomHexString();
// for extracting Auth parameters
String _extractParam(String &authReq, const String &param, const char delimit = '"');
struct RequestArgument {
String key;
String value;
};
boolean _corsEnabled;
WiFiServer _server;
WiFiClient _currentClient;
HTTPMethod _currentMethod;
String _currentUri;
uint8_t _currentVersion;
HTTPClientStatus _currentStatus;
unsigned long _statusChange;
boolean _nullDelay;
RequestHandler *_currentHandler;
RequestHandler *_firstHandler;
RequestHandler *_lastHandler;
THandlerFunction _notFoundHandler;
THandlerFunction _fileUploadHandler;
int _currentArgCount;
RequestArgument *_currentArgs;
int _postArgsLen;
RequestArgument *_postArgs;
std::unique_ptr<HTTPUpload> _currentUpload;
int _headerKeysCount;
RequestArgument *_currentHeaders;
size_t _contentLength;
String _responseHeaders;
String _hostHeader;
bool _chunked;
String _snonce; // Store noance and opaque for future comparison
String _sopaque;
String _srealm; // Store the Auth realm between Calls
};

View File

@@ -0,0 +1,53 @@
#pragma once
#include <assert.h>
#include <vector>
class RequestHandler {
public:
virtual ~RequestHandler() {}
virtual bool canHandle(HTTPMethod method, String uri) {
(void)method;
(void)uri;
return false;
}
virtual bool canUpload(String uri) {
(void)uri;
return false;
}
virtual bool handle(WebServer &server, HTTPMethod requestMethod, String requestUri) {
(void)server;
(void)requestMethod;
(void)requestUri;
return false;
}
virtual void upload(WebServer &server, String requestUri, HTTPUpload &upload) {
(void)server;
(void)requestUri;
(void)upload;
}
RequestHandler *next() {
return _next;
}
void next(RequestHandler *r) {
_next = r;
}
private:
RequestHandler *_next = nullptr;
protected:
std::vector<String> pathArgs;
public:
const String &pathArg(unsigned int i) {
assert(i < pathArgs.size());
return pathArgs[i];
}
};

View File

@@ -0,0 +1,149 @@
#pragma once
#include "RequestHandler.h"
#include "Uri.h"
#include "WString.h"
#include "mimetable.h"
using namespace mime;
class FunctionRequestHandler : public RequestHandler {
public:
FunctionRequestHandler(
WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn, const Uri &uri, HTTPMethod method
)
: _fn(fn), _ufn(ufn), _uri(uri.clone()), _method(method) {
_uri->initPathArgs(pathArgs);
}
~FunctionRequestHandler() {
delete _uri;
}
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
if (_method != HTTP_ANY && _method != requestMethod)
return false;
return _uri->canHandle(requestUri, pathArgs);
}
bool canUpload(String requestUri) override {
if (!_ufn || !canHandle(HTTP_POST, requestUri))
return false;
return true;
}
bool handle(WebServer &server, HTTPMethod requestMethod, String requestUri) override {
(void)server;
if (!canHandle(requestMethod, requestUri))
return false;
_fn();
return true;
}
void upload(WebServer &server, String requestUri, HTTPUpload &upload) override {
(void)server;
(void)upload;
if (canUpload(requestUri))
_ufn();
}
protected:
WebServer::THandlerFunction _fn;
WebServer::THandlerFunction _ufn;
Uri *_uri;
HTTPMethod _method;
};
class StaticRequestHandler : public RequestHandler {
public:
StaticRequestHandler(FS &fs, const char *path, const char *uri, const char *cache_header)
: _fs(fs), _uri(uri), _path(path), _cache_header(cache_header) {
File f = fs.open(path);
_isFile = (f && (!f.isDirectory()));
log_v(
"StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n",
path,
uri,
_isFile,
cache_header ? cache_header : ""
); // issue 5506 - cache_header can be nullptr
_baseUriLength = _uri.length();
}
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
if (requestMethod != HTTP_GET)
return false;
if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri))
return false;
return true;
}
bool handle(WebServer &server, HTTPMethod requestMethod, String requestUri) override {
if (!canHandle(requestMethod, requestUri))
return false;
log_v("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());
String path(_path);
if (!_isFile) {
// Base URI doesn't point to a file.
// If a directory is requested, look for index file.
if (requestUri.endsWith("/"))
requestUri += "index.htm";
// Append whatever follows this URI in request to get the file path.
path += requestUri.substring(_baseUriLength);
}
log_v("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
String contentType = getContentType(path);
// look for gz file, only if the original specified path is not a gz. So part only works to send gzip via
// content encoding when a non compressed is asked for if you point the the path to gzip you will serve the gzip
// as content type "application/x-gzip", not text or javascript etc...
if (!path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !_fs.exists(path)) {
String pathWithGz = path + FPSTR(mimeTable[gz].endsWith);
if (_fs.exists(pathWithGz))
path += FPSTR(mimeTable[gz].endsWith);
}
File f = _fs.open(path, "r");
if (!f || !f.available())
return false;
if (_cache_header.length() != 0)
server.sendHeader("Cache-Control", _cache_header);
server.streamFile(f, contentType);
return true;
}
static String getContentType(const String &path) {
char buff[sizeof(mimeTable[0].mimeType)];
// Check all entries but last one for match, return if found
for (size_t i = 0; i < sizeof(mimeTable) / sizeof(mimeTable[0]) - 1; i++) {
strcpy_P(buff, mimeTable[i].endsWith);
if (path.endsWith(buff)) {
strcpy_P(buff, mimeTable[i].mimeType);
return String(buff);
}
}
// Fall-through and just return default type
strcpy_P(buff, mimeTable[sizeof(mimeTable) / sizeof(mimeTable[0]) - 1].mimeType);
return String(buff);
}
protected:
FS _fs;
String _uri;
String _path;
String _cache_header;
bool _isFile;
size_t _baseUriLength;
};

View File

@@ -0,0 +1,33 @@
#include "mimetable.h"
#include "pgmspace.h"
namespace mime {
// Table of extension->MIME strings stored in PROGMEM, needs to be global due to GCC section typing rules
const Entry mimeTable[maxType] = {
{".html", "text/html" },
{".htm", "text/html" },
{".css", "text/css" },
{".txt", "text/plain" },
{".js", "application/javascript" },
{".json", "application/json" },
{".png", "image/png" },
{".gif", "image/gif" },
{".jpg", "image/jpeg" },
{".ico", "image/x-icon" },
{".svg", "image/svg+xml" },
{".ttf", "application/x-font-ttf" },
{".otf", "application/x-font-opentype" },
{".woff", "application/font-woff" },
{".woff2", "application/font-woff2" },
{".eot", "application/vnd.ms-fontobject"},
{".sfnt", "application/font-sfnt" },
{".xml", "text/xml" },
{".pdf", "application/pdf" },
{".zip", "application/zip" },
{".gz", "application/x-gzip" },
{".appcache", "text/cache-manifest" },
{"", "application/octet-stream" }
};
} // namespace mime

View File

@@ -0,0 +1,38 @@
#pragma once
namespace mime {
enum type {
html,
htm,
css,
txt,
js,
json,
png,
gif,
jpg,
ico,
svg,
ttf,
otf,
woff,
woff2,
eot,
sfnt,
xml,
pdf,
zip,
gz,
appcache,
none,
maxType
};
struct Entry {
const char endsWith[16];
const char mimeType[32];
};
extern const Entry mimeTable[maxType];
} // namespace mime

View File

@@ -0,0 +1,61 @@
#pragma once
#include "Uri.h"
class UriBraces : public Uri {
public:
explicit UriBraces(const char *uri) : Uri(uri){};
explicit UriBraces(const String &uri) : Uri(uri){};
Uri *clone() const override final {
return new UriBraces(_uri);
};
void initPathArgs(std::vector<String> &pathArgs) override final {
int numParams = 0, start = 0;
do {
start = _uri.indexOf("{}", start);
if (start > 0) {
numParams++;
start += 2;
}
} while (start > 0);
pathArgs.resize(numParams);
}
bool canHandle(const String &requestUri, std::vector<String> &pathArgs) override final {
if (Uri::canHandle(requestUri, pathArgs))
return true;
size_t uriLength = _uri.length();
unsigned int pathArgIndex = 0;
unsigned int requestUriIndex = 0;
for (unsigned int i = 0; i < uriLength; i++, requestUriIndex++) {
char uriChar = _uri[i];
char requestUriChar = requestUri[requestUriIndex];
if (uriChar == requestUriChar)
continue;
if (uriChar != '{')
return false;
i += 2; // index of char after '}'
if (i >= uriLength) {
// there is no char after '}'
pathArgs[pathArgIndex] = requestUri.substring(requestUriIndex);
return pathArgs[pathArgIndex].indexOf("/") == -1; // path argument may not contain a '/'
} else {
char charEnd = _uri[i];
int uriIndex = requestUri.indexOf(charEnd, requestUriIndex);
if (uriIndex < 0)
return false;
pathArgs[pathArgIndex] = requestUri.substring(requestUriIndex, uriIndex);
requestUriIndex = (unsigned int)uriIndex;
}
pathArgIndex++;
}
return requestUriIndex >= requestUri.length();
}
};

View File

@@ -0,0 +1,19 @@
#pragma once
#include "Uri.h"
#include <fnmatch.h>
class UriGlob : public Uri {
public:
explicit UriGlob(const char *uri) : Uri(uri){};
explicit UriGlob(const String &uri) : Uri(uri){};
Uri *clone() const override final {
return new UriGlob(_uri);
};
bool canHandle(const String &requestUri, __attribute__((unused)) std::vector<String> &pathArgs) override final {
return fnmatch(_uri.c_str(), requestUri.c_str(), 0) == 0;
}
};

View File

@@ -0,0 +1,41 @@
#pragma once
#include "Uri.h"
#include <regex>
class UriRegex : public Uri {
public:
explicit UriRegex(const char *uri) : Uri(uri){};
explicit UriRegex(const String &uri) : Uri(uri){};
Uri *clone() const override final {
return new UriRegex(_uri);
};
void initPathArgs(std::vector<String> &pathArgs) override final {
std::regex rgx((_uri + "|").c_str());
std::smatch matches;
std::string s{""};
std::regex_search(s, matches, rgx);
pathArgs.resize(matches.size() - 1);
}
bool canHandle(const String &requestUri, std::vector<String> &pathArgs) override final {
if (Uri::canHandle(requestUri, pathArgs))
return true;
unsigned int pathArgIndex = 0;
std::regex rgx(_uri.c_str());
std::smatch matches;
std::string s(requestUri.c_str());
if (std::regex_search(s, matches, rgx)) {
for (size_t i = 1; i < matches.size(); ++i) { // skip first
pathArgs[pathArgIndex] = String(matches[i].str().c_str());
pathArgIndex++;
}
return true;
}
return false;
}
};

View File

@@ -0,0 +1,238 @@
/**
*
* @file WiFiMulti.cpp
* @date 16.05.2015
* @author Markus Sattler
*
* Copyright (c) 2015 Markus Sattler. All rights reserved.
* This file is part of the esp8266 core for Arduino environment.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "WiFiMulti.h"
#include <Arduino.h>
#include <limits.h>
#include <string.h>
WiFiMulti::WiFiMulti() {}
WiFiMulti::~WiFiMulti() {
for (uint32_t i = 0; i < APlist.size(); i++) {
WifiAPlist_t entry = APlist[i];
if (entry.ssid) {
free(entry.ssid);
}
if (entry.passphrase) {
free(entry.passphrase);
}
}
APlist.clear();
}
bool WiFiMulti::addAP(const char *ssid, const char *passphrase) {
WifiAPlist_t newAP;
if (!ssid || *ssid == 0x00 || strlen(ssid) > 31) {
// fail SSID too long or missing!
LT_E("SSID missing or too long");
return false;
}
if (passphrase && strlen(passphrase) > 64) {
// fail passphrase too long!
LT_E("Passphrase too long");
return false;
}
newAP.ssid = strdup(ssid);
if (!newAP.ssid) {
LT_E("Fail newAP.ssid == 0");
return false;
}
if (passphrase && *passphrase != 0x00) {
newAP.passphrase = strdup(passphrase);
if (!newAP.passphrase) {
LT_E("Fail newAP.passphrase == 0");
free(newAP.ssid);
return false;
}
} else {
newAP.passphrase = NULL;
}
APlist.push_back(newAP);
LT_V("Add SSID: %s", newAP.ssid);
return true;
}
uint8_t WiFiMulti::run(uint32_t connectTimeout) {
int8_t scanResult;
uint8_t status = WiFi.status();
if (status == WL_CONNECTED) {
for (uint32_t x = 0; x < APlist.size(); x++) {
if (WiFi.SSID() == APlist[x].ssid) {
return status;
}
}
WiFi.disconnect(false);
delay(10);
status = WiFi.status();
}
scanResult = WiFi.scanNetworks();
if (scanResult == WIFI_SCAN_RUNNING) {
// scan is running
return WL_NO_SSID_AVAIL;
} else if (scanResult >= 0) {
// scan done analyze
WifiAPlist_t bestNetwork{NULL, NULL};
int bestNetworkDb = INT_MIN;
uint8_t bestBSSID[6];
int32_t bestChannel = 0;
LT_I("Scan finished");
if (scanResult == 0) {
LT_I("No networks found");
} else {
LT_I("%d networks found", scanResult);
for (int8_t i = 0; i < scanResult; ++i) {
String ssid_scan;
int32_t rssi_scan;
WiFiAuthMode sec_scan;
uint8_t *BSSID_scan;
int32_t chan_scan;
WiFi.getNetworkInfo(i, ssid_scan, sec_scan, rssi_scan, BSSID_scan, chan_scan);
bool known = false;
for (uint32_t x = APlist.size(); x > 0; x--) {
WifiAPlist_t entry = APlist[x - 1];
if (ssid_scan == entry.ssid) { // SSID match
known = true;
if (rssi_scan > bestNetworkDb) { // best network
if (sec_scan == WIFI_AUTH_OPEN ||
entry.passphrase) { // check for passphrase if not open wlan
bestNetworkDb = rssi_scan;
bestChannel = chan_scan;
memcpy((void *)&bestNetwork, (void *)&entry, sizeof(bestNetwork));
memcpy((void *)&bestBSSID, (void *)BSSID_scan, sizeof(bestBSSID));
}
}
break;
}
}
if (known) {
LT_D(
" ---> %d: [%d][%02X:%02X:%02X:%02X:%02X:%02X] %s (%d) %c",
i,
chan_scan,
BSSID_scan[0],
BSSID_scan[1],
BSSID_scan[2],
BSSID_scan[3],
BSSID_scan[4],
BSSID_scan[5],
ssid_scan.c_str(),
rssi_scan,
(sec_scan == WIFI_AUTH_OPEN) ? ' ' : '*'
);
} else {
LT_D(
" %d: [%d][%02X:%02X:%02X:%02X:%02X:%02X] %s (%d) %c",
i,
chan_scan,
BSSID_scan[0],
BSSID_scan[1],
BSSID_scan[2],
BSSID_scan[3],
BSSID_scan[4],
BSSID_scan[5],
ssid_scan.c_str(),
rssi_scan,
(sec_scan == WIFI_AUTH_OPEN) ? ' ' : '*'
);
}
}
}
// clean up ram
WiFi.scanDelete();
if (bestNetwork.ssid) {
LT_I(
"Connecting to BSSID: %02X:%02X:%02X:%02X:%02X:%02X SSID: %s Channel: %d (%d)",
bestBSSID[0],
bestBSSID[1],
bestBSSID[2],
bestBSSID[3],
bestBSSID[4],
bestBSSID[5],
bestNetwork.ssid,
bestChannel,
bestNetworkDb
);
WiFi.begin(bestNetwork.ssid, bestNetwork.passphrase, bestChannel, bestBSSID);
status = WiFi.status();
auto startTime = millis();
// wait for connection, fail, or timeout
while (status != WL_CONNECTED && status != WL_NO_SSID_AVAIL && status != WL_CONNECT_FAILED &&
(millis() - startTime) <= connectTimeout) {
delay(10);
status = WiFi.status();
}
switch (status) {
case WL_CONNECTED:
LT_I("Connecting done");
LT_D("SSID: %s", WiFi.SSID().c_str());
LT_D("IP: %s", WiFi.localIP().toString().c_str());
LT_D("MAC: %s", WiFi.BSSIDstr().c_str());
LT_D("Channel: %d", WiFi.channel());
break;
case WL_NO_SSID_AVAIL:
LT_E("Connecting failed; AP not found");
break;
case WL_CONNECT_FAILED:
LT_E("Connecting failed");
break;
default:
LT_E("Connecting failed (%d)", status);
break;
}
} else {
LT_E("No matching network found!");
}
} else {
// start scan
LT_V("Delete old wifi config...");
WiFi.disconnect();
LT_D("Start scan");
// scan wifi async mode
WiFi.scanNetworks(true);
}
return status;
}

View File

@@ -0,0 +1,47 @@
/**
*
* @file ESP8266WiFiMulti.h
* @date 16.05.2015
* @author Markus Sattler
*
* Copyright (c) 2015 Markus Sattler. All rights reserved.
* This file is part of the esp8266 core for Arduino environment.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#pragma once
#include "WiFi.h"
#include <vector>
typedef struct {
char *ssid;
char *passphrase;
} WifiAPlist_t;
class WiFiMulti {
public:
WiFiMulti();
~WiFiMulti();
bool addAP(const char *ssid, const char *passphrase = NULL);
uint8_t run(uint32_t connectTimeout = 10000);
private:
std::vector<WifiAPlist_t> APlist;
};

View File

@@ -0,0 +1,2 @@
DisableFormat: true
SortIncludes: Never

View File

@@ -0,0 +1,64 @@
/**
* base64.cpp
*
* Created on: 09.12.2015
*
* Copyright (c) 2015 Markus Sattler. All rights reserved.
* This file is part of the ESP31B core for Arduino.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "Arduino.h"
extern "C" {
#include "libb64/cdecode.h"
#include "libb64/cencode.h"
}
#include "base64.h"
/**
* convert input data to base64
* @param data const uint8_t *
* @param length size_t
* @return String
*/
String base64::encode(const uint8_t * data, size_t length)
{
size_t size = base64_encode_expected_len(length) + 1;
char * buffer = (char *) malloc(size);
if(buffer) {
base64_encodestate _state;
base64_init_encodestate(&_state);
int len = base64_encode_block((const char *) &data[0], length, &buffer[0], &_state);
len = base64_encode_blockend((buffer + len), &_state);
String base64 = String(buffer);
free(buffer);
return base64;
}
return String("-FAIL-");
}
/**
* convert input data to base64
* @param text const String&
* @return String
*/
String base64::encode(const String& text)
{
return base64::encode((uint8_t *) text.c_str(), text.length());
}

View File

@@ -0,0 +1,9 @@
#pragma once
#include <Arduino.h>
class base64 {
public:
static String encode(const uint8_t *data, size_t length);
static String encode(const String &text);
};

View File

@@ -0,0 +1,7 @@
libb64: Base64 Encoding/Decoding Routines
======================================
Authors:
-------
Chris Venter chris.venter@gmail.com http://rocketpod.blogspot.com

View File

@@ -0,0 +1,29 @@
Copyright-Only Dedication (based on United States law)
or Public Domain Certification
The person or persons who have associated work with this document (the
"Dedicator" or "Certifier") hereby either (a) certifies that, to the best of
his knowledge, the work of authorship identified is in the public domain of the
country from which the work is published, or (b) hereby dedicates whatever
copyright the dedicators holds in the work of authorship identified below (the
"Work") to the public domain. A certifier, moreover, dedicates any copyright
interest he may have in the associated work, and for these purposes, is
described as a "dedicator" below.
A certifier has taken reasonable steps to verify the copyright status of this
work. Certifier recognizes that his good faith efforts may not shield him from
liability if in fact the work certified is not in the public domain.
Dedicator makes this dedication for the benefit of the public at large and to
the detriment of the Dedicator's heirs and successors. Dedicator intends this
dedication to be an overt act of relinquishment in perpetuity of all present
and future rights under copyright law, whether vested or contingent, in the
Work. Dedicator understands that such relinquishment of all rights includes
the relinquishment of all rights to enforce (by lawsuit or otherwise) those
copyrights in the Work.
Dedicator recognizes that, once placed in the public domain, the Work may be
freely reproduced, distributed, transmitted, used, modified, built upon, or
otherwise exploited by anyone for any purpose, commercial or non-commercial,
and in any way, including by methods that have not yet been invented or
conceived.

View File

@@ -0,0 +1,99 @@
/*
cdecoder.c - c source to a base64 decoding algorithm implementation
This is part of the libb64 project, and has been placed in the public domain.
For details, see http://sourceforge.net/projects/libb64
*/
#include "cdecode.h"
#include <stdint.h>
static int base64_decode_value_signed(int8_t value_in){
static const int8_t decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51};
static const int8_t decoding_size = sizeof(decoding);
value_in -= 43;
if (value_in < 0 || value_in >= decoding_size) return -1;
return decoding[(int)value_in];
}
void base64_init_decodestate(base64_decodestate* state_in){
state_in->step = step_a;
state_in->plainchar = 0;
}
static int base64_decode_block_signed(const int8_t* code_in, const int length_in, int8_t* plaintext_out, base64_decodestate* state_in){
const int8_t* codechar = code_in;
int8_t* plainchar = plaintext_out;
int8_t fragment;
*plainchar = state_in->plainchar;
switch (state_in->step){
while (1){
case step_a:
do {
if (codechar == code_in+length_in){
state_in->step = step_a;
state_in->plainchar = *plainchar;
return plainchar - plaintext_out;
}
fragment = (int8_t)base64_decode_value_signed(*codechar++);
} while (fragment < 0);
*plainchar = (fragment & 0x03f) << 2;
case step_b:
do {
if (codechar == code_in+length_in){
state_in->step = step_b;
state_in->plainchar = *plainchar;
return plainchar - plaintext_out;
}
fragment = (int8_t)base64_decode_value_signed(*codechar++);
} while (fragment < 0);
*plainchar++ |= (fragment & 0x030) >> 4;
*plainchar = (fragment & 0x00f) << 4;
case step_c:
do {
if (codechar == code_in+length_in){
state_in->step = step_c;
state_in->plainchar = *plainchar;
return plainchar - plaintext_out;
}
fragment = (int8_t)base64_decode_value_signed(*codechar++);
} while (fragment < 0);
*plainchar++ |= (fragment & 0x03c) >> 2;
*plainchar = (fragment & 0x003) << 6;
case step_d:
do {
if (codechar == code_in+length_in){
state_in->step = step_d;
state_in->plainchar = *plainchar;
return plainchar - plaintext_out;
}
fragment = (int8_t)base64_decode_value_signed(*codechar++);
} while (fragment < 0);
*plainchar++ |= (fragment & 0x03f);
}
}
/* control should not reach here */
return plainchar - plaintext_out;
}
static int base64_decode_chars_signed(const int8_t* code_in, const int length_in, int8_t* plaintext_out){
base64_decodestate _state;
base64_init_decodestate(&_state);
int len = base64_decode_block_signed(code_in, length_in, plaintext_out, &_state);
if(len > 0) plaintext_out[len] = 0;
return len;
}
int base64_decode_value(char value_in){
return base64_decode_value_signed(*((int8_t *) &value_in));
}
int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in){
return base64_decode_block_signed((int8_t *) code_in, length_in, (int8_t *) plaintext_out, state_in);
}
int base64_decode_chars(const char* code_in, const int length_in, char* plaintext_out){
return base64_decode_chars_signed((int8_t *) code_in, length_in, (int8_t *) plaintext_out);
}

View File

@@ -0,0 +1,38 @@
/*
cdecode.h - c header for a base64 decoding algorithm
This is part of the libb64 project, and has been placed in the public domain.
For details, see http://sourceforge.net/projects/libb64
*/
#ifndef BASE64_CDECODE_H
#define BASE64_CDECODE_H
#define base64_decode_expected_len(n) ((n * 3) / 4)
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
step_a, step_b, step_c, step_d
} base64_decodestep;
typedef struct {
base64_decodestep step;
char plainchar;
} base64_decodestate;
void base64_init_decodestate(base64_decodestate* state_in);
int base64_decode_value(char value_in);
int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in);
int base64_decode_chars(const char* code_in, const int length_in, char* plaintext_out);
#ifdef __cplusplus
} // extern "C"
#endif
#endif /* BASE64_CDECODE_H */

View File

@@ -0,0 +1,102 @@
/*
cencoder.c - c source to a base64 encoding algorithm implementation
This is part of the libb64 project, and has been placed in the public domain.
For details, see http://sourceforge.net/projects/libb64
*/
#include "cencode.h"
void base64_init_encodestate(base64_encodestate* state_in)
{
state_in->step = step_A;
state_in->result = 0;
}
char base64_encode_value(char value_in)
{
static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
if (value_in > 63) {
return '=';
}
return encoding[(int)value_in];
}
int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in)
{
const char* plainchar = plaintext_in;
const char* const plaintextend = plaintext_in + length_in;
char* codechar = code_out;
char result;
char fragment;
result = state_in->result;
switch (state_in->step) {
while (1) {
case step_A:
if (plainchar == plaintextend) {
state_in->result = result;
state_in->step = step_A;
return codechar - code_out;
}
fragment = *plainchar++;
result = (fragment & 0x0fc) >> 2;
*codechar++ = base64_encode_value(result);
result = (fragment & 0x003) << 4;
case step_B:
if (plainchar == plaintextend) {
state_in->result = result;
state_in->step = step_B;
return codechar - code_out;
}
fragment = *plainchar++;
result |= (fragment & 0x0f0) >> 4;
*codechar++ = base64_encode_value(result);
result = (fragment & 0x00f) << 2;
case step_C:
if (plainchar == plaintextend) {
state_in->result = result;
state_in->step = step_C;
return codechar - code_out;
}
fragment = *plainchar++;
result |= (fragment & 0x0c0) >> 6;
*codechar++ = base64_encode_value(result);
result = (fragment & 0x03f) >> 0;
*codechar++ = base64_encode_value(result);
}
}
/* control should not reach here */
return codechar - code_out;
}
int base64_encode_blockend(char* code_out, base64_encodestate* state_in)
{
char* codechar = code_out;
switch (state_in->step) {
case step_B:
*codechar++ = base64_encode_value(state_in->result);
*codechar++ = '=';
*codechar++ = '=';
break;
case step_C:
*codechar++ = base64_encode_value(state_in->result);
*codechar++ = '=';
break;
case step_A:
break;
}
*codechar = 0x00;
return codechar - code_out;
}
int base64_encode_chars(const char* plaintext_in, int length_in, char* code_out)
{
base64_encodestate _state;
base64_init_encodestate(&_state);
int len = base64_encode_block(plaintext_in, length_in, code_out, &_state);
return len + base64_encode_blockend((code_out + len), &_state);
}

View File

@@ -0,0 +1,41 @@
/*
cencode.h - c header for a base64 encoding algorithm
This is part of the libb64 project, and has been placed in the public domain.
For details, see http://sourceforge.net/projects/libb64
*/
#ifndef BASE64_CENCODE_H
#define BASE64_CENCODE_H
#define base64_encode_expected_len(n) ((((4 * n) / 3) + 3) & ~3)
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
step_A, step_B, step_C
} base64_encodestep;
typedef struct {
base64_encodestep step;
char result;
int stepcount;
} base64_encodestate;
void base64_init_encodestate(base64_encodestate* state_in);
char base64_encode_value(char value_in);
int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in);
int base64_encode_blockend(char* code_out, base64_encodestate* state_in);
int base64_encode_chars(const char* plaintext_in, int length_in, char* code_out);
#ifdef __cplusplus
} // extern "C"
#endif
#endif /* BASE64_CENCODE_H */

View File

@@ -0,0 +1,5 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-23. */
#pragma once
#include "mDNS.h"

View File

@@ -0,0 +1,153 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-23. */
#ifdef LT_HAS_LWIP2
#include "mDNS.h"
extern "C" {
#include <lwip/apps/mdns.h>
#include <lwip/netif.h>
}
static u8_t mdns_netif_client_id = 0; // TODO fix this
struct mdns_domain {
/* Encoded domain name */
u8_t name[256];
/* Total length of domain name, including zero */
u16_t length;
/* Set if compression of this domain is not allowed */
u8_t skip_compression;
};
/** Description of a service */
struct mdns_service {
/** TXT record to answer with */
struct mdns_domain txtdata;
/** Name of service, like 'myweb' */
char name[MDNS_LABEL_MAXLEN + 1];
/** Type of service, like '_http' */
char service[MDNS_LABEL_MAXLEN + 1];
/** Callback function and userdata
* to update txtdata buffer */
service_get_txt_fn_t txt_fn;
void *txt_userdata;
/** TTL in seconds of SRV/TXT replies */
u32_t dns_ttl;
/** Protocol, TCP or UDP */
u16_t proto;
/** Port of the service */
u16_t port;
};
/** Description of a host/netif */
struct mdns_host {
/** Hostname */
char name[MDNS_LABEL_MAXLEN + 1];
/** Pointer to services */
struct mdns_service *services[MDNS_MAX_SERVICES];
/** TTL in seconds of A/AAAA/PTR replies */
u32_t dns_ttl;
};
static String mdnsInstanceName = "default_instance";
mDNS::mDNS() {}
mDNS::~mDNS() {}
bool mDNS::begin(const char *hostname) {
mdns_resp_init();
struct netif *netif = netif_list;
while (netif != NULL) {
// TODO: detect mdns_netif_client_id by checking netif_get_client_data()
// and finding the requested hostname in struct mdns_host
if (netif_is_up(netif) && mdns_resp_add_netif(netif, hostname, 255) != ERR_OK) {
return false;
}
netif = netif->next;
}
return true;
}
void mDNS::end() {
struct netif *netif = netif_list;
while (netif != NULL) {
if (netif_is_up(netif))
mdns_resp_remove_netif(netif);
netif = netif->next;
}
}
void mDNS::setInstanceName(String name) {
mdnsInstanceName = name;
}
bool mDNS::addService(char *service, char *proto, uint16_t port) {
char _service[strlen(service) + 2];
char _proto[strlen(proto) + 2];
_service[0] = '_';
_proto[0] = '_';
// prepend names with _
strcpy(_service + 1, service + (service[0] == '_'));
strcpy(_proto + 1, proto + (proto[0] == '_'));
mdns_sd_proto protocol = DNSSD_PROTO_UDP;
if (strncmp(_proto + 1, "tcp", 3) == 0)
protocol = DNSSD_PROTO_TCP;
struct netif *netif = netif_list;
while (netif != NULL) {
if (netif_is_up(netif)) {
mdns_resp_add_service(netif, mdnsInstanceName.c_str(), service, protocol, port, 255, NULL, NULL);
}
netif = netif->next;
}
}
bool mDNS::addServiceTxt(char *name, char *proto, char *key, char *value) {
char _name[strlen(name) + 2];
char _proto[strlen(proto) + 2];
_name[0] = '_';
_proto[0] = '_';
// prepend names with _
strcpy(_name + 1, name + (name[0] == '_'));
strcpy(_proto + 1, proto + (proto[0] == '_'));
mdns_sd_proto protocol = DNSSD_PROTO_UDP;
if (strncmp(_proto + 1, "tcp", 3) == 0)
protocol = DNSSD_PROTO_TCP;
struct netif *netif = netif_list;
struct mdns_host *mdns;
struct mdns_service *service;
uint8_t txt_len = strlen(key) + strlen(value) + 1;
char *txt = (char *)malloc(txt_len + 1);
sprintf(txt, "%s=%s", key, value);
while (netif != NULL) {
if (netif_is_up(netif)) {
mdns = (struct mdns_host *)netif_get_client_data(netif, mdns_netif_client_id);
for (uint8_t i = 0; i < MDNS_MAX_SERVICES; i++) {
service = mdns->services[i];
if (service == NULL)
continue;
if (strcmp(service->service, _name) || service->proto != protocol)
continue;
if (mdns_resp_add_service_txtitem(service, txt, txt_len) != ERR_OK) {
free(txt);
return false;
}
}
}
netif = netif->next;
}
free(txt);
return true;
}
MDNSResponder MDNS;
#endif

View File

@@ -0,0 +1,119 @@
/*
ESP8266 Multicast DNS (port of CC3000 Multicast DNS library)
Version 1.1
Copyright (c) 2013 Tony DiCola (tony@tonydicola.com)
ESP8266 port (c) 2015 Ivan Grokhotkov (ivan@esp8266.com)
MDNS-SD Suport 2015 Hristo Gochkov (hristo@espressif.com)
Extended MDNS-SD support 2016 Lars Englund (lars.englund@gmail.com)
Rewritten for ESP32 by Hristo Gochkov (hristo@espressif.com)
This is a simple implementation of multicast DNS query support for an Arduino
running on ESP32 chip.
Usage:
- Include the ESP32 Multicast DNS library in the sketch.
- Call the begin method in the sketch's setup and provide a domain name (without
the '.local' suffix, i.e. just provide 'foo' to resolve 'foo.local'), and the
Adafruit CC3000 class instance. Optionally provide a time to live (in seconds)
for the DNS record--the default is 1 hour.
- Call the update method in each iteration of the sketch's loop function.
License (MIT license):
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#include <Arduino.h>
#include <api/IPv6Address.h>
class mDNS {
public:
mDNS();
~mDNS();
bool begin(const char *hostname);
void end();
void setInstanceName(String name);
bool addService(char *service, char *proto, uint16_t port);
bool addServiceTxt(char *name, char *proto, char *key, char *value);
// void enableArduino(uint16_t port = 3232, bool auth = false);
// void disableArduino();
// void enableWorkstation(esp_interface_t interface = ESP_IF_WIFI_STA);
// void disableWorkstation();
IPAddress queryHost(char *host, uint32_t timeout = 2000);
int queryService(char *service, char *proto);
String hostname(int idx);
IPAddress IP(int idx);
IPv6Address IPv6(int idx);
uint16_t port(int idx);
int numTxt(int idx);
bool hasTxt(int idx, const char *key);
String txt(int idx, const char *key);
String txt(int idx, int txtIdx);
String txtKey(int idx, int txtIdx);
void setInstanceName(const char *name) {
setInstanceName(String(name));
}
void setInstanceName(char *name) {
setInstanceName(String(name));
}
bool addService(const char *service, const char *proto, uint16_t port) {
return addService((char *)service, (char *)proto, port);
}
bool addService(String service, String proto, uint16_t port) {
return addService(service.c_str(), proto.c_str(), port);
}
void addServiceTxt(const char *name, const char *proto, const char *key, const char *value) {
addServiceTxt((char *)name, (char *)proto, (char *)key, (char *)value);
}
void addServiceTxt(String name, String proto, String key, String value) {
addServiceTxt(name.c_str(), proto.c_str(), key.c_str(), value.c_str());
}
IPAddress queryHost(const char *host, uint32_t timeout = 2000) {
return queryHost((char *)host, timeout);
}
IPAddress queryHost(String host, uint32_t timeout = 2000) {
return queryHost(host.c_str(), timeout);
}
int queryService(const char *service, const char *proto) {
return queryService((char *)service, (char *)proto);
}
int queryService(String service, String proto) {
return queryService(service.c_str(), proto.c_str());
}
};
typedef mDNS MDNSResponder;
extern MDNSResponder MDNS;

View File

@@ -0,0 +1,25 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-24. */
#pragma once
// Flash device configuration
extern const struct fal_flash_dev flash0;
#define FAL_FLASH_DEV_NAME "flash0"
#define FAL_FLASH_DEV_TABLE \
{ &flash0, }
#define FAL_DEV_NAME_MAX 16 // no need for 24 chars (default)
// Partition table
#define FAL_PART_HAS_TABLE_CFG
#define FAL_PART_TABLE_ITEM(part_lower, part_upper) \
{ \
.magic_word = FAL_PART_MAGIC_WORD, /* magic word */ \
.name = #part_lower, /* lowercase name as string */ \
.flash_name = FAL_FLASH_DEV_NAME, /* flash device name */ \
.offset = FLASH_##part_upper##_OFFSET, /* partition offset macro as uppercase string */ \
.len = FLASH_##part_upper##_LENGTH, /* partition length macro as uppercase string */ \
},

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) 2020, Armink, <armink.ztl@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _FDB_CFG_H_
#define _FDB_CFG_H_
/* using KVDB feature */
#define FDB_USING_KVDB
#ifdef FDB_USING_KVDB
/* Auto update KV to latest default when current KVDB version number is changed. @see fdb_kvdb.ver_num */
// #define FDB_KV_AUTO_UPDATE
#endif
/* using TSDB (Time series database) feature */
// #define FDB_USING_TSDB
/* Using FAL storage mode */
#define FDB_USING_FAL_MODE
#ifdef FDB_USING_FAL_MODE
/* the flash write granularity, unit: bit
* only support 1(nor flash)/ 8(stm32f2/f4)/ 32(stm32f1) */
#define FDB_WRITE_GRAN 8
#endif
/* Using file storage mode by LIBC file API, like fopen/fread/fwrte/fclose */
// #define FDB_USING_FILE_LIBC_MODE
/* Using file storage mode by POSIX file API, like open/read/write/close */
// #define FDB_USING_FILE_POSIX_MODE
/* MCU Endian Configuration, default is Little Endian Order. */
// #define FDB_BIG_ENDIAN
/* log print macro. default EF_PRINT macro is printf() */
#define FDB_PRINT(...)
/* print debug information */
// #define FDB_DEBUG_ENABLE
#endif /* _FDB_CFG_H_ */

View File

@@ -0,0 +1,2 @@
DisableFormat: true
SortIncludes: Never

View File

@@ -0,0 +1,5 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-16. */
extern char *strdup(const char *);
extern int strcasecmp(const char *s1, const char *s2);
extern int strncasecmp(const char *s1, const char *s2, size_t n);

View File

@@ -0,0 +1,97 @@
/* $OpenBSD: strcasecmp.c,v 1.6 2005/08/08 08:05:37 espie Exp $ */
/*
* Copyright (c) 1987, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <string.h>
typedef unsigned char u_char;
/*
* This array is designed for mapping upper and lower case letter
* together for a case independent comparison. The mappings are
* based upon ascii character sequences.
*/
static const u_char charmap[] = {
'\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007',
'\010', '\011', '\012', '\013', '\014', '\015', '\016', '\017',
'\020', '\021', '\022', '\023', '\024', '\025', '\026', '\027',
'\030', '\031', '\032', '\033', '\034', '\035', '\036', '\037',
'\040', '\041', '\042', '\043', '\044', '\045', '\046', '\047',
'\050', '\051', '\052', '\053', '\054', '\055', '\056', '\057',
'\060', '\061', '\062', '\063', '\064', '\065', '\066', '\067',
'\070', '\071', '\072', '\073', '\074', '\075', '\076', '\077',
'\100', '\141', '\142', '\143', '\144', '\145', '\146', '\147',
'\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157',
'\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167',
'\170', '\171', '\172', '\133', '\134', '\135', '\136', '\137',
'\140', '\141', '\142', '\143', '\144', '\145', '\146', '\147',
'\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157',
'\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167',
'\170', '\171', '\172', '\173', '\174', '\175', '\176', '\177',
'\200', '\201', '\202', '\203', '\204', '\205', '\206', '\207',
'\210', '\211', '\212', '\213', '\214', '\215', '\216', '\217',
'\220', '\221', '\222', '\223', '\224', '\225', '\226', '\227',
'\230', '\231', '\232', '\233', '\234', '\235', '\236', '\237',
'\240', '\241', '\242', '\243', '\244', '\245', '\246', '\247',
'\250', '\251', '\252', '\253', '\254', '\255', '\256', '\257',
'\260', '\261', '\262', '\263', '\264', '\265', '\266', '\267',
'\270', '\271', '\272', '\273', '\274', '\275', '\276', '\277',
'\300', '\301', '\302', '\303', '\304', '\305', '\306', '\307',
'\310', '\311', '\312', '\313', '\314', '\315', '\316', '\317',
'\320', '\321', '\322', '\323', '\324', '\325', '\326', '\327',
'\330', '\331', '\332', '\333', '\334', '\335', '\336', '\337',
'\340', '\341', '\342', '\343', '\344', '\345', '\346', '\347',
'\350', '\351', '\352', '\353', '\354', '\355', '\356', '\357',
'\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367',
'\370', '\371', '\372', '\373', '\374', '\375', '\376', '\377',
};
int
strcasecmp(const char *s1, const char *s2)
{
const u_char *cm = charmap;
const u_char *us1 = (const u_char *)s1;
const u_char *us2 = (const u_char *)s2;
while (cm[*us1] == cm[*us2++])
if (*us1++ == '\0')
return (0);
return (cm[*us1] - cm[*--us2]);
}
int
strncasecmp(const char *s1, const char *s2, size_t n)
{
if (n != 0) {
const u_char *cm = charmap;
const u_char *us1 = (const u_char *)s1;
const u_char *us2 = (const u_char *)s2;
do {
if (cm[*us1] != cm[*us2++])
return (cm[*us1] - cm[*--us2]);
if (*us1++ == '\0')
break;
} while (--n != 0);
}
return (0);
}

View File

@@ -0,0 +1,11 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-16. */
#include <stddef.h>
__attribute__((weak)) char *strdup(const char *s) {
size_t len = strlen(s) + 1;
void *newp = malloc(len);
if (newp == NULL)
return NULL;
return (char *)memcpy(newp, s, len);
}

View File

@@ -1,58 +1,29 @@
/*
Arduino.h - Main include file for the Arduino SDK
Copyright (c) 2005-2013 Arduino Team. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/* Copyright (c) Kuba Szczodrzyński 2022-04-23. */
#pragma once
#ifdef __cplusplus
#include "WCharacterFixup.h"
#endif
#define PinMode PinModeArduino // this conflicts with SDK enum
#include <api/ArduinoAPI.h>
#include <api/LibreTuyaAPI.h>
#include <core/LibreTuyaAPI.h>
#undef PinMode
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
extern "C" uint32_t SystemCoreClock;
#else
extern uint32_t SystemCoreClock;
#define clockCyclesPerMicrosecond() ( SystemCoreClock / 1000000L )
#define clockCyclesToMicroseconds(a) ( ((a) * 1000L) / (SystemCoreClock / 1000L) )
#define microsecondsToClockCycles(a) ( (a) * (SystemCoreClock / 1000000L) )
#include "WVariant.h"
#ifdef __cplusplus
} // extern "C"
#endif
#define clockCyclesPerMicrosecond() (SystemCoreClock / 1000000L)
#define clockCyclesToMicroseconds(a) (((a)*1000L) / (SystemCoreClock / 1000L))
#define microsecondsToClockCycles(a) ((a) * (SystemCoreClock / 1000000L))
// Include board variant
#include "variant.h"
#define interrupts() vPortClearInterruptMask(0)
#define interrupts() vPortClearInterruptMask(0)
#define noInterrupts() ulPortSetInterruptMask()
/*
* \brief SAM3 products have only one reference for ADC
*/
typedef enum _eAnalogReference
{
AR_DEFAULT,
} eAnalogReference ;
// Include family-specific code
#include "WVariant.h"
// Include board variant
#include "variant.h"

View File

@@ -1,7 +0,0 @@
#include <api/Print.h>
class CountingStream : public Print
{
virtual size_t write(uint8_t) { return 1; };
virtual size_t write(const uint8_t *buffer, size_t size) { return size; };
};

View File

@@ -1,198 +0,0 @@
/* mbed Microcontroller Library
* Copyright (c) 2006-2015 ARM Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef MBED_FUNCTIONPOINTER_H
#define MBED_FUNCTIONPOINTER_H
#include <string.h>
#include <stdint.h>
/* If we had variaditic templates, this wouldn't be a problem, but until C++11 is enabled, we are stuck with multiple classes... */
/** A class for storing and calling a pointer to a static or member function
*/
template <typename R, typename A1>
class FunctionPointerArg1{
public:
/** Create a FunctionPointer, attaching a static function
*
* @param function The static function to attach (default is none)
*/
FunctionPointerArg1(R (*function)(A1) = 0) {
attach(function);
}
/** Create a FunctionPointer, attaching a member function
*
* @param object The object pointer to invoke the member function on (i.e. the this pointer)
* @param function The address of the member function to attach
*/
template<typename T>
FunctionPointerArg1(T *object, R (T::*member)(A1)) {
attach(object, member);
}
/** Attach a static function
*
* @param function The static function to attach (default is none)
*/
void attach(R (*function)(A1)) {
_p.function = function;
_membercaller = 0;
}
/** Attach a member function
*
* @param object The object pointer to invoke the member function on (i.e. the this pointer)
* @param function The address of the member function to attach
*/
template<typename T>
void attach(T *object, R (T::*member)(A1)) {
_p.object = static_cast<void*>(object);
*reinterpret_cast<R (T::**)(A1)>(_member) = member;
_membercaller = &FunctionPointerArg1::membercaller<T>;
}
/** Call the attached static or member function
*/
R call(A1 a) {
if (_membercaller == 0 && _p.function) {
return _p.function(a);
} else if (_membercaller && _p.object) {
return _membercaller(_p.object, _member, a);
}
return (R)0;
}
/** Get registered static function
*/
R(*get_function(A1))() {
return _membercaller ? (R(*)(A1))0 : (R(*)(A1))_p.function;
}
R operator ()(A1 a) {
return call(a);
}
operator bool(void) const {
return (_membercaller != NULL ? _p.object : (void*)_p.function) != NULL;
}
private:
template<typename T>
static R membercaller(void *object, uintptr_t *member, A1 a) {
T* o = static_cast<T*>(object);
R (T::**m)(A1) = reinterpret_cast<R (T::**)(A1)>(member);
return (o->**m)(a);
}
union {
R (*function)(A1); // static function pointer
void *object; // object this pointer
} _p;
uintptr_t _member[4]; // aligned raw member function pointer storage - converted back by registered _membercaller
R (*_membercaller)(void*, uintptr_t*, A1); // registered membercaller function to convert back and call _m.member on _object
};
/** A class for storing and calling a pointer to a static or member function (R ()(void))
*/
template <typename R>
class FunctionPointerArg1<R, void>{
public:
/** Create a FunctionPointer, attaching a static function
*
* @param function The static function to attach (default is none)
*/
FunctionPointerArg1(R (*function)(void) = 0) {
attach(function);
}
/** Create a FunctionPointer, attaching a member function
*
* @param object The object pointer to invoke the member function on (i.e. the this pointer)
* @param function The address of the void member function to attach
*/
template<typename T>
FunctionPointerArg1(T *object, R (T::*member)(void)) {
attach(object, member);
}
/** Attach a static function
*
* @param function The void static function to attach (default is none)
*/
void attach(R (*function)(void)) {
_p.function = function;
_membercaller = 0;
}
/** Attach a member function
*
* @param object The object pointer to invoke the member function on (i.e. the this pointer)
* @param function The address of the void member function to attach
*/
template<typename T>
void attach(T *object, R (T::*member)(void)) {
_p.object = static_cast<void*>(object);
*reinterpret_cast<R (T::**)(void)>(_member) = member;
_membercaller = &FunctionPointerArg1::membercaller<T>;
}
/** Call the attached static or member function
*/
R call(){
if (_membercaller == 0 && _p.function) {
return _p.function();
} else if (_membercaller && _p.object) {
return _membercaller(_p.object, _member);
}
return (R)0;
}
/** Get registered static function
*/
R(*get_function())() {
return _membercaller ? (R(*)())0 : (R(*)())_p.function;
}
R operator ()(void) {
return call();
}
operator bool(void) const {
return (_membercaller != NULL ? _p.object : (void*)_p.function) != NULL;
}
private:
template<typename T>
static R membercaller(void *object, uintptr_t *member) {
T* o = static_cast<T*>(object);
R (T::**m)(void) = reinterpret_cast<R (T::**)(void)>(member);
return (o->**m)();
}
union {
R (*function)(void); // static function pointer
void *object; // object this pointer
} _p;
uintptr_t _member[4]; // aligned raw member function pointer storage - converted back by registered _membercaller
R (*_membercaller)(void*, uintptr_t*); // registered membercaller function to convert back and call _m.member on _object
};
typedef FunctionPointerArg1<void, void> FunctionPointer;
typedef FunctionPointerArg1<void, int> event_callback_t;
#endif

View File

@@ -16,117 +16,83 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "Arduino.h"
#include "LOGUARTClass.h"
#include <Arduino.h>
#define LOG_UART_MODIFIABLE_BAUD_RATE 1
#ifdef __cplusplus
extern "C" {
#endif
#include "osdep_service.h"
#include "rtl8710b.h"
extern int LOGUART_SetBaud(uint32_t BaudRate); // from fixups/log_uart.c
#ifdef __cplusplus
}
#endif
RingBuffer rx_buffer0;
LOGUARTClass::LOGUARTClass(int dwIrq, RingBuffer* pRx_buffer )
{
_rx_buffer = pRx_buffer;
_dwIrq = dwIrq;
LOGUARTClass::LOGUARTClass(int dwIrq, RingBuffer *pRx_buffer) {
_rx_buffer = pRx_buffer;
_dwIrq = dwIrq;
}
// Protected Methods //////////////////////////////////////////////////////////////
void IrqHandler(void) {
uint8_t data = 0;
BOOL PullMode = _FALSE;
uint32_t IrqEn = DiagGetIsrEnReg();
DiagSetIsrEnReg(0);
// Public Methods //////////////////////////////////////////////////////////////
void IrqHandler( void )
{
uint8_t data = 0;
BOOL PullMode = _FALSE;
uint32_t IrqEn = DiagGetIsrEnReg();
DiagSetIsrEnReg(0);
data = DiagGetChar(PullMode);
if ( data > 0 )
data = DiagGetChar(PullMode);
if (data > 0)
rx_buffer0.store_char(data);
DiagSetIsrEnReg(IrqEn);
DiagSetIsrEnReg(IrqEn);
}
void LOGUARTClass::begin( const uint32_t dwBaudRate )
{
DIAG_UartReInit((IRQ_FUN) IrqHandler);
NVIC_SetPriority(UART_LOG_IRQ, 10);
LOGUART_SetBaud(dwBaudRate);
void LOGUARTClass::begin(const uint32_t dwBaudRate) {
DIAG_UartReInit((IRQ_FUN)IrqHandler);
NVIC_SetPriority(UART_LOG_IRQ, 10);
LOGUART_SetBaud(dwBaudRate);
}
void LOGUARTClass::end( void )
{
// clear any received data
_rx_buffer->_iHead = _rx_buffer->_iTail ;
void LOGUARTClass::end(void) {
// clear any received data
_rx_buffer->_iHead = _rx_buffer->_iTail;
}
int LOGUARTClass::available( void )
{
return (uint32_t)(SERIAL_BUFFER_SIZE + _rx_buffer->_iHead - _rx_buffer->_iTail) % SERIAL_BUFFER_SIZE ;
int LOGUARTClass::available(void) {
return (uint32_t)(SERIAL_BUFFER_SIZE + _rx_buffer->_iHead - _rx_buffer->_iTail) % SERIAL_BUFFER_SIZE;
}
int LOGUARTClass::peek( void )
{
int LOGUARTClass::peek(void) {
if ( _rx_buffer->_iHead == _rx_buffer->_iTail )
return -1 ;
return _rx_buffer->_aucBuffer[_rx_buffer->_iTail] ;
if (_rx_buffer->_iHead == _rx_buffer->_iTail)
return -1;
return _rx_buffer->_aucBuffer[_rx_buffer->_iTail];
}
int LOGUARTClass::read( void )
{
// if the head isn't ahead of the tail, we don't have any characters
if ( _rx_buffer->_iHead == _rx_buffer->_iTail )
return -1 ;
uint8_t uc = _rx_buffer->_aucBuffer[_rx_buffer->_iTail] ;
_rx_buffer->_iTail = (unsigned int)(_rx_buffer->_iTail + 1) % SERIAL_BUFFER_SIZE ;
return uc ;
int LOGUARTClass::read(void) {
// if the head isn't ahead of the tail, we don't have any characters
if (_rx_buffer->_iHead == _rx_buffer->_iTail)
return -1;
uint8_t uc = _rx_buffer->_aucBuffer[_rx_buffer->_iTail];
_rx_buffer->_iTail = (unsigned int)(_rx_buffer->_iTail + 1) % SERIAL_BUFFER_SIZE;
return uc;
}
void LOGUARTClass::flush( void )
{
// TODO:
// while ( serial_writable(&(this->sobj)) != 1 );
/*
// Wait for transmission to complete
while ((_pUart->UART_SR & UART_SR_TXRDY) != UART_SR_TXRDY)
;
*/
void LOGUARTClass::flush(void) {
// TODO:
// while ( serial_writable(&(this->sobj)) != 1 );
/*
// Wait for transmission to complete
while ((_pUart->UART_SR & UART_SR_TXRDY) != UART_SR_TXRDY)
;
*/
}
size_t LOGUARTClass::write( const uint8_t uc_data )
{
DiagPutChar(uc_data);
return 1;
size_t LOGUARTClass::write(const uint8_t uc_data) {
DiagPutChar(uc_data);
return 1;
}
LOGUARTClass Serial(UART_LOG_IRQ, &rx_buffer0);
bool Serial_available() {
return Serial.available() > 0;
return Serial.available() > 0;
}

Some files were not shown because too many files have changed in this diff Show More