polymech - fw latest | web ui
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
./.pio
|
||||
./.vscode
|
||||
./data
|
||||
./docs
|
||||
./examples
|
||||
./scripts
|
||||
./tmp
|
||||
./ref
|
||||
./README.md
|
||||
@@ -0,0 +1,25 @@
|
||||
# LLM Development Workflow Commands
|
||||
|
||||
|
||||
## Directory & File structure
|
||||
|
||||
- application code resides in ./src
|
||||
- application's core code resides in ./lib/polymech-base/src
|
||||
|
||||
## Environment
|
||||
|
||||
- ESP32 / Platform.io
|
||||
|
||||
## Commands
|
||||
|
||||
- build : npm run build
|
||||
- update device : npm run update (build & upload)
|
||||
|
||||
## Code
|
||||
|
||||
- dont comment code at all, unless really needed, assume expert C++ level
|
||||
- Vector needs an array as storage
|
||||
- Mermaid diagrams :dont use braces in node names
|
||||
- never do more than asked, do baby-steps
|
||||
- be silent, keep your thoughts
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# PlatformIO
|
||||
.pio/
|
||||
.cache/
|
||||
|
||||
# Build artifacts
|
||||
*.elf
|
||||
*.bin
|
||||
*.map
|
||||
|
||||
# Secrets
|
||||
src/config_secrets.h
|
||||
|
||||
# Doxygen Output
|
||||
docs/doxygen/
|
||||
firmware-docs
|
||||
|
||||
# Python build artifacts
|
||||
scripts/__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Node.js
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
tests/reports/
|
||||
@@ -0,0 +1,96 @@
|
||||
# Polymech Cassandra Firmware
|
||||
|
||||
This repository contains the firmware for the Polymech Cassandra project, running on an ESP32-S3.
|
||||
|
||||
## Overview
|
||||
|
||||
The firmware manages various hardware components and provides multiple interfaces for control and monitoring:
|
||||
|
||||
* **Serial Interface:** For debugging, testing, and direct command execution.
|
||||
* **Modbus TCP Server:** Allows interaction with components using the Modbus protocol over WiFi.
|
||||
* **REST API & Web UI:** Provides a web-based interface (status page, API documentation) and a RESTful API for interacting with the system over WiFi.
|
||||
|
||||
## Architecture
|
||||
|
||||
The firmware is built upon a component-based architecture:
|
||||
|
||||
* **`App` / `PHApp`:** The main application class (`src/PHApp.h`).
|
||||
* **`Component`:** Base class (`src/Component.h`) for all functional units.
|
||||
* **`Bridge`:** (`src/Bridge.h`) Facilitates serial command dispatch.
|
||||
* **`SerialMessage`:** (`src/SerialMessage.h`) Handles serial communication.
|
||||
* **`ModbusManager`:** (`src/ModbusManager.h`) Manages the Modbus TCP server.
|
||||
* **`RESTServer`:** (`src/RestServer.h`) Implements the web server and REST API.
|
||||
* **Details:** See [docs/components.md](./docs/components.md) for how components are structured, configured, and added.
|
||||
|
||||
## Interfaces
|
||||
|
||||
### 1. Serial Communication
|
||||
|
||||
A command-line interface is available over the USB serial port used for programming and monitoring.
|
||||
|
||||
* **Purpose:** Debugging, direct method calls on components.
|
||||
* **Command Format:** `<<component_id;call_type;flags;payload:arg1:arg2...>>`
|
||||
* **Examples & Details:** See [docs/serial.md](./docs/serial.md)
|
||||
* **Sending Commands:** Use `npm run send -- "<command_string>"` or a standard serial terminal.
|
||||
|
||||
### 2. Modbus TCP
|
||||
|
||||
The device runs a Modbus TCP server (slave/responder) on port 502.
|
||||
|
||||
* **Implementation:** Managed by `ModbusManager` using the `eModbus` library.
|
||||
* **Component Interaction:** Components supporting Modbus (like `Relay` or `PHApp` itself) implement `readNetworkValue` and `writeNetworkValue` methods. They are registered with the `ModbusManager` along with their corresponding Modbus addresses.
|
||||
* **Configuration:** Modbus addresses are defined in `src/config-modbus.h`.
|
||||
* **Design Details:** See [docs/design-network.md](./docs/design-network.md)
|
||||
* **Testing:** Use the `python scripts/modbus_*.py` scripts directly (npm script argument passing has issues):
|
||||
```bash
|
||||
# Read Coil 51
|
||||
python scripts/modbus_read_coils.py --address 51 --ip-address <DEVICE_IP>
|
||||
# Write Coil 51 ON
|
||||
python scripts/modbus_write_coil.py --address 51 --value 1 --ip-address <DEVICE_IP>
|
||||
# Read Register 20
|
||||
python scripts/modbus_read_registers.py --address 20 --ip-address <DEVICE_IP>
|
||||
# Write Register 20
|
||||
python scripts/modbus_write_register.py --address 20 --value 123 --ip-address <DEVICE_IP>
|
||||
```
|
||||
|
||||
### 3. Web Interface & REST API
|
||||
|
||||
A web server runs on port 80, providing:
|
||||
|
||||
* **Status Page:** `http://<DEVICE_IP>/`
|
||||
* **Swagger UI:** `http://<DEVICE_IP>/api-docs` (Interactive API documentation)
|
||||
* **REST API:** `http://<DEVICE_IP>/api/v1/...`
|
||||
* **Working Endpoints:**
|
||||
* `GET /api/v1/system/info`: Device status.
|
||||
* `GET /api/v1/coils`: List of registered coils.
|
||||
* `GET /api/v1/registers`: List of registered registers.
|
||||
* `GET /api/v1/coils?address=<addr>`: Read a specific registered coil.
|
||||
* `GET /api/v1/registers?address=<addr>`: Read a specific registered register.
|
||||
* `POST /api/v1/relay/test`: Trigger internal relay test.
|
||||
* **Non-Functional Endpoints (Known Issue):**
|
||||
* `POST /coils/{address}`: Returns 404.
|
||||
* `POST /registers/{address}`: Returns 404.
|
||||
* **Details & Examples:** See [docs/web.md](./docs/web.md)
|
||||
* **Testing:** Use `npm run test-api-ip` (note that some tests related to POST endpoints will fail).
|
||||
|
||||
## Building and Development
|
||||
|
||||
See [firmware/.cursor/rules](./.cursor/rules) for a summary of common workflow commands.
|
||||
|
||||
**Key `npm` Scripts:**
|
||||
|
||||
* `npm run build`: Compile the firmware.
|
||||
* `npm run upload`: Upload the firmware to the device.
|
||||
* `npm run clean`: Clean build artifacts.
|
||||
* `npm run monitor`: Open the serial monitor.
|
||||
* `npm run build-web`: Generate web assets (from `swagger.yaml`, `front/`) and build firmware.
|
||||
* `npm run send -- "<command>"`: Send a serial command.
|
||||
* `npm run test-api-ip`: Run the REST API test script against the device IP.
|
||||
* *(Modbus tests need direct python execution, see Modbus section above)*
|
||||
|
||||
## Configuration
|
||||
|
||||
* **Hardware Pins, Features:** `src/config.h`, `src/config_adv.h`
|
||||
* **WiFi Credentials:** `src/config_secrets.h` (ensure this file exists and is populated)
|
||||
* **Modbus Addresses:** `src/config-modbus.h`
|
||||
* **Component IDs:** `src/enums.h` (enum `COMPONENT_KEY`)
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"sta_ssid": "Livebox6-EBCD",
|
||||
"sta_password": "c4RK35h4PZNS",
|
||||
"sta_local_ip": "192.168.1.250",
|
||||
"sta_gateway": "192.168.1.1",
|
||||
"sta_subnet": "255.255.0.0",
|
||||
"sta_primary_dns": "8.8.8.8",
|
||||
"sta_secondary_dns": "8.8.4.4",
|
||||
"ap_ssid": "PolyMechAP",
|
||||
"ap_password": "poly1234",
|
||||
"ap_config_ip": "192.168.4.1",
|
||||
"ap_config_gateway": "192.168.4.1",
|
||||
"ap_config_subnet": "255.255.255.240"
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"speedSlowHz": 16,
|
||||
"speedMediumHz": 20,
|
||||
"speedFastHz": 35,
|
||||
"speedFillPlungeHz": 25,
|
||||
"speedFillHomeHz": 20,
|
||||
"currentJamThresholdMa": 1000,
|
||||
"jammedDurationHomingMs": 100,
|
||||
"jammedDurationMs": 1400,
|
||||
"autoModeHoldDurationMs": 1000,
|
||||
"maxUniversalJamTimeMs": 7000,
|
||||
"fillJoystickHoldDurationMs": 1000,
|
||||
"fillPlungedWaitDurationMs": 1000,
|
||||
"fillHomedWaitDurationMs": 50,
|
||||
"recordHoldDurationMs": 2000,
|
||||
"maxRecordDurationMs": 30000,
|
||||
"replayDurationMs": 4500,
|
||||
"enablePostFlow": false,
|
||||
"postFlowDurationMs": 3000,
|
||||
"postFlowSpeedHz": 15,
|
||||
"currentPostFlowMa": 1300,
|
||||
"postFlowStoppingWaitMs": 500,
|
||||
"postFlowCompleteWaitMs": 500,
|
||||
"defaultMaxOperationDurationMs": 18000
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"speedSlowHz": 16,
|
||||
"speedMediumHz": 20,
|
||||
"speedFastHz": 35,
|
||||
"speedFillPlungeHz": 25,
|
||||
"speedFillHomeHz": 20,
|
||||
"currentJamThresholdMa": 1000,
|
||||
"jammedDurationHomingMs": 100,
|
||||
"jammedDurationMs": 1400,
|
||||
"autoModeHoldDurationMs": 1000,
|
||||
"maxUniversalJamTimeMs": 7000,
|
||||
"fillJoystickHoldDurationMs": 1000,
|
||||
"fillPlungedWaitDurationMs": 1000,
|
||||
"fillHomedWaitDurationMs": 50,
|
||||
"recordHoldDurationMs": 2000,
|
||||
"maxRecordDurationMs": 30000,
|
||||
"replayDurationMs": 4500,
|
||||
"enablePostFlow": false,
|
||||
"postFlowDurationMs": 3000,
|
||||
"postFlowSpeedHz": 15,
|
||||
"currentPostFlowMa": 1300,
|
||||
"postFlowStoppingWaitMs": 500,
|
||||
"postFlowCompleteWaitMs": 500,
|
||||
"defaultMaxOperationDurationMs": 18000
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"sta_ssid": "Livebox6-EBCD",
|
||||
"sta_password": "c4RK35h4PZNS",
|
||||
"sta_local_ip": "192.168.1.250",
|
||||
"sta_gateway": "192.168.1.1",
|
||||
"sta_subnet": "255.255.0.0",
|
||||
"sta_primary_dns": "8.8.8.8",
|
||||
"sta_secondary_dns": "8.8.4.4",
|
||||
"ap_ssid": "PolyMechAP",
|
||||
"ap_password": "poly1234",
|
||||
"ap_config_ip": "192.168.4.1",
|
||||
"ap_config_gateway": "192.168.4.1",
|
||||
"ap_config_subnet": "255.255.255.240"
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"speedSlowHz": 16,
|
||||
"speedMediumHz": 20,
|
||||
"speedFastHz": 35,
|
||||
"speedFillPlungeHz": 25,
|
||||
"speedFillHomeHz": 20,
|
||||
"currentJamThresholdMa": 1000,
|
||||
"jammedDurationHomingMs": 100,
|
||||
"jammedDurationMs": 1400,
|
||||
"autoModeHoldDurationMs": 1000,
|
||||
"maxUniversalJamTimeMs": 7000,
|
||||
"fillJoystickHoldDurationMs": 1000,
|
||||
"fillPlungedWaitDurationMs": 1000,
|
||||
"fillHomedWaitDurationMs": 50,
|
||||
"recordHoldDurationMs": 2000,
|
||||
"maxRecordDurationMs": 30000,
|
||||
"replayDurationMs": 4500,
|
||||
"enablePostFlow": false,
|
||||
"postFlowDurationMs": 3000,
|
||||
"postFlowSpeedHz": 15,
|
||||
"currentPostFlowMa": 1300,
|
||||
"postFlowStoppingWaitMs": 500,
|
||||
"postFlowCompleteWaitMs": 500,
|
||||
"defaultMaxOperationDurationMs": 18000
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"speedSlowHz": 16,
|
||||
"speedMediumHz": 20,
|
||||
"speedFastHz": 35,
|
||||
"speedFillPlungeHz": 25,
|
||||
"speedFillHomeHz": 20,
|
||||
"currentJamThresholdMa": 1000,
|
||||
"jammedDurationHomingMs": 100,
|
||||
"jammedDurationMs": 1400,
|
||||
"autoModeHoldDurationMs": 1000,
|
||||
"maxUniversalJamTimeMs": 7000,
|
||||
"fillJoystickHoldDurationMs": 1000,
|
||||
"fillPlungedWaitDurationMs": 1000,
|
||||
"fillHomedWaitDurationMs": 50,
|
||||
"recordHoldDurationMs": 2000,
|
||||
"maxRecordDurationMs": 30000,
|
||||
"replayDurationMs": 4500,
|
||||
"enablePostFlow": false,
|
||||
"postFlowDurationMs": 3000,
|
||||
"postFlowSpeedHz": 15,
|
||||
"currentPostFlowMa": 1300,
|
||||
"postFlowStoppingWaitMs": 500,
|
||||
"postFlowCompleteWaitMs": 500,
|
||||
"defaultMaxOperationDurationMs": 18000
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
# Modbus Logic Language (mb-lang)
|
||||
|
||||
This document describes the design and usage of the simple logic engine configurable via Modbus TCP.
|
||||
|
||||
## Purpose
|
||||
|
||||
The Modbus Logic Engine allows users to define simple conditional automation rules directly by writing to specific Modbus holding registers. This enables basic automation sequences like "if sensor value X exceeds Y, then turn on relay Z" without modifying the core firmware code.
|
||||
|
||||
## Architecture
|
||||
|
||||
A dedicated component, `ModbusLogicEngine`, runs within the firmware.
|
||||
- It exposes a block of Modbus Holding Registers for configuration and status.
|
||||
- It periodically evaluates the enabled rules.
|
||||
- It can read the state of other Modbus registers/coils.
|
||||
- It can perform actions like writing to Modbus registers/coils or calling pre-defined methods on other firmware components.
|
||||
- It updates status registers after each rule evaluation/action attempt.
|
||||
|
||||
## Configuration and Status
|
||||
|
||||
The engine supports a fixed number of logic rules, defined by `MAX_LOGIC_RULES` (e.g., 8). Each rule is configured and monitored using a block of `REGISTERS_PER_RULE` (now 13) consecutive Holding Registers.
|
||||
|
||||
- **Base Address:** `MODBUS_LOGIC_RULES_START` (Defined in `config-modbus.h`)
|
||||
- **Rule N Address:** `MODBUS_LOGIC_RULES_START + (N * REGISTERS_PER_RULE)` where `N` is the rule index (0 to `MAX_LOGIC_RULES - 1`).
|
||||
|
||||
### Register Map per Rule (N)
|
||||
|
||||
| Offset | Register Name | Address Offset | R/W | Description | Notes |
|
||||
| :----- | :---------------------------- | :-------------------------------- | :-- | :--------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------- |
|
||||
| +0 | `Rule_N_Enabled` | `Base + (N*13) + 0` | R/W | **Enable/Disable Rule:** 0 = Disabled, 1 = Enabled | Rules are ignored if disabled. |
|
||||
| +1 | `Rule_N_Cond_Src_Type` | `Base + (N*13) + 1` | R/W | **Condition Source Type:** 0 = Holding Register, 1 = Coil | Specifies what type of Modbus item the condition reads. |
|
||||
| +2 | `Rule_N_Cond_Src_Addr` | `Base + (N*13) + 2` | R/W | **Condition Source Address:** Modbus address of the register/coil to read for the condition. | The address of the data point to check. |
|
||||
| +3 | `Rule_N_Cond_Operator` | `Base + (N*13) + 3` | R/W | **Condition Operator:** 0=`==`, 1=`!=`, 2=`<`, 3=`<=`, 4=`>`, 5=`>=` | The comparison to perform. |
|
||||
| +4 | `Rule_N_Cond_Value` | `Base + (N*13) + 4` | R/W | **Condition Value:** The value to compare the source against. | For Coils, use 0 for OFF and 1 for ON. |
|
||||
| +5 | `Rule_N_Action_Type` | `Base + (N*13) + 5` | R/W | **Action Type:** 0=None, 1=Write Holding Reg, 2=Write Coil, 3=Call Component Method | Specifies what to do if the condition is true. |
|
||||
| +6 | `Rule_N_Action_Target` | `Base + (N*13) + 6` | R/W | **Action Target:** Modbus Address (for Write Reg/Coil) or Component ID (for Call Method) | Specifies *what* to act upon (Register Address, Coil Address, Component ID). |
|
||||
| +7 | `Rule_N_Action_Param1` | `Base + (N*13) + 7` | R/W | **Action Parameter 1:** Value (for Write Reg), ON/OFF (for Write Coil, 0=OFF, 1=ON), Method ID (for Call) | Specifies *how* to act upon the target. |
|
||||
| +8 | `Rule_N_Action_Param2` | `Base + (N*13) + 8` | R/W | **Action Parameter 2:** Argument 1 for `Call Component Method` | First argument for the component method. Ignored for Write actions. |
|
||||
| +9 | `Rule_N_Action_Param3` | `Base + (N*13) + 9` | R/W | **Action Parameter 3:** Argument 2 for `Call Component Method` | Second argument for the component method. Ignored for Write actions. |
|
||||
| +10 | `Rule_N_Last_Status` | `Base + (N*13) + 10` | R | **Last Action Status:** 0=Idle/OK, 1=Err Cond Read, 2=Err Action Write/Call, 3=Invalid Action Params | Reports the outcome of the last trigger attempt. (See Status Codes below) |
|
||||
| +11 | `Rule_N_Last_Trigger_Timestamp` | `Base + (N*13) + 11` | R | **Last Trigger Timestamp:** System time (e.g., seconds since boot) when rule last triggered. | 0 if never triggered. Wraps eventually. |
|
||||
| +12 | `Rule_N_Trigger_Count` | `Base + (N*13) + 12` | R | **Trigger Count:** Number of times this rule's action has been successfully triggered. | Wraps eventually. Can be reset by writing 0 via Modbus. |
|
||||
|
||||
*Note: `Base` refers to `MODBUS_LOGIC_RULES_START`. R=Read-Only, W=Writeable (from Modbus perspective). Status registers (+10 to +12) are updated internally but the count (+12) can potentially be reset via write.*
|
||||
|
||||
### Status Codes (`Rule_N_Last_Status`)
|
||||
|
||||
| Code | Meaning |
|
||||
| :--- | :-------------------------- |
|
||||
| 0 | Idle / Action OK |
|
||||
| 1 | Error Reading Condition Src |
|
||||
| 2 | Error Performing Action |
|
||||
| 3 | Invalid Action Parameters |
|
||||
| 4 | Component Method Call Failed|
|
||||
| ... | (Other specific errors TBD) |
|
||||
|
||||
## Actions
|
||||
|
||||
### 1. Write Holding Register
|
||||
|
||||
- **Action Type:** 1
|
||||
- **Action Target:** Modbus address of the Holding Register.
|
||||
- **Action Parameter 1:** Value to write.
|
||||
- **Action Parameters 2 & 3:** Ignored.
|
||||
|
||||
### 2. Write Coil
|
||||
|
||||
- **Action Type:** 2
|
||||
- **Action Target:** Modbus address of the Coil.
|
||||
- **Action Parameter 1:** Value to write (0 for OFF, 1 for ON). Other non-zero values may also be interpreted as ON.
|
||||
- **Action Parameters 2 & 3:** Ignored.
|
||||
|
||||
### 3. Call Component Method
|
||||
|
||||
- **Action Type:** 3
|
||||
- **Action Target:** The numeric `Component ID`.
|
||||
- **Action Parameter 1:** The numeric `Method ID`.
|
||||
- **Action Parameter 2:** The first integer argument (`arg1`).
|
||||
- **Action Parameter 3:** The second integer argument (`arg2`).
|
||||
|
||||
**Important:** Only specific, pre-registered methods can be called. Available Component/Method IDs need separate documentation.
|
||||
|
||||
## Execution Flow
|
||||
|
||||
1. The `ModbusLogicEngine` loops periodically.
|
||||
2. For each rule `N` from 0 to `MAX_LOGIC_RULES - 1`:
|
||||
a. Read `Rule_N_Enabled`. If 0, skip.
|
||||
b. Read condition parameters.
|
||||
c. Attempt to read the current value from `Cond_Src_Addr`.
|
||||
d. If read fails, update `Rule_N_Last_Status` (e.g., to 1) and skip to the next rule.
|
||||
e. Evaluate the condition.
|
||||
f. If the condition is TRUE:
|
||||
i. Read action parameters.
|
||||
ii. Attempt to perform the specified action.
|
||||
iii. Update `Rule_N_Last_Status` based on action success (0) or failure (e.g., 2, 3, 4).
|
||||
iv. If action was successful, increment `Rule_N_Trigger_Count` and update `Rule_N_Last_Trigger_Timestamp`.
|
||||
g. If the condition is FALSE, potentially reset `Rule_N_Last_Status` to 0 (Idle), unless it holds an error state.
|
||||
|
||||
## Example Scenarios
|
||||
|
||||
*(Addresses updated for REGISTERS_PER_RULE = 13)*
|
||||
|
||||
### Example 1: Turn on Relay 5 if Register 200 >= 100
|
||||
|
||||
Assume:
|
||||
- `MODBUS_LOGIC_RULES_START` = 1000
|
||||
- Rule Index `N = 0` (`Base = 1000`)
|
||||
- Relay 5 is mapped to Coil address 5
|
||||
- Register 200
|
||||
|
||||
Write:
|
||||
|
||||
| Register Address | Value | Meaning |
|
||||
| :--------------- | :---- | :------------------------ |
|
||||
| 1000 | 1 | Rule 0: Enabled |
|
||||
| 1001 | 0 | Cond Src Type: Reg |
|
||||
| 1002 | 200 | Cond Src Addr: 200 |
|
||||
| 1003 | 5 | Cond Operator: >= (5) |
|
||||
| 1004 | 100 | Cond Value: 100 |
|
||||
| 1005 | 2 | Action Type: Write Coil |
|
||||
| 1006 | 5 | Action Target: Coil Addr 5|
|
||||
| 1007 | 1 | Action Param 1: Value ON |
|
||||
| 1008 | 0 | Action Param 2: (Ignored) |
|
||||
| 1009 | 0 | Action Param 3: (Ignored) |
|
||||
|
||||
Read Status (after trigger):
|
||||
|
||||
| Register Address | Value | Meaning |
|
||||
| :--------------- | :------------ | :-------------------------- |
|
||||
| 1010 | 0 | Last Status: OK |
|
||||
| 1011 | e.g., 12345 | Last Trigger: Timestamp |
|
||||
| 1012 | e.g., 1 | Trigger Count: 1 |
|
||||
|
||||
### Example 2: Call `resetCounter()` Method on Component `StatsTracker` if Coil 10 is ON
|
||||
|
||||
Assume:
|
||||
- Rule Index `N = 1` (`Base = 1000 + 13 = 1013`)
|
||||
- Coil 10
|
||||
- Component `StatsTracker` ID = 5
|
||||
- Method `resetCounter` ID = 1 (takes no args)
|
||||
|
||||
Write:
|
||||
|
||||
| Register Address | Value | Meaning |
|
||||
| :--------------- | :---- | :---------------------------- |
|
||||
| 1013 | 1 | Rule 1: Enabled |
|
||||
| 1014 | 1 | Cond Src Type: Coil |
|
||||
| 1015 | 10 | Cond Src Addr: Coil 10 |
|
||||
| 1016 | 0 | Cond Operator: == (0) |
|
||||
| 1017 | 1 | Cond Value: ON (1) |
|
||||
| 1018 | 3 | Action Type: Call Method |
|
||||
| 1019 | 5 | Action Target: Component ID 5 |
|
||||
| 1020 | 1 | Action Param 1: Method ID 1 |
|
||||
| 1021 | 0 | Action Param 2: Arg1 = 0 |
|
||||
| 1022 | 0 | Action Param 3: Arg2 = 0 |
|
||||
|
||||
Read Status (after trigger):
|
||||
|
||||
| Register Address | Value | Meaning |
|
||||
| :--------------- | :------------ | :-------------------------- |
|
||||
| 1023 | 0 | Last Status: OK |
|
||||
| 1024 | e.g., 12360 | Last Trigger: Timestamp |
|
||||
| 1025 | e.g., 1 | Trigger Count: 1 |
|
||||
|
||||
## Limitations & Considerations
|
||||
|
||||
- **Complexity:** Only simple, single conditions per rule. No `AND`/`OR`/`ELSE`.
|
||||
- **Execution Order:** Rules evaluated sequentially.
|
||||
- **Performance:** Rule evaluation takes time. Consider impact and execution frequency.
|
||||
- **Timestamp:** The `Last_Trigger_Timestamp` resolution and potential for wrapping depend on the firmware implementation (e.g., `millis()` overflow, using seconds since boot).
|
||||
- **Error Handling:** Status codes provide basic feedback. More detailed logging might be needed for complex debugging.
|
||||
- **Method Availability:** Callable methods are fixed in firmware.
|
||||
- **Concurrency:** Actions (especially method calls) might take time. The engine design needs to consider if rule evaluation should block or if actions run asynchronously (current design implies synchronous execution within the loop).
|
||||
|
||||
|
||||
## Limitations & Considerations
|
||||
|
||||
- **Complexity:** Only simple, single conditions per rule are supported. No `AND`/`OR` or `ELSE` logic.
|
||||
- **Execution Order:** Rules are evaluated sequentially. Be mindful of potential interactions if multiple rules modify the same target.
|
||||
- **Performance:** Reading Modbus values and executing actions takes time. Complex rules or a large number of rules might impact overall system performance. Rule execution frequency should be considered.
|
||||
- **Error Handling:** The current design doesn't explicitly define Modbus registers for rule execution status or errors. This could be added later.
|
||||
- **Method Availability:** The list of callable methods (`Component ID`, `Method ID`) is fixed in the firmware and needs to be documented for users.
|
||||
@@ -0,0 +1,109 @@
|
||||
# Comparison: Modbus Logic Engine (MB_SCRIPT) vs. PLC Ladder Logic
|
||||
|
||||
## 1. Introduction
|
||||
|
||||
This document compares the custom Modbus Logic Engine (`MB_SCRIPT`) implemented in this firmware with traditional PLC (Programmable Logic Controller) programming languages, primarily focusing on Ladder Logic (LD).
|
||||
|
||||
* **Modbus Logic Engine (MB_SCRIPT):** A feature enabling simple, rule-based automation directly within the ESP32 firmware. Rules are configured and monitored via Modbus registers. It allows defining conditions based on Modbus values and triggering actions like writing Modbus values or calling internal C++ functions.
|
||||
* **PLC Ladder Logic (LD):** A graphical programming language widely used in industrial automation. It mimics the appearance of electrical relay logic diagrams, making it intuitive for electricians and technicians. It runs on dedicated PLC hardware in a cyclic scan execution model.
|
||||
|
||||
## 2. Core Concepts Comparison
|
||||
|
||||
| Feature | PLC Ladder Logic (Typical) | Modbus Logic Engine (MB_SCRIPT) |
|
||||
| :---------------------- | :------------------------------------------------------------- | :------------------------------------------------------------------ |
|
||||
| **Programming Model** | Graphical (Relay Logic Diagram), Textual (ST, FBD often avail.) | Rule-Based (IF condition THEN action), Configuration via Registers |
|
||||
| **Representation** | Rungs, Contacts (NO/NC), Coils, Function Blocks (Timers, Cnt) | Blocks of Holding Registers defining rules |
|
||||
| **Execution Model** | Cyclic Scan (Read Inputs -> Solve Logic -> Write Outputs) | Iterative `loop()` checks rules sequentially based on interval |
|
||||
| **Data Addressing** | Standardized I/O points (%I, %Q), Memory (%M), Data Regs (%MW) | Modbus Addresses (Coils/Registers) managed by firmware components |
|
||||
| **Built-in Functions** | Timers (TON/TOF), Counters (CTU/CTD), Math, Compare, Move | Basic Comparisons (==, !=, <, >...), Write Coil/Reg, Call Method |
|
||||
| **Complex Logic** | Subroutines, Jumps, Structured Text (ST), Function Blocks | Requires registering custom C++ methods (`CallableMethod`) |
|
||||
| **Configuration Tool** | Dedicated PLC IDE (e.g., TIA Portal, RSLogix) | Standard Modbus Client/Master Software |
|
||||
| **Deployment** | Download program binary to PLC hardware | Write configuration values to Modbus registers |
|
||||
| **Debugging/Monitor** | Online monitoring in IDE, Forcing I/O, Status LEDs | Reading Status registers via Modbus, Serial Logs (Debug/Receipt Flags) |
|
||||
| **Hardware** | Dedicated Industrial PLC Hardware | Runs on the ESP32 microcontroller within the firmware |
|
||||
| **Extensibility** | Adding I/O modules, using advanced function blocks, libraries | Adding/Registering C++ methods, potentially modifying engine code |
|
||||
| **Target User** | Automation Engineers, Technicians, Electricians | Firmware developers, System integrators familiar with Modbus |
|
||||
|
||||
## 3. Detailed Comparison
|
||||
|
||||
### 3.1. Programming Model & Representation
|
||||
|
||||
* **Ladder Logic:** Visual and intuitive for those familiar with electrical schematics. Logic flows left-to-right across rungs. Standard symbols for contacts, coils, timers, etc., are well-understood in the industry.
|
||||
* **MB_SCRIPT:** Abstract. Logic is defined by setting numerical values in specific Modbus registers according to predefined offsets (`ModbusLogicEngineOffsets`). Requires understanding the specific register map and enum values (`E_RegType`, `ConditionOperator`, `CommandType`, `MB_Error`). Less visual and more data-entry focused.
|
||||
|
||||
### 3.2. Execution
|
||||
|
||||
* **Ladder Logic:** Typically deterministic cyclic scan. The PLC reads all inputs, executes the entire ladder program from top to bottom, and then updates all outputs in a predictable cycle time.
|
||||
* **MB_SCRIPT:** Executes within the ESP32's main `loop()`. Rules are checked sequentially within the `ModbusLogicEngine::loop()` call, which runs periodically based on `loopInterval`. Execution time can be influenced by other tasks running on the ESP32. It's event-driven based on Modbus value changes *observed* during rule evaluation, but the evaluation itself is interval-based.
|
||||
|
||||
### 3.3. Data Handling
|
||||
|
||||
* **Ladder Logic:** Uses well-defined memory areas for inputs, outputs, internal bits, timers, counters, and data registers, accessed via standardized addressing.
|
||||
* **MB_SCRIPT:** Relies entirely on the existing Modbus address space managed by other firmware components (`ModbusManager`). Condition sources and action targets are arbitrary Modbus addresses. Internal rule state (status, timestamp, count) is exposed via dedicated status registers within the rule's block. Complex data manipulation requires C++ functions.
|
||||
|
||||
### 3.4. Capabilities & Functionality
|
||||
|
||||
* **Ladder Logic:** Offers standard, pre-built function blocks for common automation tasks like timing delays, counting events, basic arithmetic, and data manipulation. More advanced PLCs include PID loops, communication protocols, motion control, etc.
|
||||
* **MB_SCRIPT:** Provides fundamental building blocks: compare Modbus values and trigger simple actions (write Modbus value, call internal function). It lacks built-in timers, counters, or complex math operations within the rule definition itself. Such functionality must be implemented in C++ and exposed via the `CALL_COMPONENT_METHOD` command.
|
||||
|
||||
### 3.5. Configuration & Deployment
|
||||
|
||||
* **Ladder Logic:** Requires specialized, often vendor-specific, programming software. The compiled logic is downloaded as a binary program to the PLC. Configuration is done offline in the IDE.
|
||||
* **MB_SCRIPT:** Configured "online" by writing values to Modbus registers using any standard Modbus master tool. No separate compilation or download step for the logic itself is needed (only for the firmware containing the engine). This allows dynamic reconfiguration without reflashing firmware (though registered methods are fixed in firmware).
|
||||
|
||||
### 3.6. Debugging & Monitoring
|
||||
|
||||
* **Ladder Logic:** IDEs provide powerful online monitoring tools, allowing visualization of rung state, live value tracing, and forcing of inputs/outputs for testing. PLCs often have hardware status LEDs.
|
||||
* **MB_SCRIPT:** Debugging relies on reading the `LAST_STATUS`, `LAST_TRIGGER_TS`, and `TRIGGER_COUNT` registers via Modbus. More detail requires enabling the `RULE_FLAG_DEBUG` and `RULE_FLAG_RECEIPT` flags and monitoring the device's serial output (`npm run build:monitor`). Less interactive than typical PLC debugging.
|
||||
|
||||
## 4. Strengths & Weaknesses
|
||||
|
||||
**MB_SCRIPT:**
|
||||
|
||||
* **Strengths:**
|
||||
* Simple rule structure, easy to understand if the register map is known.
|
||||
* Configuration via standard Modbus tools - no specialized IDE needed.
|
||||
* Runs directly on the ESP32, reducing need for external micro-controllers for simple logic.
|
||||
* Extensible via C++ (`CALL_COMPONENT_METHOD`) for complex custom actions.
|
||||
* Logic configuration can potentially be updated without firmware reflash.
|
||||
* **Weaknesses:**
|
||||
* Limited built-in functions (no timers, counters, complex math within rules).
|
||||
* Logic representation is abstract (register values) and not visual.
|
||||
* Debugging relies heavily on Modbus reads and serial logs.
|
||||
* Sequential rule execution might have timing implications compared to cyclic scan.
|
||||
* Scalability limited by `MAX_LOGIC_RULES` and ESP32 resources.
|
||||
* Complex logic requires C++ development and method registration.
|
||||
|
||||
**PLC Ladder Logic:**
|
||||
|
||||
* **Strengths:**
|
||||
* Visual and intuitive programming paradigm (especially LD).
|
||||
* Industry standard, widely understood by technicians and engineers.
|
||||
* Rich set of built-in standard functions (timers, counters, etc.).
|
||||
* Mature, powerful IDEs with excellent online monitoring and debugging capabilities.
|
||||
* Deterministic execution (typically).
|
||||
* Highly scalable with modular hardware.
|
||||
* **Weaknesses:**
|
||||
* Requires dedicated, often expensive, PLC hardware.
|
||||
* Requires specialized, often expensive, vendor-specific IDE software.
|
||||
* Less straightforward to integrate directly with custom C++ functions or device-specific hardware features compared to code running *on* the device.
|
||||
* Configuration/updates typically require offline editing and download.
|
||||
|
||||
## 5. Use Cases
|
||||
|
||||
* **MB_SCRIPT is suitable for:**
|
||||
* Simple, reactive logic based directly on Modbus values on the device.
|
||||
* Triggering predefined C++ functions based on Modbus conditions.
|
||||
* Implementing basic interlocks or sequences configurable via Modbus.
|
||||
* Situations where adding a full PLC is overkill or impractical.
|
||||
* Environments where configuration via standard Modbus tools is preferred.
|
||||
* **PLC Ladder Logic is suitable for:**
|
||||
* Complex industrial automation and control sequences.
|
||||
* Applications requiring standard timing, counting, and process control functions.
|
||||
* Systems needing robust, deterministic execution.
|
||||
* Environments where technicians are primarily familiar with Ladder Logic.
|
||||
* Large-scale systems with extensive I/O requirements.
|
||||
|
||||
## 6. Conclusion
|
||||
|
||||
The Modbus Logic Engine (`MB_SCRIPT`) provides a useful, lightweight mechanism for implementing simple, Modbus-driven automation rules directly on the ESP32 firmware. It leverages the existing Modbus infrastructure for configuration and interaction but relies on C++ for complex actions. It is not a replacement for a full PLC system using Ladder Logic, which offers a more comprehensive, standardized, and visually intuitive environment with richer built-in capabilities tailored for industrial control, albeit with the requirement of dedicated hardware and software. The choice depends on the complexity of the required logic, the target environment, user expertise, and integration needs.
|
||||
@@ -0,0 +1,240 @@
|
||||
# Modbus Logic Engine (MB_SCRIPT) Design
|
||||
|
||||
## 1. Overview
|
||||
|
||||
The Modbus Logic Engine (`ModbusLogicEngine` class, enabled by `#define ENABLE_MB_SCRIPT`) provides a way to implement simple automation rules directly on the device, configured and monitored via Modbus. It allows users to define rules based on the state of Modbus registers or coils (conditions) and trigger actions like writing to other registers/coils or calling internal component methods.
|
||||
|
||||
This enables reactive logic without requiring an external controller polling and writing values constantly.
|
||||
|
||||
## 2. Modbus Register Layout
|
||||
|
||||
Each logic rule occupies a contiguous block of Modbus holding registers. The number of rules is defined by `MAX_LOGIC_RULES` (default 8) and the number of registers per rule by `LOGIC_ENGINE_REGISTERS_PER_RULE` (currently 13). The starting address for the first rule is defined by `MODBUS_LOGIC_RULES_START`.
|
||||
|
||||
**Register Layout per Rule:**
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Rule N
|
||||
Reg0[Offset 0: ENABLED (0/1)]
|
||||
Reg1[Offset 1: COND_SRC_TYPE (E_RegType)]
|
||||
Reg2[Offset 2: COND_SRC_ADDR]
|
||||
Reg3[Offset 3: COND_OPERATOR (ConditionOperator)]
|
||||
Reg4[Offset 4: COND_VALUE]
|
||||
Reg5[Offset 5: COMMAND_TYPE (CommandType)]
|
||||
Reg6[Offset 6: COMMAND_TARGET (Addr/CompID)]
|
||||
Reg7[Offset 7: COMMAND_PARAM1 (Value/MethodID)]
|
||||
Reg8[Offset 8: COMMAND_PARAM2 (Arg1)]
|
||||
Reg9[Offset 9: FLAGS (Debug/Receipt)]
|
||||
Reg10[Offset 10: LAST_STATUS (MB_Error)]
|
||||
Reg11[Offset 11: LAST_TRIGGER_TS (Lower 16bit)]
|
||||
Reg12[Offset 12: TRIGGER_COUNT]
|
||||
end
|
||||
|
||||
style Reg0 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Reg1 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Reg2 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Reg3 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Reg4 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Reg5 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Reg6 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Reg7 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Reg8 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Reg9 fill:#f9f,stroke:#333,stroke-width:2px
|
||||
style Reg10 fill:#ccf,stroke:#333,stroke-width:2px
|
||||
style Reg11 fill:#ccf,stroke:#333,stroke-width:2px
|
||||
style Reg12 fill:#ccf,stroke:#333,stroke-width:2px
|
||||
|
||||
```
|
||||
|
||||
**Register Descriptions (Offsets defined in `ModbusLogicEngineOffsets`):**
|
||||
|
||||
* **Configuration Registers (Read/Write via Modbus):**
|
||||
* `ENABLED` (0): `0` = Disabled, `1` = Enabled.
|
||||
* `COND_SRC_TYPE` (1): Type of the Modbus entity to check for the condition. Uses `RegisterState::E_RegType` enum values (e.g., `REG_HOLDING = 3`, `REG_COIL = 2`).
|
||||
* `COND_SRC_ADDR` (2): Modbus address of the register/coil for the condition.
|
||||
* `COND_OPERATOR` (3): Comparison operator to use. Uses `ConditionOperator` enum values (e.g., `EQUAL = 0`, `NOT_EQUAL = 1`, ...).
|
||||
* `COND_VALUE` (4): The value to compare the source register/coil against.
|
||||
* `COMMAND_TYPE` (5): The action to perform if the condition is met. Uses `CommandType` enum values (e.g., `WRITE_COIL = 2`, `WRITE_HOLDING_REGISTER = 3`, `CALL_COMPONENT_METHOD = 100`).
|
||||
* `COMMAND_TARGET` (6): Target of the command.
|
||||
* For `WRITE_*`: The Modbus address to write to.
|
||||
* For `CALL_COMPONENT_METHOD`: The `Component::id` of the target component.
|
||||
* `COMMAND_PARAM1` (7): First parameter for the command.
|
||||
* For `WRITE_*`: The value to write.
|
||||
* For `CALL_COMPONENT_METHOD`: The `methodId` registered with `ModbusLogicEngine::registerMethod`.
|
||||
* `COMMAND_PARAM2` (8): Second parameter for the command.
|
||||
* For `WRITE_*`: Unused.
|
||||
* For `CALL_COMPONENT_METHOD`: The first argument (`arg1`) passed to the registered method.
|
||||
* `FLAGS` (9): Bit flags for rule behavior (See Section 7).
|
||||
|
||||
* **Status Registers (Read-Only via Modbus, except TRIGGER_COUNT reset):**
|
||||
* `LAST_STATUS` (10): Result of the last evaluation/action attempt for this rule. Uses `MB_Error` enum values (e.g., `Success = 0`, `IllegalDataAddress = 2`, `ServerDeviceFailure = 4`).
|
||||
* `LAST_TRIGGER_TS` (11): Timestamp (lower 16 bits of `millis()/1000`) of the last successful trigger.
|
||||
* `TRIGGER_COUNT` (12): Counter of successful triggers. Can be reset by writing `0`.
|
||||
|
||||
## 3. Data Structures
|
||||
|
||||
* **`LogicRule` Struct:**
|
||||
* Holds the internal representation of a single rule.
|
||||
* `config[9]`: An array storing the 9 configuration registers (Offset 0 to 8) read from/written to Modbus.
|
||||
* `lastStatus` (type `RuleStatus`/`MB_Error`): Internal state variable reflecting the last status.
|
||||
* `lastTriggerTimestamp` (type `uint32_t`): Internal state variable for the full timestamp.
|
||||
* `triggerCount` (type `uint16_t`): Internal state variable for the trigger count.
|
||||
* Helper methods (`isEnabled()`, `getCondSourceType()`, `getCommandType()`, `getFlags()`, etc.) provide convenient access to the values stored in the `config` array.
|
||||
* **`std::vector<LogicRule> rules`:** Member variable in `ModbusLogicEngine` holding all configured rules.
|
||||
* **`std::map<uint32_t, CallableMethod> callableMethods`:** Member variable storing methods registered via `registerMethod`, keyed by `(componentId << 16) | methodId`.
|
||||
|
||||
## 4. Core Logic Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant ModbusClient
|
||||
participant ModbusManager
|
||||
participant ModbusLogicEngine as MLE
|
||||
participant TargetComponent
|
||||
|
||||
Note over ModbusClient, MLE: Initialization
|
||||
PHApp->>MLE: constructor(app)
|
||||
PHApp->>MLE: setup()
|
||||
MLE->>MLE: rules.resize(MAX_LOGIC_RULES)
|
||||
Note over MLE: Methods registered via registerMethod()
|
||||
PHApp->>MLE: registerMethod(compID, methodID, function)
|
||||
MLE->>MLE: callableMethods.insert(...)
|
||||
|
||||
loop Application Loop
|
||||
PHApp->>MLE: loop()
|
||||
alt Not Initialized or Interval Not Met
|
||||
MLE-->>PHApp: return E_OK
|
||||
else Rule Evaluation
|
||||
MLE->>MLE: For each rule in rules vector
|
||||
alt Rule Enabled?
|
||||
MLE->>MLE: evaluateCondition(rule)
|
||||
Note over MLE: Reads condition source
|
||||
MLE->>ModbusManager: findComponentForAddress(condAddr)
|
||||
ModbusManager-->>MLE: TargetComponent*
|
||||
MLE->>TargetComponent: readNetworkValue(condAddr)
|
||||
TargetComponent-->>MLE: currentValue (or error)
|
||||
Note over MLE: Performs comparison
|
||||
alt Condition Met?
|
||||
MLE->>MLE: performAction(rule)
|
||||
opt CommandType == WRITE_*
|
||||
Note over MLE: Performs write action
|
||||
MLE->>ModbusManager: findComponentForAddress(targetAddr)
|
||||
ModbusManager-->>MLE: TargetComponent*
|
||||
MLE->>TargetComponent: writeNetworkValue(targetAddr, value)
|
||||
TargetComponent-->>MLE: E_OK (or error)
|
||||
MLE->>MLE: updateRuleStatus(rule, MB_Error::Success / ServerDeviceFailure)
|
||||
opt CommandType == CALL_COMPONENT_METHOD
|
||||
Note over MLE: Performs method call
|
||||
MLE->>MLE: callableMethods.find(key)
|
||||
alt Method Found?
|
||||
MLE->>TargetComponent: Execute registered std::function(arg1)
|
||||
TargetComponent-->>MLE: E_OK (or error)
|
||||
MLE->>MLE: updateRuleStatus(rule, MB_Error::Success / OpExecutionFailed)
|
||||
else Method Not Found
|
||||
MLE->>MLE: updateRuleStatus(rule, MB_Error::IllegalDataAddress)
|
||||
end
|
||||
end
|
||||
MLE->>MLE: update timestamp & trigger count
|
||||
else Condition Not Met
|
||||
MLE->>MLE: updateRuleStatus(rule, MB_Error::Success) if not error
|
||||
end
|
||||
else Rule Disabled
|
||||
MLE->>MLE: Skip rule
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Note over ModbusClient, MLE: Configuration/Status Access
|
||||
ModbusClient->>ModbusManager: Read/Write Request (Holding Registers)
|
||||
ModbusManager->>MLE: readNetworkValue(addr) / writeNetworkValue(addr, val)
|
||||
MLE->>MLE: getRuleInfoFromAddress(addr)
|
||||
alt Read Request
|
||||
MLE->>MLE: Access rule.config[] or internal status
|
||||
MLE-->>ModbusManager: value / status
|
||||
else Write Request
|
||||
MLE->>MLE: rule.setConfigValue(offset, val) / rule.triggerCount = 0
|
||||
MLE-->>ModbusManager: E_OK / error
|
||||
end
|
||||
ModbusManager-->>ModbusClient: Response
|
||||
|
||||
```
|
||||
|
||||
* **`setup()`:** Initializes the `rules` vector based on `MAX_LOGIC_RULES`.
|
||||
* **`loop()`:** Called periodically by the main application loop (`PHApp`).
|
||||
* Checks if initialized and if the evaluation `loopInterval` has passed.
|
||||
* Iterates through each `LogicRule` in the `rules` vector.
|
||||
* If a rule `isEnabled()`, it calls `evaluateCondition()`.
|
||||
* If `evaluateCondition()` returns `true` (condition met), it calls `performAction()`.
|
||||
* **`evaluateCondition()`:**
|
||||
* Retrieves condition parameters (source type, address, operator, value) from the rule.
|
||||
* Calls `readConditionSourceValue()` to get the current value of the source register/coil.
|
||||
* Performs the comparison based on the `condOperator`.
|
||||
* Updates `rule.lastStatus` (e.g., `MB_Error::IllegalDataAddress` on read failure, `MB_Error::IllegalDataValue` on invalid operator).
|
||||
* Returns `true` if the condition is met, `false` otherwise (including errors).
|
||||
* **`performAction()`:**
|
||||
* Retrieves command parameters (type, target, params) from the rule.
|
||||
* Based on `commandType`:
|
||||
* Calls `performWriteAction()` for `WRITE_*` commands.
|
||||
* Calls `performCallAction()` for `CALL_COMPONENT_METHOD`.
|
||||
* Updates `rule.lastStatus` based on the success/failure of the action (e.g., `MB_Error::Success`, `MB_Error::ServerDeviceFailure`, `MB_Error::OpExecutionFailed`, `MB_Error::IllegalFunction`).
|
||||
* If successful, updates `rule.lastTriggerTimestamp` and increments `rule.triggerCount`.
|
||||
* Returns `true` on success, `false` on failure.
|
||||
|
||||
## 5. Interaction with ModbusManager
|
||||
|
||||
The `ModbusLogicEngine` relies on the `ModbusManager` (accessed via the `PHApp* app` pointer) to interact with other components' Modbus values.
|
||||
|
||||
* **`readConditionSourceValue()`:**
|
||||
* Takes the source type (`RegisterState::E_RegType`) and address.
|
||||
* Uses `app->modbusManager->findComponentForAddress(address)` to find the component responsible for that address.
|
||||
* Calls the target component's `readNetworkValue(address)` method.
|
||||
* Returns the value or indicates failure.
|
||||
* **`performWriteAction()`:**
|
||||
* Takes the command type (`WRITE_COIL` or `WRITE_HOLDING_REGISTER`), target address, and value.
|
||||
* Uses `app->modbusManager->findComponentForAddress(address)` to find the target component.
|
||||
* Calls the target component's `writeNetworkValue(address, value)` method.
|
||||
* Returns `true` if the write call returns `E_OK`, `false` otherwise.
|
||||
|
||||
## 6. Method Calling (`CALL_COMPONENT_METHOD`)
|
||||
|
||||
This command type allows rules to trigger C++ methods within other components.
|
||||
|
||||
* **Registration:** Components (like `PHApp` or custom components) that want to expose methods to the Logic Engine must call `ModbusLogicEngine::registerMethod(componentId, methodId, method)`.
|
||||
* `componentId`: The unique `Component::id` of the component exposing the method.
|
||||
* `methodId`: A unique ID (within that component) for the specific method being exposed.
|
||||
* `method`: A `std::function<short(short, short)>` object wrapping the actual C++ method (often created using `std::bind`). The function should accept two `short` arguments and return `E_OK` (or another error code).
|
||||
* The engine stores this registration in the `callableMethods` map.
|
||||
* **Configuration:** A rule is configured with `COMMAND_TYPE = CALL_COMPONENT_METHOD`.
|
||||
* `COMMAND_TARGET` is set to the `componentId`.
|
||||
* `COMMAND_PARAM1` is set to the `methodId`.
|
||||
* `COMMAND_PARAM2` is set to the value to be passed as the first argument (`arg1`) to the registered C++ method.
|
||||
* **Execution (`performCallAction()`):**
|
||||
* Combines `componentId` (from Target) and `methodId` (from Param1) into a key.
|
||||
* Looks up the key in the `callableMethods` map.
|
||||
* If found, executes the stored `std::function`, passing `param2` (as `arg1`) and a dummy `0` (as `arg2`) to the bound C++ method.
|
||||
* Updates status based on the return value of the C++ method (`MB_Error::Success` if `E_OK`, `MB_Error::OpExecutionFailed` otherwise) or `MB_Error::IllegalDataAddress` if the method was not found in the map.
|
||||
|
||||
*Note: This `std::function`-based registration is internal to the `ModbusLogicEngine` and separate from the `Bridge` mechanism used for serial commands.*
|
||||
|
||||
## 7. Flags (`FLAGS` Register - Offset 9)
|
||||
|
||||
The `FLAGS` register allows modifying rule behavior using bitmasks:
|
||||
|
||||
* **`RULE_FLAG_DEBUG` (Bit 0 / Value 1):** If set, enables verbose logging (`Log.verboseln`) during the evaluation and action phases for this specific rule, showing details like addresses, values, and outcomes.
|
||||
* **`RULE_FLAG_RECEIPT` (Bit 1 / Value 2):** If set, logs an informational message (`Log.infoln`) whenever the rule's action is executed successfully.
|
||||
|
||||
These flags can be combined (e.g., value `3` enables both).
|
||||
|
||||
## 8. Modbus Interface (`read/writeNetworkValue`)
|
||||
|
||||
* The `ModbusLogicEngine` implements `readNetworkValue` and `writeNetworkValue` as required by the `Component` base class.
|
||||
* These methods are called by `ModbusManager` when a Modbus client reads/writes registers within the engine's address range (`MODBUS_LOGIC_RULES_START` onwards).
|
||||
* They calculate the `ruleIndex` and `offset` from the requested Modbus address.
|
||||
* **Read:**
|
||||
* If the offset corresponds to a configuration register (0-8), it returns the value from `rule.config[offset]`.
|
||||
* If the offset is `FLAGS` (9), it returns the flag value via `getFlags()`.
|
||||
* If the offset corresponds to a status register (10-12), it returns the value from the internal state variables (`rule.lastStatus`, `rule.lastTriggerTimestamp & 0xFFFF`, `rule.triggerCount`).
|
||||
* **Write:**
|
||||
* If the offset corresponds to a configuration register (0-9, including FLAGS), it updates `rule.config[offset]` using `rule.setConfigValue()`.
|
||||
* If the offset is `TRIGGER_COUNT` (12) and the value is `0`, it resets `rule.triggerCount`.
|
||||
* Writes to read-only status registers (10, 11) are disallowed and return an error.
|
||||
@@ -0,0 +1,197 @@
|
||||
# Modbus Logic Engine (MB_SCRIPT) Functional Testing Strategy
|
||||
|
||||
## 1. Objective
|
||||
|
||||
To verify the functional correctness of the `ModbusLogicEngine` component (enabled by the `ENABLE_MB_SCRIPT` define). This includes:
|
||||
* Correct configuration of logic rules via Modbus.
|
||||
* Accurate evaluation of rule conditions based on Modbus register/coil values.
|
||||
* Proper execution of defined actions (writing registers/coils, calling component methods).
|
||||
* Correct reporting of rule status, trigger timestamps, and trigger counts via Modbus.
|
||||
* Handling of various error conditions.
|
||||
|
||||
## 2. Scope
|
||||
|
||||
This strategy focuses on black-box functional testing from the perspective of a Modbus client interacting with the device. It does not cover unit testing of individual `ModbusLogicEngine` methods or performance/stress testing (which may have separate test plans).
|
||||
|
||||
## 3. Approach
|
||||
|
||||
Testing will primarily utilize the existing `npm` scripts which wrap Python helper scripts to interact with the device over Modbus TCP.
|
||||
|
||||
* **Configuration:** Rules will be configured by writing to the specific Modbus holding registers allocated to the Logic Engine (`MODBUS_LOGIC_RULES_START` and subsequent offsets defined in `src/ModbusLogicEngine.h`).
|
||||
* **Triggering:** Rule conditions will be triggered by writing appropriate values to the source Modbus holding registers or coils specified in the rule's condition.
|
||||
* **Verification:**
|
||||
* Action outcomes will be verified by reading the target Modbus holding registers or coils.
|
||||
* Rule status, timestamps, and trigger counts will be verified by reading the corresponding status registers for the rule.
|
||||
* Internal behavior and potential errors can be monitored using device logs (`npm run build:monitor` or `npm run debug:serial`).
|
||||
* **No New Scripts:** This strategy aims to use only the pre-existing `npm` scripts for Modbus interaction.
|
||||
|
||||
## 4. Tools
|
||||
|
||||
* **Modbus Read/Write:**
|
||||
* `npm run modbus:read:holding -- --address <addr>`
|
||||
* `npm run modbus:write:holding -- --address <addr> --value <val>`
|
||||
* `npm run modbus:read:coil -- --address <addr>`
|
||||
* `npm run modbus:write:coil -- --address <addr> --value <0|1>`
|
||||
* **Logging:**
|
||||
* `npm run build:monitor` (Live serial monitoring)
|
||||
* `npm run debug:serial` (Fetch buffered logs via REST API)
|
||||
* **Reference:**
|
||||
* `src/ModbusLogicEngine.h` (for register offsets, enums)
|
||||
* `src/config-modbus.h` (for `MODBUS_LOGIC_RULES_START` address)
|
||||
|
||||
## 5. Prerequisites
|
||||
|
||||
* Firmware compiled with `#define ENABLE_MB_SCRIPT` in `config.h` and flashed to the ESP32.
|
||||
* Device connected to the network and accessible via its IP address or mDNS name (`modbus-esp32.local` by default).
|
||||
* Python environment configured correctly to run the scripts in the `scripts/` directory.
|
||||
* Knowledge of the Modbus register mapping for the Logic Engine (starting address + offsets).
|
||||
|
||||
## 6. Test Cases
|
||||
|
||||
Let `RULE_START = MODBUS_LOGIC_RULES_START`.
|
||||
Let `RULE_0_BASE = RULE_START`.
|
||||
Let `RULE_1_BASE = RULE_START + LOGIC_ENGINE_REGISTERS_PER_RULE`.
|
||||
Offsets are defined in `ModbusLogicEngineOffsets`.
|
||||
|
||||
*(Note: Choose suitable, otherwise unused Modbus addresses for source/target registers/coils for testing)*
|
||||
|
||||
**TC 1: Basic Rule - Write Holding Register**
|
||||
1. **Configure:**
|
||||
* Write `1` to `RULE_0_BASE + ENABLED`.
|
||||
* Write `3` (REG_HOLDING) to `RULE_0_BASE + COND_SRC_TYPE`.
|
||||
* Write `2000` to `RULE_0_BASE + COND_SRC_ADDR`.
|
||||
* Write `0` (EQUAL) to `RULE_0_BASE + COND_OPERATOR`.
|
||||
* Write `123` to `RULE_0_BASE + COND_VALUE`.
|
||||
* Write `3` (WRITE_HOLDING_REGISTER) to `RULE_0_BASE + COMMAND_TYPE`.
|
||||
* Write `2001` to `RULE_0_BASE + COMMAND_TARGET`.
|
||||
* Write `456` to `RULE_0_BASE + COMMAND_PARAM1` (Value to write).
|
||||
* Write `0` to `RULE_0_BASE + COMMAND_PARAM2` (Unused).
|
||||
* Write `0` to `RULE_0_BASE + FLAGS`.
|
||||
2. **Trigger:** Write `123` to Modbus address `2000`.
|
||||
3. **Verify:**
|
||||
* Read address `2001`. Expected: `456`.
|
||||
* Read `RULE_0_BASE + LAST_STATUS`. Expected: `0` (MB_Error::Success).
|
||||
* Read `RULE_0_BASE + TRIGGER_COUNT`. Expected: `1`.
|
||||
|
||||
**TC 2: Condition Operator - Not Equal**
|
||||
1. **Configure:** Similar to TC 1, but:
|
||||
* Write `1` (NOT_EQUAL) to `RULE_0_BASE + COND_OPERATOR`.
|
||||
* Write `123` to `RULE_0_BASE + COND_VALUE`.
|
||||
2. **Trigger 1:** Write `123` to address `2000`.
|
||||
3. **Verify 1:** Read address `2001`. Expected: *Unchanged* from previous state (action should *not* run).
|
||||
4. **Trigger 2:** Write `124` to address `2000`.
|
||||
5. **Verify 2:**
|
||||
* Read address `2001`. Expected: `456`.
|
||||
* Read `RULE_0_BASE + LAST_STATUS`. Expected: `0` (MB_Error::Success).
|
||||
* Read `RULE_0_BASE + TRIGGER_COUNT`. Expected: Incremented.
|
||||
* *(Repeat for other operators: `<`, `<=`, `>`, `>=`)*
|
||||
|
||||
**TC 3: Source Type - Coil**
|
||||
1. **Configure:** Similar to TC 1, but:
|
||||
* Write `2` (REG_COIL) to `RULE_0_BASE + COND_SRC_TYPE`.
|
||||
* Write `100` (Test Coil Addr) to `RULE_0_BASE + COND_SRC_ADDR`.
|
||||
* Write `1` to `RULE_0_BASE + COND_VALUE` (Condition is Coil ON).
|
||||
2. **Trigger:** Write `1` to Coil address `100`.
|
||||
3. **Verify:**
|
||||
* Read address `2001`. Expected: `456`.
|
||||
* Read `RULE_0_BASE + LAST_STATUS`. Expected: `0` (MB_Error::Success).
|
||||
|
||||
**TC 4: Action Type - Write Coil**
|
||||
1. **Configure:** Similar to TC 1, but:
|
||||
* Write `2` (WRITE_COIL) to `RULE_0_BASE + COMMAND_TYPE`.
|
||||
* Write `101` to `RULE_0_BASE + COMMAND_TARGET`.
|
||||
* Write `1` to `RULE_0_BASE + COMMAND_PARAM1` (Value: ON).
|
||||
* Write `0` to `RULE_0_BASE + COMMAND_PARAM2` (Unused).
|
||||
2. **Trigger:** Write `123` to address `2000`.
|
||||
3. **Verify:**
|
||||
* Read Coil address `101`. Expected: `1`.
|
||||
* Read `RULE_0_BASE + LAST_STATUS`. Expected: `0` (MB_Error::Success).
|
||||
|
||||
**TC 5: Action Type - Call Component Method (Requires Setup)**
|
||||
* **Prerequisite:** A method must be registered with the `ModbusLogicEngine` that has a verifiable side-effect readable via Modbus. Example: A simple method in `PHApp` that increments a counter stored in a Modbus register (`e.g., address 3000`).
|
||||
```cpp
|
||||
// In PHApp.h (or a test component)
|
||||
short testMethod(short p1, short p2) {
|
||||
testMethodCounter += p1; // Use p1 (arg1 from rule)
|
||||
Log.infoln("Test Method Called! Arg1=%d, Counter: %d", p1, testMethodCounter);
|
||||
return E_OK;
|
||||
}
|
||||
uint16_t testMethodCounter = 0;
|
||||
|
||||
// In PHApp::setup() or where ModbusLogicEngine is initialized
|
||||
logicEngine->registerMethod(this->id, 1, // Use app ID and method ID 1
|
||||
std::bind(&PHApp::testMethod, this, std::placeholders::_1, std::placeholders::_2));
|
||||
|
||||
// In PHApp::readNetworkValue()
|
||||
if (address == 3000) return testMethodCounter;
|
||||
```
|
||||
1. **Configure:** Similar to TC 1, but:
|
||||
* Write `100` (CALL_COMPONENT_METHOD) to `RULE_0_BASE + COMMAND_TYPE`.
|
||||
* Write `app->id` to `RULE_0_BASE + COMMAND_TARGET` (Component ID).
|
||||
* Write `1` to `RULE_0_BASE + COMMAND_PARAM1` (Method ID).
|
||||
* Write `5` to `RULE_0_BASE + COMMAND_PARAM2` (Argument 1).
|
||||
2. **Initial Read:** Read address `3000`. Note the value (e.g., `X`).
|
||||
3. **Trigger:** Write `123` to address `2000`.
|
||||
4. **Verify:**
|
||||
* Read address `3000`. Expected: `X + 5`.
|
||||
* Read `RULE_0_BASE + LAST_STATUS`. Expected: `0` (MB_Error::Success).
|
||||
* Check logs (`build:monitor`) for "Test Method Called! Arg1=5".
|
||||
|
||||
**TC 6: Rule Enable/Disable**
|
||||
1. **Configure:** Configure rule as in TC 1.
|
||||
2. **Disable:** Write `0` to `RULE_0_BASE + ENABLED`.
|
||||
3. **Trigger:** Write `123` to address `2000`.
|
||||
4. **Verify (Disabled):** Read address `2001`. Expected: *Unchanged*. Trigger count should *not* increment.
|
||||
5. **Enable:** Write `1` to `RULE_0_BASE + ENABLED`.
|
||||
6. **Trigger:** Write `123` to address `2000`.
|
||||
7. **Verify (Enabled):** Read address `2001`. Expected: `456`. Trigger count *should* increment.
|
||||
|
||||
**TC 7: Error - Invalid Condition Source Address**
|
||||
1. **Configure:** Similar to TC 1, but:
|
||||
* Write `9999` (Invalid/Unregistered address) to `RULE_0_BASE + COND_SRC_ADDR`.
|
||||
2. **Trigger:** Let the engine loop run.
|
||||
3. **Verify:** Read `RULE_0_BASE + LAST_STATUS`. Expected: `2` (MB_Error::IllegalDataAddress).
|
||||
|
||||
**TC 8: Error - Invalid Action Target Address (Write)**
|
||||
1. **Configure:** Similar to TC 1, but:
|
||||
* Write `9998` (Invalid/Unregistered address) to `RULE_0_BASE + COMMAND_TARGET`.
|
||||
2. **Trigger:** Write `123` to address `2000`.
|
||||
3. **Verify:** Read `RULE_0_BASE + LAST_STATUS`. Expected: `4` (MB_Error::ServerDeviceFailure).
|
||||
|
||||
**TC 9: Error - Invalid Action Target Method (Call)**
|
||||
1. **Configure:** Similar to TC 5, but:
|
||||
* Write `99` (Non-existent Method ID) to `RULE_0_BASE + COMMAND_PARAM1`.
|
||||
2. **Trigger:** Write `123` to address `2000`.
|
||||
3. **Verify:** Read `RULE_0_BASE + LAST_STATUS`. Expected: `2` (MB_Error::IllegalDataAddress).
|
||||
|
||||
**TC 10: Status/Counter Reset**
|
||||
1. **Configure & Trigger:** Perform TC 1.
|
||||
2. **Verify Count:** Read `RULE_0_BASE + TRIGGER_COUNT`. Expected: `1` (or current count).
|
||||
3. **Reset Counter:** Write `0` to `RULE_0_BASE + TRIGGER_COUNT`.
|
||||
4. **Verify Reset:** Read `RULE_0_BASE + TRIGGER_COUNT`. Expected: `0`.
|
||||
5. **Trigger Again:** Write `123` to address `2000`.
|
||||
6. **Verify Increment:** Read `RULE_0_BASE + TRIGGER_COUNT`. Expected: `1`.
|
||||
|
||||
**TC 11: Debug Flag**
|
||||
1. **Configure:** Configure as in TC 1, but:
|
||||
* Write `RULE_FLAG_DEBUG` (value 1) to `RULE_0_BASE + FLAGS`.
|
||||
2. **Trigger:** Write `123` to address `2000`.
|
||||
3. **Verify:**
|
||||
* Check logs (`build:monitor`). Expected: Verbose logs like "MLE Eval [0]: ...", "MLE Action [0]: ...".
|
||||
* Read address `2001`. Expected: `456`.
|
||||
|
||||
**TC 12: Receipt Flag**
|
||||
1. **Configure:** Configure as in TC 1, but:
|
||||
* Write `RULE_FLAG_RECEIPT` (value 2) to `RULE_0_BASE + FLAGS`.
|
||||
2. **Trigger:** Write `123` to address `2000`.
|
||||
3. **Verify:**
|
||||
* Check logs (`build:monitor`). Expected: Info log "MLE: Rule 0 action successful.".
|
||||
* Read address `2001`. Expected: `456`.
|
||||
|
||||
**TC 13: Debug + Receipt Flags**
|
||||
1. **Configure:** Configure as in TC 1, but:
|
||||
* Write `RULE_FLAG_DEBUG | RULE_FLAG_RECEIPT` (value 3) to `RULE_0_BASE + FLAGS`.
|
||||
2. **Trigger:** Write `123` to address `2000`.
|
||||
3. **Verify:**
|
||||
* Check logs (`build:monitor`). Expected: Both verbose debug logs and the receipt log.
|
||||
* Read address `2001`. Expected: `456`.
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
This directory is intended for project header files.
|
||||
|
||||
A header file is a file containing C declarations and macro definitions
|
||||
to be shared between several project source files. You request the use of a
|
||||
header file in your project source file (C, C++, etc) located in `src` folder
|
||||
by including it, with the C preprocessing directive `#include'.
|
||||
|
||||
```src/main.c
|
||||
|
||||
#include "header.h"
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Including a header file produces the same results as copying the header file
|
||||
into each source file that needs it. Such copying would be time-consuming
|
||||
and error-prone. With a header file, the related declarations appear
|
||||
in only one place. If they need to be changed, they can be changed in one
|
||||
place, and programs that include the header file will automatically use the
|
||||
new version when next recompiled. The header file eliminates the labor of
|
||||
finding and changing all the copies as well as the risk that a failure to
|
||||
find one copy will result in inconsistencies within a program.
|
||||
|
||||
In C, the convention is to give header files names that end with `.h'.
|
||||
|
||||
Read more about using header files in official GCC documentation:
|
||||
|
||||
* Include Syntax
|
||||
* Include Operation
|
||||
* Once-Only Headers
|
||||
* Computed Includes
|
||||
|
||||
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||
@@ -0,0 +1,5 @@
|
||||
// ArduinoJson - https://arduinojson.org
|
||||
// Copyright © 2014-2025, Benoit BLANCHON
|
||||
// MIT License
|
||||
|
||||
#include "src/ArduinoJson.h"
|
||||
@@ -0,0 +1,239 @@
|
||||
ArduinoJson: change log
|
||||
=======================
|
||||
|
||||
v7.3.1 (2025-02-27)
|
||||
------
|
||||
|
||||
* Fix conversion from static string to number
|
||||
* Slightly reduce code size
|
||||
|
||||
v7.3.0 (2024-12-29)
|
||||
------
|
||||
|
||||
* Fix support for NUL characters in `deserializeJson()`
|
||||
* Make `ElementProxy` and `MemberProxy` non-copyable
|
||||
* Change string copy policy: only string literal are stored by pointer
|
||||
* `JsonString` is now stored by copy, unless specified otherwise
|
||||
* Replace undocumented `JsonString::Ownership` with `bool`
|
||||
* Rename undocumented `JsonString::isLinked()` to `isStatic()`
|
||||
* Move public facing SFINAEs to template declarations
|
||||
|
||||
> ### BREAKING CHANGES
|
||||
>
|
||||
> In previous versions, `MemberProxy` (the class returned by `operator[]`) could lead to dangling pointers when used with a temporary string.
|
||||
> To prevent this issue, `MemberProxy` and `ElementProxy` are now non-copyable.
|
||||
>
|
||||
> Your code is likely to be affected if you use `auto` to store the result of `operator[]`. For example, the following line won't compile anymore:
|
||||
>
|
||||
> ```cpp
|
||||
> auto value = doc["key"];
|
||||
> ```
|
||||
>
|
||||
> To fix the issue, you must append either `.as<T>()` or `.to<T>()`, depending on the situation.
|
||||
>
|
||||
> For example, if you are extracting values from a JSON document, you should update like this:
|
||||
>
|
||||
> ```diff
|
||||
> - auto config = doc["config"];
|
||||
> + auto config = doc["config"].as<JsonObject>();
|
||||
> const char* name = config["name"];
|
||||
> ```
|
||||
>
|
||||
> However, if you are building a JSON document, you should update like this:
|
||||
>
|
||||
> ```diff
|
||||
> - auto config = doc["config"];
|
||||
> + auto config = doc["config"].to<JsonObject>();
|
||||
> config["name"] = "ArduinoJson";
|
||||
> ```
|
||||
|
||||
v7.2.1 (2024-11-15)
|
||||
------
|
||||
|
||||
* Forbid `deserializeJson(JsonArray|JsonObject, ...)` (issue #2135)
|
||||
* Fix VLA support in `JsonDocument::set()`
|
||||
* Fix `operator[](variant)` ignoring NUL characters
|
||||
|
||||
v7.2.0 (2024-09-18)
|
||||
------
|
||||
|
||||
* Store object members with two slots: one for the key and one for the value
|
||||
* Store 64-bit numbers (`double` and `long long`) in an additional slot
|
||||
* Reduce the slot size (see table below)
|
||||
* Improve message when user forgets third arg of `serializeJson()` et al.
|
||||
* Set `ARDUINOJSON_USE_DOUBLE` to `0` by default on 8-bit architectures
|
||||
* Deprecate `containsKey()` in favor of `doc["key"].is<T>()`
|
||||
* Add support for escape sequence `\'` (issue #2124)
|
||||
|
||||
| Architecture | before | after |
|
||||
|--------------|----------|----------|
|
||||
| 8-bit | 8 bytes | 6 bytes |
|
||||
| 32-bit | 16 bytes | 8 bytes |
|
||||
| 64-bit | 24 bytes | 16 bytes |
|
||||
|
||||
> ### BREAKING CHANGES
|
||||
>
|
||||
> After being on the death row for years, the `containsKey()` method has finally been deprecated.
|
||||
> You should replace `doc.containsKey("key")` with `doc["key"].is<T>()`, which not only checks that the key exists but also that the value is of the expected type.
|
||||
>
|
||||
> ```cpp
|
||||
> // Before
|
||||
> if (doc.containsKey("value")) {
|
||||
> int value = doc["value"];
|
||||
> // ...
|
||||
> }
|
||||
>
|
||||
> // After
|
||||
> if (doc["value"].is<int>()) {
|
||||
> int value = doc["value"];
|
||||
> // ...
|
||||
> }
|
||||
> ```
|
||||
|
||||
v7.1.0 (2024-06-27)
|
||||
------
|
||||
|
||||
* Add `ARDUINOJSON_STRING_LENGTH_SIZE` to the namespace name
|
||||
* Add support for MsgPack binary (PR #2078 by @Sanae6)
|
||||
* Add support for MsgPack extension
|
||||
* Make string support even more generic (PR #2084 by @d-a-v)
|
||||
* Optimize `deserializeMsgPack()`
|
||||
* Allow using a `JsonVariant` as a key or index (issue #2080)
|
||||
Note: works only for reading, not for writing
|
||||
* Support `ElementProxy` and `MemberProxy` in `JsonDocument`'s constructor
|
||||
* Don't add partial objects when allocation fails (issue #2081)
|
||||
* Read MsgPack's 64-bit integers even if `ARDUINOJSON_USE_LONG_LONG` is `0`
|
||||
(they are set to `null` if they don't fit in a `long`)
|
||||
|
||||
v7.0.4 (2024-03-12)
|
||||
------
|
||||
|
||||
* Make `JSON_STRING_SIZE(N)` return `N+1` to fix third-party code (issue #2054)
|
||||
|
||||
v7.0.3 (2024-02-05)
|
||||
------
|
||||
|
||||
* Improve error messages when using `char` or `char*` (issue #2043)
|
||||
* Reduce stack consumption (issue #2046)
|
||||
* Fix compatibility with GCC 4.8 (issue #2045)
|
||||
|
||||
v7.0.2 (2024-01-19)
|
||||
------
|
||||
|
||||
* Fix assertion `poolIndex < count_` after `JsonDocument::clear()` (issue #2034)
|
||||
|
||||
v7.0.1 (2024-01-10)
|
||||
------
|
||||
|
||||
* Fix "no matching function" with `JsonObjectConst::operator[]` (issue #2019)
|
||||
* Remove unused files in the PlatformIO package
|
||||
* Fix `volatile bool` serialized as `1` or `0` instead of `true` or `false` (issue #2029)
|
||||
|
||||
v7.0.0 (2024-01-03)
|
||||
------
|
||||
|
||||
* Remove `BasicJsonDocument`
|
||||
* Remove `StaticJsonDocument`
|
||||
* Add abstract `Allocator` class
|
||||
* Merge `DynamicJsonDocument` with `JsonDocument`
|
||||
* Remove `JSON_ARRAY_SIZE()`, `JSON_OBJECT_SIZE()`, and `JSON_STRING_SIZE()`
|
||||
* Remove `ARDUINOJSON_ENABLE_STRING_DEDUPLICATION` (string deduplication cannot be disabled anymore)
|
||||
* Remove `JsonDocument::capacity()`
|
||||
* Store the strings in the heap
|
||||
* Reference-count shared strings
|
||||
* Always store `serialized("string")` by copy (#1915)
|
||||
* Remove the zero-copy mode of `deserializeJson()` and `deserializeMsgPack()`
|
||||
* Fix double lookup in `to<JsonVariant>()`
|
||||
* Fix double call to `size()` in `serializeMsgPack()`
|
||||
* Include `ARDUINOJSON_SLOT_OFFSET_SIZE` in the namespace name
|
||||
* Remove `JsonVariant::shallowCopy()`
|
||||
* `JsonDocument`'s capacity grows as needed, no need to pass it to the constructor anymore
|
||||
* `JsonDocument`'s allocator is not monotonic anymore, removed values get recycled
|
||||
* Show a link to the documentation when user passes an unsupported input type
|
||||
* Remove `JsonDocument::memoryUsage()`
|
||||
* Remove `JsonDocument::garbageCollect()`
|
||||
* Add `deserializeJson(JsonVariant, ...)` and `deserializeMsgPack(JsonVariant, ...)` (#1226)
|
||||
* Call `shrinkToFit()` in `deserializeJson()` and `deserializeMsgPack()`
|
||||
* `serializeJson()` and `serializeMsgPack()` replace the content of `std::string` and `String` instead of appending to it
|
||||
* Replace `add()` with `add<T>()` (`add(T)` is still supported)
|
||||
* Remove `createNestedArray()` and `createNestedObject()` (use `to<JsonArray>()` and `to<JsonObject>()` instead)
|
||||
|
||||
> ### BREAKING CHANGES
|
||||
>
|
||||
> As every major release, ArduinoJson 7 introduces several breaking changes.
|
||||
> I added some stubs so that most existing programs should compile, but I highty recommend you upgrade your code.
|
||||
>
|
||||
> #### `JsonDocument`
|
||||
>
|
||||
> In ArduinoJson 6, you could allocate the memory pool on the stack (with `StaticJsonDocument`) or in the heap (with `DynamicJsonDocument`).
|
||||
> In ArduinoJson 7, the memory pool is always allocated in the heap, so `StaticJsonDocument` and `DynamicJsonDocument` have been merged into `JsonDocument`.
|
||||
>
|
||||
> In ArduinoJson 6, `JsonDocument` had a fixed capacity; in ArduinoJson 7, it has an elastic capacity that grows as needed.
|
||||
> Therefore, you don't need to specify the capacity anymore, so the macros `JSON_ARRAY_SIZE()`, `JSON_OBJECT_SIZE()`, and `JSON_STRING_SIZE()` have been removed.
|
||||
>
|
||||
> ```c++
|
||||
> // ArduinoJson 6
|
||||
> StaticJsonDocument<256> doc;
|
||||
> // or
|
||||
> DynamicJsonDocument doc(256);
|
||||
>
|
||||
> // ArduinoJson 7
|
||||
> JsonDocument doc;
|
||||
> ```
|
||||
>
|
||||
> In ArduinoJson 7, `JsonDocument` reuses released memory, so `garbageCollect()` has been removed.
|
||||
> `shrinkToFit()` is still available and releases the over-allocated memory.
|
||||
>
|
||||
> Due to a change in the implementation, it's not possible to store a pointer to a variant from another `JsonDocument`, so `shallowCopy()` has been removed.
|
||||
>
|
||||
> In ArduinoJson 6, the meaning of `memoryUsage()` was clear: it returned the number of bytes used in the memory pool.
|
||||
> In ArduinoJson 7, the meaning of `memoryUsage()` would be ambiguous, so it has been removed.
|
||||
>
|
||||
> #### Custom allocators
|
||||
>
|
||||
> In ArduinoJson 6, you could specify a custom allocator class as a template parameter of `BasicJsonDocument`.
|
||||
> In ArduinoJson 7, you must inherit from `ArduinoJson::Allocator` and pass a pointer to an instance of your class to the constructor of `JsonDocument`.
|
||||
>
|
||||
> ```c++
|
||||
> // ArduinoJson 6
|
||||
> class MyAllocator {
|
||||
> // ...
|
||||
> };
|
||||
> BasicJsonDocument<MyAllocator> doc(256);
|
||||
>
|
||||
> // ArduinoJson 7
|
||||
> class MyAllocator : public ArduinoJson::Allocator {
|
||||
> // ...
|
||||
> };
|
||||
> MyAllocator myAllocator;
|
||||
> JsonDocument doc(&myAllocator);
|
||||
> ```
|
||||
>
|
||||
> #### `createNestedArray()` and `createNestedObject()`
|
||||
>
|
||||
> In ArduinoJson 6, you could create a nested array or object with `createNestedArray()` and `createNestedObject()`.
|
||||
> In ArduinoJson 7, you must use `add<T>()` or `to<T>()` instead.
|
||||
>
|
||||
> For example, to create `[[],{}]`, you would write:
|
||||
>
|
||||
> ```c++
|
||||
> // ArduinoJson 6
|
||||
> arr.createNestedArray();
|
||||
> arr.createNestedObject();
|
||||
>
|
||||
> // ArduinoJson 7
|
||||
> arr.add<JsonArray>();
|
||||
> arr.add<JsonObject>();
|
||||
> ```
|
||||
>
|
||||
> And to create `{"array":[],"object":{}}`, you would write:
|
||||
>
|
||||
> ```c++
|
||||
> // ArduinoJson 6
|
||||
> obj.createNestedArray("array");
|
||||
> obj.createNestedObject("object");
|
||||
>
|
||||
> // ArduinoJson 7
|
||||
> obj["array"].to<JsonArray>();
|
||||
> obj["object"].to<JsonObject>();
|
||||
> ```
|
||||
@@ -0,0 +1,25 @@
|
||||
# ArduinoJson - https://arduinojson.org
|
||||
# Copyright © 2014-2025, Benoit BLANCHON
|
||||
# MIT License
|
||||
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
|
||||
if(ESP_PLATFORM)
|
||||
# Build ArduinoJson as an ESP-IDF component
|
||||
idf_component_register(INCLUDE_DIRS src)
|
||||
return()
|
||||
endif()
|
||||
|
||||
project(ArduinoJson VERSION 7.3.1)
|
||||
|
||||
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
|
||||
include(CTest)
|
||||
endif()
|
||||
|
||||
add_subdirectory(src)
|
||||
|
||||
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING)
|
||||
include(extras/CompileOptions.cmake)
|
||||
add_subdirectory(extras/tests)
|
||||
add_subdirectory(extras/fuzzing)
|
||||
endif()
|
||||
@@ -0,0 +1,10 @@
|
||||
# Contribution to ArduinoJson
|
||||
|
||||
First, thank you for taking the time to contribute to this project.
|
||||
|
||||
You can submit changes via GitHub Pull Requests.
|
||||
|
||||
Please:
|
||||
|
||||
1. Update the test suite for any change of behavior
|
||||
2. Use clang-format in "file" mode to format the code
|
||||
@@ -0,0 +1,10 @@
|
||||
The MIT License (MIT)
|
||||
---------------------
|
||||
|
||||
Copyright © 2014-2025, Benoit BLANCHON
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,158 @@
|
||||
<p align="center">
|
||||
<a href="https://arduinojson.org/"><img alt="ArduinoJson" src="https://arduinojson.org/images/logo.svg" width="200" /></a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
[](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A7.x)
|
||||
[](https://ci.appveyor.com/project/bblanchon/arduinojson/branch/7.x)
|
||||
[](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:arduinojson)
|
||||
[](https://coveralls.io/github/bblanchon/ArduinoJson?branch=7.x)
|
||||
[](https://github.com/bblanchon/ArduinoJson/stargazers)
|
||||
[](https://github.com/sponsors/bblanchon)
|
||||
|
||||
ArduinoJson is a C++ JSON library for Arduino and IoT (Internet Of Things).
|
||||
|
||||
## Features
|
||||
|
||||
* [JSON deserialization](https://arduinojson.org/v7/api/json/deserializejson/)
|
||||
* [Optionally decodes UTF-16 escape sequences to UTF-8](https://arduinojson.org/v7/api/config/decode_unicode/)
|
||||
* [Optionally supports comments in the input](https://arduinojson.org/v7/api/config/enable_comments/)
|
||||
* [Optionally filters the input to keep only desired values](https://arduinojson.org/v7/api/json/deserializejson/#filtering)
|
||||
* Supports single quotes as a string delimiter
|
||||
* Compatible with [NDJSON](http://ndjson.org/) and [JSON Lines](https://jsonlines.org/)
|
||||
* [JSON serialization](https://arduinojson.org/v7/api/json/serializejson/)
|
||||
* [Can write to a buffer or a stream](https://arduinojson.org/v7/api/json/serializejson/)
|
||||
* [Optionally indents the document (prettified JSON)](https://arduinojson.org/v7/api/json/serializejsonpretty/)
|
||||
* [MessagePack serialization](https://arduinojson.org/v7/api/msgpack/serializemsgpack/)
|
||||
* [MessagePack deserialization](https://arduinojson.org/v7/api/msgpack/deserializemsgpack/)
|
||||
* Efficient
|
||||
* [Twice smaller than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/)
|
||||
* [Almost 10% faster than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/)
|
||||
* [Consumes roughly 10% less RAM than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/)
|
||||
* [Deduplicates strings](https://arduinojson.org/news/2020/08/01/version-6-16-0/)
|
||||
* Versatile
|
||||
* Supports [custom allocators (to use external RAM chip, for example)](https://arduinojson.org/v7/how-to/use-external-ram-on-esp32/)
|
||||
* Supports [`String`](https://arduinojson.org/v7/api/config/enable_arduino_string/), [`std::string`](https://arduinojson.org/v7/api/config/enable_std_string/), and [`std::string_view`](https://arduinojson.org/v7/api/config/enable_string_view/)
|
||||
* Supports [`Stream`](https://arduinojson.org/v7/api/config/enable_arduino_stream/) and [`std::istream`/`std::ostream`](https://arduinojson.org/v7/api/config/enable_std_stream/)
|
||||
* Supports [Flash strings](https://arduinojson.org/v7/api/config/enable_progmem/)
|
||||
* Supports [custom readers](https://arduinojson.org/v7/api/json/deserializejson/#custom-reader) and [custom writers](https://arduinojson.org/v7/api/json/serializejson/#custom-writer)
|
||||
* Supports [custom converters](https://arduinojson.org/news/2021/05/04/version-6-18-0/)
|
||||
* Portable
|
||||
* Usable on any C++ project (not limited to Arduino)
|
||||
* Compatible with C++11, C++14 and C++17
|
||||
* Support for C++98/C++03 available on [ArduinoJson 6.20.x](https://github.com/bblanchon/ArduinoJson/tree/6.20.x)
|
||||
* Zero warnings with `-Wall -Wextra -pedantic` and `/W4`
|
||||
* [Header-only library](https://en.wikipedia.org/wiki/Header-only)
|
||||
* Works with virtually any board
|
||||
* Arduino boards: [Uno](https://amzn.to/38aL2ik), [Due](https://amzn.to/36YkWi2), [Micro](https://amzn.to/35WkdwG), [Nano](https://amzn.to/2QTvwRX), [Mega](https://amzn.to/36XWhuf), [Yun](https://amzn.to/30odURc), [Leonardo](https://amzn.to/36XWjlR)...
|
||||
* Espressif chips: [ESP8266](https://amzn.to/36YluV8), [ESP32](https://amzn.to/2G4pRCB)
|
||||
* Lolin (WeMos) boards: [D1 mini](https://amzn.to/2QUpz7q), [D1 Mini Pro](https://amzn.to/36UsGSs)...
|
||||
* Teensy boards: [4.0](https://amzn.to/30ljXGq), [3.2](https://amzn.to/2FT0EuC), [2.0](https://amzn.to/2QXUMXj)
|
||||
* Particle boards: [Argon](https://amzn.to/2FQHa9X), [Boron](https://amzn.to/36WgLUd), [Electron](https://amzn.to/30vEc4k), [Photon](https://amzn.to/387F9Cd)...
|
||||
* Texas Instruments boards: [MSP430](https://amzn.to/30nJWgg)...
|
||||
* Soft cores: [Nios II](https://en.wikipedia.org/wiki/Nios_II)...
|
||||
* Tested on all major development environments
|
||||
* [Arduino IDE](https://www.arduino.cc/en/Main/Software)
|
||||
* [Atmel Studio](http://www.atmel.com/microsite/atmel-studio/)
|
||||
* [Atollic TrueSTUDIO](https://atollic.com/truestudio/)
|
||||
* [Energia](http://energia.nu/)
|
||||
* [IAR Embedded Workbench](https://www.iar.com/iar-embedded-workbench/)
|
||||
* [Keil uVision](http://www.keil.com/)
|
||||
* [MPLAB X IDE](http://www.microchip.com/mplab/mplab-x-ide)
|
||||
* [Particle](https://www.particle.io/)
|
||||
* [PlatformIO](http://platformio.org/)
|
||||
* [Sloeber plugin for Eclipse](https://eclipse.baeyens.it/)
|
||||
* [Visual Micro](http://www.visualmicro.com/)
|
||||
* [Visual Studio](https://www.visualstudio.com/)
|
||||
* [Even works with online compilers like wandbox.org](https://wandbox.org/permlink/RlZSKy17DjJ6HcdN)
|
||||
* [CMake friendly](https://arduinojson.org/v7/how-to/use-arduinojson-with-cmake/)
|
||||
* Well designed
|
||||
* [Elegant API](http://arduinojson.org/v7/example/)
|
||||
* [Thread-safe](https://en.wikipedia.org/wiki/Thread_safety)
|
||||
* Self-contained (no external dependency)
|
||||
* `const` friendly
|
||||
* [`for` friendly](https://arduinojson.org/v7/api/jsonobject/begin_end/)
|
||||
* [TMP friendly](https://en.wikipedia.org/wiki/Template_metaprogramming)
|
||||
* Handles [integer overflows](https://arduinojson.org/v7/api/jsonvariant/as/#integer-overflows)
|
||||
* Well tested
|
||||
* [Unit test coverage close to 100%](https://coveralls.io/github/bblanchon/ArduinoJson?branch=7.x)
|
||||
* Continuously tested on
|
||||
* [Visual Studio 2017, 2019, 2022](https://ci.appveyor.com/project/bblanchon/arduinojson/branch/7.x)
|
||||
* [GCC 4.8, 5, 6, 7, 8, 9, 10, 11, 12](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22)
|
||||
* [Clang 3.9, 4.0, 5.0, 6.0, 7, 8, 9, 10, 11, 12, 13, 14, 15](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22)
|
||||
* [Continuously fuzzed with Google OSS Fuzz](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:arduinojson)
|
||||
* Passes all default checks of [clang-tidy](https://releases.llvm.org/10.0.0/tools/clang/tools/extra/docs/clang-tidy/)
|
||||
* Well documented
|
||||
* [Tutorials](https://arduinojson.org/v7/doc/deserialization/)
|
||||
* [Examples](https://arduinojson.org/v7/example/)
|
||||
* [How-tos](https://arduinojson.org/v7/example/)
|
||||
* [FAQ](https://arduinojson.org/v7/faq/)
|
||||
* [Troubleshooter](https://arduinojson.org/v7/troubleshooter/)
|
||||
* [Book](https://arduinojson.org/book/)
|
||||
* [Changelog](CHANGELOG.md)
|
||||
* Vibrant user community
|
||||
* Most popular of all Arduino libraries on [GitHub](https://github.com/search?o=desc&q=arduino+library&s=stars&type=Repositories)
|
||||
* [Used in hundreds of projects](https://www.hackster.io/search?i=projects&q=arduinojson)
|
||||
* [Responsive support](https://github.com/bblanchon/ArduinoJson/issues?q=is%3Aissue+is%3Aclosed)
|
||||
|
||||
## Quickstart
|
||||
|
||||
### Deserialization
|
||||
|
||||
Here is a program that parses a JSON document with ArduinoJson.
|
||||
|
||||
```c++
|
||||
const char* json = "{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038]}";
|
||||
|
||||
JsonDocument doc;
|
||||
deserializeJson(doc, json);
|
||||
|
||||
const char* sensor = doc["sensor"];
|
||||
long time = doc["time"];
|
||||
double latitude = doc["data"][0];
|
||||
double longitude = doc["data"][1];
|
||||
```
|
||||
|
||||
See the [tutorial on arduinojson.org](https://arduinojson.org/v7/doc/deserialization/)
|
||||
|
||||
### Serialization
|
||||
|
||||
Here is a program that generates a JSON document with ArduinoJson:
|
||||
|
||||
```c++
|
||||
JsonDocument doc;
|
||||
|
||||
doc["sensor"] = "gps";
|
||||
doc["time"] = 1351824120;
|
||||
doc["data"][0] = 48.756080;
|
||||
doc["data"][1] = 2.302038;
|
||||
|
||||
serializeJson(doc, Serial);
|
||||
// This prints:
|
||||
// {"sensor":"gps","time":1351824120,"data":[48.756080,2.302038]}
|
||||
```
|
||||
|
||||
See the [tutorial on arduinojson.org](https://arduinojson.org/v7/doc/serialization/)
|
||||
|
||||
## Sponsors
|
||||
|
||||
ArduinoJson is thankful to its sponsors. Please give them a visit; they deserve it!
|
||||
|
||||
<p>
|
||||
<a href="https://www.programmingelectronics.com/" rel="sponsored">
|
||||
<img src="https://arduinojson.org/images/2021/10/programmingeleactronicsacademy.png" alt="Programming Electronics Academy" width="200">
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://github.com/1technophile" rel="sponsored">
|
||||
<img alt="1technophile" src="https://avatars.githubusercontent.com/u/12672732?s=40&v=4">
|
||||
</a>
|
||||
<a href="https://github.com/LArkema" rel="sponsored">
|
||||
<img alt="LArkema" src="https://avatars.githubusercontent.com/u/38381313?s=40&v=4">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
If you run a commercial project that embeds ArduinoJson, think about [sponsoring the library's development](https://github.com/sponsors/bblanchon): it ensures the code that your products rely on stays actively maintained. It can also give your project some exposure to the makers' community.
|
||||
|
||||
If you are an individual user and want to support the development (or give a sign of appreciation), consider purchasing the book [Mastering ArduinoJson](https://arduinojson.org/book/) ❤, or simply [cast a star](https://github.com/bblanchon/ArduinoJson/stargazers) ⭐.
|
||||
@@ -0,0 +1,27 @@
|
||||
# ArduinoJson Support
|
||||
|
||||
First off, thank you very much for using ArduinoJson.
|
||||
|
||||
We'll be very happy to help you, but first please read the following.
|
||||
|
||||
## Before asking for help
|
||||
|
||||
1. Read the [FAQ](https://arduinojson.org/faq/?utm_source=github&utm_medium=support)
|
||||
2. Search in the [API Reference](https://arduinojson.org/api/?utm_source=github&utm_medium=support)
|
||||
|
||||
If you did not find the answer, please create a [new issue on GitHub](https://github.com/bblanchon/ArduinoJson/issues/new).
|
||||
|
||||
It is OK to add a comment to a currently opened issue, but please avoid adding comments to a closed issue.
|
||||
|
||||
## Before hitting the Submit button
|
||||
|
||||
Please provide all the relevant information:
|
||||
|
||||
* Good title
|
||||
* Short description of the problem
|
||||
* Target platform
|
||||
* Compiler model and version
|
||||
* [MVCE](https://stackoverflow.com/help/mcve)
|
||||
* Compiler output
|
||||
|
||||
Good questions get fast answers!
|
||||
@@ -0,0 +1,28 @@
|
||||
version: 7.3.1.{build}
|
||||
environment:
|
||||
matrix:
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
|
||||
CMAKE_GENERATOR: Visual Studio 17 2022
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
CMAKE_GENERATOR: Visual Studio 16 2019
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
CMAKE_GENERATOR: Visual Studio 15 2017
|
||||
- CMAKE_GENERATOR: Ninja
|
||||
MINGW32: i686-6.3.0-posix-dwarf-rt_v5-rev1 # MinGW-w64 6.3.0 i686
|
||||
- CMAKE_GENERATOR: Ninja
|
||||
MINGW64: x86_64-6.3.0-posix-seh-rt_v5-rev1 # MinGW-w64 6.3.0 x86_64
|
||||
- CMAKE_GENERATOR: Ninja
|
||||
MINGW64: x86_64-7.3.0-posix-seh-rt_v5-rev0 # MinGW-w64 7.3.0 x86_64
|
||||
- CMAKE_GENERATOR: Ninja
|
||||
MINGW64: x86_64-8.1.0-posix-seh-rt_v6-rev0 # MinGW-w64 8.1.0 x86_64
|
||||
configuration: Debug
|
||||
before_build:
|
||||
- set PATH=%PATH:C:\Program Files\Git\usr\bin;=% # Workaround for CMake not wanting sh.exe on PATH for MinGW
|
||||
- if defined MINGW set PATH=C:\%MINGW%\bin;%PATH%
|
||||
- if defined MINGW32 set PATH=C:\mingw-w64\%MINGW32%\mingw32\bin;%PATH%
|
||||
- if defined MINGW64 set PATH=C:\mingw-w64\%MINGW64%\mingw64\bin;%PATH%
|
||||
- cmake -DCMAKE_BUILD_TYPE=%CONFIGURATION% -G "%CMAKE_GENERATOR%" .
|
||||
build_script:
|
||||
- cmake --build . --config %CONFIGURATION%
|
||||
test_script:
|
||||
- ctest -C %CONFIGURATION% --output-on-failure .
|
||||
@@ -0,0 +1 @@
|
||||
COMPONENT_ADD_INCLUDEDIRS := src
|
||||
@@ -0,0 +1,152 @@
|
||||
// ArduinoJson - https://arduinojson.org
|
||||
// Copyright © 2014-2025, Benoit BLANCHON
|
||||
// MIT License
|
||||
//
|
||||
// This example shows how to store your project configuration in a file.
|
||||
// It uses the SD library but can be easily modified for any other file-system.
|
||||
//
|
||||
// The file contains a JSON document with the following content:
|
||||
// {
|
||||
// "hostname": "examples.com",
|
||||
// "port": 2731
|
||||
// }
|
||||
//
|
||||
// To run this program, you need an SD card connected to the SPI bus as follows:
|
||||
// * MOSI <-> pin 11
|
||||
// * MISO <-> pin 12
|
||||
// * CLK <-> pin 13
|
||||
// * CS <-> pin 4
|
||||
//
|
||||
// https://arduinojson.org/v7/example/config/
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <SD.h>
|
||||
#include <SPI.h>
|
||||
|
||||
// Our configuration structure.
|
||||
struct Config {
|
||||
char hostname[64];
|
||||
int port;
|
||||
};
|
||||
|
||||
const char* filename = "/config.txt"; // <- SD library uses 8.3 filenames
|
||||
Config config; // <- global configuration object
|
||||
|
||||
// Loads the configuration from a file
|
||||
void loadConfiguration(const char* filename, Config& config) {
|
||||
// Open file for reading
|
||||
File file = SD.open(filename);
|
||||
|
||||
// Allocate a temporary JsonDocument
|
||||
JsonDocument doc;
|
||||
|
||||
// Deserialize the JSON document
|
||||
DeserializationError error = deserializeJson(doc, file);
|
||||
if (error)
|
||||
Serial.println(F("Failed to read file, using default configuration"));
|
||||
|
||||
// Copy values from the JsonDocument to the Config
|
||||
config.port = doc["port"] | 2731;
|
||||
strlcpy(config.hostname, // <- destination
|
||||
doc["hostname"] | "example.com", // <- source
|
||||
sizeof(config.hostname)); // <- destination's capacity
|
||||
|
||||
// Close the file (Curiously, File's destructor doesn't close the file)
|
||||
file.close();
|
||||
}
|
||||
|
||||
// Saves the configuration to a file
|
||||
void saveConfiguration(const char* filename, const Config& config) {
|
||||
// Delete existing file, otherwise the configuration is appended to the file
|
||||
SD.remove(filename);
|
||||
|
||||
// Open file for writing
|
||||
File file = SD.open(filename, FILE_WRITE);
|
||||
if (!file) {
|
||||
Serial.println(F("Failed to create file"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate a temporary JsonDocument
|
||||
JsonDocument doc;
|
||||
|
||||
// Set the values in the document
|
||||
doc["hostname"] = config.hostname;
|
||||
doc["port"] = config.port;
|
||||
|
||||
// Serialize JSON to file
|
||||
if (serializeJson(doc, file) == 0) {
|
||||
Serial.println(F("Failed to write to file"));
|
||||
}
|
||||
|
||||
// Close the file
|
||||
file.close();
|
||||
}
|
||||
|
||||
// Prints the content of a file to the Serial
|
||||
void printFile(const char* filename) {
|
||||
// Open file for reading
|
||||
File file = SD.open(filename);
|
||||
if (!file) {
|
||||
Serial.println(F("Failed to read file"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract each characters by one by one
|
||||
while (file.available()) {
|
||||
Serial.print((char)file.read());
|
||||
}
|
||||
Serial.println();
|
||||
|
||||
// Close the file
|
||||
file.close();
|
||||
}
|
||||
|
||||
void setup() {
|
||||
// Initialize serial port
|
||||
Serial.begin(9600);
|
||||
while (!Serial)
|
||||
continue;
|
||||
|
||||
// Initialize SD library
|
||||
const int chipSelect = 4;
|
||||
while (!SD.begin(chipSelect)) {
|
||||
Serial.println(F("Failed to initialize SD library"));
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
// Should load default config if run for the first time
|
||||
Serial.println(F("Loading configuration..."));
|
||||
loadConfiguration(filename, config);
|
||||
|
||||
// Create configuration file
|
||||
Serial.println(F("Saving configuration..."));
|
||||
saveConfiguration(filename, config);
|
||||
|
||||
// Dump config file
|
||||
Serial.println(F("Print config file..."));
|
||||
printFile(filename);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// not used in this example
|
||||
}
|
||||
|
||||
// Performance issue?
|
||||
// ------------------
|
||||
//
|
||||
// File is an unbuffered stream, which is not optimal for ArduinoJson.
|
||||
// See: https://arduinojson.org/v7/how-to/improve-speed/
|
||||
|
||||
// See also
|
||||
// --------
|
||||
//
|
||||
// https://arduinojson.org/ contains the documentation for all the functions
|
||||
// used above. It also includes an FAQ that will help you solve any
|
||||
// serialization or deserialization problem.
|
||||
//
|
||||
// The book "Mastering ArduinoJson" contains a case study of a project that has
|
||||
// a complex configuration with nested members.
|
||||
// Contrary to this example, the project in the book uses the SPIFFS filesystem.
|
||||
// Learn more at https://arduinojson.org/book/
|
||||
// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤
|
||||
@@ -0,0 +1,64 @@
|
||||
// ArduinoJson - https://arduinojson.org
|
||||
// Copyright © 2014-2025, Benoit BLANCHON
|
||||
// MIT License
|
||||
//
|
||||
// This example shows how to use DeserializationOption::Filter
|
||||
//
|
||||
// https://arduinojson.org/v7/example/filter/
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
void setup() {
|
||||
// Initialize serial port
|
||||
Serial.begin(9600);
|
||||
while (!Serial)
|
||||
continue;
|
||||
|
||||
// The huge input: an extract from OpenWeatherMap response
|
||||
auto input_json = F(
|
||||
"{\"cod\":\"200\",\"message\":0,\"list\":[{\"dt\":1581498000,\"main\":{"
|
||||
"\"temp\":3.23,\"feels_like\":-3.63,\"temp_min\":3.23,\"temp_max\":4.62,"
|
||||
"\"pressure\":1014,\"sea_level\":1014,\"grnd_level\":1010,\"humidity\":"
|
||||
"58,\"temp_kf\":-1.39},\"weather\":[{\"id\":800,\"main\":\"Clear\","
|
||||
"\"description\":\"clear "
|
||||
"sky\",\"icon\":\"01d\"}],\"clouds\":{\"all\":0},\"wind\":{\"speed\":6."
|
||||
"19,\"deg\":266},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-12 "
|
||||
"09:00:00\"},{\"dt\":1581508800,\"main\":{\"temp\":6.09,\"feels_like\":-"
|
||||
"1.07,\"temp_min\":6.09,\"temp_max\":7.13,\"pressure\":1015,\"sea_"
|
||||
"level\":1015,\"grnd_level\":1011,\"humidity\":48,\"temp_kf\":-1.04},"
|
||||
"\"weather\":[{\"id\":800,\"main\":\"Clear\",\"description\":\"clear "
|
||||
"sky\",\"icon\":\"01d\"}],\"clouds\":{\"all\":9},\"wind\":{\"speed\":6."
|
||||
"64,\"deg\":268},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2020-02-12 "
|
||||
"12:00:00\"}],\"city\":{\"id\":2643743,\"name\":\"London\",\"coord\":{"
|
||||
"\"lat\":51.5085,\"lon\":-0.1257},\"country\":\"GB\",\"population\":"
|
||||
"1000000,\"timezone\":0,\"sunrise\":1581492085,\"sunset\":1581527294}}");
|
||||
|
||||
// The filter: it contains "true" for each value we want to keep
|
||||
JsonDocument filter;
|
||||
filter["list"][0]["dt"] = true;
|
||||
filter["list"][0]["main"]["temp"] = true;
|
||||
|
||||
// Deserialize the document
|
||||
JsonDocument doc;
|
||||
deserializeJson(doc, input_json, DeserializationOption::Filter(filter));
|
||||
|
||||
// Print the result
|
||||
serializeJsonPretty(doc, Serial);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// not used in this example
|
||||
}
|
||||
|
||||
// See also
|
||||
// --------
|
||||
//
|
||||
// https://arduinojson.org/ contains the documentation for all the functions
|
||||
// used above. It also includes an FAQ that will help you solve any
|
||||
// deserialization problem.
|
||||
//
|
||||
// The book "Mastering ArduinoJson" contains a tutorial on deserialization.
|
||||
// It begins with a simple example, like the one above, and then adds more
|
||||
// features like deserializing directly from a file or an HTTP request.
|
||||
// Learn more at https://arduinojson.org/book/
|
||||
// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤
|
||||
@@ -0,0 +1,65 @@
|
||||
// ArduinoJson - https://arduinojson.org
|
||||
// Copyright © 2014-2025, Benoit BLANCHON
|
||||
// MIT License
|
||||
//
|
||||
// This example shows how to generate a JSON document with ArduinoJson.
|
||||
//
|
||||
// https://arduinojson.org/v7/example/generator/
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
void setup() {
|
||||
// Initialize Serial port
|
||||
Serial.begin(9600);
|
||||
while (!Serial)
|
||||
continue;
|
||||
|
||||
// Allocate the JSON document
|
||||
JsonDocument doc;
|
||||
|
||||
// Add values in the document
|
||||
doc["sensor"] = "gps";
|
||||
doc["time"] = 1351824120;
|
||||
|
||||
// Add an array
|
||||
JsonArray data = doc["data"].to<JsonArray>();
|
||||
data.add(48.756080);
|
||||
data.add(2.302038);
|
||||
|
||||
// Generate the minified JSON and send it to the Serial port
|
||||
serializeJson(doc, Serial);
|
||||
// The above line prints:
|
||||
// {"sensor":"gps","time":1351824120,"data":[48.756080,2.302038]}
|
||||
|
||||
// Start a new line
|
||||
Serial.println();
|
||||
|
||||
// Generate the prettified JSON and send it to the Serial port
|
||||
serializeJsonPretty(doc, Serial);
|
||||
// The above line prints:
|
||||
// {
|
||||
// "sensor": "gps",
|
||||
// "time": 1351824120,
|
||||
// "data": [
|
||||
// 48.756080,
|
||||
// 2.302038
|
||||
// ]
|
||||
// }
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// not used in this example
|
||||
}
|
||||
|
||||
// See also
|
||||
// --------
|
||||
//
|
||||
// https://arduinojson.org/ contains the documentation for all the functions
|
||||
// used above. It also includes an FAQ that will help you solve any
|
||||
// serialization problem.
|
||||
//
|
||||
// The book "Mastering ArduinoJson" contains a tutorial on serialization.
|
||||
// It begins with a simple example, like the one above, and then adds more
|
||||
// features like serializing directly to a file or an HTTP request.
|
||||
// Learn more at https://arduinojson.org/book/
|
||||
// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤
|
||||
@@ -0,0 +1,125 @@
|
||||
// ArduinoJson - https://arduinojson.org
|
||||
// Copyright © 2014-2025, Benoit BLANCHON
|
||||
// MIT License
|
||||
//
|
||||
// This example shows how to parse a JSON document in an HTTP response.
|
||||
// It uses the Ethernet library, but can be easily adapted for Wifi.
|
||||
//
|
||||
// It performs a GET resquest on https://arduinojson.org/example.json
|
||||
// Here is the expected response:
|
||||
// {
|
||||
// "sensor": "gps",
|
||||
// "time": 1351824120,
|
||||
// "data": [
|
||||
// 48.756080,
|
||||
// 2.302038
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
// https://arduinojson.org/v7/example/http-client/
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <Ethernet.h>
|
||||
#include <SPI.h>
|
||||
|
||||
void setup() {
|
||||
// Initialize Serial port
|
||||
Serial.begin(9600);
|
||||
while (!Serial)
|
||||
continue;
|
||||
|
||||
// Initialize Ethernet library
|
||||
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
|
||||
if (!Ethernet.begin(mac)) {
|
||||
Serial.println(F("Failed to configure Ethernet"));
|
||||
return;
|
||||
}
|
||||
delay(1000);
|
||||
|
||||
Serial.println(F("Connecting..."));
|
||||
|
||||
// Connect to HTTP server
|
||||
EthernetClient client;
|
||||
client.setTimeout(10000);
|
||||
if (!client.connect("arduinojson.org", 80)) {
|
||||
Serial.println(F("Connection failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.println(F("Connected!"));
|
||||
|
||||
// Send HTTP request
|
||||
client.println(F("GET /example.json HTTP/1.0"));
|
||||
client.println(F("Host: arduinojson.org"));
|
||||
client.println(F("Connection: close"));
|
||||
if (client.println() == 0) {
|
||||
Serial.println(F("Failed to send request"));
|
||||
client.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check HTTP status
|
||||
char status[32] = {0};
|
||||
client.readBytesUntil('\r', status, sizeof(status));
|
||||
// It should be "HTTP/1.0 200 OK" or "HTTP/1.1 200 OK"
|
||||
if (strcmp(status + 9, "200 OK") != 0) {
|
||||
Serial.print(F("Unexpected response: "));
|
||||
Serial.println(status);
|
||||
client.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip HTTP headers
|
||||
char endOfHeaders[] = "\r\n\r\n";
|
||||
if (!client.find(endOfHeaders)) {
|
||||
Serial.println(F("Invalid response"));
|
||||
client.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate the JSON document
|
||||
JsonDocument doc;
|
||||
|
||||
// Parse JSON object
|
||||
DeserializationError error = deserializeJson(doc, client);
|
||||
if (error) {
|
||||
Serial.print(F("deserializeJson() failed: "));
|
||||
Serial.println(error.f_str());
|
||||
client.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract values
|
||||
Serial.println(F("Response:"));
|
||||
Serial.println(doc["sensor"].as<const char*>());
|
||||
Serial.println(doc["time"].as<long>());
|
||||
Serial.println(doc["data"][0].as<float>(), 6);
|
||||
Serial.println(doc["data"][1].as<float>(), 6);
|
||||
|
||||
// Disconnect
|
||||
client.stop();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// not used in this example
|
||||
}
|
||||
|
||||
// Performance issue?
|
||||
// ------------------
|
||||
//
|
||||
// EthernetClient is an unbuffered stream, which is not optimal for ArduinoJson.
|
||||
// See: https://arduinojson.org/v7/how-to/improve-speed/
|
||||
|
||||
// See also
|
||||
// --------
|
||||
//
|
||||
// https://arduinojson.org/ contains the documentation for all the functions
|
||||
// used above. It also includes an FAQ that will help you solve any
|
||||
// serialization problem.
|
||||
//
|
||||
// The book "Mastering ArduinoJson" contains a tutorial on deserialization
|
||||
// showing how to parse the response from GitHub's API. In the last chapter,
|
||||
// it shows how to parse the huge documents from OpenWeatherMap
|
||||
// and Reddit.
|
||||
// Learn more at https://arduinojson.org/book/
|
||||
// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤
|
||||
@@ -0,0 +1,65 @@
|
||||
// ArduinoJson - https://arduinojson.org
|
||||
// Copyright © 2014-2025, Benoit BLANCHON
|
||||
// MIT License
|
||||
//
|
||||
// This example shows how to deserialize a JSON document with ArduinoJson.
|
||||
//
|
||||
// https://arduinojson.org/v7/example/parser/
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
void setup() {
|
||||
// Initialize serial port
|
||||
Serial.begin(9600);
|
||||
while (!Serial)
|
||||
continue;
|
||||
|
||||
// Allocate the JSON document
|
||||
JsonDocument doc;
|
||||
|
||||
// JSON input string.
|
||||
const char* json =
|
||||
"{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038]}";
|
||||
|
||||
// Deserialize the JSON document
|
||||
DeserializationError error = deserializeJson(doc, json);
|
||||
|
||||
// Test if parsing succeeds
|
||||
if (error) {
|
||||
Serial.print(F("deserializeJson() failed: "));
|
||||
Serial.println(error.f_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch the values
|
||||
//
|
||||
// Most of the time, you can rely on the implicit casts.
|
||||
// In other case, you can do doc["time"].as<long>();
|
||||
const char* sensor = doc["sensor"];
|
||||
long time = doc["time"];
|
||||
double latitude = doc["data"][0];
|
||||
double longitude = doc["data"][1];
|
||||
|
||||
// Print the values
|
||||
Serial.println(sensor);
|
||||
Serial.println(time);
|
||||
Serial.println(latitude, 6);
|
||||
Serial.println(longitude, 6);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// not used in this example
|
||||
}
|
||||
|
||||
// See also
|
||||
// --------
|
||||
//
|
||||
// https://arduinojson.org/ contains the documentation for all the functions
|
||||
// used above. It also includes an FAQ that will help you solve any
|
||||
// deserialization problem.
|
||||
//
|
||||
// The book "Mastering ArduinoJson" contains a tutorial on deserialization.
|
||||
// It begins with a simple example, like the one above, and then adds more
|
||||
// features like deserializing directly from a file or an HTTP request.
|
||||
// Learn more at https://arduinojson.org/book/
|
||||
// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤
|
||||
@@ -0,0 +1,118 @@
|
||||
// ArduinoJson - https://arduinojson.org
|
||||
// Copyright © 2014-2025, Benoit BLANCHON
|
||||
// MIT License
|
||||
//
|
||||
// This example shows how to implement an HTTP server that sends a JSON document
|
||||
// in the response.
|
||||
// It uses the Ethernet library but can be easily adapted for Wifi.
|
||||
//
|
||||
// The JSON document contains the values of the analog and digital pins.
|
||||
// It looks like that:
|
||||
// {
|
||||
// "analog": [0, 76, 123, 158, 192, 205],
|
||||
// "digital": [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0]
|
||||
// }
|
||||
//
|
||||
// https://arduinojson.org/v7/example/http-server/
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <Ethernet.h>
|
||||
#include <SPI.h>
|
||||
|
||||
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
|
||||
EthernetServer server(80);
|
||||
|
||||
void setup() {
|
||||
// Initialize serial port
|
||||
Serial.begin(9600);
|
||||
while (!Serial)
|
||||
continue;
|
||||
|
||||
// Initialize Ethernet libary
|
||||
if (!Ethernet.begin(mac)) {
|
||||
Serial.println(F("Failed to initialize Ethernet library"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Start to listen
|
||||
server.begin();
|
||||
|
||||
Serial.println(F("Server is ready."));
|
||||
Serial.print(F("Please connect to http://"));
|
||||
Serial.println(Ethernet.localIP());
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Wait for an incomming connection
|
||||
EthernetClient client = server.available();
|
||||
|
||||
// Do we have a client?
|
||||
if (!client)
|
||||
return;
|
||||
|
||||
Serial.println(F("New client"));
|
||||
|
||||
// Read the request (we ignore the content in this example)
|
||||
while (client.available())
|
||||
client.read();
|
||||
|
||||
// Allocate a temporary JsonDocument
|
||||
JsonDocument doc;
|
||||
|
||||
// Create the "analog" array
|
||||
JsonArray analogValues = doc["analog"].to<JsonArray>();
|
||||
for (int pin = 0; pin < 6; pin++) {
|
||||
// Read the analog input
|
||||
int value = analogRead(pin);
|
||||
|
||||
// Add the value at the end of the array
|
||||
analogValues.add(value);
|
||||
}
|
||||
|
||||
// Create the "digital" array
|
||||
JsonArray digitalValues = doc["digital"].to<JsonArray>();
|
||||
for (int pin = 0; pin < 14; pin++) {
|
||||
// Read the digital input
|
||||
int value = digitalRead(pin);
|
||||
|
||||
// Add the value at the end of the array
|
||||
digitalValues.add(value);
|
||||
}
|
||||
|
||||
Serial.print(F("Sending: "));
|
||||
serializeJson(doc, Serial);
|
||||
Serial.println();
|
||||
|
||||
// Write response headers
|
||||
client.println(F("HTTP/1.0 200 OK"));
|
||||
client.println(F("Content-Type: application/json"));
|
||||
client.println(F("Connection: close"));
|
||||
client.print(F("Content-Length: "));
|
||||
client.println(measureJsonPretty(doc));
|
||||
client.println();
|
||||
|
||||
// Write JSON document
|
||||
serializeJsonPretty(doc, client);
|
||||
|
||||
// Disconnect
|
||||
client.stop();
|
||||
}
|
||||
|
||||
// Performance issue?
|
||||
// ------------------
|
||||
//
|
||||
// EthernetClient is an unbuffered stream, which is not optimal for ArduinoJson.
|
||||
// See: https://arduinojson.org/v7/how-to/improve-speed/
|
||||
|
||||
// See also
|
||||
// --------
|
||||
//
|
||||
// https://arduinojson.org/ contains the documentation for all the functions
|
||||
// used above. It also includes an FAQ that will help you solve any
|
||||
// serialization problem.
|
||||
//
|
||||
// The book "Mastering ArduinoJson" contains a tutorial on serialization.
|
||||
// It begins with a simple example, then adds more features like serializing
|
||||
// directly to a file or an HTTP client.
|
||||
// Learn more at https://arduinojson.org/book/
|
||||
// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤
|
||||
@@ -0,0 +1,106 @@
|
||||
// ArduinoJson - https://arduinojson.org
|
||||
// Copyright © 2014-2025, Benoit BLANCHON
|
||||
// MIT License
|
||||
//
|
||||
// This example shows how to send a JSON document to a UDP socket.
|
||||
// At regular interval, it sends a UDP packet that contains the status of
|
||||
// analog and digital pins.
|
||||
// It looks like that:
|
||||
// {
|
||||
// "analog": [0, 76, 123, 158, 192, 205],
|
||||
// "digital": [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0]
|
||||
// }
|
||||
//
|
||||
// If you want to test this program, you need to be able to receive the UDP
|
||||
// packets.
|
||||
// For example, you can run netcat on your computer
|
||||
// $ ncat -ulp 8888
|
||||
// See https://nmap.org/ncat/
|
||||
//
|
||||
// https://arduinojson.org/v7/example/udp-beacon/
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <Ethernet.h>
|
||||
#include <SPI.h>
|
||||
|
||||
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
|
||||
IPAddress remoteIp(192, 168, 0, 108); // <- EDIT!!!!
|
||||
unsigned short remotePort = 8888;
|
||||
unsigned short localPort = 8888;
|
||||
EthernetUDP udp;
|
||||
|
||||
void setup() {
|
||||
// Initialize serial port
|
||||
Serial.begin(9600);
|
||||
while (!Serial)
|
||||
continue;
|
||||
|
||||
// Initialize Ethernet libary
|
||||
if (!Ethernet.begin(mac)) {
|
||||
Serial.println(F("Failed to initialize Ethernet library"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable UDP
|
||||
udp.begin(localPort);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Allocate a temporary JsonDocument
|
||||
JsonDocument doc;
|
||||
|
||||
// Create the "analog" array
|
||||
JsonArray analogValues = doc["analog"].to<JsonArray>();
|
||||
for (int pin = 0; pin < 6; pin++) {
|
||||
// Read the analog input
|
||||
int value = analogRead(pin);
|
||||
|
||||
// Add the value at the end of the array
|
||||
analogValues.add(value);
|
||||
}
|
||||
|
||||
// Create the "digital" array
|
||||
JsonArray digitalValues = doc["digital"].to<JsonArray>();
|
||||
for (int pin = 0; pin < 14; pin++) {
|
||||
// Read the digital input
|
||||
int value = digitalRead(pin);
|
||||
|
||||
// Add the value at the end of the array
|
||||
digitalValues.add(value);
|
||||
}
|
||||
|
||||
// Log
|
||||
Serial.print(F("Sending to "));
|
||||
Serial.print(remoteIp);
|
||||
Serial.print(F(" on port "));
|
||||
Serial.println(remotePort);
|
||||
serializeJson(doc, Serial);
|
||||
|
||||
// Send UDP packet
|
||||
udp.beginPacket(remoteIp, remotePort);
|
||||
serializeJson(doc, udp);
|
||||
udp.println();
|
||||
udp.endPacket();
|
||||
|
||||
// Wait
|
||||
delay(10000);
|
||||
}
|
||||
|
||||
// Performance issue?
|
||||
// ------------------
|
||||
//
|
||||
// EthernetUDP is an unbuffered stream, which is not optimal for ArduinoJson.
|
||||
// See: https://arduinojson.org/v7/how-to/improve-speed/
|
||||
|
||||
// See also
|
||||
// --------
|
||||
//
|
||||
// https://arduinojson.org/ contains the documentation for all the functions
|
||||
// used above. It also includes an FAQ that will help you solve any
|
||||
// serialization problem.
|
||||
//
|
||||
// The book "Mastering ArduinoJson" contains a tutorial on serialization.
|
||||
// It begins with a simple example, then adds more features like serializing
|
||||
// directly to a file or any stream.
|
||||
// Learn more at https://arduinojson.org/book/
|
||||
// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤
|
||||
@@ -0,0 +1,61 @@
|
||||
// ArduinoJson - https://arduinojson.org
|
||||
// Copyright © 2014-2025, Benoit BLANCHON
|
||||
// MIT License
|
||||
//
|
||||
// This example shows how to deserialize a MessagePack document with
|
||||
// ArduinoJson.
|
||||
//
|
||||
// https://arduinojson.org/v7/example/msgpack-parser/
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
void setup() {
|
||||
// Initialize serial port
|
||||
Serial.begin(9600);
|
||||
while (!Serial)
|
||||
continue;
|
||||
|
||||
// Allocate the JSON document
|
||||
JsonDocument doc;
|
||||
|
||||
// The MessagePack input string
|
||||
uint8_t input[] = {131, 166, 115, 101, 110, 115, 111, 114, 163, 103, 112, 115,
|
||||
164, 116, 105, 109, 101, 206, 80, 147, 50, 248, 164, 100,
|
||||
97, 116, 97, 146, 203, 64, 72, 96, 199, 58, 188, 148,
|
||||
112, 203, 64, 2, 106, 146, 230, 33, 49, 169};
|
||||
// This MessagePack document contains:
|
||||
// {
|
||||
// "sensor": "gps",
|
||||
// "time": 1351824120,
|
||||
// "data": [48.75608, 2.302038]
|
||||
// }
|
||||
|
||||
// Parse the input
|
||||
DeserializationError error = deserializeMsgPack(doc, input);
|
||||
|
||||
// Test if parsing succeeded
|
||||
if (error) {
|
||||
Serial.print("deserializeMsgPack() failed: ");
|
||||
Serial.println(error.f_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch the values
|
||||
//
|
||||
// Most of the time, you can rely on the implicit casts.
|
||||
// In other case, you can do doc["time"].as<long>();
|
||||
const char* sensor = doc["sensor"];
|
||||
long time = doc["time"];
|
||||
double latitude = doc["data"][0];
|
||||
double longitude = doc["data"][1];
|
||||
|
||||
// Print the values
|
||||
Serial.println(sensor);
|
||||
Serial.println(time);
|
||||
Serial.println(latitude, 6);
|
||||
Serial.println(longitude, 6);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// not used in this example
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// ArduinoJson - https://arduinojson.org
|
||||
// Copyright © 2014-2025, Benoit BLANCHON
|
||||
// MIT License
|
||||
//
|
||||
// This example shows the different ways you can use Flash strings with
|
||||
// ArduinoJson.
|
||||
//
|
||||
// Use Flash strings sparingly, because ArduinoJson duplicates them in the
|
||||
// JsonDocument. Prefer plain old char*, as they are more efficient in term of
|
||||
// code size, speed, and memory usage.
|
||||
//
|
||||
// https://arduinojson.org/v7/example/progmem/
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
void setup() {
|
||||
JsonDocument doc;
|
||||
|
||||
// You can use a Flash String as your JSON input.
|
||||
// WARNING: the strings in the input will be duplicated in the JsonDocument.
|
||||
deserializeJson(doc, F("{\"sensor\":\"gps\",\"time\":1351824120,"
|
||||
"\"data\":[48.756080,2.302038]}"));
|
||||
|
||||
// You can use a Flash String as a key to get a member from JsonDocument
|
||||
// No duplication is done.
|
||||
long time = doc[F("time")];
|
||||
|
||||
// You can use a Flash String as a key to set a member of a JsonDocument
|
||||
// WARNING: the content of the Flash String will be duplicated in the
|
||||
// JsonDocument.
|
||||
doc[F("time")] = time;
|
||||
|
||||
// You can set a Flash String as the content of a JsonVariant
|
||||
// WARNING: the content of the Flash String will be duplicated in the
|
||||
// JsonDocument.
|
||||
doc["sensor"] = F("gps");
|
||||
|
||||
// It works with serialized() too:
|
||||
doc["sensor"] = serialized(F("\"gps\""));
|
||||
doc["sensor"] = serialized(F("\xA3gps"), 3);
|
||||
|
||||
// You can compare the content of a JsonVariant to a Flash String
|
||||
if (doc["sensor"] == F("gps")) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// not used in this example
|
||||
}
|
||||
|
||||
// See also
|
||||
// --------
|
||||
//
|
||||
// https://arduinojson.org/ contains the documentation for all the functions
|
||||
// used above. It also includes an FAQ that will help you solve any memory
|
||||
// problem.
|
||||
//
|
||||
// The book "Mastering ArduinoJson" contains a quick C++ course that explains
|
||||
// how your microcontroller stores strings in memory. It also tells why you
|
||||
// should not abuse Flash strings with ArduinoJson.
|
||||
// Learn more at https://arduinojson.org/book/
|
||||
// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤
|
||||
@@ -0,0 +1,76 @@
|
||||
// ArduinoJson - https://arduinojson.org
|
||||
// Copyright © 2014-2025, Benoit BLANCHON
|
||||
// MIT License
|
||||
//
|
||||
// This example shows the different ways you can use String with ArduinoJson.
|
||||
//
|
||||
// Use String objects sparingly, because ArduinoJson duplicates them in the
|
||||
// JsonDocument. Prefer plain old char[], as they are more efficient in term of
|
||||
// code size, speed, and memory usage.
|
||||
//
|
||||
// https://arduinojson.org/v7/example/string/
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
void setup() {
|
||||
JsonDocument doc;
|
||||
|
||||
// You can use a String as your JSON input.
|
||||
// WARNING: the string in the input will be duplicated in the JsonDocument.
|
||||
String input =
|
||||
"{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038]}";
|
||||
deserializeJson(doc, input);
|
||||
|
||||
// You can use a String as a key to get a member from JsonDocument
|
||||
// No duplication is done.
|
||||
long time = doc[String("time")];
|
||||
|
||||
// You can use a String as a key to set a member of a JsonDocument
|
||||
// WARNING: the content of the String will be duplicated in the JsonDocument.
|
||||
doc[String("time")] = time;
|
||||
|
||||
// You can get the content of a JsonVariant as a String
|
||||
// No duplication is done, at least not in the JsonDocument.
|
||||
String sensor = doc["sensor"];
|
||||
|
||||
// Unfortunately, the following doesn't work (issue #118):
|
||||
// sensor = doc["sensor"]; // <- error "ambiguous overload for 'operator='"
|
||||
// As a workaround, you need to replace by:
|
||||
sensor = doc["sensor"].as<String>();
|
||||
|
||||
// You can set a String as the content of a JsonVariant
|
||||
// WARNING: the content of the String will be duplicated in the JsonDocument.
|
||||
doc["sensor"] = sensor;
|
||||
|
||||
// It works with serialized() too:
|
||||
doc["sensor"] = serialized(sensor);
|
||||
|
||||
// You can also concatenate strings
|
||||
// WARNING: the content of the String will be duplicated in the JsonDocument.
|
||||
doc[String("sen") + "sor"] = String("gp") + "s";
|
||||
|
||||
// You can compare the content of a JsonObject with a String
|
||||
if (doc["sensor"] == sensor) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// Lastly, you can print the resulting JSON to a String
|
||||
String output;
|
||||
serializeJson(doc, output);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// not used in this example
|
||||
}
|
||||
|
||||
// See also
|
||||
// --------
|
||||
//
|
||||
// https://arduinojson.org/ contains the documentation for all the functions
|
||||
// used above. It also includes an FAQ that will help you solve any problem.
|
||||
//
|
||||
// The book "Mastering ArduinoJson" contains a quick C++ course that explains
|
||||
// how your microcontroller stores strings in memory. On several occasions, it
|
||||
// shows how you can avoid String in your program.
|
||||
// Learn more at https://arduinojson.org/book/
|
||||
// Use the coupon code TWENTY for a 20% discount ❤❤❤❤❤
|
||||
@@ -0,0 +1,4 @@
|
||||
@PACKAGE_INIT@
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
|
||||
check_required_components("@PROJECT_NAME@")
|
||||
@@ -0,0 +1,112 @@
|
||||
if(NOT DEFINED COVERAGE)
|
||||
set(COVERAGE OFF)
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)")
|
||||
add_compile_options(
|
||||
-pedantic
|
||||
-Wall
|
||||
-Wcast-align
|
||||
-Wcast-qual
|
||||
-Wconversion
|
||||
-Wctor-dtor-privacy
|
||||
-Wdisabled-optimization
|
||||
-Werror
|
||||
-Wextra
|
||||
-Wformat=2
|
||||
-Winit-self
|
||||
-Wmissing-include-dirs
|
||||
-Wnon-virtual-dtor
|
||||
-Wold-style-cast
|
||||
-Woverloaded-virtual
|
||||
-Wparentheses
|
||||
-Wredundant-decls
|
||||
-Wshadow
|
||||
-Wsign-conversion
|
||||
-Wsign-promo
|
||||
-Wstrict-aliasing
|
||||
-Wundef
|
||||
)
|
||||
|
||||
if(${COVERAGE})
|
||||
set(CMAKE_CXX_FLAGS "-fprofile-arcs -ftest-coverage")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
if((CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.9) AND(NOT ${COVERAGE}))
|
||||
add_compile_options(-g -Og)
|
||||
else() # GCC 4.8
|
||||
add_compile_options(
|
||||
-g
|
||||
-O0 # GCC 4.8 doesn't support -Og
|
||||
-Wno-shadow # allow the same name for a function parameter and a member functions
|
||||
-Wp,-w # Disable preprocessing warnings (see below)
|
||||
)
|
||||
# GCC 4.8 doesn't support __has_include, so we need to help him
|
||||
add_definitions(
|
||||
-DARDUINOJSON_ENABLE_STD_STRING=1
|
||||
-DARDUINOJSON_ENABLE_STD_STREAM=1
|
||||
)
|
||||
endif()
|
||||
|
||||
add_compile_options(
|
||||
-Wstrict-null-sentinel
|
||||
-Wno-vla # Allow VLA in tests
|
||||
)
|
||||
add_definitions(-DHAS_VARIABLE_LENGTH_ARRAY)
|
||||
|
||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.5)
|
||||
add_compile_options(-Wlogical-op) # the flag exists in 4.4 but is buggy
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.6)
|
||||
add_compile_options(-Wnoexcept)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
add_compile_options(
|
||||
-Wc++11-compat
|
||||
-Wdeprecated-register
|
||||
-Wno-vla-extension # Allow VLA in tests
|
||||
)
|
||||
add_definitions(
|
||||
-DHAS_VARIABLE_LENGTH_ARRAY
|
||||
-DSUBSCRIPT_CONFLICTS_WITH_BUILTIN_OPERATOR
|
||||
)
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
add_compile_options(-stdlib=libc++)
|
||||
link_libraries(c++ m)
|
||||
|
||||
if((CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.0) AND(NOT ${COVERAGE}))
|
||||
add_compile_options(-g -Og)
|
||||
else()
|
||||
add_compile_options(-g -O0)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
|
||||
if((CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 9.0) AND(NOT ${COVERAGE}))
|
||||
add_compile_options(-g -Og)
|
||||
else()
|
||||
add_compile_options(-g -O0)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||
add_compile_options(
|
||||
/W4 # Set warning level
|
||||
/WX # Treats all compiler warnings as errors.
|
||||
/Zc:__cplusplus # Enable updated __cplusplus macro
|
||||
)
|
||||
endif()
|
||||
|
||||
if(MINGW)
|
||||
# Static link on MinGW to avoid linking with the wrong DLLs when multiple
|
||||
# versions are installed.
|
||||
add_link_options(-static)
|
||||
endif()
|
||||
@@ -0,0 +1,8 @@
|
||||
# ArduinoJson - https://arduinojson.org
|
||||
# Copyright © 2014-2025, Benoit BLANCHON
|
||||
# MIT License
|
||||
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(example)
|
||||
@@ -0,0 +1,8 @@
|
||||
# ArduinoJson - https://arduinojson.org
|
||||
# Copyright © 2014-2025, Benoit BLANCHON
|
||||
# MIT License
|
||||
|
||||
idf_component_register(
|
||||
SRCS "main.cpp"
|
||||
INCLUDE_DIRS ""
|
||||
)
|
||||
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
@@ -0,0 +1,16 @@
|
||||
// ArduinoJson - https://arduinojson.org
|
||||
// Copyright © 2014-2025, Benoit BLANCHON
|
||||
// MIT License
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
extern "C" void app_main() {
|
||||
char buffer[256];
|
||||
JsonDocument doc;
|
||||
|
||||
doc["hello"] = "world";
|
||||
serializeJson(doc, buffer);
|
||||
deserializeJson(doc, buffer);
|
||||
serializeMsgPack(doc, buffer);
|
||||
deserializeMsgPack(doc, buffer);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh -ex
|
||||
|
||||
BOARD=$1
|
||||
|
||||
cd "$(dirname "$0")/../../"
|
||||
|
||||
cp extras/particle/src/smocktest.ino src/
|
||||
cp extras/particle/project.properties ./
|
||||
|
||||
particle compile "$BOARD"
|
||||
@@ -0,0 +1,18 @@
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
static_assert(ARDUINOJSON_ENABLE_PROGMEM == 1, "ARDUINOJSON_ENABLE_PROGMEM");
|
||||
|
||||
static_assert(ARDUINOJSON_USE_LONG_LONG == 0, "ARDUINOJSON_USE_LONG_LONG");
|
||||
|
||||
static_assert(ARDUINOJSON_SLOT_ID_SIZE == 1, "ARDUINOJSON_SLOT_ID_SIZE");
|
||||
|
||||
static_assert(ARDUINOJSON_POOL_CAPACITY == 16, "ARDUINOJSON_POOL_CAPACITY");
|
||||
|
||||
static_assert(ARDUINOJSON_LITTLE_ENDIAN == 1, "ARDUINOJSON_LITTLE_ENDIAN");
|
||||
|
||||
static_assert(ARDUINOJSON_USE_DOUBLE == 0, "ARDUINOJSON_USE_DOUBLE");
|
||||
|
||||
static_assert(ArduinoJson::detail::ResourceManager::slotSize == 6, "slot size");
|
||||
|
||||
void setup() {}
|
||||
void loop() {}
|
||||
@@ -0,0 +1,16 @@
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
static_assert(ARDUINOJSON_USE_LONG_LONG == 1, "ARDUINOJSON_USE_LONG_LONG");
|
||||
|
||||
static_assert(ARDUINOJSON_SLOT_ID_SIZE == 2, "ARDUINOJSON_SLOT_ID_SIZE");
|
||||
|
||||
static_assert(ARDUINOJSON_POOL_CAPACITY == 128, "ARDUINOJSON_POOL_CAPACITY");
|
||||
|
||||
static_assert(ARDUINOJSON_LITTLE_ENDIAN == 1, "ARDUINOJSON_LITTLE_ENDIAN");
|
||||
|
||||
static_assert(ARDUINOJSON_USE_DOUBLE == 1, "ARDUINOJSON_USE_DOUBLE");
|
||||
|
||||
static_assert(ArduinoJson::detail::ResourceManager::slotSize == 8, "slot size");
|
||||
|
||||
void setup() {}
|
||||
void loop() {}
|
||||
@@ -0,0 +1,16 @@
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
static_assert(ARDUINOJSON_USE_LONG_LONG == 1, "ARDUINOJSON_USE_LONG_LONG");
|
||||
|
||||
static_assert(ARDUINOJSON_SLOT_ID_SIZE == 4, "ARDUINOJSON_SLOT_ID_SIZE");
|
||||
|
||||
static_assert(ARDUINOJSON_POOL_CAPACITY == 256, "ARDUINOJSON_POOL_CAPACITY");
|
||||
|
||||
static_assert(ARDUINOJSON_LITTLE_ENDIAN == 1, "ARDUINOJSON_LITTLE_ENDIAN");
|
||||
|
||||
static_assert(ARDUINOJSON_USE_DOUBLE == 1, "ARDUINOJSON_USE_DOUBLE");
|
||||
|
||||
static_assert(ArduinoJson::detail::ResourceManager::slotSize == 16,
|
||||
"slot size");
|
||||
|
||||
int main() {}
|
||||
@@ -0,0 +1,15 @@
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
static_assert(ARDUINOJSON_USE_LONG_LONG == 1, "ARDUINOJSON_USE_LONG_LONG");
|
||||
|
||||
static_assert(ARDUINOJSON_SLOT_ID_SIZE == 2, "ARDUINOJSON_SLOT_ID_SIZE");
|
||||
|
||||
static_assert(ARDUINOJSON_POOL_CAPACITY == 128, "ARDUINOJSON_POOL_CAPACITY");
|
||||
|
||||
static_assert(ARDUINOJSON_LITTLE_ENDIAN == 1, "ARDUINOJSON_LITTLE_ENDIAN");
|
||||
|
||||
static_assert(ARDUINOJSON_USE_DOUBLE == 1, "ARDUINOJSON_USE_DOUBLE");
|
||||
|
||||
static_assert(ArduinoJson::detail::ResourceManager::slotSize == 8, "slot size");
|
||||
|
||||
int main() {}
|
||||
@@ -0,0 +1,67 @@
|
||||
# ArduinoJson - https://arduinojson.org
|
||||
# Copyright © 2014-2025, Benoit BLANCHON
|
||||
# MIT License
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if(MSVC)
|
||||
add_compile_options(-D_CRT_SECURE_NO_WARNINGS)
|
||||
endif()
|
||||
|
||||
add_executable(msgpack_reproducer
|
||||
msgpack_fuzzer.cpp
|
||||
reproducer.cpp
|
||||
)
|
||||
target_link_libraries(msgpack_reproducer
|
||||
ArduinoJson
|
||||
)
|
||||
|
||||
add_executable(json_reproducer
|
||||
json_fuzzer.cpp
|
||||
reproducer.cpp
|
||||
)
|
||||
target_link_libraries(json_reproducer
|
||||
ArduinoJson
|
||||
)
|
||||
|
||||
macro(add_fuzzer name)
|
||||
set(FUZZER "${name}_fuzzer")
|
||||
set(CORPUS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${name}_corpus")
|
||||
set(SEED_CORPUS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${name}_seed_corpus")
|
||||
add_executable("${FUZZER}"
|
||||
"${name}_fuzzer.cpp"
|
||||
)
|
||||
target_link_libraries("${FUZZER}"
|
||||
ArduinoJson
|
||||
)
|
||||
set_target_properties("${FUZZER}"
|
||||
PROPERTIES
|
||||
COMPILE_FLAGS "-fprofile-instr-generate -fcoverage-mapping -fsanitize=fuzzer -fno-sanitize-recover=all"
|
||||
LINK_FLAGS "-fprofile-instr-generate -fcoverage-mapping -fsanitize=fuzzer -fno-sanitize-recover=all"
|
||||
)
|
||||
|
||||
add_test(
|
||||
NAME "${FUZZER}"
|
||||
COMMAND "${FUZZER}" "${CORPUS_DIR}" "${SEED_CORPUS_DIR}" -max_total_time=5 -timeout=1
|
||||
)
|
||||
|
||||
set_tests_properties("${FUZZER}"
|
||||
PROPERTIES
|
||||
LABELS "Fuzzing"
|
||||
)
|
||||
endmacro()
|
||||
|
||||
# Needs Clang 6+ to compile
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 6)
|
||||
if(DEFINED ENV{GITHUB_ACTIONS} AND CMAKE_CXX_COMPILER_VERSION MATCHES "^11\\.")
|
||||
# Clang 11 fails on GitHub Actions with the following error:
|
||||
# > ERROR: UndefinedBehaviorSanitizer failed to allocate 0x0 (0) bytes of SetAlternateSignalStack (error code: 22)
|
||||
# > Sanitizer CHECK failed: /build/llvm-toolchain-11-mnvtwk/llvm-toolchain-11-11.1.0/compiler-rt/lib/sanitizer_common/sanitizer_common.cpp:54 ((0 && "unable to mmap")) != (0) (0, 0)
|
||||
message(WARNING "Fuzzing is disabled on GitHub Actions to workaround a bug in Clang 11")
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_fuzzer(json)
|
||||
add_fuzzer(msgpack)
|
||||
endif()
|
||||
@@ -0,0 +1,22 @@
|
||||
# CAUTION: this file is invoked by https://github.com/google/oss-fuzz
|
||||
|
||||
CXXFLAGS += -I../../src -DARDUINOJSON_DEBUG=1 -std=c++11
|
||||
|
||||
all: \
|
||||
$(OUT)/json_fuzzer \
|
||||
$(OUT)/json_fuzzer_seed_corpus.zip \
|
||||
$(OUT)/json_fuzzer.options \
|
||||
$(OUT)/msgpack_fuzzer \
|
||||
$(OUT)/msgpack_fuzzer_seed_corpus.zip \
|
||||
$(OUT)/msgpack_fuzzer.options
|
||||
|
||||
$(OUT)/%_fuzzer: %_fuzzer.cpp $(shell find ../../src -type f)
|
||||
$(CXX) $(CXXFLAGS) $< -o$@ $(LIB_FUZZING_ENGINE)
|
||||
|
||||
$(OUT)/%_fuzzer_seed_corpus.zip: %_seed_corpus/*
|
||||
zip -j $@ $?
|
||||
|
||||
$(OUT)/%_fuzzer.options:
|
||||
@echo "[libfuzzer]" > $@
|
||||
@echo "max_len = 256" >> $@
|
||||
@echo "timeout = 10" >> $@
|
||||
@@ -0,0 +1,11 @@
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
JsonDocument doc;
|
||||
DeserializationError error = deserializeJson(doc, data, size);
|
||||
if (!error) {
|
||||
std::string json;
|
||||
serializeJson(doc, json);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
//comment
|
||||
/*comment*/
|
||||
[ //comment
|
||||
/*comment*/"comment"/*comment*/,//comment
|
||||
/*comment*/{//comment
|
||||
/* comment*/"key"//comment
|
||||
: //comment
|
||||
"value"//comment
|
||||
}/*comment*/
|
||||
]//comment
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1 @@
|
||||
[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,[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,[52,[53,[54,[55,[56,[57,[58,[59,[60,[61,[62,[63,[64,[65,[66,[67,[68,[69,[70,[71,[72,[73,[74,[75,[76,[77,[78,[79,[80,[81,[82,[83,[84,[85,[86,[87,[88,[89,[90,[91,[92,[93,[94,[95,[96,[97,[98,[99,[100,[101,[102,[103,[104,[105,[106,[107,[108,[109,[110,[111,[112,[113,[114,[115,[116,[117,[118,[119,[120]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
|
||||
@@ -0,0 +1 @@
|
||||
9720730739393920739
|
||||
@@ -0,0 +1,24 @@
|
||||
[
|
||||
123,
|
||||
-123,
|
||||
123.456,
|
||||
-123.456,
|
||||
12e34,
|
||||
12e-34,
|
||||
12e+34,
|
||||
12E34,
|
||||
12E-34,
|
||||
12E+34,
|
||||
12.34e56,
|
||||
12.34e-56,
|
||||
12.34e+56,
|
||||
12.34E56,
|
||||
12.34E-56,
|
||||
12.34E+56,
|
||||
NaN,
|
||||
-NaN,
|
||||
+NaN,
|
||||
Infinity,
|
||||
+Infinity,
|
||||
-Infinity
|
||||
]
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"coord": {
|
||||
"lon": -0.13,
|
||||
"lat": 51.51
|
||||
},
|
||||
"weather": [
|
||||
{
|
||||
"id": 301,
|
||||
"main": "Drizzle",
|
||||
"description": "drizzle",
|
||||
"icon": "09n"
|
||||
},
|
||||
{
|
||||
"id": 701,
|
||||
"main": "Mist",
|
||||
"description": "mist",
|
||||
"icon": "50n"
|
||||
},
|
||||
{
|
||||
"id": 741,
|
||||
"main": "Fog",
|
||||
"description": "fog",
|
||||
"icon": "50n"
|
||||
}
|
||||
],
|
||||
"base": "stations",
|
||||
"main": {
|
||||
"temp": 281.87,
|
||||
"pressure": 1032,
|
||||
"humidity": 100,
|
||||
"temp_min": 281.15,
|
||||
"temp_max": 283.15
|
||||
},
|
||||
"visibility": 2900,
|
||||
"wind": {
|
||||
"speed": 1.5
|
||||
},
|
||||
"clouds": {
|
||||
"all": 90
|
||||
},
|
||||
"dt": 1483820400,
|
||||
"sys": {
|
||||
"type": 1,
|
||||
"id": 5091,
|
||||
"message": 0.0226,
|
||||
"country": "GB",
|
||||
"sunrise": 1483776245,
|
||||
"sunset": 1483805443
|
||||
},
|
||||
"id": 2643743,
|
||||
"name": "London",
|
||||
"cod": 200
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
[
|
||||
"hello",
|
||||
'hello',
|
||||
hello,
|
||||
{"hello":"world"},
|
||||
{'hello':'world'},
|
||||
{hello:world}
|
||||
]
|
||||
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"response": {
|
||||
"version": "0.1",
|
||||
"termsofService": "http://www.wunderground.com/weather/api/d/terms.html",
|
||||
"features": {
|
||||
"conditions": 1
|
||||
}
|
||||
},
|
||||
"current_observation": {
|
||||
"image": {
|
||||
"url": "http://icons-ak.wxug.com/graphics/wu2/logo_130x80.png",
|
||||
"title": "Weather Underground",
|
||||
"link": "http://www.wunderground.com"
|
||||
},
|
||||
"display_location": {
|
||||
"full": "San Francisco, CA",
|
||||
"city": "San Francisco",
|
||||
"state": "CA",
|
||||
"state_name": "California",
|
||||
"country": "US",
|
||||
"country_iso3166": "US",
|
||||
"zip": "94101",
|
||||
"latitude": "37.77500916",
|
||||
"longitude": "-122.41825867",
|
||||
"elevation": "47.00000000"
|
||||
},
|
||||
"observation_location": {
|
||||
"full": "SOMA - Near Van Ness, San Francisco, California",
|
||||
"city": "SOMA - Near Van Ness, San Francisco",
|
||||
"state": "California",
|
||||
"country": "US",
|
||||
"country_iso3166": "US",
|
||||
"latitude": "37.773285",
|
||||
"longitude": "-122.417725",
|
||||
"elevation": "49 ft"
|
||||
},
|
||||
"estimated": {},
|
||||
"station_id": "KCASANFR58",
|
||||
"observation_time": "Last Updated on June 27, 5:27 PM PDT",
|
||||
"observation_time_rfc822": "Wed, 27 Jun 2012 17:27:13 -0700",
|
||||
"observation_epoch": "1340843233",
|
||||
"local_time_rfc822": "Wed, 27 Jun 2012 17:27:14 -0700",
|
||||
"local_epoch": "1340843234",
|
||||
"local_tz_short": "PDT",
|
||||
"local_tz_long": "America/Los_Angeles",
|
||||
"local_tz_offset": "-0700",
|
||||
"weather": "Partly Cloudy",
|
||||
"temperature_string": "66.3 F (19.1 C)",
|
||||
"temp_f": 66.3,
|
||||
"temp_c": 19.1,
|
||||
"relative_humidity": "65%",
|
||||
"wind_string": "From the NNW at 22.0 MPH Gusting to 28.0 MPH",
|
||||
"wind_dir": "NNW",
|
||||
"wind_degrees": 346,
|
||||
"wind_mph": 22,
|
||||
"wind_gust_mph": "28.0",
|
||||
"wind_kph": 35.4,
|
||||
"wind_gust_kph": "45.1",
|
||||
"pressure_mb": "1013",
|
||||
"pressure_in": "29.93",
|
||||
"pressure_trend": "+",
|
||||
"dewpoint_string": "54 F (12 C)",
|
||||
"dewpoint_f": 54,
|
||||
"dewpoint_c": 12,
|
||||
"heat_index_string": "NA",
|
||||
"heat_index_f": "NA",
|
||||
"heat_index_c": "NA",
|
||||
"windchill_string": "NA",
|
||||
"windchill_f": "NA",
|
||||
"windchill_c": "NA",
|
||||
"feelslike_string": "66.3 F (19.1 C)",
|
||||
"feelslike_f": "66.3",
|
||||
"feelslike_c": "19.1",
|
||||
"visibility_mi": "10.0",
|
||||
"visibility_km": "16.1",
|
||||
"solarradiation": "",
|
||||
"UV": "5",
|
||||
"precip_1hr_string": "0.00 in ( 0 mm)",
|
||||
"precip_1hr_in": "0.00",
|
||||
"precip_1hr_metric": " 0",
|
||||
"precip_today_string": "0.00 in (0 mm)",
|
||||
"precip_today_in": "0.00",
|
||||
"precip_today_metric": "0",
|
||||
"icon": "partlycloudy",
|
||||
"icon_url": "http://icons-ak.wxug.com/i/c/k/partlycloudy.gif",
|
||||
"forecast_url": "http://www.wunderground.com/US/CA/San_Francisco.html",
|
||||
"history_url": "http://www.wunderground.com/history/airport/KCASANFR58/2012/6/27/DailyHistory.html",
|
||||
"ob_url": "http://www.wunderground.com/cgi-bin/findweather/getForecast?query=37.773285,-122.417725"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
JsonDocument doc;
|
||||
DeserializationError error = deserializeMsgPack(doc, data, size);
|
||||
if (!error) {
|
||||
std::string json;
|
||||
serializeMsgPack(doc, json);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
�
|
||||
@@ -0,0 +1 @@
|
||||
’¥hello¥world
|
||||
@@ -0,0 +1 @@
|
||||
�
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
‚£one£two
|
||||
@@ -0,0 +1 @@
|
||||
«hello world
|
||||
@@ -0,0 +1 @@
|
||||
ハ@H�
|
||||
@@ -0,0 +1 @@
|
||||
Л@ !КАѓo
|
||||
@@ -0,0 +1 @@
|
||||
拮�
|
||||
@@ -0,0 +1 @@
|
||||
Ҷi�.
|
||||
@@ -0,0 +1 @@
|
||||
モ4Vx埔゙�
|
||||
@@ -0,0 +1 @@
|
||||
��
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
�
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
Ùhello
|
||||
@@ -0,0 +1 @@
|
||||
�
|
||||
@@ -0,0 +1 @@
|
||||
�09
|
||||
@@ -0,0 +1 @@
|
||||
�4Vx
|
||||
@@ -0,0 +1 @@
|
||||
マ4Vx埔゙�
|
||||
@@ -0,0 +1 @@
|
||||
��
|
||||
@@ -0,0 +1,50 @@
|
||||
// ArduinoJson - https://arduinojson.org
|
||||
// Copyright © 2014-2025, Benoit BLANCHON
|
||||
// MIT License
|
||||
|
||||
// This file is NOT use by Google's OSS fuzz
|
||||
// I only use it to reproduce the bugs found
|
||||
|
||||
#include <stdint.h> // size_t
|
||||
#include <stdio.h> // fopen et al.
|
||||
#include <stdlib.h> // exit
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
|
||||
|
||||
std::vector<uint8_t> read(const char* path) {
|
||||
FILE* f = fopen(path, "rb");
|
||||
if (!f) {
|
||||
std::cerr << "Failed to open " << path << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
size_t size = static_cast<size_t>(ftell(f));
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
std::vector<uint8_t> buffer(size);
|
||||
if (fread(buffer.data(), 1, size, f) != size) {
|
||||
fclose(f);
|
||||
std::cerr << "Failed to read " << path << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
if (argc < 2) {
|
||||
std::cerr << "Usage: msgpack_fuzzer files" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
std::cout << "Loading " << argv[i] << std::endl;
|
||||
std::vector<uint8_t> buffer = read(argv[i]);
|
||||
LLVMFuzzerTestOneInput(buffer.data(), buffer.size());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
name=ArduinoJsonCI
|
||||
@@ -0,0 +1,5 @@
|
||||
#include "ArduinoJson.h"
|
||||
|
||||
void setup() {}
|
||||
|
||||
void loop() {}
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
RE_RELATIVE_INCLUDE='^#[[:space:]]*include[[:space:]]*"(.*)"'
|
||||
RE_ABSOLUTE_INCLUDE='^#[[:space:]]*include[[:space:]]*<(ArduinoJson/.*)>'
|
||||
RE_SYSTEM_INCLUDE='^#[[:space:]]*include[[:space:]]*<(.*)>'
|
||||
RE_EMPTY='^(#[[:space:]]*pragma[[:space:]]+once)?[[:space:]]*(//.*)?$'
|
||||
SRC_DIRECTORY="$(realpath "$(dirname $0)/../../src")"
|
||||
|
||||
|
||||
declare -A INCLUDED
|
||||
|
||||
process()
|
||||
{
|
||||
local PARENT=$1
|
||||
local FOLDER=$(dirname $1)
|
||||
local SHOW_COMMENT=$2
|
||||
while IFS= read -r LINE; do
|
||||
if [[ $LINE =~ $RE_ABSOLUTE_INCLUDE ]]; then
|
||||
local CHILD=${BASH_REMATCH[1]}
|
||||
local CHILD_PATH
|
||||
CHILD_PATH=$(realpath "$SRC_DIRECTORY/$CHILD")
|
||||
echo "$PARENT -> $CHILD" >&2
|
||||
if [[ ! ${INCLUDED[$CHILD_PATH]} ]]; then
|
||||
INCLUDED[$CHILD_PATH]=true
|
||||
process "$CHILD" false
|
||||
fi
|
||||
elif [[ $LINE =~ $RE_RELATIVE_INCLUDE ]]; then
|
||||
local CHILD=${BASH_REMATCH[1]}
|
||||
pushd "$FOLDER" > /dev/null
|
||||
local CHILD_PATH
|
||||
CHILD_PATH=$(realpath "$CHILD")
|
||||
echo "$PARENT -> $CHILD" >&2
|
||||
if [[ ! ${INCLUDED[$CHILD_PATH]} ]]; then
|
||||
INCLUDED[$CHILD_PATH]=true
|
||||
process "$CHILD" false
|
||||
fi
|
||||
popd > /dev/null
|
||||
elif [[ $LINE =~ $RE_SYSTEM_INCLUDE ]]; then
|
||||
local CHILD=${BASH_REMATCH[1]}
|
||||
echo "$PARENT -> <$CHILD>" >&2
|
||||
if [[ ! ${INCLUDED[$CHILD]} ]]; then
|
||||
echo "#include <$CHILD>"
|
||||
INCLUDED[$CHILD]=true
|
||||
fi
|
||||
elif [[ "${SHOW_COMMENT}" = "true" ]] ; then
|
||||
echo "$LINE"
|
||||
elif [[ ! $LINE =~ $RE_EMPTY ]]; then
|
||||
echo "$LINE"
|
||||
fi
|
||||
done < $PARENT
|
||||
}
|
||||
|
||||
simplify_namespaces() {
|
||||
perl -p0i -e 's|ARDUINOJSON_END_PUBLIC_NAMESPACE\r?\nARDUINOJSON_BEGIN_PUBLIC_NAMESPACE\r?\n||igs' "$1"
|
||||
perl -p0i -e 's|ARDUINOJSON_END_PRIVATE_NAMESPACE\r?\nARDUINOJSON_BEGIN_PRIVATE_NAMESPACE\r?\n||igs' "$1"
|
||||
rm -f "$1.bak"
|
||||
}
|
||||
|
||||
INCLUDED=()
|
||||
INPUT=$1
|
||||
OUTPUT=$2
|
||||
process "$INPUT" true > "$OUTPUT"
|
||||
simplify_namespaces "$OUTPUT"
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/awk -f
|
||||
|
||||
# Start echoing after the first list item
|
||||
/\* / {
|
||||
STARTED=1
|
||||
EMPTY_LINE=0
|
||||
}
|
||||
|
||||
# Remember if we have seen an empty line
|
||||
/^[[:space:]]*$/ {
|
||||
EMPTY_LINE=1
|
||||
}
|
||||
|
||||
# Exit when seeing a new version number
|
||||
/^v[[:digit:]]/ {
|
||||
if (STARTED) exit
|
||||
}
|
||||
|
||||
# Print if the line is not empty
|
||||
# and restore the empty line we have skipped
|
||||
!/^[[:space:]]*$/ {
|
||||
if (STARTED) {
|
||||
if (EMPTY_LINE) {
|
||||
print ""
|
||||
EMPTY_LINE=0
|
||||
}
|
||||
print
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
VERSION="$1"
|
||||
CHANGELOG="$2"
|
||||
ARDUINOJSON_H="$3"
|
||||
|
||||
cat << END
|
||||
---
|
||||
branch: v7
|
||||
version: $VERSION
|
||||
date: '$(date +'%Y-%m-%d')'
|
||||
$(extras/scripts/wandbox/publish.sh "$ARDUINOJSON_H")
|
||||
---
|
||||
|
||||
$(extras/scripts/extract_changes.awk "$CHANGELOG")
|
||||
END
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eu
|
||||
|
||||
SOURCE_DIR="$(dirname "$0")/../.."
|
||||
WORK_DIR=$(mktemp -d)
|
||||
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||
|
||||
cp "$SOURCE_DIR/README.md" "$WORK_DIR/README.md"
|
||||
cp "$SOURCE_DIR/CHANGELOG.md" "$WORK_DIR/CHANGELOG.md"
|
||||
cp "$SOURCE_DIR/library.properties" "$WORK_DIR/library.properties"
|
||||
cp "$SOURCE_DIR/LICENSE.txt" "$WORK_DIR/LICENSE.txt"
|
||||
cp -r "$SOURCE_DIR/src" "$WORK_DIR/"
|
||||
cp -r "$SOURCE_DIR/examples" "$WORK_DIR/"
|
||||
|
||||
cd "$WORK_DIR"
|
||||
particle library upload
|
||||
particle library publish
|
||||
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eu
|
||||
|
||||
which awk sed jq curl perl >/dev/null
|
||||
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
if ! git diff --quiet --exit-code; then
|
||||
echo "Repository contains uncommitted changes"
|
||||
exit
|
||||
fi
|
||||
|
||||
VERSION="$1"
|
||||
DATE=$(date +%F)
|
||||
TAG="v$VERSION"
|
||||
VERSION_REGEX='[0-9]+\.[0-9]+\.[0-9]+(-[a-z0-9]+)?'
|
||||
STARS=$(curl -s https://api.github.com/repos/bblanchon/ArduinoJson | jq '.stargazers_count')
|
||||
|
||||
update_version_in_source () {
|
||||
IFS=".-" read MAJOR MINOR REVISION EXTRA < <(echo "$VERSION")
|
||||
UNDERLINE=$(printf -- '-%.0s' $(seq 1 ${#TAG}))
|
||||
|
||||
sed -i~ -bE "1,20{s/$VERSION_REGEX/$VERSION/g}" README.md
|
||||
rm README.md~
|
||||
|
||||
sed -i~ -bE "4s/HEAD/$TAG ($DATE)/; 5s/-+/$UNDERLINE/" CHANGELOG.md
|
||||
rm CHANGELOG.md~
|
||||
|
||||
sed -i~ -bE "s/(project\\s*\\(ArduinoJson\\s+VERSION\\s+).*?\\)/\\1$MAJOR.$MINOR.$REVISION)/" CMakeLists.txt
|
||||
rm CMakeLists.txt~
|
||||
|
||||
sed -i~ -bE \
|
||||
-e "s/\"version\":.*$/\"version\": \"$VERSION\",/" \
|
||||
-e "s/[0-9]+ stars/$STARS stars/" \
|
||||
library.json
|
||||
rm library.json~
|
||||
|
||||
sed -i~ -bE \
|
||||
-e "s/version=.*$/version=$VERSION/" \
|
||||
-e "s/[0-9]+ stars/$STARS stars/" \
|
||||
library.properties
|
||||
rm library.properties~
|
||||
|
||||
sed -i~ -bE "s/version: .*$/version: $VERSION.{build}/" appveyor.yml
|
||||
rm appveyor.yml~
|
||||
|
||||
sed -i~ -bE \
|
||||
-e "s/^version: .*$/version: \"$VERSION\"/" \
|
||||
-e "s/[0-9]+ stars/$STARS stars/" \
|
||||
idf_component.yml
|
||||
rm idf_component.yml~
|
||||
|
||||
sed -i~ -bE \
|
||||
-e "s/ARDUINOJSON_VERSION .*$/ARDUINOJSON_VERSION \"$VERSION\"/" \
|
||||
-e "s/ARDUINOJSON_VERSION_MAJOR .*$/ARDUINOJSON_VERSION_MAJOR $MAJOR/" \
|
||||
-e "s/ARDUINOJSON_VERSION_MINOR .*$/ARDUINOJSON_VERSION_MINOR $MINOR/" \
|
||||
-e "s/ARDUINOJSON_VERSION_REVISION .*$/ARDUINOJSON_VERSION_REVISION $REVISION/" \
|
||||
-e "s/ARDUINOJSON_VERSION_MACRO .*$/ARDUINOJSON_VERSION_MACRO V$MAJOR$MINOR$REVISION/" \
|
||||
src/ArduinoJson/version.hpp
|
||||
rm src/ArduinoJson/version.hpp*~
|
||||
}
|
||||
|
||||
commit_new_version () {
|
||||
git add src/ArduinoJson/version.hpp README.md CHANGELOG.md library.json library.properties appveyor.yml CMakeLists.txt idf_component.yml
|
||||
git commit -m "Set version to $VERSION"
|
||||
}
|
||||
|
||||
add_tag () {
|
||||
CHANGES=$(awk '/\* /{ FOUND=1; print; next } { if (FOUND) exit}' CHANGELOG.md)
|
||||
git tag -m "ArduinoJson $VERSION"$'\n'"$CHANGES" "$TAG"
|
||||
}
|
||||
|
||||
push () {
|
||||
git push --follow-tags
|
||||
}
|
||||
|
||||
update_version_in_source
|
||||
commit_new_version
|
||||
add_tag
|
||||
push
|
||||
|
||||
extras/scripts/build-single-header.sh "src/ArduinoJson.h" "../ArduinoJson-$TAG.h"
|
||||
extras/scripts/build-single-header.sh "src/ArduinoJson.hpp" "../ArduinoJson-$TAG.hpp"
|
||||
extras/scripts/get-release-page.sh "$VERSION" "CHANGELOG.md" "../ArduinoJson-$TAG.h" > "../ArduinoJson-$TAG.md"
|
||||
|
||||
echo "You can now copy ../ArduinoJson-$TAG.md into arduinojson.org/collections/_versions/$VERSION.md"
|
||||
@@ -0,0 +1,42 @@
|
||||
// ArduinoJson - https://arduinojson.org
|
||||
// Copyright © 2014-2025, Benoit BLANCHON
|
||||
// MIT License
|
||||
//
|
||||
// This example shows how to generate a JSON document with ArduinoJson.
|
||||
|
||||
#include <iostream>
|
||||
#include "ArduinoJson.h"
|
||||
|
||||
int main() {
|
||||
// Allocate the JSON document
|
||||
JsonDocument doc;
|
||||
|
||||
// Add values in the document.
|
||||
doc["sensor"] = "gps";
|
||||
doc["time"] = 1351824120;
|
||||
|
||||
// Add an array
|
||||
JsonArray data = doc["data"].to<JsonArray>();
|
||||
data.add(48.756080);
|
||||
data.add(2.302038);
|
||||
|
||||
// Generate the minified JSON and send it to STDOUT
|
||||
serializeJson(doc, std::cout);
|
||||
// The above line prints:
|
||||
// {"sensor":"gps","time":1351824120,"data":[48.756080,2.302038]}
|
||||
|
||||
// Start a new line
|
||||
std::cout << std::endl;
|
||||
|
||||
// Generate the prettified JSON and send it to STDOUT
|
||||
serializeJsonPretty(doc, std::cout);
|
||||
// The above line prints:
|
||||
// {
|
||||
// "sensor": "gps",
|
||||
// "time": 1351824120,
|
||||
// "data": [
|
||||
// 48.756080,
|
||||
// 2.302038
|
||||
// ]
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// ArduinoJson - https://arduinojson.org
|
||||
// Copyright © 2014-2025, Benoit BLANCHON
|
||||
// MIT License
|
||||
//
|
||||
// This example shows how to deserialize a JSON document with ArduinoJson.
|
||||
|
||||
#include <iostream>
|
||||
#include "ArduinoJson.h"
|
||||
|
||||
int main() {
|
||||
// Allocate the JSON document
|
||||
JsonDocument doc;
|
||||
|
||||
// JSON input string
|
||||
const char* json =
|
||||
"{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038]}";
|
||||
|
||||
// Deserialize the JSON document
|
||||
DeserializationError error = deserializeJson(doc, json);
|
||||
|
||||
// Test if parsing succeeds
|
||||
if (error) {
|
||||
std::cerr << "deserializeJson() failed: " << error.c_str() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Fetch the values
|
||||
//
|
||||
// Most of the time, you can rely on the implicit casts.
|
||||
// In other case, you can do doc["time"].as<long>();
|
||||
const char* sensor = doc["sensor"];
|
||||
long time = doc["time"];
|
||||
double latitude = doc["data"][0];
|
||||
double longitude = doc["data"][1];
|
||||
|
||||
// Print the values
|
||||
std::cout << sensor << std::endl;
|
||||
std::cout << time << std::endl;
|
||||
std::cout << latitude << std::endl;
|
||||
std::cout << longitude << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// ArduinoJson - https://arduinojson.org
|
||||
// Copyright © 2014-2025, Benoit BLANCHON
|
||||
// MIT License
|
||||
//
|
||||
// This example shows how to generate a JSON document with ArduinoJson.
|
||||
|
||||
#include <iostream>
|
||||
#include "ArduinoJson.h"
|
||||
|
||||
int main() {
|
||||
// Allocate the JSON document
|
||||
JsonDocument doc;
|
||||
|
||||
// The MessagePack input string
|
||||
uint8_t input[] = {131, 166, 115, 101, 110, 115, 111, 114, 163, 103, 112, 115,
|
||||
164, 116, 105, 109, 101, 206, 80, 147, 50, 248, 164, 100,
|
||||
97, 116, 97, 146, 203, 64, 72, 96, 199, 58, 188, 148,
|
||||
112, 203, 64, 2, 106, 146, 230, 33, 49, 169};
|
||||
// This MessagePack document contains:
|
||||
// {
|
||||
// "sensor": "gps",
|
||||
// "time": 1351824120,
|
||||
// "data": [48.75608, 2.302038]
|
||||
// }
|
||||
|
||||
// Parse the input
|
||||
DeserializationError error = deserializeMsgPack(doc, input);
|
||||
|
||||
// Test if parsing succeeds
|
||||
if (error) {
|
||||
std::cerr << "deserializeMsgPack() failed: " << error.c_str() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Fetch the values
|
||||
//
|
||||
// Most of the time, you can rely on the implicit casts.
|
||||
// In other case, you can do doc["time"].as<long>();
|
||||
const char* sensor = doc["sensor"];
|
||||
long time = doc["time"];
|
||||
double latitude = doc["data"][0];
|
||||
double longitude = doc["data"][1];
|
||||
|
||||
// Print the values
|
||||
std::cout << sensor << std::endl;
|
||||
std::cout << time << std::endl;
|
||||
std::cout << latitude << std::endl;
|
||||
std::cout << longitude << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eu
|
||||
|
||||
ARDUINOJSON_H="$1"
|
||||
|
||||
read_string() {
|
||||
jq --slurp --raw-input '.' "$1"
|
||||
}
|
||||
|
||||
compile() {
|
||||
FILE_PATH="$(dirname $0)/$1.cpp"
|
||||
cat >parameters.json <<END
|
||||
{
|
||||
"code":$(read_string "$FILE_PATH"),
|
||||
"codes": [{"file":"ArduinoJson.h","code":$(read_string "$ARDUINOJSON_H")}],
|
||||
"options": "warning,c++11",
|
||||
"compiler": "gcc-head",
|
||||
"save": true
|
||||
}
|
||||
END
|
||||
URL=$(curl -sS -H "Content-type: application/json" -d @parameters.json https://wandbox.org/api/compile.json | jq --raw-output .url)
|
||||
rm parameters.json
|
||||
[ -n "$URL" ] && echo "$1: $URL"
|
||||
}
|
||||
|
||||
compile "JsonGeneratorExample"
|
||||
compile "JsonParserExample"
|
||||
compile "MsgPackParserExample"
|
||||
@@ -0,0 +1,36 @@
|
||||
# ArduinoJson - https://arduinojson.org
|
||||
# Copyright © 2014-2025, Benoit BLANCHON
|
||||
# MIT License
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
link_libraries(ArduinoJson)
|
||||
|
||||
# Failing builds should only link with ArduinoJson, not catch
|
||||
add_subdirectory(FailingBuilds)
|
||||
|
||||
add_subdirectory(catch)
|
||||
link_libraries(catch)
|
||||
|
||||
include_directories(Helpers)
|
||||
add_subdirectory(Cpp17)
|
||||
add_subdirectory(Cpp20)
|
||||
add_subdirectory(Deprecated)
|
||||
add_subdirectory(IntegrationTests)
|
||||
add_subdirectory(JsonArray)
|
||||
add_subdirectory(JsonArrayConst)
|
||||
add_subdirectory(JsonDeserializer)
|
||||
add_subdirectory(JsonDocument)
|
||||
add_subdirectory(JsonObject)
|
||||
add_subdirectory(JsonObjectConst)
|
||||
add_subdirectory(JsonSerializer)
|
||||
add_subdirectory(JsonVariant)
|
||||
add_subdirectory(JsonVariantConst)
|
||||
add_subdirectory(ResourceManager)
|
||||
add_subdirectory(Misc)
|
||||
add_subdirectory(MixedConfiguration)
|
||||
add_subdirectory(MsgPackDeserializer)
|
||||
add_subdirectory(MsgPackSerializer)
|
||||
add_subdirectory(Numbers)
|
||||
add_subdirectory(TextFormatter)
|
||||
@@ -0,0 +1,29 @@
|
||||
# ArduinoJson - https://arduinojson.org
|
||||
# Copyright © 2014-2025, Benoit BLANCHON
|
||||
# MIT License
|
||||
|
||||
if(MSVC_VERSION LESS 1910)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_executable(Cpp17Tests
|
||||
string_view.cpp
|
||||
)
|
||||
|
||||
add_test(Cpp17 Cpp17Tests)
|
||||
|
||||
set_tests_properties(Cpp17
|
||||
PROPERTIES
|
||||
LABELS "Catch"
|
||||
)
|
||||
@@ -0,0 +1,113 @@
|
||||
// ArduinoJson - https://arduinojson.org
|
||||
// Copyright © 2014-2025, Benoit BLANCHON
|
||||
// MIT License
|
||||
|
||||
// we expect ArduinoJson.h to include <string_view>
|
||||
// but we don't want it to included accidentally
|
||||
#undef ARDUINO
|
||||
#define ARDUINOJSON_ENABLE_STD_STREAM 0
|
||||
#define ARDUINOJSON_ENABLE_STD_STRING 0
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <catch.hpp>
|
||||
|
||||
#include "Allocators.hpp"
|
||||
#include "Literals.hpp"
|
||||
|
||||
#if !ARDUINOJSON_ENABLE_STRING_VIEW
|
||||
# error ARDUINOJSON_ENABLE_STRING_VIEW must be set to 1
|
||||
#endif
|
||||
|
||||
using ArduinoJson::detail::sizeofArray;
|
||||
|
||||
TEST_CASE("string_view") {
|
||||
SpyingAllocator spy;
|
||||
JsonDocument doc(&spy);
|
||||
JsonVariant variant = doc.to<JsonVariant>();
|
||||
|
||||
SECTION("deserializeJson()") {
|
||||
auto err = deserializeJson(doc, std::string_view("123", 2));
|
||||
REQUIRE(err == DeserializationError::Ok);
|
||||
REQUIRE(doc.as<int>() == 12);
|
||||
}
|
||||
|
||||
SECTION("JsonDocument::set()") {
|
||||
doc.set(std::string_view("123", 2));
|
||||
REQUIRE(doc.as<std::string_view>() == "12");
|
||||
}
|
||||
|
||||
SECTION("JsonDocument::operator[]() const") {
|
||||
doc["ab"] = "Yes";
|
||||
doc["abc"] = "No";
|
||||
REQUIRE(doc[std::string_view("abc", 2)] == "Yes");
|
||||
}
|
||||
|
||||
SECTION("JsonDocument::operator[]()") {
|
||||
doc[std::string_view("abc", 2)] = "Yes";
|
||||
REQUIRE(doc["ab"] == "Yes");
|
||||
}
|
||||
|
||||
SECTION("JsonVariant::operator==()") {
|
||||
variant.set("A");
|
||||
REQUIRE(variant == std::string_view("AX", 1));
|
||||
REQUIRE_FALSE(variant == std::string_view("BX", 1));
|
||||
}
|
||||
|
||||
SECTION("JsonVariant::operator>()") {
|
||||
variant.set("B");
|
||||
REQUIRE(variant > std::string_view("AX", 1));
|
||||
REQUIRE_FALSE(variant > std::string_view("CX", 1));
|
||||
}
|
||||
|
||||
SECTION("JsonVariant::operator<()") {
|
||||
variant.set("B");
|
||||
REQUIRE(variant < std::string_view("CX", 1));
|
||||
REQUIRE_FALSE(variant < std::string_view("AX", 1));
|
||||
}
|
||||
|
||||
SECTION("String deduplication") {
|
||||
doc.add(std::string_view("example one", 7));
|
||||
doc.add(std::string_view("example two", 7));
|
||||
doc.add(std::string_view("example\0tree", 12));
|
||||
doc.add(std::string_view("example\0tree and a half", 12));
|
||||
|
||||
REQUIRE(spy.log() == AllocatorLog{
|
||||
Allocate(sizeofPool()),
|
||||
Allocate(sizeofString("example")),
|
||||
Allocate(sizeofString("example tree")),
|
||||
});
|
||||
}
|
||||
|
||||
SECTION("as<std::string_view>()") {
|
||||
doc["s"] = "Hello World";
|
||||
doc["i"] = 42;
|
||||
REQUIRE(doc["s"].as<std::string_view>() == std::string_view("Hello World"));
|
||||
REQUIRE(doc["i"].as<std::string_view>() == std::string_view());
|
||||
}
|
||||
|
||||
SECTION("is<std::string_view>()") {
|
||||
doc["s"] = "Hello World";
|
||||
doc["i"] = 42;
|
||||
REQUIRE(doc["s"].is<std::string_view>() == true);
|
||||
REQUIRE(doc["i"].is<std::string_view>() == false);
|
||||
}
|
||||
|
||||
SECTION("String containing NUL") {
|
||||
doc.set("hello\0world"_s);
|
||||
REQUIRE(doc.as<std::string_view>().size() == 11);
|
||||
REQUIRE(doc.as<std::string_view>() == std::string_view("hello\0world", 11));
|
||||
}
|
||||
}
|
||||
|
||||
using ArduinoJson::detail::adaptString;
|
||||
|
||||
TEST_CASE("StringViewAdapter") {
|
||||
std::string_view str("bravoXXX", 5);
|
||||
auto adapter = adaptString(str);
|
||||
|
||||
CHECK(stringCompare(adapter, adaptString("alpha", 5)) > 0);
|
||||
CHECK(stringCompare(adapter, adaptString("bravo", 5)) == 0);
|
||||
CHECK(stringCompare(adapter, adaptString("charlie", 7)) < 0);
|
||||
|
||||
CHECK(adapter.size() == 5);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
# ArduinoJson - https://arduinojson.org
|
||||
# Copyright © 2014-2025, Benoit BLANCHON
|
||||
# MIT License
|
||||
|
||||
if(MSVC_VERSION LESS 1910)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_executable(Cpp20Tests
|
||||
smoke_test.cpp
|
||||
)
|
||||
|
||||
add_test(Cpp20 Cpp20Tests)
|
||||
|
||||
set_tests_properties(Cpp20
|
||||
PROPERTIES
|
||||
LABELS "Catch"
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <catch.hpp>
|
||||
#include <string>
|
||||
|
||||
TEST_CASE("C++20 smoke test") {
|
||||
JsonDocument doc;
|
||||
|
||||
deserializeJson(doc, "{\"hello\":\"world\"}");
|
||||
REQUIRE(doc["hello"] == "world");
|
||||
|
||||
std::string json;
|
||||
serializeJson(doc, json);
|
||||
REQUIRE(json == "{\"hello\":\"world\"}");
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user