Compare commits

72 Commits

Author SHA1 Message Date
e60093c419 Merge pull request 'feature/base-firmware-size-optimization' (#18) from feature/base-firmware-size-optimization into main
Reviewed-on: #18
2025-11-04 12:18:31 +01:00
633957c95c feat: configurable PixelStream 2025-10-28 21:05:02 +01:00
ad879bfe7b feat: remove CpuUsage calculation 2025-10-27 10:38:56 +01:00
4559e13d7d fix: revert RAW message 2025-10-27 07:48:06 +01:00
682849650d Merge pull request 'feat: new cluster protocol, event naming' (#17) from feature/cluster-protocol-update into main
Reviewed-on: #17
2025-10-26 12:51:41 +01:00
0f003335b3 feat: new cluster protocol, event naming 2025-10-26 12:43:22 +01:00
eab10cffa5 Merge pull request 'refactoring/firmware-optimizations' (#16) from refactoring/firmware-optimizations into main
Reviewed-on: #16
2025-10-21 13:51:03 +02:00
7f40626187 feat: improve local node initalization 2025-10-21 11:19:12 +02:00
e796375a9f feat: memberlist optimization 2025-10-21 10:50:46 +02:00
daae29dd3f refactor: update local node 2025-10-20 21:35:08 +02:00
37a68e26d8 refactor: remove unused and obsolet stuff 2025-10-20 21:21:02 +02:00
7bd3e87271 Merge pull request 'feature/improved-cluster-forming' (#15) from feature/improved-cluster-forming into main
Reviewed-on: #15
2025-10-19 17:47:05 +02:00
0d09c5900c docs: update 2025-10-19 16:55:48 +02:00
407b651b82 feat: change event naming schema 2025-10-19 13:48:13 +02:00
23289d9f09 fix: latency calculation 2025-10-19 13:11:36 +02:00
b6ad479352 feat: calculate latency during heartbeat 2025-10-19 12:59:26 +02:00
3ed44cd00f feat: improve cluster forming; just use heartbeat to form the cluster 2025-10-19 12:50:43 +02:00
ce70830678 fix: neopattern example wdt reset 2025-10-18 13:49:39 +02:00
b404852fc7 feat: GET node config endpoint 2025-10-15 22:36:20 +02:00
7063b1ab16 feat: persistent custom labels 2025-10-15 22:23:00 +02:00
1a74d1fe90 Merge pull request 'feat: persistent config' (#14) from feature/persistent-config into main
Reviewed-on: #14
2025-10-15 21:56:34 +02:00
993a431310 feat: persistent config 2025-10-15 21:52:24 +02:00
7a7400422e Merge pull request 'feature/refactoring' (#13) from feature/multimatrix-example into main
Reviewed-on: #13
2025-10-14 18:17:23 +02:00
a45871625e refactor: harmonize method names 2025-10-14 18:01:37 +02:00
52f9098c1b feat: example with multiple various functionallity 2025-10-08 12:25:31 +02:00
0fcebc0459 feat: multimatrix example 2025-10-04 13:54:06 +02:00
d07c1f40de chore: move neopattern tests, add new pixelstream scripts 2025-10-03 21:45:26 +02:00
3077f8685b feat: tetris test 2025-10-03 21:41:05 +02:00
3ff5df5a6f Merge pull request 'feat: udp stream' (#12) from feature/udp-raw into main
Reviewed-on: #12
2025-10-03 21:20:31 +02:00
be1a2f1e21 feat: add snek game pixel stream example 2025-10-03 21:20:10 +02:00
1383f6d32f feat: even more matrix stream examples 2025-10-02 22:08:40 +02:00
d1fb5fc96e feat: more matrix stream examples 2025-10-02 21:46:51 +02:00
f78dd8b843 feat: matrix stream example 2025-10-02 21:36:46 +02:00
f3d99b174f feat: udp stream 2025-10-01 22:34:32 +02:00
99e7ed4809 feat: do not store each infos of each node 2025-10-01 20:45:55 +02:00
dbb3b5bfd3 Merge pull request 'feature/streaming' (#10) from feature/streaming into main
Reviewed-on: #10
2025-09-29 06:29:11 +02:00
2aa887a037 docs(streaming): add 'Things to consider' on high-frequency WS usage and throttling
Notes on heap fragmentation, UDP saturation, throttling/coalescing, buffer reuse, yielding, and payload sizing for reliable streaming/broadcast.
2025-09-28 22:10:18 +02:00
49fe0dabf4 remove node_modules 2025-09-28 22:07:07 +02:00
c0bf16fecb chore(test): remove accidentally committed test/node_modules and ignore it 2025-09-28 21:50:39 +02:00
af46b5c2f4 feat: update logging, add high performance example 2025-09-28 21:48:53 +02:00
7a37901fb2 feat(streaming): suppress WS echo via origin tagging
Inject _origin=ws:<clientId> into JSON payloads on inbound WS messages and strip it on broadcast while skipping the origin client. Documents behavior in StreamingAPI.md.
2025-09-28 21:23:05 +02:00
3cc5405292 feat(streaming): introduce WebSocket Streaming API bridging event bus
ApiServer: add AsyncWebSocket at /ws; accept JSON {event, payload} (string or object) and dispatch via ctx.fire; mirror all local events to clients using NodeContext::onAny.\nNodeContext: add onAny subscriber API.\nNeoPatternService: add api/neopattern/color event to set solid color.\nCluster: centralize cluster/broadcast sending in core; services delegate.\nAPI: add generic /api/node/event and /api/cluster/event endpoints in respective services.\nTests: add ws-color-client, ws-cluster-broadcast-color, http-cluster-broadcast-color.\nDocs: add StreamingAPI.md; update README and test/README.\nFixes: robust WS JSON parsing on ESP8266 and payload handling.
2025-09-28 21:10:26 +02:00
f0df84dc87 feat(api): add generic event endpoints in NodeService and ClusterService
NodeService: add POST /api/node/event to fire local events (params: event, payload).\nClusterService: add POST /api/cluster/event to broadcast events via cluster/broadcast (params: event, payload).\nApiServer: remove inline generic endpoints; services own their registrations.\nUnifies event dispatch through service layer and keeps core server lean.
2025-09-28 17:26:20 +02:00
950142bf7f docs(readme): add Cluster Broadcast section and link to detailed guide
Documents centralized cluster broadcasting flow: service fires local event, core sends UDP CLUSTER_EVENT, peers re-fire locally. Includes usage example and notes; links to docs/ClusterBroadcast.md.
2025-09-28 13:49:48 +02:00
b9b91d71b5 docs: add ClusterBroadcast guide for CLUSTER_EVENT and cluster/broadcast flow
Documents centralized cluster broadcasting: service -> ctx.fire("cluster/broadcast", eventJson) -> UDP CLUSTER_EVENT -> peer ctx.fire(event, data). Includes message format, service/core responsibilities, logging, networking notes, and troubleshooting.
2025-09-28 13:45:10 +02:00
0e6adc999f Merge pull request 'feature/cluster-message' (#9) from feature/cluster-message into main
Reviewed-on: #9
2025-09-28 13:43:53 +02:00
f4ccb1c7ef feat(cluster): centralize cluster/broadcast event for UDP CLUSTER_EVENT send
Register core handler ctx.on("cluster/broadcast") in ClusterManager to send CLUSTER_EVENT via subnet-directed UDP broadcast; services now delegate broadcasting by firing this event. Fix lambda to reference this->ctx inside handler. Update NeoPatternService to fire cluster/broadcast with event JSON instead of sending UDP directly. Improves consistency and removes duplicated UDP code in services.
2025-09-28 13:35:42 +02:00
8da9f77441 refactor(neopattern): unify control handling via event-based single path
Build control payload once from request params and apply locally through ctx.fire("api/neopattern"). If broadcast=true, send the same payload as a CLUSTER_EVENT over UDP.\n\nChanges:\n- Remove duplicated per-parameter setters in REST handler\n- Always fire local event to update state consistently\n- Broadcast reuses same payload; logs retained\n\nImpact:\n- Less duplication, clearer flow, identical behavior for local/remote updates, and simpler maintenance.
2025-09-28 13:27:02 +02:00
cabf857bbd feat(cluster, neopattern): add CLUSTER_EVENT and broadcast handling
Add CLUSTER_EVENT message type and end-to-end handling across cluster and NeoPattern example.\n\nCluster protocol / core:\n- Add ClusterProtocol::CLUSTER_EVENT_MSG\n- ClusterManager: register predicate/handler for CLUSTER_EVENT\n- Robust parsing: accept data as string or nested JSON; serialize nested data to string before firing\n- Add defensive null-termination for full UDP reads; log unknown message head if no handler matches\n- Logging: source IP, payload length, missing fields, and pre-fire event details\n\nNeoPatternService:\n- Constructor now accepts NodeContext and registers ctx.on(api/neopattern) handler\n- /api/neopattern: add optional boolean 'broadcast' flag\n- If broadcast=true: build event payload and send CLUSTER_EVENT over UDP (subnet-directed broadcast); log target and payload size; also fire locally so sender applies immediately\n- Implement applyControlParams with robust ArduinoJson is<T>() checks (replaces deprecated containsKey()) and flexible string/number parsing for color, brightness, steps, interval\n- Minor fixes: include Globals for ClusterProtocol, include ESP8266WiFi for broadcast IP calc, safer brightness clamping\n\nExample:\n- Update neopattern main to pass NodeContext into NeoPatternService\n\nResult:\n- Nodes receive and process CLUSTER_EVENT (api/neopattern) via ctx.fire/ctx.on\n- Broadcast reliably reaches peers; parsing handles both stringified and nested JSON data\n- Additional logs aid diagnosis of delivery/format issues and remove deprecation warnings
2025-09-28 13:18:25 +02:00
2bb0742850 Merge pull request 'feature/node-info-sync' (#8) from feature/node-info-sync into main
Reviewed-on: #8
2025-09-28 12:26:38 +02:00
69bc3fc829 docs: update 2025-09-25 22:47:28 +02:00
eaeb9bbea8 config: more frequent cluster_listen 2025-09-25 22:07:22 +02:00
096cf12704 feat: measure latency 2025-09-25 21:54:25 +02:00
356ec3d381 feat: simplify udp listen 2025-09-25 20:44:31 +02:00
51bd7bd909 feat: introduce udp state machine 2025-09-24 21:23:00 +02:00
921eec3848 docs: update 2025-09-24 21:12:22 +02:00
921e2c7152 feat(cluster): move member info sync to UDP heartbeat; remove HTTP polling
Broadcast CLUSTER_HEARTBEAT every 5s; peers reply with CLUSTER_NODE_INFO containing resources and labels. Update memberList from received node info and set status/lastSeen; keep UDP discovery responses.

Disable HTTP-based updateAllMembersInfoTaskCallback loop to reduce network and memory overhead.

Add protocol constants HEARTBEAT_MSG and NODE_INFO_MSG; increase UDP buffer to 512 bytes.

Set default heartbeat_interval_ms to 5000 ms.

Fix sdkVersion JSON fallback by converting to const char* before assigning to String.
2025-09-23 22:11:49 +02:00
c11652c123 feat: optimize neopattern example 2025-09-20 22:05:52 +02:00
51d4d4bc94 feat: remove hostname from labels 2025-09-20 15:04:11 +02:00
e95eb09a11 feat: introduce new param type numberRange 2025-09-19 21:49:59 +02:00
4727405be1 feat: rewrite NeoPattern example 2025-09-19 21:02:26 +02:00
93f09c3bb4 feat: unified monitoring service 2025-09-16 20:09:48 +02:00
83d87159fc Merge pull request 'feature/relay-example-ui' (#6) from feature/relay-example-ui into main
Reviewed-on: #6
2025-09-16 18:14:58 +02:00
f7f5918509 fix: builds 2025-09-16 18:14:41 +02:00
95a7e3167e fix: revert low mem treshold 2025-09-16 17:06:14 +02:00
702eec5a13 feat: releay ui example, simplify logging 2025-09-16 15:32:49 +02:00
2d85f560bb feat: serve static files, relay example 2025-09-16 12:12:27 +02:00
0b63efece0 Merge pull request 'feat: logging service' (#5) from feature/logging into main
Reviewed-on: #5
2025-09-16 10:11:27 +02:00
8a2988cb50 feat: logging service 2025-09-16 10:10:23 +02:00
6a30dc0dc1 Merge pull request 'feature/web' (#4) from feature/web into main
Reviewed-on: #4
2025-09-16 08:29:04 +02:00
5229848600 chore: cleanup 2025-09-16 08:28:28 +02:00
adde7a3676 docs: add FS and FileService info 2025-09-16 08:27:05 +02:00
117 changed files with 12474 additions and 2581 deletions

View File

@@ -9,6 +9,8 @@ SPORE is a cluster engine for ESP8266 microcontrollers that provides automatic n
- [Features](#features) - [Features](#features)
- [Supported Hardware](#supported-hardware) - [Supported Hardware](#supported-hardware)
- [Architecture](#architecture) - [Architecture](#architecture)
- [Cluster Broadcast](#cluster-broadcast)
- [Streaming API](#streaming-api)
- [API Reference](#api-reference) - [API Reference](#api-reference)
- [Configuration](#configuration) - [Configuration](#configuration)
- [Development](#development) - [Development](#development)
@@ -26,6 +28,8 @@ SPORE is a cluster engine for ESP8266 microcontrollers that provides automatic n
- **Service Registry**: Dynamic API endpoint discovery and registration - **Service Registry**: Dynamic API endpoint discovery and registration
- **Health Monitoring**: Real-time node status tracking with resource monitoring - **Health Monitoring**: Real-time node status tracking with resource monitoring
- **Event System**: Local and cluster-wide event publishing/subscription - **Event System**: Local and cluster-wide event publishing/subscription
- **Cluster Broadcast**: Centralized UDP broadcast of events (CLUSTER_EVENT)
- **Streaming API**: WebSocket bridge for real-time event send/receive
- **Over-The-Air Updates**: Seamless firmware updates across the cluster - **Over-The-Air Updates**: Seamless firmware updates across the cluster
- **REST API**: HTTP-based cluster management and monitoring - **REST API**: HTTP-based cluster management and monitoring
- **Capability Discovery**: Automatic API endpoint and service capability detection - **Capability Discovery**: Automatic API endpoint and service capability detection
@@ -89,12 +93,12 @@ void setup() {
spore.setup(); spore.setup();
// Create and register custom services // Create and register custom services
RelayService* relayService = new RelayService(spore.getTaskManager(), 2); RelayService* relayService = new RelayService(spore.getContext(), spore.getTaskManager(), 2);
spore.addService(relayService); spore.registerService(relayService);
// Or using smart pointers // Or using smart pointers
auto sensorService = std::make_shared<SensorService>(); auto sensorService = std::make_shared<SensorService>(spore.getContext(), spore.getTaskManager());
spore.addService(sensorService); spore.registerService(sensorService);
// Start the API server and complete initialization // Start the API server and complete initialization
spore.begin(); spore.begin();
@@ -103,6 +107,52 @@ void setup() {
**Examples:** See [`examples/base/`](./examples/base/) for basic usage and [`examples/relay/`](./examples/relay/) for custom service integration. **Examples:** See [`examples/base/`](./examples/base/) for basic usage and [`examples/relay/`](./examples/relay/) for custom service integration.
## Cluster Broadcast
Broadcast an event to all peers using the centralized core broadcaster. Services never touch UDP directly; instead they fire a local event that the core transmits as a `CLUSTER_EVENT`.
Usage:
```cpp
// 1) Apply locally via the same event your service already handles
JsonDocument payload;
payload["pattern"] = "rainbow_cycle";
payload["brightness"] = 100;
String payloadStr; serializeJson(payload, payloadStr);
ctx.fire("api/neopattern", &payloadStr);
// 2) Broadcast to peers via the core
JsonDocument envelope;
envelope["event"] = "api/neopattern";
envelope["data"] = payloadStr; // JSON string
String eventJson; serializeJson(envelope, eventJson);
ctx.fire("cluster/broadcast", &eventJson);
```
Notes:
- The core sends subnet-directed broadcasts (e.g., 192.168.1.255) for reliability.
- Peers receive `CLUSTER_EVENT` and forward to local subscribers with `ctx.fire(event, data)`.
- `data` can be a JSON string or nested JSON; receivers handle both.
📖 See the dedicated guide: [`docs/ClusterBroadcast.md`](./docs/ClusterBroadcast.md)
## Streaming API
Real-time event bridge available at `/ws` using WebSocket.
- Send JSON `{ event, payload }` to dispatch events via `ctx.fire`.
- Receive all local events as `{ event, payload }`.
Examples:
```json
{ "event": "api/neopattern/color", "payload": { "color": "#FF0000", "brightness": 128 } }
```
```json
{ "event": "cluster/broadcast", "payload": { "event": "api/neopattern/color", "data": { "color": "#00FF00" } } }
```
📖 See the dedicated guide: [`docs/StreamingAPI.md`](./docs/StreamingAPI.md)
## API Reference ## API Reference
The system provides a comprehensive RESTful API for monitoring and controlling the embedded device. All endpoints return JSON responses and support standard HTTP status codes. The system provides a comprehensive RESTful API for monitoring and controlling the embedded device. All endpoints return JSON responses and support standard HTTP status codes.

23
cmds.sh
View File

@@ -1,23 +0,0 @@
# -> file not found
mklittlefs -c data \
-s 0x9000 \
-b 4096 \
-p 256 \
littlefs.bin
esptool.py --port /dev/ttyUSB0 \
--baud 115200 \
write_flash 0x71000 littlefs.bin
# pio -->
"mklittlefs" -c data -p 256 -b 4096 -s 262144 .pio/build/esp01_1m/littlefs.bin
esptool.py --before no_reset \
--after soft_reset \
--chip esp8266 \
--port "/dev/ttyUSB0" \
--baud 115200 \
write_flash 765952 .pio/build/esp01_1m/littlefs.bin

View File

@@ -1,824 +0,0 @@
[
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/Crypto.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/Crypto.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/Crypto.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/Crypto.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/Esp-frag.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/Esp-frag.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/Esp-frag.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/Esp-frag.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/Esp-version.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/Esp-version.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/Esp-version.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/Esp-version.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/Esp.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/Esp.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/Esp.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/Esp.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/FS.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/FS.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/FS.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/FS.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/FSnoop.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/FSnoop.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/FSnoop.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/FSnoop.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/FunctionalInterrupt.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/FunctionalInterrupt.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/FunctionalInterrupt.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/FunctionalInterrupt.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/HardwareSerial.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/HardwareSerial.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/HardwareSerial.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/HardwareSerial.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/IPAddress.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/IPAddress.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/IPAddress.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/IPAddress.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/LwipDhcpServer-NonOS.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/LwipDhcpServer-NonOS.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/LwipDhcpServer-NonOS.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/LwipDhcpServer-NonOS.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/LwipDhcpServer.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/LwipDhcpServer.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/LwipDhcpServer.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/LwipDhcpServer.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/LwipIntf.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/LwipIntf.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/LwipIntf.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/LwipIntf.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/LwipIntfCB.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/LwipIntfCB.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/LwipIntfCB.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/LwipIntfCB.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/MD5Builder.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/MD5Builder.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/MD5Builder.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/MD5Builder.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/Print.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/Print.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/Print.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/Print.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/Schedule.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/Schedule.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/Schedule.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/Schedule.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/StackThunk.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/StackThunk.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/StackThunk.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/StackThunk.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/Stream.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/Stream.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/Stream.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/Stream.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/StreamSend.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/StreamSend.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/StreamSend.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/StreamSend.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/Tone.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/Tone.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/Tone.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/Tone.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/TypeConversion.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/TypeConversion.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/TypeConversion.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/TypeConversion.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/Updater.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/Updater.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/Updater.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/Updater.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/WMath.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/WMath.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/WMath.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/WMath.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/WString.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/WString.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/WString.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/WString.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/abi.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/abi.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/abi.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/abi.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/aes_unwrap.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/aes_unwrap.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/aes_unwrap.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/aes_unwrap.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/base64.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/base64.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/base64.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/base64.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/cbuf.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/cbuf.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/cbuf.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/cbuf.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-gcc -mlongcalls -mtext-section-literals -x assembler-with-cpp -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini -c -o .pio/build/d1_mini_static_web/FrameworkArduino/cont.S.o /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/cont.S",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/cont.S",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/cont.S.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/cont_util.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/cont_util.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/cont_util.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/cont_util.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_app_entry_noextra4k.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_app_entry_noextra4k.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_app_entry_noextra4k.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_app_entry_noextra4k.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_eboot_command.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_eboot_command.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_eboot_command.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_eboot_command.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_features.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_features.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_features.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_features.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_flash_quirks.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_flash_quirks.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_flash_quirks.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_flash_quirks.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_flash_utils.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_flash_utils.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_flash_utils.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_flash_utils.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_i2s.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_i2s.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_i2s.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_i2s.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_main.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_main.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_main.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_main.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_non32xfer.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_non32xfer.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_non32xfer.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_non32xfer.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_noniso.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_noniso.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_noniso.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_noniso.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_phy.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_phy.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_phy.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_phy.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_postmortem.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_postmortem.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_postmortem.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_postmortem.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_si2c.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_si2c.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_si2c.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_si2c.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_sigma_delta.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_sigma_delta.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_sigma_delta.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_sigma_delta.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_spi_utils.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_spi_utils.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_spi_utils.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_spi_utils.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_timer.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_timer.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_timer.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_timer.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_vm.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_vm.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_vm.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_vm.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_waveform_phase.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_waveform_phase.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_waveform_phase.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_waveform_phase.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_waveform_pwm.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_waveform_pwm.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_waveform_pwm.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_waveform_pwm.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_wiring.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_wiring.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_wiring.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_wiring.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_wiring_analog.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_wiring_analog.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_wiring_analog.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_wiring_analog.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_wiring_digital.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_wiring_digital.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_wiring_digital.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_wiring_digital.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_wiring_pulse.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_wiring_pulse.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_wiring_pulse.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_wiring_pulse.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_wiring_pwm.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_wiring_pwm.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_wiring_pwm.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_wiring_pwm.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_wiring_shift.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_wiring_shift.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/core_esp8266_wiring_shift.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/core_esp8266_wiring_shift.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/crc32.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/crc32.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/crc32.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/crc32.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/debug.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/debug.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/debug.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/debug.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-gcc -mlongcalls -mtext-section-literals -x assembler-with-cpp -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini -c -o .pio/build/d1_mini_static_web/FrameworkArduino/exc-c-wrapper-handler.S.o /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/exc-c-wrapper-handler.S",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/exc-c-wrapper-handler.S",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/exc-c-wrapper-handler.S.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/exc-sethandler.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/exc-sethandler.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/exc-sethandler.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/exc-sethandler.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/flash_hal.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/flash_hal.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/flash_hal.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/flash_hal.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/gdb_hooks.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/gdb_hooks.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/gdb_hooks.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/gdb_hooks.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/heap.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/heap.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/heap.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/heap.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/hwdt_app_entry.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/hwdt_app_entry.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/hwdt_app_entry.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/hwdt_app_entry.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/libb64/cdecode.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/libb64/cdecode.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/libb64/cdecode.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/libb64/cdecode.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/libb64/cencode.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/libb64/cencode.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/libb64/cencode.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/libb64/cencode.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/libc_replacements.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/libc_replacements.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/libc_replacements.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/libc_replacements.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/mmu_iram.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/mmu_iram.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/mmu_iram.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/mmu_iram.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/reboot_uart_dwnld.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/reboot_uart_dwnld.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/reboot_uart_dwnld.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/reboot_uart_dwnld.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/spiffs/spiffs_cache.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/spiffs/spiffs_cache.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/spiffs/spiffs_cache.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/spiffs/spiffs_cache.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/spiffs/spiffs_check.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/spiffs/spiffs_check.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/spiffs/spiffs_check.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/spiffs/spiffs_check.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/spiffs/spiffs_gc.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/spiffs/spiffs_gc.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/spiffs/spiffs_gc.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/spiffs/spiffs_gc.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/spiffs/spiffs_hydrogen.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/spiffs/spiffs_hydrogen.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/spiffs/spiffs_hydrogen.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/spiffs/spiffs_hydrogen.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/spiffs/spiffs_nucleus.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/spiffs/spiffs_nucleus.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/spiffs/spiffs_nucleus.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/spiffs/spiffs_nucleus.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/spiffs_api.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/spiffs_api.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/spiffs_api.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/spiffs_api.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/sqrt32.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/sqrt32.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/sqrt32.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/sqrt32.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/stdlib_noniso.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/stdlib_noniso.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/stdlib_noniso.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/stdlib_noniso.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/time.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/time.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/time.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/time.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/uart.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/uart.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/uart.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/uart.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-gcc -o .pio/build/d1_mini_static_web/FrameworkArduino/umm_malloc/umm_info.c.o -c -std=gnu17 -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/umm_malloc/umm_info.c",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/umm_malloc/umm_info.c",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/umm_malloc/umm_info.c.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-gcc -o .pio/build/d1_mini_static_web/FrameworkArduino/umm_malloc/umm_integrity.c.o -c -std=gnu17 -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/umm_malloc/umm_integrity.c",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/umm_malloc/umm_integrity.c",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/umm_malloc/umm_integrity.c.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-gcc -o .pio/build/d1_mini_static_web/FrameworkArduino/umm_malloc/umm_local.c.o -c -std=gnu17 -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/umm_malloc/umm_local.c",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/umm_malloc/umm_local.c",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/umm_malloc/umm_local.c.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/umm_malloc/umm_malloc.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/umm_malloc/umm_malloc.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/umm_malloc/umm_malloc.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/umm_malloc/umm_malloc.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-gcc -o .pio/build/d1_mini_static_web/FrameworkArduino/umm_malloc/umm_poison.c.o -c -std=gnu17 -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/umm_malloc/umm_poison.c",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/umm_malloc/umm_poison.c",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/umm_malloc/umm_poison.c.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/FrameworkArduino/wpa2_eap_patch.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/wpa2_eap_patch.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266/wpa2_eap_patch.cpp",
"output": ".pio/build/d1_mini_static_web/FrameworkArduino/wpa2_eap_patch.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/libe54/Hash/Hash.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src/Hash.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src/Hash.cpp",
"output": ".pio/build/d1_mini_static_web/libe54/Hash/Hash.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/libe48/ESPAsyncTCP/AsyncPrinter.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini .pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src/AsyncPrinter.cpp",
"directory": "/home/master/src/embedded/spore",
"file": ".pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src/AsyncPrinter.cpp",
"output": ".pio/build/d1_mini_static_web/libe48/ESPAsyncTCP/AsyncPrinter.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/libe48/ESPAsyncTCP/ESPAsyncTCP.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini .pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src/ESPAsyncTCP.cpp",
"directory": "/home/master/src/embedded/spore",
"file": ".pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src/ESPAsyncTCP.cpp",
"output": ".pio/build/d1_mini_static_web/libe48/ESPAsyncTCP/ESPAsyncTCP.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/libe48/ESPAsyncTCP/ESPAsyncTCPbuffer.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini .pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src/ESPAsyncTCPbuffer.cpp",
"directory": "/home/master/src/embedded/spore",
"file": ".pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src/ESPAsyncTCPbuffer.cpp",
"output": ".pio/build/d1_mini_static_web/libe48/ESPAsyncTCP/ESPAsyncTCPbuffer.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/libe48/ESPAsyncTCP/SyncClient.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini .pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src/SyncClient.cpp",
"directory": "/home/master/src/embedded/spore",
"file": ".pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src/SyncClient.cpp",
"output": ".pio/build/d1_mini_static_web/libe48/ESPAsyncTCP/SyncClient.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-gcc -o .pio/build/d1_mini_static_web/libe48/ESPAsyncTCP/tcp_axtls.c.o -c -std=gnu17 -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini .pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src/tcp_axtls.c",
"directory": "/home/master/src/embedded/spore",
"file": ".pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src/tcp_axtls.c",
"output": ".pio/build/d1_mini_static_web/libe48/ESPAsyncTCP/tcp_axtls.c.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/BearSSLHelpers.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/BearSSLHelpers.cpp",
"output": ".pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/BearSSLHelpers.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/CertStoreBearSSL.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/CertStoreBearSSL.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/CertStoreBearSSL.cpp",
"output": ".pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/CertStoreBearSSL.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/ESP8266WiFi.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/ESP8266WiFi.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/ESP8266WiFi.cpp",
"output": ".pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/ESP8266WiFi.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/ESP8266WiFiAP.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/ESP8266WiFiAP.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/ESP8266WiFiAP.cpp",
"output": ".pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/ESP8266WiFiAP.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/ESP8266WiFiGeneric.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.cpp",
"output": ".pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/ESP8266WiFiGeneric.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/ESP8266WiFiGratuitous.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/ESP8266WiFiGratuitous.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/ESP8266WiFiGratuitous.cpp",
"output": ".pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/ESP8266WiFiGratuitous.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/ESP8266WiFiMulti.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/ESP8266WiFiMulti.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/ESP8266WiFiMulti.cpp",
"output": ".pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/ESP8266WiFiMulti.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/ESP8266WiFiSTA-WPS.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/ESP8266WiFiSTA-WPS.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/ESP8266WiFiSTA-WPS.cpp",
"output": ".pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/ESP8266WiFiSTA-WPS.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/ESP8266WiFiSTA.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/ESP8266WiFiSTA.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/ESP8266WiFiSTA.cpp",
"output": ".pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/ESP8266WiFiSTA.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/ESP8266WiFiScan.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/ESP8266WiFiScan.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/ESP8266WiFiScan.cpp",
"output": ".pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/ESP8266WiFiScan.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/WiFiClient.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/WiFiClient.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/WiFiClient.cpp",
"output": ".pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/WiFiClient.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/WiFiClientSecureBearSSL.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/WiFiClientSecureBearSSL.cpp",
"output": ".pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/WiFiClientSecureBearSSL.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/WiFiServer.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/WiFiServer.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/WiFiServer.cpp",
"output": ".pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/WiFiServer.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/WiFiServerSecureBearSSL.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/WiFiServerSecureBearSSL.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/WiFiServerSecureBearSSL.cpp",
"output": ".pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/WiFiServerSecureBearSSL.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/WiFiUdp.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/WiFiUdp.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/WiFiUdp.cpp",
"output": ".pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/WiFiUdp.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/enable_wifi_at_boot_time.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/enable_wifi_at_boot_time.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src/enable_wifi_at_boot_time.cpp",
"output": ".pio/build/d1_mini_static_web/lib39d/ESP8266WiFi/enable_wifi_at_boot_time.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/AsyncEventSource.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini .pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/AsyncEventSource.cpp",
"directory": "/home/master/src/embedded/spore",
"file": ".pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/AsyncEventSource.cpp",
"output": ".pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/AsyncEventSource.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/AsyncJson.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini .pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/AsyncJson.cpp",
"directory": "/home/master/src/embedded/spore",
"file": ".pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/AsyncJson.cpp",
"output": ".pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/AsyncJson.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/AsyncMessagePack.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini .pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/AsyncMessagePack.cpp",
"directory": "/home/master/src/embedded/spore",
"file": ".pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/AsyncMessagePack.cpp",
"output": ".pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/AsyncMessagePack.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/AsyncWebHeader.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini .pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/AsyncWebHeader.cpp",
"directory": "/home/master/src/embedded/spore",
"file": ".pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/AsyncWebHeader.cpp",
"output": ".pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/AsyncWebHeader.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/AsyncWebServerRequest.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini .pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/AsyncWebServerRequest.cpp",
"directory": "/home/master/src/embedded/spore",
"file": ".pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/AsyncWebServerRequest.cpp",
"output": ".pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/AsyncWebServerRequest.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/AsyncWebSocket.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini .pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/AsyncWebSocket.cpp",
"directory": "/home/master/src/embedded/spore",
"file": ".pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/AsyncWebSocket.cpp",
"output": ".pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/AsyncWebSocket.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/BackPort_SHA1Builder.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini .pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/BackPort_SHA1Builder.cpp",
"directory": "/home/master/src/embedded/spore",
"file": ".pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/BackPort_SHA1Builder.cpp",
"output": ".pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/BackPort_SHA1Builder.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/ChunkPrint.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini .pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/ChunkPrint.cpp",
"directory": "/home/master/src/embedded/spore",
"file": ".pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/ChunkPrint.cpp",
"output": ".pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/ChunkPrint.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/Middleware.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini .pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/Middleware.cpp",
"directory": "/home/master/src/embedded/spore",
"file": ".pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/Middleware.cpp",
"output": ".pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/Middleware.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/WebAuthentication.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini .pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/WebAuthentication.cpp",
"directory": "/home/master/src/embedded/spore",
"file": ".pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/WebAuthentication.cpp",
"output": ".pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/WebAuthentication.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/WebHandlers.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini .pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/WebHandlers.cpp",
"directory": "/home/master/src/embedded/spore",
"file": ".pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/WebHandlers.cpp",
"output": ".pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/WebHandlers.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/WebRequest.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini .pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/WebRequest.cpp",
"directory": "/home/master/src/embedded/spore",
"file": ".pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/WebRequest.cpp",
"output": ".pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/WebRequest.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/WebResponses.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini .pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/WebResponses.cpp",
"directory": "/home/master/src/embedded/spore",
"file": ".pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/WebResponses.cpp",
"output": ".pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/WebResponses.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/WebServer.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini .pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/WebServer.cpp",
"directory": "/home/master/src/embedded/spore",
"file": ".pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src/WebServer.cpp",
"output": ".pio/build/d1_mini_static_web/lib327/ESPAsyncWebServer/WebServer.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib5ca/LittleFS/LittleFS.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src/LittleFS.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src/LittleFS.cpp",
"output": ".pio/build/d1_mini_static_web/lib5ca/LittleFS/LittleFS.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-gcc -o .pio/build/d1_mini_static_web/lib5ca/LittleFS/lfs.c.o -c -std=gnu17 -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src/lfs.c",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src/lfs.c",
"output": ".pio/build/d1_mini_static_web/lib5ca/LittleFS/lfs.c.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-gcc -o .pio/build/d1_mini_static_web/lib5ca/LittleFS/lfs_util.c.o -c -std=gnu17 -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src/lfs_util.c",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src/lfs_util.c",
"output": ".pio/build/d1_mini_static_web/lib5ca/LittleFS/lfs_util.c.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/lib111/ESP8266HTTPClient/ESP8266HTTPClient.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266HTTPClient/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini /home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266HTTPClient/src/ESP8266HTTPClient.cpp",
"output": ".pio/build/d1_mini_static_web/lib111/ESP8266HTTPClient/ESP8266HTTPClient.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/src/examples/static_web/main.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I. -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266HTTPClient/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini examples/static_web/main.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "examples/static_web/main.cpp",
"output": ".pio/build/d1_mini_static_web/src/examples/static_web/main.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/src/src/spore/Spore.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I. -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266HTTPClient/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini src/spore/Spore.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "src/spore/Spore.cpp",
"output": ".pio/build/d1_mini_static_web/src/src/spore/Spore.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/src/src/spore/core/ApiServer.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I. -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266HTTPClient/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini src/spore/core/ApiServer.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "src/spore/core/ApiServer.cpp",
"output": ".pio/build/d1_mini_static_web/src/src/spore/core/ApiServer.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/src/src/spore/core/ClusterManager.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I. -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266HTTPClient/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini src/spore/core/ClusterManager.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "src/spore/core/ClusterManager.cpp",
"output": ".pio/build/d1_mini_static_web/src/src/spore/core/ClusterManager.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/src/src/spore/core/NetworkManager.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I. -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266HTTPClient/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini src/spore/core/NetworkManager.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "src/spore/core/NetworkManager.cpp",
"output": ".pio/build/d1_mini_static_web/src/src/spore/core/NetworkManager.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/src/src/spore/core/NodeContext.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I. -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266HTTPClient/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini src/spore/core/NodeContext.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "src/spore/core/NodeContext.cpp",
"output": ".pio/build/d1_mini_static_web/src/src/spore/core/NodeContext.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/src/src/spore/core/TaskManager.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I. -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266HTTPClient/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini src/spore/core/TaskManager.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "src/spore/core/TaskManager.cpp",
"output": ".pio/build/d1_mini_static_web/src/src/spore/core/TaskManager.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/src/src/spore/services/ClusterService.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I. -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266HTTPClient/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini src/spore/services/ClusterService.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "src/spore/services/ClusterService.cpp",
"output": ".pio/build/d1_mini_static_web/src/src/spore/services/ClusterService.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/src/src/spore/services/NetworkService.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I. -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266HTTPClient/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini src/spore/services/NetworkService.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "src/spore/services/NetworkService.cpp",
"output": ".pio/build/d1_mini_static_web/src/src/spore/services/NetworkService.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/src/src/spore/services/NodeService.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I. -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266HTTPClient/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini src/spore/services/NodeService.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "src/spore/services/NodeService.cpp",
"output": ".pio/build/d1_mini_static_web/src/src/spore/services/NodeService.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/src/src/spore/services/StaticFileService.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I. -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266HTTPClient/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini src/spore/services/StaticFileService.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "src/spore/services/StaticFileService.cpp",
"output": ".pio/build/d1_mini_static_web/src/src/spore/services/StaticFileService.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/src/src/spore/services/TaskService.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I. -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266HTTPClient/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini src/spore/services/TaskService.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "src/spore/services/TaskService.cpp",
"output": ".pio/build/d1_mini_static_web/src/src/spore/services/TaskService.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/src/src/spore/types/Config.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I. -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266HTTPClient/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini src/spore/types/Config.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "src/spore/types/Config.cpp",
"output": ".pio/build/d1_mini_static_web/src/src/spore/types/Config.cpp.o"
},
{
"command": "/home/master/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-g++ -o .pio/build/d1_mini_static_web/src/src/spore/types/NodeInfo.cpp.o -c -fno-rtti -std=gnu++17 -fno-exceptions -Os -mlongcalls -mtext-section-literals -falign-functions=4 -U__STRICT_ANSI__ -ffunction-sections -fdata-sections -Wall -Werror=return-type -free -fipa-pta -DPLATFORMIO=60118 -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_WEMOS_D1MINI -DF_CPU=80000000L -D__ets__ -DICACHE_FLASH -D_GNU_SOURCE -DARDUINO=10805 -DARDUINO_BOARD=\\\"PLATFORMIO_D1_MINI\\\" -DARDUINO_BOARD_ID=\\\"d1_mini\\\" -DFLASHMODE_DIO -DLWIP_OPEN_SRC -DNONOSDK22x_190703=1 -DTCP_MSS=536 -DLWIP_FEATURES=1 -DLWIP_IPV6=0 -DVTABLES_IN_FLASH -DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000 -Iinclude -I. -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266HTTPClient/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/LittleFS/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncWebServer/src -I.pio/libdeps/d1_mini_static_web/ArduinoJson/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/ESP8266WiFi/src -I.pio/libdeps/d1_mini_static_web/ESPAsyncTCP/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/libraries/Hash/src -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/cores/esp8266 -I/home/master/.platformio/packages/toolchain-xtensa/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lwip2/include -I/home/master/.platformio/packages/framework-arduinoespressif8266/variants/d1_mini src/spore/types/NodeInfo.cpp",
"directory": "/home/master/src/embedded/spore",
"file": "src/spore/types/NodeInfo.cpp",
"output": ".pio/build/d1_mini_static_web/src/src/spore/types/NodeInfo.cpp.o"
}
]

417
ctl.sh
View File

@@ -4,6 +4,36 @@ set -e
source .env source .env
## Spore Control Script
## Usage: ./ctl.sh <command> [options]
##
## Commands:
## build [target] - Build firmware for target (base, d1_mini, etc.)
## flash [target] - Flash firmware to device
## uploadfs [target] - Upload filesystem to device
## ota update <ip> <target> - OTA update specific node
## ota all <target> - OTA update all nodes in cluster
## cluster members - List cluster members
## node wifi <ssid> <password> [ip] - Configure WiFi on node
## node label set <key=value> [ip] - Set a label on node
## node label delete <key> [ip] - Delete a label from node
## node config get [ip] - Get node configuration
## node status [ip] - Get node status and information
## monitor - Monitor serial output
##
## Examples:
## ./ctl.sh build base
## ./ctl.sh flash d1_mini
## ./ctl.sh node wifi "MyNetwork" "MyPassword"
## ./ctl.sh node wifi "MyNetwork" "MyPassword" 192.168.1.100
## ./ctl.sh node label set "environment=production"
## ./ctl.sh node label set "location=office" 192.168.1.100
## ./ctl.sh node label delete "environment"
## ./ctl.sh node config get
## ./ctl.sh node config get 192.168.1.100
## ./ctl.sh node status
## ./ctl.sh node status 192.168.1.100
function info { function info {
sed -n 's/^##//p' ctl.sh sed -n 's/^##//p' ctl.sh
} }
@@ -69,6 +99,393 @@ function cluster {
${@:-info} ${@:-info}
} }
function node {
function wifi {
if [ $# -lt 2 ]; then
echo "Usage: $0 node wifi <ssid> <password> [node_ip]"
echo " ssid: WiFi network name"
echo " password: WiFi password"
echo " node_ip: Optional IP address (defaults to API_NODE from .env)"
return 1
fi
local ssid="$1"
local password="$2"
local node_ip="${3:-$API_NODE}"
echo "Configuring WiFi on node $node_ip..."
echo "SSID: $ssid"
# Configure WiFi using the API endpoint
response=$(curl -s -w "\n%{http_code}" -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "ssid=$ssid&password=$password" \
"http://$node_ip/api/network/wifi/config" 2>/dev/null || echo -e "\n000")
# Extract HTTP status code and response body
http_code=$(echo "$response" | tail -n1)
response_body=$(echo "$response" | head -n -1)
# Check if curl succeeded
if [ "$http_code" = "000" ] || [ -z "$response_body" ]; then
echo "Error: Failed to connect to node at $node_ip"
echo "Please check:"
echo " - Node is powered on and connected to network"
echo " - IP address is correct"
echo " - Node is running Spore firmware"
return 1
fi
# Check HTTP status code
if [ "$http_code" != "200" ]; then
echo "Error: HTTP $http_code - Server error"
echo "Response: $response_body"
return 1
fi
# Parse and display the response
status=$(echo "$response_body" | jq -r '.status // "unknown"')
message=$(echo "$response_body" | jq -r '.message // "No message"')
config_saved=$(echo "$response_body" | jq -r '.config_saved // false')
restarting=$(echo "$response_body" | jq -r '.restarting // false')
connected=$(echo "$response_body" | jq -r '.connected // false')
ip=$(echo "$response_body" | jq -r '.ip // "N/A"')
echo "Status: $status"
echo "Message: $message"
echo "Config saved: $config_saved"
if [ "$restarting" = "true" ]; then
echo "Restarting: true"
echo "Note: Node will restart to apply new WiFi settings"
fi
echo "Connected: $connected"
if [ "$connected" = "true" ]; then
echo "IP Address: $ip"
fi
# Return appropriate exit code
if [ "$status" = "success" ]; then
echo "WiFi configuration completed successfully!"
return 0
else
echo "WiFi configuration failed!"
return 1
fi
}
function label {
function set {
if [ $# -lt 1 ]; then
echo "Usage: $0 node label set <key=value> [node_ip]"
echo " key=value: Label key and value in format 'key=value'"
echo " node_ip: Optional IP address (defaults to API_NODE from .env)"
return 1
fi
local key_value="$1"
local node_ip="${2:-$API_NODE}"
# Parse key=value format
if [[ ! "$key_value" =~ ^[^=]+=.+$ ]]; then
echo "Error: Label must be in format 'key=value'"
echo "Example: environment=production"
return 1
fi
local key="${key_value%%=*}"
local value="${key_value#*=}"
echo "Setting label '$key=$value' on node $node_ip..."
# First get current labels
current_labels=$(curl -s "http://$node_ip/api/node/status" | jq -r '.labels // {}')
# Add/update the new label
updated_labels=$(echo "$current_labels" | jq --arg key "$key" --arg value "$value" '. + {($key): $value}')
# Send updated labels to the node
response=$(curl -s -w "\n%{http_code}" -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "labels=$updated_labels" \
"http://$node_ip/api/node/config" 2>/dev/null || echo -e "\n000")
# Extract HTTP status code and response body
http_code=$(echo "$response" | tail -n1)
response_body=$(echo "$response" | head -n -1)
# Check if curl succeeded
if [ "$http_code" = "000" ] || [ -z "$response_body" ]; then
echo "Error: Failed to connect to node at $node_ip"
echo "Please check:"
echo " - Node is powered on and connected to network"
echo " - IP address is correct"
echo " - Node is running Spore firmware"
return 1
fi
# Check HTTP status code
if [ "$http_code" != "200" ]; then
echo "Error: HTTP $http_code - Server error"
echo "Response: $response_body"
return 1
fi
# Parse and display the response
status=$(echo "$response_body" | jq -r '.status // "unknown"')
message=$(echo "$response_body" | jq -r '.message // "No message"')
echo "Status: $status"
echo "Message: $message"
# Return appropriate exit code
if [ "$status" = "success" ]; then
echo "Label '$key=$value' set successfully!"
return 0
else
echo "Failed to set label!"
return 1
fi
}
function delete {
if [ $# -lt 1 ]; then
echo "Usage: $0 node label delete <key> [node_ip]"
echo " key: Label key to delete"
echo " node_ip: Optional IP address (defaults to API_NODE from .env)"
return 1
fi
local key="$1"
local node_ip="${2:-$API_NODE}"
echo "Deleting label '$key' from node $node_ip..."
# First get current labels
current_labels=$(curl -s "http://$node_ip/api/node/status" | jq -r '.labels // {}')
# Check if key exists
if [ "$(echo "$current_labels" | jq -r --arg key "$key" 'has($key)')" != "true" ]; then
echo "Warning: Label '$key' does not exist on node"
return 0
fi
# Remove the key
updated_labels=$(echo "$current_labels" | jq --arg key "$key" 'del(.[$key])')
# Send updated labels to the node
response=$(curl -s -w "\n%{http_code}" -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "labels=$updated_labels" \
"http://$node_ip/api/node/config" 2>/dev/null || echo -e "\n000")
# Extract HTTP status code and response body
http_code=$(echo "$response" | tail -n1)
response_body=$(echo "$response" | head -n -1)
# Check if curl succeeded
if [ "$http_code" = "000" ] || [ -z "$response_body" ]; then
echo "Error: Failed to connect to node at $node_ip"
echo "Please check:"
echo " - Node is powered on and connected to network"
echo " - IP address is correct"
echo " - Node is running Spore firmware"
return 1
fi
# Check HTTP status code
if [ "$http_code" != "200" ]; then
echo "Error: HTTP $http_code - Server error"
echo "Response: $response_body"
return 1
fi
# Parse and display the response
status=$(echo "$response_body" | jq -r '.status // "unknown"')
message=$(echo "$response_body" | jq -r '.message // "No message"')
echo "Status: $status"
echo "Message: $message"
# Return appropriate exit code
if [ "$status" = "success" ]; then
echo "Label '$key' deleted successfully!"
return 0
else
echo "Failed to delete label!"
return 1
fi
}
${@:-info}
}
function config {
function get {
local node_ip="${1:-$API_NODE}"
echo "Getting configuration for node $node_ip..."
# Get node configuration
response=$(curl -s -w "\n%{http_code}" "http://$node_ip/api/node/config" 2>/dev/null || echo -e "\n000")
# Extract HTTP status code and response body
http_code=$(echo "$response" | tail -n1)
response_body=$(echo "$response" | head -n -1)
# Check if curl succeeded
if [ "$http_code" = "000" ] || [ -z "$response_body" ]; then
echo "Error: Failed to connect to node at $node_ip"
echo "Please check:"
echo " - Node is powered on and connected to network"
echo " - IP address is correct"
echo " - Node is running Spore firmware"
return 1
fi
# Check HTTP status code
if [ "$http_code" != "200" ]; then
echo "Error: HTTP $http_code - Server error"
echo "Response: $response_body"
return 1
fi
# Parse and display the response in a nice format
echo ""
echo "=== Node Configuration ==="
echo "Node IP: $node_ip"
echo "Retrieved at: $(date)"
echo ""
# WiFi Configuration
echo "=== WiFi Configuration ==="
echo "SSID: $(echo "$response_body" | jq -r '.wifi.ssid // "N/A"')"
echo "Connect Timeout: $(echo "$response_body" | jq -r '.wifi.connect_timeout_ms // "N/A"') ms"
echo "Retry Delay: $(echo "$response_body" | jq -r '.wifi.retry_delay_ms // "N/A"') ms"
echo "Password: [HIDDEN]"
echo ""
# Network Configuration
echo "=== Network Configuration ==="
echo "UDP Port: $(echo "$response_body" | jq -r '.network.udp_port // "N/A"')"
echo "API Server Port: $(echo "$response_body" | jq -r '.network.api_server_port // "N/A"')"
echo ""
# Cluster Configuration
echo "=== Cluster Configuration ==="
echo "Heartbeat Interval: $(echo "$response_body" | jq -r '.cluster.heartbeat_interval_ms // "N/A"') ms"
echo "Cluster Listen Interval: $(echo "$response_body" | jq -r '.cluster.cluster_listen_interval_ms // "N/A"') ms"
echo "Status Update Interval: $(echo "$response_body" | jq -r '.cluster.status_update_interval_ms // "N/A"') ms"
echo ""
# Node Status Thresholds
echo "=== Node Status Thresholds ==="
echo "Active Threshold: $(echo "$response_body" | jq -r '.thresholds.node_active_threshold_ms // "N/A"') ms"
echo "Inactive Threshold: $(echo "$response_body" | jq -r '.thresholds.node_inactive_threshold_ms // "N/A"') ms"
echo "Dead Threshold: $(echo "$response_body" | jq -r '.thresholds.node_dead_threshold_ms // "N/A"') ms"
echo ""
# System Configuration
echo "=== System Configuration ==="
echo "Restart Delay: $(echo "$response_body" | jq -r '.system.restart_delay_ms // "N/A"') ms"
echo "JSON Doc Size: $(echo "$response_body" | jq -r '.system.json_doc_size // "N/A"') bytes"
echo ""
# Memory Management
echo "=== Memory Management ==="
echo "Low Memory Threshold: $(echo "$response_body" | jq -r '.memory.low_memory_threshold_bytes // "N/A"') bytes"
echo "Critical Memory Threshold: $(echo "$response_body" | jq -r '.memory.critical_memory_threshold_bytes // "N/A"') bytes"
echo "Max Concurrent HTTP Requests: $(echo "$response_body" | jq -r '.memory.max_concurrent_http_requests // "N/A"')"
echo ""
# Custom Labels
labels=$(echo "$response_body" | jq -r '.labels // {}')
if [ "$labels" != "{}" ] && [ "$labels" != "null" ]; then
echo "=== Custom Labels ==="
echo "$labels" | jq -r 'to_entries[] | "\(.key): \(.value)"'
echo ""
else
echo "=== Custom Labels ==="
echo "No custom labels set"
echo ""
fi
# Metadata
echo "=== Metadata ==="
echo "Configuration Version: $(echo "$response_body" | jq -r '.version // "N/A"')"
echo "Retrieved Timestamp: $(echo "$response_body" | jq -r '.retrieved_at // "N/A"')"
echo ""
echo "=== Raw JSON Response ==="
echo "$response_body" | jq '.'
return 0
}
${@:-info}
}
function status {
local node_ip="${1:-$API_NODE}"
echo "Getting status for node $node_ip..."
# Get node status
response=$(curl -s -w "\n%{http_code}" "http://$node_ip/api/node/status" 2>/dev/null || echo -e "\n000")
# Extract HTTP status code and response body
http_code=$(echo "$response" | tail -n1)
response_body=$(echo "$response" | head -n -1)
# Check if curl succeeded
if [ "$http_code" = "000" ] || [ -z "$response_body" ]; then
echo "Error: Failed to connect to node at $node_ip"
echo "Please check:"
echo " - Node is powered on and connected to network"
echo " - IP address is correct"
echo " - Node is running Spore firmware"
return 1
fi
# Check HTTP status code
if [ "$http_code" != "200" ]; then
echo "Error: HTTP $http_code - Server error"
echo "Response: $response_body"
return 1
fi
# Parse and display the response in a nice format
echo ""
echo "=== Node Status ==="
echo "Hostname: $(echo "$response_body" | jq -r '.hostname // "N/A"')"
echo "IP Address: $node_ip"
echo "Free Heap: $(echo "$response_body" | jq -r '.freeHeap // "N/A"') bytes"
echo "Chip ID: $(echo "$response_body" | jq -r '.chipId // "N/A"')"
echo "SDK Version: $(echo "$response_body" | jq -r '.sdkVersion // "N/A"')"
echo "CPU Frequency: $(echo "$response_body" | jq -r '.cpuFreqMHz // "N/A"') MHz"
echo "Flash Size: $(echo "$response_body" | jq -r '.flashChipSize // "N/A"') bytes"
# Display labels if present
labels=$(echo "$response_body" | jq -r '.labels // {}')
if [ "$labels" != "{}" ] && [ "$labels" != "null" ]; then
echo ""
echo "=== Labels ==="
echo "$labels" | jq -r 'to_entries[] | "\(.key): \(.value)"'
else
echo ""
echo "=== Labels ==="
echo "No labels set"
fi
echo ""
echo "=== Raw JSON Response ==="
echo "$response_body" | jq '.'
return 0
}
${@:-info}
}
function monitor { function monitor {
pio run --target monitor pio run --target monitor
} }

View File

@@ -15,12 +15,18 @@ The SPORE system provides a comprehensive RESTful API for monitoring and control
| Endpoint | Method | Description | Response | | Endpoint | Method | Description | Response |
|----------|--------|-------------|----------| |----------|--------|-------------|----------|
| `/api/node/status` | GET | System resource information and API endpoint registry | System metrics and API catalog | | `/api/node/status` | GET | System resource information | System metrics |
| `/api/node/endpoints` | GET | API endpoints and parameters | Detailed endpoint specifications | | `/api/node/endpoints` | GET | API endpoints and parameters | Detailed endpoint specifications |
| `/api/cluster/members` | GET | Cluster membership and node health information | Cluster topology and health status | | `/api/cluster/members` | GET | Cluster membership and node health information | Cluster topology and health status |
| `/api/node/update` | POST | Handle firmware updates via OTA | Update progress and status | | `/api/node/update` | POST | Handle firmware updates via OTA | Update progress and status |
| `/api/node/restart` | POST | Trigger system restart | Restart confirmation | | `/api/node/restart` | POST | Trigger system restart | Restart confirmation |
### Monitoring API
| Endpoint | Method | Description | Response |
|----------|--------|-------------|----------|
| `/api/monitoring/resources` | GET | CPU, memory, filesystem, and uptime | System resource metrics |
### Network Management API ### Network Management API
| Endpoint | Method | Description | Response | | Endpoint | Method | Description | Response |
@@ -140,7 +146,7 @@ Controls the execution state of individual tasks. Supports enabling, disabling,
#### GET /api/node/status #### GET /api/node/status
Returns comprehensive system resource information including memory usage, chip details, and a registry of all available API endpoints. Returns comprehensive system resource information including memory usage and chip details. For a list of available API endpoints, use `/api/node/endpoints`.
**Response Fields:** **Response Fields:**
- `freeHeap`: Available RAM in bytes - `freeHeap`: Available RAM in bytes
@@ -168,7 +174,7 @@ Returns comprehensive system resource information including memory usage, chip d
#### GET /api/node/endpoints #### GET /api/node/endpoints
Returns detailed information about all available API endpoints, including their parameters, types, and validation rules. Returns detailed information about all available API endpoints, including their parameters, types, and validation rules. Methods are returned as strings (e.g., "GET", "POST").
**Response Fields:** **Response Fields:**
- `endpoints[]`: Array of endpoint capability objects - `endpoints[]`: Array of endpoint capability objects
@@ -236,6 +242,54 @@ Initiates an over-the-air firmware update. The firmware file should be uploaded
Triggers a system restart. The response will be sent before the restart occurs. Triggers a system restart. The response will be sent before the restart occurs.
### Monitoring
#### GET /api/monitoring/resources
Returns real-time system resource metrics.
Response Fields:
- `cpu.current_usage`: Current CPU usage percent
- `cpu.average_usage`: Average CPU usage percent
- `cpu.max_usage`: Max observed CPU usage
- `cpu.min_usage`: Min observed CPU usage
- `cpu.measurement_count`: Number of measurements
- `cpu.is_measuring`: Whether measurement is active
- `memory.free_heap`: Free heap bytes
- `memory.total_heap`: Total heap bytes (approximate)
- `memory.heap_fragmentation`: Fragmentation percent (0 on ESP8266)
- `filesystem.total_bytes`: LittleFS total bytes
- `filesystem.used_bytes`: Used bytes
- `filesystem.free_bytes`: Free bytes
- `filesystem.usage_percent`: Usage percent
- `system.uptime_ms`: Uptime in milliseconds
Example Response:
```json
{
"cpu": {
"current_usage": 3.5,
"average_usage": 2.1,
"max_usage": 15.2,
"min_usage": 0.0,
"measurement_count": 120,
"is_measuring": true
},
"memory": {
"free_heap": 48748,
"total_heap": 81920,
"heap_fragmentation": 0
},
"filesystem": {
"total_bytes": 65536,
"used_bytes": 10240,
"free_bytes": 55296,
"usage_percent": 15.6
},
"system": {
"uptime_ms": 123456
}
}
```
### Network Management ### Network Management
#### GET /api/network/status #### GET /api/network/status

View File

@@ -21,13 +21,15 @@ The system architecture consists of several key components working together:
### API Server ### API Server
- **HTTP API Server**: RESTful API for cluster management - **HTTP API Server**: RESTful API for cluster management
- **Dynamic Endpoint Registration**: Automatic API endpoint discovery - **Dynamic Endpoint Registration**: Services register endpoints via `registerEndpoints(ApiServer&)`
- **Service Registry**: Track available services across the cluster - **Service Registry**: Track available services across the cluster
- **Service Lifecycle**: Services register both endpoints and tasks through unified interface
### Task Scheduler ### Task Scheduler
- **Cooperative Multitasking**: Background task management system - **Cooperative Multitasking**: Background task management system (`TaskManager`)
- **Task Lifecycle Management**: Automatic task execution and monitoring - **Service Task Registration**: Services register tasks via `registerTasks(TaskManager&)`
- **Resource Optimization**: Efficient task scheduling and execution - **Task Lifecycle Management**: Enable/disable tasks and set intervals at runtime
- **Execution Model**: Tasks run in `Spore::loop()` when their interval elapses
### Node Context ### Node Context
- **Central Context**: Shared resources and configuration - **Central Context**: Shared resources and configuration
@@ -40,27 +42,89 @@ The cluster uses a UDP-based discovery protocol for automatic node detection:
### Discovery Process ### Discovery Process
1. **Discovery Broadcast**: Nodes periodically send UDP packets on port 4210 1. **Discovery Broadcast**: Nodes periodically send heartbeat messages on port `udp_port` (default 4210)
2. **Response Handling**: Nodes respond with their hostname and IP address 2. **Response Handling**: Nodes respond with node update information containing their current state
3. **Member Management**: Discovered nodes are automatically added to the cluster 3. **Member Management**: Discovered nodes are added/updated in the cluster with current information
4. **Health Monitoring**: Continuous status checking via HTTP API calls 4. **Node Synchronization**: Periodic broadcasts ensure all nodes maintain current cluster state
### Protocol Details ### Protocol Details
- **UDP Port**: 4210 (configurable) - **UDP Port**: 4210 (configurable via `Config.udp_port`)
- **Discovery Message**: `CLUSTER_DISCOVERY` - **Heartbeat Message**: `CLUSTER_HEARTBEAT:hostname`
- **Response Message**: `CLUSTER_RESPONSE` - **Node Update Message**: `NODE_UPDATE:hostname:{json}`
- **Broadcast Address**: 255.255.255.255 - **Broadcast Address**: 255.255.255.255
- **Discovery Interval**: 1 second (configurable) - **Listen Interval**: `Config.cluster_listen_interval_ms` (default 10 ms)
- **Listen Interval**: 100ms (configurable) - **Heartbeat Interval**: `Config.heartbeat_interval_ms` (default 5000 ms)
### Message Formats
- **Heartbeat**: `CLUSTER_HEARTBEAT:hostname`
- Sender: each node, broadcast to 255.255.255.255:`udp_port` on interval
- Purpose: announce presence, prompt peers for node info, and keep liveness
- **Node Update**: `NODE_UPDATE:hostname:{json}`
- Sender: node responding to heartbeat or broadcasting current state
- JSON fields: hostname, ip, uptime, optional labels
- Purpose: provide current node information for cluster synchronization
### Discovery Flow
1. **A node broadcasts** `CLUSTER_HEARTBEAT:hostname` to announce its presence
2. **Each receiver responds** with `NODE_UPDATE:hostname:{json}` containing current node state
3. **The sender**:
- Ensures the responding node exists or creates it with current IP and information
- Parses JSON and updates node info, `status = ACTIVE`, `lastSeen = now`
- Calculates `latency = now - lastHeartbeatSentAt` for network performance monitoring
### Node Synchronization
1. **Event-driven broadcasts**: Nodes broadcast `NODE_UPDATE:hostname:{json}` when node information changes
2. **All receivers**: Update their memberlist entry for the broadcasting node
3. **Purpose**: Ensures all nodes maintain current cluster state and configuration
### Sequence Diagram
```mermaid
sequenceDiagram
participant N1 as Node A (esp-node1)
participant N2 as Node B (esp-node2)
Note over N1,N2: Discovery via heartbeat broadcast
N1->>+N2: CLUSTER_HEARTBEAT:esp-node1
Note over N2: Node B responds with its current state
N2->>+N1: NODE_UPDATE:esp-node1:{"hostname":"esp-node2","uptime":12345,"labels":{"role":"sensor"}}
Note over N1: Process NODE_UPDATE response
N1-->>N1: Update memberlist for Node B
N1-->>N1: Set Node B status = ACTIVE
N1-->>N1: Calculate latency for Node B
Note over N1,N2: Event-driven node synchronization
N1->>+N2: NODE_UPDATE:esp-node1:{"hostname":"esp-node1","uptime":12346,"labels":{"role":"controller"}}
Note over N2: Update memberlist with latest information
N2-->>N2: Update Node A info, maintain ACTIVE status
```
### Listener Behavior
The `cluster_listen` task parses one UDP packet per run and dispatches by prefix to:
- **Heartbeat** → add/update responding node and send `NODE_UPDATE` response
- **Node Update** → update node information and trigger memberlist logging
### Timing and Intervals
- **UDP Port**: `Config.udp_port` (default 4210)
- **Listen Interval**: `Config.cluster_listen_interval_ms` (default 10 ms)
- **Heartbeat Interval**: `Config.heartbeat_interval_ms` (default 5000 ms)
### Node Status Categories ### Node Status Categories
Nodes are automatically categorized by their activity: Nodes are automatically categorized by their activity:
- **ACTIVE**: Responding within 10 seconds - **ACTIVE**: lastSeen < `node_inactive_threshold_ms` (default 10s)
- **INACTIVE**: No response for 10-60 seconds - **INACTIVE**: < `node_dead_threshold_ms` (default 120s)
- **DEAD**: No response for over 60 seconds - **DEAD**: `node_dead_threshold_ms`
## Task Scheduling System ## Task Scheduling System
@@ -68,14 +132,11 @@ The system runs several background tasks at different intervals:
### Core System Tasks ### Core System Tasks
| Task | Interval | Purpose | | Task | Interval (default) | Purpose |
|------|----------|---------| |------|--------------------|---------|
| **Discovery Send** | 1 second | Send UDP discovery packets | | `cluster_listen` | 10 ms | Listen for heartbeat/node-info messages |
| **Discovery Listen** | 100ms | Listen for discovery responses | | `status_update` | 1000 ms | Update node status categories, purge dead |
| **Status Updates** | 1 second | Monitor cluster member health | | `heartbeat` | 5000 ms | Broadcast heartbeat and update local resources |
| **Heartbeat** | 2 seconds | Maintain cluster connectivity |
| **Member Info** | 10 seconds | Update detailed node information |
| **Debug Output** | 5 seconds | Print cluster status |
### Task Management Features ### Task Management Features
@@ -92,12 +153,12 @@ The `NodeContext` provides an event-driven architecture for system-wide communic
```cpp ```cpp
// Subscribe to events // Subscribe to events
ctx.on("node_discovered", [](void* data) { ctx.on("node/discovered", [](void* data) {
NodeInfo* node = static_cast<NodeInfo*>(data); NodeInfo* node = static_cast<NodeInfo*>(data);
// Handle new node discovery // Handle new node discovery
}); });
ctx.on("cluster_updated", [](void* data) { ctx.on("cluster/updated", [](void* data) {
// Handle cluster membership changes // Handle cluster membership changes
}); });
``` ```
@@ -106,16 +167,13 @@ ctx.on("cluster_updated", [](void* data) {
```cpp ```cpp
// Publish events // Publish events
ctx.fire("node_discovered", &newNode); ctx.fire("node/discovered", &newNode);
ctx.fire("cluster_updated", &clusterData); ctx.fire("cluster/updated", &clusterData);
``` ```
### Available Events ### Available Events
- **`node_discovered`**: New node added to cluster - **`node/discovered`**: New node added or local node refreshed
- **`cluster_updated`**: Cluster membership changed
- **`resource_update`**: Node resources updated
- **`health_check`**: Node health status changed
## Resource Monitoring ## Resource Monitoring
@@ -155,10 +213,8 @@ The system includes automatic WiFi fallback for robust operation:
### Configuration ### Configuration
- **SSID Format**: `SPORE_<MAC_LAST_4>` - **Hostname**: Derived from MAC (`esp-<mac>`) and assigned to `ctx.hostname`
- **Password**: Configurable fallback password - **AP Mode**: If STA connection fails, device switches to AP mode with configured SSID/password
- **IP Range**: 192.168.4.x subnet
- **Gateway**: 192.168.4.1
## Cluster Topology ## Cluster Topology
@@ -170,32 +226,30 @@ The system includes automatic WiFi fallback for robust operation:
### Network Architecture ### Network Architecture
- **Mesh-like Structure**: Nodes can communicate with each other - UDP broadcast-based discovery and heartbeats on local subnet
- **Dynamic Routing**: Automatic path discovery between nodes - Optional HTTP polling (disabled by default; node info exchanged via UDP)
- **Load Distribution**: Tasks distributed across available nodes
- **Fault Tolerance**: Automatic failover and recovery
## Data Flow ## Data Flow
### Node Discovery ### Node Discovery
1. **UDP Broadcast**: Nodes broadcast discovery packets on port 4210 1. **UDP Broadcast**: Nodes broadcast discovery packets on port 4210
2. **UDP Response**: Receiving nodes responds with hostname 2. **UDP Response**: Receiving nodes respond with hostname
3. **Registration**: Discovered nodes are added to local cluster member list 3. **Registration**: Discovered nodes are added to local cluster member list
### Health Monitoring ### Health Monitoring
1. **Periodic Checks**: Cluster manager polls member nodes every 1 second 1. **Periodic Checks**: Cluster manager updates node status categories
2. **Status Collection**: Each node returns resource usage and health metrics 2. **Status Collection**: Each node updates resources via UDP node-info messages
### Task Management ### Task Management
1. **Scheduling**: TaskScheduler executes registered tasks at configured intervals 1. **Scheduling**: `TaskManager` executes registered tasks at configured intervals
2. **Execution**: Tasks run cooperatively, yielding control to other tasks 2. **Execution**: Tasks run cooperatively in the main loop without preemption
3. **Monitoring**: Task status and results are exposed via REST API endpoints 3. **Monitoring**: Task status is exposed via REST (`/api/tasks/status`)
## Performance Characteristics ## Performance Characteristics
### Memory Usage ### Memory Usage
- **Base System**: ~15-20KB RAM - **Base System**: ~15-20KB RAM (device dependent)
- **Per Task**: ~100-200 bytes per task - **Per Task**: ~100-200 bytes per task
- **Cluster Members**: ~50-100 bytes per member - **Cluster Members**: ~50-100 bytes per member
- **API Endpoints**: ~20-30 bytes per endpoint - **API Endpoints**: ~20-30 bytes per endpoint
@@ -219,7 +273,7 @@ The system includes automatic WiFi fallback for robust operation:
### Current Implementation ### Current Implementation
- **Network Access**: Local network only (no internet exposure) - **Network Access**: Local network only (no internet exposure)
- **Authentication**: None currently implemented - **Authentication**: None currently implemented; LAN-only access assumed
- **Data Validation**: Basic input validation - **Data Validation**: Basic input validation
- **Resource Limits**: Memory and processing constraints - **Resource Limits**: Memory and processing constraints
@@ -248,6 +302,51 @@ The system includes automatic WiFi fallback for robust operation:
## Configuration Management ## Configuration Management
SPORE implements a persistent configuration system that manages device settings across reboots and provides runtime reconfiguration capabilities.
### Configuration Architecture
The configuration system consists of several key components:
- **`Config` Class**: Central configuration management with default constants
- **LittleFS Storage**: Persistent file-based storage (`/config.json`)
- **Runtime Updates**: Live configuration changes via HTTP API
- **Automatic Persistence**: Configuration changes are automatically saved
### Configuration Categories
| Category | Description | Examples |
|----------|-------------|----------|
| **WiFi Configuration** | Network connection settings | SSID, password, timeouts |
| **Network Configuration** | Network service settings | UDP port, API server port |
| **Cluster Configuration** | Cluster management settings | Discovery intervals, heartbeat timing |
| **Node Status Thresholds** | Health monitoring thresholds | Active/inactive/dead timeouts |
| **System Configuration** | Core system settings | Restart delay, JSON document size |
| **Memory Management** | Resource management settings | Memory thresholds, HTTP request limits |
### Configuration Lifecycle
1. **Boot Process**: Load configuration from `/config.json` or use defaults
2. **Runtime Updates**: Configuration changes via HTTP API
3. **Persistent Storage**: Changes automatically saved to LittleFS
4. **Service Integration**: Configuration applied to all system services
### Default Value Management
All default values are defined as `constexpr` constants in the `Config` class:
```cpp
static constexpr const char* DEFAULT_WIFI_SSID = "shroud";
static constexpr uint16_t DEFAULT_UDP_PORT = 4210;
static constexpr unsigned long DEFAULT_HEARTBEAT_INTERVAL_MS = 5000;
```
This ensures:
- **Single Source of Truth**: All defaults defined once
- **Type Safety**: Compile-time type checking
- **Maintainability**: Easy to update default values
- **Consistency**: Same defaults used in `setDefaults()` and `loadFromFile()`
### Environment Variables ### Environment Variables
```bash ```bash
@@ -337,6 +436,8 @@ pio device monitor
## Related Documentation ## Related Documentation
- **[Configuration Management](./ConfigurationManagement.md)** - Persistent configuration system
- **[WiFi Configuration](./WiFiConfiguration.md)** - WiFi setup and reconfiguration process
- **[Task Management](./TaskManagement.md)** - Background task system - **[Task Management](./TaskManagement.md)** - Background task system
- **[API Reference](./API.md)** - REST API documentation - **[API Reference](./API.md)** - REST API documentation
- **[TaskManager API](./TaskManager.md)** - TaskManager class reference - **[TaskManager API](./TaskManager.md)** - TaskManager class reference

91
docs/ClusterBroadcast.md Normal file
View File

@@ -0,0 +1,91 @@
## Cluster Broadcast (CLUSTER_EVENT)
### Overview
Spore supports cluster-wide event broadcasting via UDP. Services publish a local event, and the core broadcasts it to peers as a `CLUSTER_EVENT`. Peers receive the event and forward it to local subscribers through the internal event bus.
- **Local trigger**: `ctx.fire("cluster/broadcast", eventJson)`
- **UDP message**: `CLUSTER_EVENT:{json}`
- **Receiver action**: Parses `{json}` and calls `ctx.fire(event, data)`
This centralizes network broadcast in core, so services never touch UDP directly.
### Message format
- UDP payload prefix: `CLUSTER_EVENT:`
- JSON body:
```json
{
"event": "<event-name>",
"data": "<json-string>" // or an inline JSON object/array
}
```
Notes:
- The receiver accepts `data` as either a JSON string or a nested JSON object/array. Nested JSON is serialized back to a string before firing the local event.
- Keep payloads small (UDP, default buffer 512 bytes).
### Core responsibilities
- `ClusterManager` registers a centralized handler:
- Subscribes to `cluster/broadcast` to send the provided event JSON over UDP broadcast.
- Listens for incoming UDP `CLUSTER_EVENT` messages and forwards them to local subscribers via `ctx.fire(event, data)`.
- Broadcast target uses subnet-directed broadcast (e.g., `192.168.1.255`) for better reliability. Both nodes must share the same `udp_port`.
### Service responsibilities
Services send and receive events using the local event bus.
1) Subscribe to an event name and apply state from `data`:
```cpp
ctx.on("api/neopattern", [this](void* dataPtr) {
String* jsonStr = static_cast<String*>(dataPtr);
if (!jsonStr) return;
JsonDocument doc;
if (deserializeJson(doc, *jsonStr)) return;
JsonObject obj = doc.as<JsonObject>();
// Parse and apply fields from obj
});
```
2) Build a control payload and update locally via the same event:
```cpp
JsonDocument payload;
payload["pattern"] = "rainbow_cycle"; // example
payload["brightness"] = 100;
String payloadStr; serializeJson(payload, payloadStr);
ctx.fire("api/neopattern", &payloadStr);
``;
3) Broadcast to peers by delegating to core:
```cpp
JsonDocument envelope;
envelope["event"] = "api/neopattern";
envelope["data"] = payloadStr; // JSON string
String eventJson; serializeJson(envelope, eventJson);
ctx.fire("cluster/broadcast", &eventJson);
```
With this flow, services have a single codepath for applying state (the event handler). Broadcasting simply reuses the same payload.
### Logging
- Core logs source IP, payload length, and event name for received `CLUSTER_EVENT`s.
- Services can log when submitting `cluster/broadcast` and when applying control events.
### Networking considerations
- Ensure all nodes:
- Listen on the same `udp_port`.
- Are in the same subnet (for subnet-directed broadcast).
- Some networks may block global broadcast (`255.255.255.255`). Subnet-directed broadcast is used by default.
### Troubleshooting
- If peers do not react:
- Confirm logs show `CLUSTER_EVENT raw from <ip>` on the receiver.
- Verify UDP port alignment and WiFi connection/subnet.
- Check payload size (<512 bytes by default) and JSON validity.
- Ensure the service subscribed to the correct `event` name and handles `data`.

View File

@@ -0,0 +1,337 @@
# SPORE Configuration Management
## Overview
SPORE implements a persistent configuration system that manages device settings across reboots and provides runtime reconfiguration capabilities. The system uses LittleFS for persistent storage and provides both programmatic and HTTP API access to configuration parameters.
## Configuration Architecture
### Core Components
- **`Config` Class**: Central configuration management
- **LittleFS Storage**: Persistent file-based storage (`/config.json`)
- **Default Constants**: Single source of truth for all default values
- **Runtime Updates**: Live configuration changes via HTTP API
- **Automatic Persistence**: Configuration changes are automatically saved
### Configuration Categories
The configuration system manages several categories of settings:
| Category | Description | Examples |
|----------|-------------|----------|
| **WiFi Configuration** | Network connection settings | SSID, password, timeouts |
| **Network Configuration** | Network service settings | UDP port, API server port |
| **Cluster Configuration** | Cluster management settings | Discovery intervals, heartbeat timing |
| **Node Status Thresholds** | Health monitoring thresholds | Active/inactive/dead timeouts |
| **System Configuration** | Core system settings | Restart delay, JSON document size |
| **Memory Management** | Resource management settings | Memory thresholds, HTTP request limits |
## Configuration Lifecycle
### 1. Boot Process
```mermaid
graph TD
A[System Boot] --> B[Initialize LittleFS]
B --> C{Config File Exists?}
C -->|Yes| D[Load from File]
C -->|No| E[Use Defaults]
D --> F[Apply Configuration]
E --> G[Save Defaults to File]
G --> F
F --> H[Start Services]
```
**Boot Sequence:**
1. **LittleFS Initialization**: Mount the filesystem for persistent storage
2. **Configuration Loading**: Attempt to load `/config.json`
3. **Fallback to Defaults**: If no config file exists, use hardcoded defaults
4. **Default Persistence**: Save default configuration to file for future boots
5. **Service Initialization**: Apply configuration to all system services
### 2. Runtime Configuration
```mermaid
graph TD
A[HTTP API Request] --> B[Validate Parameters]
B --> C[Update Config Object]
C --> D[Save to File]
D --> E{Requires Restart?}
E -->|Yes| F[Schedule Restart]
E -->|No| G[Apply Changes]
F --> H[Send Response]
G --> H
```
**Runtime Update Process:**
1. **API Request**: Configuration change via HTTP API
2. **Parameter Validation**: Validate input parameters
3. **Memory Update**: Update configuration object in memory
4. **Persistent Save**: Save changes to `/config.json`
5. **Service Notification**: Notify affected services of changes
6. **Restart if Needed**: Restart system for certain configuration changes
## Configuration File Format
### JSON Structure
The configuration is stored as a JSON file with the following structure:
```json
{
"wifi": {
"ssid": "MyNetwork",
"password": "mypassword",
"connect_timeout_ms": 15000,
"retry_delay_ms": 500
},
"network": {
"udp_port": 4210,
"api_server_port": 80
},
"cluster": {
"heartbeat_interval_ms": 5000,
"cluster_listen_interval_ms": 10,
"status_update_interval_ms": 1000
},
"thresholds": {
"node_active_threshold_ms": 10000,
"node_inactive_threshold_ms": 60000,
"node_dead_threshold_ms": 120000
},
"system": {
"restart_delay_ms": 10,
"json_doc_size": 1024
},
"memory": {
"low_memory_threshold_bytes": 10000,
"critical_memory_threshold_bytes": 5000,
"max_concurrent_http_requests": 3
},
"_meta": {
"version": "1.0",
"saved_at": 1234567890
}
}
```
### Metadata Fields
- **`version`**: Configuration schema version for future compatibility
- **`saved_at`**: Timestamp when configuration was saved (millis())
## Default Configuration Constants
All default values are defined as `constexpr` constants in the `Config` class header:
```cpp
// Default Configuration Constants
static constexpr const char* DEFAULT_WIFI_SSID = "shroud";
static constexpr const char* DEFAULT_WIFI_PASSWORD = "th3r31sn0sp00n";
static constexpr uint16_t DEFAULT_UDP_PORT = 4210;
static constexpr uint16_t DEFAULT_API_SERVER_PORT = 80;
// ... additional constants
```
### Benefits of Constants
- **Single Source of Truth**: All defaults defined once
- **Type Safety**: Compile-time type checking
- **Maintainability**: Easy to update default values
- **Consistency**: Same defaults used in `setDefaults()` and `loadFromFile()`
## Configuration Methods
### Core Methods
| Method | Purpose | Parameters |
|--------|---------|------------|
| `setDefaults()` | Initialize with default values | None |
| `loadFromFile()` | Load configuration from persistent storage | `filename` (optional) |
| `saveToFile()` | Save configuration to persistent storage | `filename` (optional) |
### Loading Process
```cpp
bool Config::loadFromFile(const String& filename) {
// 1. Initialize LittleFS
if (!LittleFS.begin()) {
LOG_ERROR("Config", "LittleFS not initialized");
return false;
}
// 2. Check file existence
if (!LittleFS.exists(filename)) {
LOG_DEBUG("Config", "Config file does not exist");
return false;
}
// 3. Parse JSON with fallback defaults
wifi_ssid = doc["wifi"]["ssid"] | DEFAULT_WIFI_SSID;
wifi_password = doc["wifi"]["password"] | DEFAULT_WIFI_PASSWORD;
// ... additional fields
return true;
}
```
### Saving Process
```cpp
bool Config::saveToFile(const String& filename) {
// 1. Create JSON document
JsonDocument doc;
// 2. Serialize all configuration fields
doc["wifi"]["ssid"] = wifi_ssid;
doc["wifi"]["password"] = wifi_password;
// ... additional fields
// 3. Add metadata
doc["_meta"]["version"] = "1.0";
doc["_meta"]["saved_at"] = millis();
// 4. Write to file
size_t bytesWritten = serializeJson(doc, file);
return bytesWritten > 0;
}
```
## Error Handling
### Common Error Scenarios
| Scenario | Error Handling | Recovery |
|----------|----------------|----------|
| **LittleFS Init Failure** | Log warning, use defaults | Continue with default configuration |
| **File Not Found** | Log debug message, return false | Caller handles fallback to defaults |
| **JSON Parse Error** | Log error, return false | Caller handles fallback to defaults |
| **Write Failure** | Log error, return false | Configuration not persisted |
| **Memory Allocation Failure** | Log error, return false | Operation aborted |
### Logging Levels
- **ERROR**: Critical failures that prevent operation
- **WARN**: Non-critical issues that affect functionality
- **INFO**: Normal operation events
- **DEBUG**: Detailed diagnostic information
## Configuration Validation
### Input Validation
- **Required Fields**: SSID and password are mandatory for WiFi configuration
- **Range Validation**: Numeric values are validated against reasonable ranges
- **Type Validation**: JSON parsing ensures correct data types
- **Length Limits**: String fields have maximum length constraints
### Default Value Fallback
The system uses the `|` operator for safe fallback to defaults:
```cpp
// Safe loading with fallback
wifi_ssid = doc["wifi"]["ssid"] | DEFAULT_WIFI_SSID;
udp_port = doc["network"]["udp_port"] | DEFAULT_UDP_PORT;
```
This ensures that:
- Missing fields use default values
- Invalid values are replaced with defaults
- System remains functional with partial configuration
## Performance Considerations
### Memory Usage
- **Configuration Object**: ~200 bytes in RAM
- **JSON Document**: ~1KB during parsing/saving
- **LittleFS Overhead**: ~2-4KB for filesystem
### Storage Requirements
- **Config File**: ~500-800 bytes on disk
- **LittleFS Minimum**: ~64KB partition size
- **Available Space**: Depends on flash size (1MB+ recommended)
### Processing Overhead
- **Load Time**: ~10-50ms for JSON parsing
- **Save Time**: ~20-100ms for JSON serialization
- **File I/O**: Minimal impact on system performance
## Security Considerations
### Current Implementation
- **Local Storage Only**: Configuration stored on device filesystem
- **No Encryption**: Plain text storage (LAN-only access assumed)
- **Access Control**: No authentication for configuration changes
### Future Enhancements
- **Configuration Encryption**: Encrypt sensitive fields (passwords)
- **Access Control**: Authentication for configuration changes
- **Audit Logging**: Track configuration modifications
- **Backup/Restore**: Configuration backup and restore capabilities
## Troubleshooting
### Common Issues
1. **Configuration Not Persisting**
- Check LittleFS initialization
- Verify file write permissions
- Monitor available flash space
2. **Default Values Not Applied**
- Verify constants are properly defined
- Check JSON parsing errors
- Ensure fallback logic is working
3. **Configuration Corruption**
- Delete `/config.json` to reset to defaults
- Check for JSON syntax errors
- Verify file system integrity
### Debug Commands
```bash
# Check configuration status
curl -s http://192.168.1.100/api/network/status | jq '.'
# View current WiFi settings
curl -s http://192.168.1.100/api/network/status | jq '.wifi'
# Test configuration save
curl -X POST http://192.168.1.100/api/network/wifi/config \
-d "ssid=TestNetwork&password=testpass"
```
## Best Practices
### Configuration Management
1. **Use Constants**: Always define defaults as constants
2. **Validate Input**: Check all configuration parameters
3. **Handle Errors**: Implement proper error handling
4. **Log Changes**: Log configuration modifications
5. **Test Fallbacks**: Ensure default fallbacks work correctly
### Development Guidelines
1. **Single Source**: Define each default value only once
2. **Type Safety**: Use appropriate data types
3. **Documentation**: Document all configuration parameters
4. **Versioning**: Include version metadata in config files
5. **Backward Compatibility**: Handle old configuration formats
## Related Documentation
- **[WiFi Configuration Process](./WiFiConfiguration.md)** - Detailed WiFi setup workflow
- **[API Reference](./API.md)** - HTTP API for configuration management
- **[Architecture Overview](./Architecture.md)** - System architecture and components
- **[OpenAPI Specification](../api/)** - Machine-readable API specification

View File

@@ -20,59 +20,131 @@
``` ```
spore/ spore/
├── src/ # Source code ├── src/ # Source code (framework under src/spore)
── main.cpp # Main application entry point ── spore/
├── ApiServer.cpp # HTTP API server implementation ├── Spore.cpp # Framework lifecycle (setup/begin/loop)
├── ClusterManager.cpp # Cluster management logic ├── core/ # Core components
├── NetworkManager.cpp # WiFi and network handling │ ├── ApiServer.cpp # HTTP API server implementation
│ ├── TaskManager.cpp # Background task management │ ├── ClusterManager.cpp # Cluster management logic
└── NodeContext.cpp # Central context and events │ ├── NetworkManager.cpp # WiFi and network handling
│ │ ├── TaskManager.cpp # Background task management
│ │ └── NodeContext.cpp # Central context and events
│ ├── services/ # Built-in services (implement Service interface)
│ │ ├── NodeService.cpp # registerEndpoints() + registerTasks()
│ │ ├── NetworkService.cpp # registerEndpoints() + registerTasks()
│ │ ├── ClusterService.cpp # registerEndpoints() + registerTasks()
│ │ ├── TaskService.cpp # registerEndpoints() + registerTasks()
│ │ ├── StaticFileService.cpp # registerEndpoints() + registerTasks()
│ │ └── MonitoringService.cpp # registerEndpoints() + registerTasks()
│ └── types/ # Shared types
├── include/ # Header files ├── include/ # Header files
├── lib/ # Library files ├── examples/ # Example apps per env (base, relay, neopattern)
├── docs/ # Documentation ├── docs/ # Documentation
├── api/ # OpenAPI specification ├── api/ # OpenAPI specification
├── examples/ # Example code ├── platformio.ini # PlatformIO configuration
── test/ # Test files ── ctl.sh # Build and deployment scripts
├── platformio.ini # PlatformIO configuration
└── ctl.sh # Build and deployment scripts
``` ```
## PlatformIO Configuration ## PlatformIO Configuration
### Framework and Board ### Framework and Board
The project uses PlatformIO with the following configuration: The project uses PlatformIO with the following configuration (excerpt):
```ini ```ini
[env:esp01_1m] [platformio]
default_envs = base
src_dir = .
data_dir = ${PROJECT_DIR}/examples/${PIOENV}/data
[common]
monitor_speed = 115200
lib_deps =
esp32async/ESPAsyncWebServer@^3.8.0
bblanchon/ArduinoJson@^7.4.2
[env:base]
platform = platformio/espressif8266@^4.2.1 platform = platformio/espressif8266@^4.2.1
board = esp01_1m board = esp01_1m
framework = arduino framework = arduino
upload_speed = 115200 upload_speed = 115200
flash_mode = dout monitor_speed = 115200
board_build.f_cpu = 80000000L
board_build.flash_mode = qio
board_build.filesystem = littlefs
; note: somehow partition table is not working, so we need to use the ldscript
board_build.ldscript = eagle.flash.1m64.ld
lib_deps = ${common.lib_deps}
build_src_filter =
+<examples/base/*.cpp>
+<src/spore/*.cpp>
+<src/spore/core/*.cpp>
+<src/spore/services/*.cpp>
+<src/spore/types/*.cpp>
+<src/spore/util/*.cpp>
+<src/internal/*.cpp>
[env:d1_mini]
platform = platformio/espressif8266@^4.2.1
board = d1_mini
framework = arduino
upload_speed = 115200
monitor_speed = 115200
board_build.filesystem = littlefs
board_build.flash_mode = dio ; D1 Mini uses DIO on 4 Mbit flash
board_build.flash_size = 4M
board_build.ldscript = eagle.flash.4m1m.ld
lib_deps = ${common.lib_deps}
build_src_filter =
+<examples/base/*.cpp>
+<src/spore/*.cpp>
+<src/spore/core/*.cpp>
+<src/spore/services/*.cpp>
+<src/spore/types/*.cpp>
+<src/spore/util/*.cpp>
+<src/internal/*.cpp>
``` ```
### Key Configuration Details
- **Framework**: Arduino
- **Board**: ESP-01 with 1MB flash
- **Upload Speed**: 115200 baud
- **Flash Mode**: DOUT (required for ESP-01S)
- **Build Type**: Release (optimized for production)
### Dependencies ### Dependencies
The project requires the following libraries: The project requires the following libraries (resolved via PlatformIO):
```ini ```ini
lib_deps = lib_deps =
esp32async/ESPAsyncWebServer@^3.8.0 esp32async/ESPAsyncWebServer@^3.8.0
bblanchon/ArduinoJson@^7.4.2 bblanchon/ArduinoJson@^7.4.2
arkhipenko/TaskScheduler@^3.8.5
ESP8266HTTPClient@1.2
ESP8266WiFi@1.0
``` ```
### Filesystem, Linker Scripts, and Flash Layout
This project uses LittleFS as the filesystem on ESP8266. Flash layout is controlled by the linker script (ldscript) selected per environment in `platformio.ini`.
- **Filesystem**: LittleFS (replaces SPIFFS). Although ldscripts reference SPIFFS in their names, the reserved region is used by LittleFS transparently.
- **Why ldscript matters**: It defines maximum sketch size and the size of the filesystem area in flash.
| Board / Env | Flash size | ldscript | Filesystem | FS size | Notes |
|-------------|------------|---------|------------|---------|-------|
| esp01_1m | 1MB | `eagle.flash.1m64.ld` | LittleFS | 64KB | Prioritizes sketch size on 1MB modules. OTA is constrained on 1MB.
| d1_mini | 4MB | `eagle.flash.4m1m.ld` | LittleFS | 1MB | Standard 4M/1M layout; ample space for firmware and data.
Configured in `platformio.ini`:
```ini
[env:esp01_1m]
board_build.filesystem = littlefs
board_build.ldscript = eagle.flash.1m64.ld
[env:d1_mini]
board_build.filesystem = littlefs
board_build.ldscript = eagle.flash.4m1m.ld
```
Notes:
- The ldscript name indicates total flash and filesystem size (e.g., `4m1m` = 4MB flash with 1MB FS; `1m64` = 1MB flash with 64KB FS).
- LittleFS works within the filesystem region defined by the ldscript, despite SPIFFS naming.
- If you need a different FS size, select an appropriate ldscript variant and keep `board_build.filesystem = littlefs`.
- On ESP8266, custom partition CSVs are not used for layout; the linker script defines the flash map. This project removed prior `board_build.partitions` usage in favor of explicit `board_build.ldscript` entries per environment.
## Building ## Building
### Basic Build Commands ### Basic Build Commands
@@ -277,7 +349,7 @@ export API_NODE=192.168.1.100
Key configuration files: Key configuration files:
- **`platformio.ini`**: Build and upload configuration - **`platformio.ini`**: Build and upload configuration
- **`src/Config.cpp`**: Application configuration - **`src/spore/types/Config.cpp`**: Default runtime configuration
- **`.env`**: Environment variables - **`.env`**: Environment variables
- **`ctl.sh`**: Build and deployment scripts - **`ctl.sh`**: Build and deployment scripts

79
docs/MonitoringService.md Normal file
View File

@@ -0,0 +1,79 @@
# Monitoring Service
Exposes system resource metrics via HTTP for observability.
## Overview
- **Service name**: `MonitoringService`
- **Endpoint**: `GET /api/monitoring/resources`
- **Metrics**: CPU usage, memory, filesystem, uptime
## Endpoint
### GET /api/monitoring/resources
Returns real-time system resource metrics.
Response fields:
- `cpu.current_usage`: Current CPU usage percent
- `cpu.average_usage`: Average CPU usage percent
- `cpu.max_usage`: Max observed CPU usage
- `cpu.min_usage`: Min observed CPU usage
- `cpu.measurement_count`: Number of measurements
- `cpu.is_measuring`: Whether measurement is active
- `memory.free_heap`: Free heap bytes
- `memory.total_heap`: Total heap bytes (approximate)
- `memory.min_free_heap`: Minimum free heap (0 on ESP8266)
- `memory.max_alloc_heap`: Max allocatable heap (0 on ESP8266)
- `memory.heap_fragmentation`: Fragmentation percent (0 on ESP8266)
- `filesystem.total_bytes`: LittleFS total bytes
- `filesystem.used_bytes`: Used bytes
- `filesystem.free_bytes`: Free bytes
- `filesystem.usage_percent`: Usage percent
- `system.uptime_ms`: Uptime in milliseconds
- `system.uptime_seconds`: Uptime in seconds
- `system.uptime_formatted`: Human-readable uptime
Example:
```json
{
"cpu": {
"current_usage": 3.5,
"average_usage": 2.1,
"max_usage": 15.2,
"min_usage": 0.0,
"measurement_count": 120,
"is_measuring": true
},
"memory": {
"free_heap": 48748,
"total_heap": 81920,
"min_free_heap": 0,
"max_alloc_heap": 0,
"heap_fragmentation": 0,
"heap_usage_percent": 40.4
},
"filesystem": {
"total_bytes": 65536,
"used_bytes": 10240,
"free_bytes": 55296,
"usage_percent": 15.6
},
"system": {
"uptime_ms": 123456,
"uptime_seconds": 123,
"uptime_formatted": "0h 2m 3s"
}
}
```
## Implementation Notes
- `MonitoringService` reads from `CpuUsage` and ESP8266 SDK APIs.
- Filesystem metrics are gathered from LittleFS.
- CPU measurement is bracketed by `Spore::loop()` calling `cpuUsage.startMeasurement()` and `cpuUsage.endMeasurement()`.
## Troubleshooting
- If `filesystem.total_bytes` is zero, ensure LittleFS is enabled in `platformio.ini` and an FS image is uploaded.
- CPU usage values remain zero until the main loop runs and CPU measurement is started.

View File

@@ -15,15 +15,8 @@ Complete API reference with detailed endpoint documentation, examples, and integ
- Task management workflows - Task management workflows
- Cluster monitoring examples - Cluster monitoring examples
### 📖 [TaskManager.md](./TaskManager.md) ### 📖 [MonitoringService.md](./MonitoringService.md)
Comprehensive guide to the TaskManager system for background task management. System resource monitoring API for CPU, memory, filesystem, and uptime.
**Includes:**
- Basic usage examples
- Advanced binding techniques
- Task status monitoring
- API integration details
- Performance considerations
### 📖 [TaskManagement.md](./TaskManagement.md) ### 📖 [TaskManagement.md](./TaskManagement.md)
Complete guide to the task management system with examples and best practices. Complete guide to the task management system with examples and best practices.
@@ -52,6 +45,7 @@ Complete development and deployment guide.
**Includes:** **Includes:**
- PlatformIO configuration - PlatformIO configuration
- Filesystem, linker scripts, and flash layout mapping
- Build and flash instructions - Build and flash instructions
- OTA update procedures - OTA update procedures
- Cluster management commands - Cluster management commands
@@ -59,6 +53,9 @@ Complete development and deployment guide.
- Troubleshooting guide - Troubleshooting guide
- Best practices - Best practices
### 📖 [StaticFileService.md](./StaticFileService.md)
Static file hosting over HTTP using LittleFS.
## Quick Links ## Quick Links
- **Main Project**: [../README.md](../README.md) - **Main Project**: [../README.md](../README.md)

84
docs/StaticFileService.md Normal file
View File

@@ -0,0 +1,84 @@
# StaticFileService
Serves static files from the device's LittleFS filesystem over HTTP.
## Overview
- Service name: `StaticFileService`
- Mounts: LittleFS at startup
- Endpoints:
- GET / → serves /index.html
- GET /* → serves any file under LittleFS by path
Files are looked up relative to the filesystem root. If a requested path is empty or `/`, it falls back to `index.html`. Unknown paths return 404; a helper exists to fall back to `index.html` for SPA routing if desired.
## Content types
Determined by filename extension:
- .html, .htm → text/html
- .css → text/css
- .js → application/javascript
- .json → application/json
- .png → image/png
- .jpg, .jpeg → image/jpeg
- .gif → image/gif
- .svg → image/svg+xml
- .ico → image/x-icon
- default → application/octet-stream
## Building and uploading the filesystem
Place your web assets under the `data/` folder at the project root. The build system packages `data/` into a LittleFS image matching the environment's flash layout.
### Upload via PlatformIO
```bash
# Build and upload filesystem for esp01_1m
pio run -e esp01_1m -t uploadfs
# Build and upload filesystem for d1_mini
pio run -e d1_mini -t uploadfs
```
### Manual image build (optional)
There are helper commands in `ctl.sh` if you need to build and flash the FS image manually:
```bash
# Build image from ./data into littlefs.bin
./ctl.sh mkfs
# Flash image at preconfigured offset
./ctl.sh flashfs
```
Note: Offsets and sizes must match the environment's ldscript. The project defaults are:
- esp01_1m: ldscript `eagle.flash.1m64.ld` (64KB FS)
- d1_mini: ldscript `eagle.flash.4m1m.ld` (1MB FS)
See `docs/Development.md` for details on filesystem and ldscript mapping.
## Enabling the service
`StaticFileService` is registered as a core service in the framework and enabled by default:
```cpp
// part of Spore::registerCoreServices()
auto staticFileService = std::make_shared<StaticFileService>(ctx, apiServer);
services.push_back(staticFileService);
```
If you maintain a custom build without the default services, ensure you instantiate and add the service before starting the API server.
## Routing notes
- Place your SPA assets in `data/` and reference paths relatively.
- To support client-side routing, you can adapt `handleNotFound` to always serve `/index.html` for unknown paths.
## Troubleshooting
- "LittleFS Mount Failed": Ensure `board_build.filesystem = littlefs` is set and the ldscript provides a filesystem region.
- 404 for known files: Confirm the file exists under `data/` and was uploaded (`pio run -t uploadfs`).
- Incorrect MIME type: Add the extension mapping to `getContentType()` if using custom extensions.

98
docs/StreamingAPI.md Normal file
View File

@@ -0,0 +1,98 @@
## Streaming API (WebSocket)
### Overview
The streaming API exposes an event-driven WebSocket at `/ws`. It bridges between external clients and the internal event bus:
- Incoming WebSocket JSON `{ event, payload }``ctx.fire(event, payload)`
- Local events → broadcasted to all connected WebSocket clients as `{ event, payload }`
This allows real-time control and observation of the system without polling.
### URL
- `ws://<device-ip>/ws`
### Message Format
- Client → Device
```json
{
"event": "<event-name>",
"payload": "<json-string>" | { /* inline JSON */ }
}
```
- Device → Client
```json
{
"event": "<event-name>",
"payload": "<json-string>"
}
```
Notes:
- The device accepts `payload` as a string or a JSON object/array. Objects are serialized into a string before dispatching to local subscribers to keep a consistent downstream contract.
- A minimal ack `{ "ok": true }` is sent after a valid inbound message.
#### Echo suppression (origin tagging)
- To prevent the sender from receiving an immediate echo of its own message, the server injects a private field into JSON payloads:
- `_origin: "ws:<clientId>"`
- When re-broadcasting local events to WebSocket clients, the server:
- Strips the `_origin` field from the outgoing payload
- Skips the originating `clientId` so only other clients receive the message
- If a payload is not valid JSON (plain string), no origin tag is injected and the message may be echoed
### Event Bus Integration
- The WebSocket registers an `onAny` subscriber to `NodeContext` so that all local events are mirrored to clients.
- Services should subscribe to specific events via `ctx.on("<name>", ...)`.
### Examples
1) Set a solid color on NeoPattern:
```json
{
"event": "api/neopattern/color",
"payload": { "color": "#FF0000", "brightness": 128 }
}
```
2) Broadcast a cluster event (delegated to core):
```json
{
"event": "cluster/broadcast",
"payload": {
"event": "api/neopattern/color",
"data": { "color": "#00FF00", "brightness": 128 }
}
}
```
### Reference Implementation
- WebSocket setup and bridging are implemented in `ApiServer`.
- Global event subscription uses `NodeContext::onAny`.
Related docs:
- [`ClusterBroadcast.md`](./ClusterBroadcast.md) — centralized UDP broadcasting and CLUSTER_EVENT format
### Things to consider
- High-frequency updates can overwhelm ESP8266:
- Frequent JSON parse/serialize and `String` allocations fragment heap and may cause resets (e.g., Exception(3)).
- UDP broadcast on every message amplifies load; WiFi/UDP buffers can back up.
- Prefer ≥50100 ms intervals; microbursts at 10 ms are risky.
- Throttle and coalesce:
- Add a minimum interval in the core `cluster/broadcast` handler.
- Optionally drop redundant updates (e.g., same color as previous).
- Reduce allocations:
- Reuse `StaticJsonDocument`/preallocated buffers in hot paths.
- Avoid re-serializing when possible; pass-through payload strings.
- Reserve `String` capacity when reuse is needed.
- Yielding:
- Call `yield()` in long-running or bursty paths to avoid WDT.
- Packet size:
- Keep payloads small to fit `ClusterProtocol::UDP_BUF_SIZE` and reduce airtime.

View File

@@ -11,6 +11,31 @@ The TaskManager system provides:
- **Status Monitoring**: View task status and configuration - **Status Monitoring**: View task status and configuration
- **Automatic Lifecycle**: Tasks are automatically managed and executed - **Automatic Lifecycle**: Tasks are automatically managed and executed
## Service Interface Integration
Services now implement a unified interface for both endpoint and task registration:
```cpp
class MyService : public Service {
public:
void registerEndpoints(ApiServer& api) override {
// Register HTTP endpoints
api.registerEndpoint("/api/my/status", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleStatus(request); });
}
void registerTasks(TaskManager& taskManager) override {
// Register background tasks
taskManager.registerTask("my_heartbeat", 2000,
[this]() { sendHeartbeat(); });
taskManager.registerTask("my_maintenance", 30000,
[this]() { performMaintenance(); });
}
const char* getName() const override { return "MyService"; }
};
```
## Basic Usage ## Basic Usage
```cpp ```cpp
@@ -27,6 +52,46 @@ taskManager.registerTask("maintenance", 30000, maintenanceFunction);
taskManager.initialize(); taskManager.initialize();
``` ```
## Service Lifecycle
The Spore framework automatically manages service registration and task lifecycle:
### Service Registration Process
1. **Service Creation**: Services are created with required dependencies (NodeContext, TaskManager, etc.)
2. **Service Registration**: Services are registered with the Spore framework via `spore.registerService()`
3. **Endpoint Registration**: When `spore.begin()` is called, `registerEndpoints()` is called for each service
4. **Task Registration**: Simultaneously, `registerTasks()` is called for each service
5. **Task Initialization**: The TaskManager initializes all registered tasks
6. **Execution**: Tasks run in the main loop when their intervals elapse
### Framework Integration
```cpp
void setup() {
spore.setup();
// Create service with dependencies
MyService* service = new MyService(spore.getContext(), spore.getTaskManager());
// Register service (endpoints and tasks will be registered when begin() is called)
spore.registerService(service);
// This triggers registerEndpoints() and registerTasks() for all services
spore.begin();
}
```
### Dynamic Service Addition
Services can be added after the framework has started:
```cpp
// Add service to running framework
MyService* newService = new MyService(spore.getContext(), spore.getTaskManager());
spore.registerService(newService); // Immediately registers endpoints and tasks
```
## Task Registration Methods ## Task Registration Methods
### Using std::bind with Member Functions (Recommended) ### Using std::bind with Member Functions (Recommended)
@@ -153,7 +218,62 @@ taskManager.registerTask("lambda_task", 2000,
## Adding Custom Tasks ## Adding Custom Tasks
### Method 1: Using std::bind (Recommended) ### Method 1: Service Interface (Recommended)
1. **Create your service class implementing the Service interface**:
```cpp
class SensorService : public Service {
public:
SensorService(NodeContext& ctx, TaskManager& taskManager)
: ctx(ctx), taskManager(taskManager) {}
void registerEndpoints(ApiServer& api) override {
api.registerEndpoint("/api/sensor/status", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleStatus(request); });
}
void registerTasks(TaskManager& taskManager) override {
taskManager.registerTask("temp_read", 1000,
[this]() { readTemperature(); });
taskManager.registerTask("calibrate", 60000,
[this]() { calibrateSensors(); });
}
const char* getName() const override { return "SensorService"; }
private:
NodeContext& ctx;
TaskManager& taskManager;
void readTemperature() {
// Read sensor logic
Serial.println("Reading temperature");
}
void calibrateSensors() {
// Calibration logic
Serial.println("Calibrating sensors");
}
void handleStatus(AsyncWebServerRequest* request) {
// Handle status request
}
};
```
2. **Register with Spore framework**:
```cpp
void setup() {
spore.setup();
SensorService* sensorService = new SensorService(spore.getContext(), spore.getTaskManager());
spore.registerService(sensorService);
spore.begin(); // This will call registerTasks() automatically
}
```
### Method 2: Direct TaskManager Registration
1. **Create your service class**: 1. **Create your service class**:
```cpp ```cpp
@@ -181,7 +301,7 @@ taskManager.registerTask("lambda_task", 2000,
std::bind(&SensorService::calibrateSensors, &sensors)); std::bind(&SensorService::calibrateSensors, &sensors));
``` ```
### Method 2: Traditional Functions ### Method 3: Traditional Functions
1. **Define your task function**: 1. **Define your task function**:
```cpp ```cpp
@@ -308,38 +428,80 @@ curl -X POST http://192.168.1.100/api/tasks/control \
## Best Practices ## Best Practices
1. **Use std::bind for member functions**: Cleaner than wrapper functions 1. **Use Service Interface**: Implement the Service interface for clean integration with the framework
2. **Group related tasks**: Register multiple related operations in a single task 2. **Group related tasks**: Register multiple related operations in a single service
3. **Monitor task health**: Use the status API to monitor task performance 3. **Monitor task health**: Use the status API to monitor task performance
4. **Plan intervals carefully**: Balance responsiveness with system resources 4. **Plan intervals carefully**: Balance responsiveness with system resources
5. **Use descriptive names**: Make task names clear and meaningful 5. **Use descriptive names**: Make task names clear and meaningful
6. **Separate concerns**: Use registerEndpoints() for HTTP API and registerTasks() for background work
7. **Dependency injection**: Pass required dependencies (NodeContext, TaskManager) to service constructors
## Migration from Wrapper Functions ## Migration to Service Interface
### Before (manual task registration in constructor):
```cpp
class MyService : public Service {
public:
MyService(TaskManager& taskManager) : taskManager(taskManager) {
// Tasks registered in constructor
taskManager.registerTask("heartbeat", 2000, [this]() { sendHeartbeat(); });
}
void registerEndpoints(ApiServer& api) override {
// Only endpoints registered here
}
};
```
### After (using Service interface):
```cpp
class MyService : public Service {
public:
MyService(TaskManager& taskManager) : taskManager(taskManager) {
// No task registration in constructor
}
void registerEndpoints(ApiServer& api) override {
// Register HTTP endpoints
api.registerEndpoint("/api/my/status", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleStatus(request); });
}
void registerTasks(TaskManager& taskManager) override {
// Register background tasks
taskManager.registerTask("heartbeat", 2000, [this]() { sendHeartbeat(); });
}
const char* getName() const override { return "MyService"; }
};
```
### Migration from Wrapper Functions
### Before (with wrapper functions): ### Before (with wrapper functions):
```cpp ```cpp
void discoverySendTask() { cluster.sendDiscovery(); } void discoverySendTask() { cluster.sendDiscovery(); }
void discoveryListenTask() { cluster.listenForDiscovery(); } void clusterListenTask() { cluster.listen(); }
taskManager.registerTask("discovery_send", interval, discoverySendTask); taskManager.registerTask("discovery_send", interval, discoverySendTask);
taskManager.registerTask("discovery_listen", interval, discoveryListenTask); taskManager.registerTask("cluster_listen", interval, clusterListenTask);
``` ```
### After (with std::bind): ### After (with std::bind):
```cpp ```cpp
taskManager.registerTask("discovery_send", interval, taskManager.registerTask("discovery_send", interval,
std::bind(&ClusterManager::sendDiscovery, &cluster)); std::bind(&ClusterManager::sendDiscovery, &cluster));
taskManager.registerTask("discovery_listen", interval, taskManager.registerTask("cluster_listen", interval,
std::bind(&ClusterManager::listenForDiscovery, &cluster)); std::bind(&ClusterManager::listen, &cluster));
``` ```
## Compatibility ## Compatibility
- The new `std::bind` support is fully backward compatible - The new Service interface is fully backward compatible
- Existing code using function pointers will continue to work - Existing code using direct TaskManager registration will continue to work
- You can mix both approaches in the same project - You can mix Service interface and direct registration in the same project
- All existing TaskManager methods remain unchanged - All existing TaskManager methods remain unchanged
- New status monitoring methods are additive and don't break existing functionality - The Service interface provides a cleaner, more organized approach for framework integration
## Related Documentation ## Related Documentation

478
docs/WiFiConfiguration.md Normal file
View File

@@ -0,0 +1,478 @@
# SPORE WiFi Configuration Process
## Overview
SPORE implements a WiFi configuration system that handles initial setup, runtime reconfiguration, and automatic fallback mechanisms. The system supports both Station (STA) and Access Point (AP) modes with seamless switching between them.
## WiFi Configuration Architecture
### Core Components
- **`NetworkManager`**: Handles WiFi operations and configuration
- **`NetworkService`**: Provides HTTP API endpoints for WiFi management
- **`Config`**: Stores WiFi credentials and connection parameters
- **LittleFS**: Persistent storage for WiFi configuration
- **ESP8266 WiFi Library**: Low-level WiFi operations
### Configuration Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `wifi_ssid` | String | "shroud" | Network SSID for connection |
| `wifi_password` | String | "th3r31sn0sp00n" | Network password |
| `wifi_connect_timeout_ms` | uint32_t | 15000 | Connection timeout (15 seconds) |
| `wifi_retry_delay_ms` | uint32_t | 500 | Delay between connection attempts |
## WiFi Configuration Lifecycle
### 1. Boot Process
```mermaid
graph TD
A[System Boot] --> B[Initialize LittleFS]
B --> C[Load Configuration]
C --> D[Initialize WiFi Mode]
D --> E[Set Hostname from MAC]
E --> F[Attempt STA Connection]
F --> G{Connection Successful?}
G -->|Yes| H[STA Mode Active]
G -->|No| I[Switch to AP Mode]
I --> J[Create Access Point]
J --> K[AP Mode Active]
H --> L[Start UDP Services]
K --> L
L --> M[Initialize Node Context]
M --> N[Start Cluster Services]
```
**Detailed Boot Sequence:**
1. **LittleFS Initialization**
```cpp
if (!LittleFS.begin()) {
LOG_WARN("Config", "Failed to initialize LittleFS, using defaults");
setDefaults();
return;
}
```
2. **Configuration Loading**
- Load WiFi credentials from `/config.json`
- Fall back to defaults if file doesn't exist
- Validate configuration parameters
3. **WiFi Mode Initialization**
```cpp
WiFi.mode(WIFI_STA);
WiFi.begin(ctx.config.wifi_ssid.c_str(), ctx.config.wifi_password.c_str());
```
4. **Hostname Generation**
```cpp
void NetworkManager::setHostnameFromMac() {
uint8_t mac[6];
WiFi.macAddress(mac);
char buf[32];
sprintf(buf, "esp-%02X%02X%02X%02X%02X%02X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
WiFi.hostname(buf);
ctx.hostname = String(buf);
}
```
5. **Connection Attempt**
```cpp
unsigned long startAttemptTime = millis();
while (WiFi.status() != WL_CONNECTED &&
millis() - startAttemptTime < ctx.config.wifi_connect_timeout_ms) {
delay(ctx.config.wifi_retry_delay_ms);
}
```
6. **Fallback to AP Mode**
```cpp
if (WiFi.status() != WL_CONNECTED) {
LOG_WARN("WiFi", "Failed to connect to AP. Creating AP...");
WiFi.mode(WIFI_AP);
WiFi.softAP(ctx.config.wifi_ssid.c_str(), ctx.config.wifi_password.c_str());
}
```
### 2. Runtime Reconfiguration
```mermaid
graph TD
A[HTTP API Request] --> B[Validate Parameters]
B --> C[Update Config Object]
C --> D[Save to Persistent Storage]
D --> E[Send Response to Client]
E --> F[Schedule Node Restart]
F --> G[Node Restarts]
G --> H[Apply New Configuration]
H --> I[Attempt New Connection]
```
**Runtime Reconfiguration Process:**
1. **API Request Validation**
```cpp
if (!request->hasParam("ssid", true) || !request->hasParam("password", true)) {
request->send(400, "application/json", "{\"error\": \"Missing required parameters\"}");
return;
}
```
2. **Configuration Update**
```cpp
void NetworkManager::setWiFiConfig(const String& ssid, const String& password,
uint32_t connect_timeout_ms, uint32_t retry_delay_ms) {
ctx.config.wifi_ssid = ssid;
ctx.config.wifi_password = password;
ctx.config.wifi_connect_timeout_ms = connect_timeout_ms;
ctx.config.wifi_retry_delay_ms = retry_delay_ms;
}
```
3. **Persistent Storage**
```cpp
bool configSaved = networkManager.saveConfig();
if (!configSaved) {
LOG_WARN("NetworkService", "Failed to save WiFi configuration to persistent storage");
}
```
4. **Restart Scheduling**
```cpp
request->onDisconnect([this]() {
LOG_INFO("NetworkService", "Restarting node to apply WiFi configuration...");
delay(100); // Give time for response to be sent
networkManager.restartNode();
});
```
## WiFi Modes
### Station Mode (STA)
**Purpose**: Connect to existing WiFi network as a client
**Configuration**:
- SSID and password required
- Automatic IP assignment via DHCP
- Hostname set from MAC address
- UDP services on configured port
**Connection Process**:
1. Set WiFi mode to STA
2. Begin connection with credentials
3. Wait for connection with timeout
4. Set hostname and start services
### Access Point Mode (AP)
**Purpose**: Create WiFi hotspot when STA connection fails
**Configuration**:
- Uses configured SSID/password for AP
- Fixed IP address (usually 192.168.4.1)
- Allows other devices to connect
- Maintains cluster functionality
**AP Creation Process**:
1. Switch WiFi mode to AP
2. Create soft access point
3. Set AP IP address
4. Start services on AP IP
## HTTP API Endpoints
### Network Status
#### GET `/api/network/status`
Returns comprehensive WiFi and network status information.
**Response Fields**:
```json
{
"wifi": {
"connected": true,
"mode": "STA",
"ssid": "MyNetwork",
"ip": "192.168.1.100",
"mac": "AA:BB:CC:DD:EE:FF",
"hostname": "esp-AABBCCDDEEFF",
"rssi": -45,
"ap_ip": "192.168.4.1",
"ap_mac": "AA:BB:CC:DD:EE:FF",
"stations_connected": 0
}
}
```
### WiFi Scanning
#### GET `/api/network/wifi/scan`
Returns list of available WiFi networks from last scan.
**Response Fields**:
```json
{
"access_points": [
{
"ssid": "MyNetwork",
"rssi": -45,
"channel": 6,
"encryption_type": 4,
"hidden": false,
"bssid": "AA:BB:CC:DD:EE:FF"
}
]
}
```
#### POST `/api/network/wifi/scan`
Initiates a new WiFi network scan.
**Response**:
```json
{
"status": "scanning",
"message": "WiFi scan started"
}
```
### WiFi Configuration
#### POST `/api/network/wifi/config`
Configures WiFi connection with new credentials.
**Parameters**:
- `ssid` (required): Network SSID
- `password` (required): Network password
- `connect_timeout_ms` (optional): Connection timeout (default: 10000)
- `retry_delay_ms` (optional): Retry delay (default: 500)
**Request Example**:
```bash
curl -X POST http://192.168.1.100/api/network/wifi/config \
-d "ssid=MyNewNetwork&password=newpassword&connect_timeout_ms=15000"
```
**Response**:
```json
{
"status": "success",
"message": "WiFi configuration updated and saved",
"config_saved": true,
"restarting": true
}
```
## WiFi Scanning Process
### Scanning Implementation
```cpp
void NetworkManager::scanWifi() {
if (!isScanning) {
isScanning = true;
LOG_INFO("WiFi", "Starting WiFi scan...");
WiFi.scanNetworksAsync([this](int networksFound) {
LOG_INFO("WiFi", "Scan completed, found " + String(networksFound) + " networks");
this->processAccessPoints();
this->isScanning = false;
}, true);
}
}
```
### Access Point Processing
```cpp
void NetworkManager::processAccessPoints() {
int numNetworks = WiFi.scanComplete();
if (numNetworks <= 0) return;
accessPoints.clear();
for (int i = 0; i < numNetworks; i++) {
AccessPoint ap;
ap.ssid = WiFi.SSID(i);
ap.rssi = WiFi.RSSI(i);
ap.encryptionType = WiFi.encryptionType(i);
ap.channel = WiFi.channel(i);
ap.isHidden = ap.ssid.length() == 0;
uint8_t* newBssid = new uint8_t[6];
memcpy(newBssid, WiFi.BSSID(i), 6);
ap.bssid = newBssid;
accessPoints.push_back(ap);
}
WiFi.scanDelete();
}
```
## Error Handling
### Connection Failures
| Error Type | Handling | Recovery |
|------------|----------|----------|
| **Invalid Credentials** | Log error, switch to AP mode | Manual reconfiguration via API |
| **Network Unavailable** | Log warning, switch to AP mode | Automatic retry on next boot |
| **Timeout** | Log timeout, switch to AP mode | Increase timeout or check network |
| **Hardware Failure** | Log error, continue in AP mode | Hardware replacement required |
### API Error Responses
```json
{
"error": "Missing required parameters",
"status": "error"
}
```
```json
{
"error": "Failed to save configuration",
"status": "error",
"config_saved": false
}
```
## Security Considerations
### Current Implementation
- **Plain Text Storage**: WiFi passwords stored unencrypted
- **Local Network Only**: No internet exposure
- **No Authentication**: API access without authentication
- **MAC-based Hostnames**: Predictable hostname generation
### Security Best Practices
1. **Network Isolation**: Keep SPORE devices on isolated network
2. **Strong Passwords**: Use complex WiFi passwords
3. **Regular Updates**: Keep firmware updated
4. **Access Control**: Implement network-level access control
### Future Security Enhancements
- **Password Encryption**: Encrypt stored WiFi credentials
- **API Authentication**: Add authentication to configuration API
- **Certificate-based Security**: Implement TLS/SSL
- **Access Control Lists**: Role-based configuration access
## Troubleshooting
### Common Issues
1. **WiFi Connection Fails**
- Check SSID and password
- Verify network availability
- Check signal strength
- Increase connection timeout
2. **Configuration Not Persisting**
- Check LittleFS initialization
- Verify file write permissions
- Monitor available flash space
3. **AP Mode Not Working**
- Check AP credentials
- Verify IP address assignment
- Check for IP conflicts
4. **Scan Not Finding Networks**
- Wait for scan completion
- Check WiFi hardware
- Verify scan permissions
### Debug Commands
```bash
# Check WiFi status
curl -s http://192.168.1.100/api/network/status | jq '.wifi'
# Scan for networks
curl -X POST http://192.168.1.100/api/network/wifi/scan
# View scan results
curl -s http://192.168.1.100/api/network/wifi/scan | jq '.'
# Test configuration
curl -X POST http://192.168.1.100/api/network/wifi/config \
-d "ssid=TestNetwork&password=testpass"
```
### Log Analysis
**Successful Connection**:
```
[INFO] WiFi: Connected to AP, IP: 192.168.1.100
[INFO] WiFi: Hostname set to: esp-AABBCCDDEEFF
[INFO] WiFi: UDP listening on port 4210
```
**Connection Failure**:
```
[WARN] WiFi: Failed to connect to AP. Creating AP...
[INFO] WiFi: AP created, IP: 192.168.4.1
```
**Configuration Update**:
```
[INFO] NetworkService: Restarting node to apply WiFi configuration...
[INFO] WiFi: Connecting to AP...
```
## Performance Considerations
### Connection Time
- **STA Connection**: 5-15 seconds typical
- **AP Creation**: 1-3 seconds
- **Scan Duration**: 5-10 seconds
- **Configuration Save**: 100-500ms
### Memory Usage
- **WiFi Stack**: ~20-30KB RAM
- **Scan Results**: ~1KB per network
- **Configuration**: ~200 bytes
- **API Buffers**: ~2-4KB
### Network Overhead
- **Scan Packets**: Minimal impact
- **Configuration API**: ~500 bytes per request
- **Status Updates**: ~200 bytes per response
## Best Practices
### Configuration Management
1. **Use Strong Passwords**: Implement password complexity requirements
2. **Set Appropriate Timeouts**: Balance connection speed vs reliability
3. **Monitor Connection Quality**: Track RSSI and connection stability
4. **Implement Retry Logic**: Handle temporary network issues
5. **Log Configuration Changes**: Audit trail for troubleshooting
### Development Guidelines
1. **Handle All Error Cases**: Implement comprehensive error handling
2. **Provide Clear Feedback**: Inform users of connection status
3. **Optimize Scan Frequency**: Balance discovery vs performance
4. **Test Fallback Scenarios**: Ensure AP mode works correctly
5. **Document Configuration Options**: Clear parameter documentation
## Related Documentation
- **[Configuration Management](./ConfigurationManagement.md)** - Persistent configuration system
- **[API Reference](./API.md)** - Complete HTTP API documentation
- **[Architecture Overview](./Architecture.md)** - System architecture and components
- **[OpenAPI Specification](../api/)** - Machine-readable API specification

View File

@@ -0,0 +1,248 @@
#include "MultiMatrixService.h"
#include "spore/core/ApiServer.h"
#include "spore/util/Logging.h"
#include <ArduinoJson.h>
#include <SoftwareSerial.h>
namespace {
constexpr uint8_t DEFAULT_VOLUME = 15;
constexpr char API_STATUS_ENDPOINT[] = "/api/audio/status";
constexpr char API_CONTROL_ENDPOINT[] = "/api/audio";
constexpr char EVENT_TOPIC[] = "audio/player";
}
MultiMatrixService::MultiMatrixService(NodeContext& ctx, TaskManager& taskManager, uint8_t rxPin, uint8_t txPin, uint8_t potentiometerPin)
: m_ctx(ctx),
m_taskManager(taskManager),
m_serial(std::make_unique<SoftwareSerial>(rxPin, txPin)),
m_potentiometerPin(potentiometerPin),
m_volume(DEFAULT_VOLUME),
m_playerReady(false),
m_loopEnabled(false) {
pinMode(m_potentiometerPin, INPUT);
m_serial->begin(9600);
// DFPlayer Mini requires time to initialize after power-on
delay(1000);
if (m_player.begin(*m_serial)) {
m_playerReady = true;
m_player.setTimeOut(500);
m_player.EQ(DFPLAYER_EQ_NORMAL);
m_player.volume(m_volume);
LOG_INFO("MultiMatrixService", "DFPlayer initialized successfully");
publishEvent("ready");
} else {
LOG_ERROR("MultiMatrixService", "Failed to initialize DFPlayer");
}
}
void MultiMatrixService::registerEndpoints(ApiServer& api) {
api.registerEndpoint(API_STATUS_ENDPOINT, HTTP_GET,
[this](AsyncWebServerRequest* request) { handleStatusRequest(request); },
std::vector<ParamSpec>{});
api.registerEndpoint(API_CONTROL_ENDPOINT, HTTP_POST,
[this](AsyncWebServerRequest* request) { handleControlRequest(request); },
std::vector<ParamSpec>{
ParamSpec{String("action"), true, String("body"), String("string"),
{String("play"), String("stop"), String("pause"), String("resume"), String("next"), String("previous"), String("volume"), String("loop")}},
ParamSpec{String("volume"), false, String("body"), String("numberRange"), {}, String("15")},
ParamSpec{String("loop"), false, String("body"), String("boolean"), {}}
});
}
bool MultiMatrixService::isReady() const {
return m_playerReady;
}
uint8_t MultiMatrixService::getVolume() const {
return m_volume;
}
bool MultiMatrixService::isLoopEnabled() const {
return m_loopEnabled;
}
void MultiMatrixService::play() {
if (!m_playerReady) {
return;
}
m_player.play();
publishEvent("play");
LOG_INFO("MultiMatrixService", "Playback started");
}
void MultiMatrixService::stop() {
if (!m_playerReady) {
return;
}
m_player.stop();
publishEvent("stop");
LOG_INFO("MultiMatrixService", "Playback stopped");
}
void MultiMatrixService::pause() {
if (!m_playerReady) {
return;
}
m_player.pause();
publishEvent("pause");
LOG_INFO("MultiMatrixService", "Playback paused");
}
void MultiMatrixService::resume() {
if (!m_playerReady) {
return;
}
m_player.start();
publishEvent("resume");
LOG_INFO("MultiMatrixService", "Playback resumed");
}
void MultiMatrixService::next() {
if (!m_playerReady) {
return;
}
m_player.next();
publishEvent("next");
LOG_INFO("MultiMatrixService", "Next track");
}
void MultiMatrixService::previous() {
if (!m_playerReady) {
return;
}
m_player.previous();
publishEvent("previous");
LOG_INFO("MultiMatrixService", "Previous track");
}
void MultiMatrixService::setVolume(uint8_t volume) {
if (!m_playerReady) {
return;
}
const uint8_t clampedVolume = std::min<uint8_t>(volume, MAX_VOLUME);
if (clampedVolume == m_volume) {
return;
}
applyVolume(clampedVolume);
}
void MultiMatrixService::setLoop(bool enabled) {
if (!m_playerReady) {
return;
}
m_loopEnabled = enabled;
if (enabled) {
m_player.enableLoop();
} else {
m_player.disableLoop();
}
publishEvent("loop");
LOG_INFO("MultiMatrixService", String("Loop ") + (enabled ? "enabled" : "disabled"));
}
void MultiMatrixService::registerTasks(TaskManager& taskManager) {
taskManager.registerTask("multimatrix_potentiometer", POTENTIOMETER_SAMPLE_INTERVAL_MS,
[this]() { pollPotentiometer(); });
}
void MultiMatrixService::pollPotentiometer() {
if (!m_playerReady) {
return;
}
const uint16_t rawValue = analogRead(static_cast<uint8_t>(m_potentiometerPin));
const uint8_t targetVolume = calculateVolumeFromPotentiometer(rawValue);
if (targetVolume > m_volume + POT_VOLUME_EPSILON || targetVolume + POT_VOLUME_EPSILON < m_volume) {
applyVolume(targetVolume);
}
}
uint8_t MultiMatrixService::calculateVolumeFromPotentiometer(uint16_t rawValue) const {
// Clamp raw value to prevent underflow (analogRead can return 1024)
const uint16_t clampedValue = std::min<uint16_t>(rawValue, 1023U);
// Invert: all down (0) = max volume, all up (1023) = min volume
const uint16_t invertedValue = 1023U - clampedValue;
const uint8_t scaledVolume = static_cast<uint8_t>((static_cast<uint32_t>(invertedValue) * MAX_VOLUME) / 1023U);
return std::min<uint8_t>(scaledVolume, MAX_VOLUME);
}
void MultiMatrixService::applyVolume(uint8_t targetVolume) {
m_volume = targetVolume;
m_player.volume(m_volume);
publishEvent("volume");
LOG_INFO("MultiMatrixService", String("Volume set to ") + String(m_volume));
}
void MultiMatrixService::handleStatusRequest(AsyncWebServerRequest* request) {
StaticJsonDocument<192> doc;
doc["ready"] = m_playerReady;
doc["volume"] = static_cast<int>(m_volume);
doc["loop"] = m_loopEnabled;
String json;
serializeJson(doc, json);
request->send(200, "application/json", json);
}
void MultiMatrixService::handleControlRequest(AsyncWebServerRequest* request) {
String action = request->hasParam("action", true) ? request->getParam("action", true)->value() : "";
bool ok = true;
if (action.equalsIgnoreCase("play")) {
play();
} else if (action.equalsIgnoreCase("stop")) {
stop();
} else if (action.equalsIgnoreCase("pause")) {
pause();
} else if (action.equalsIgnoreCase("resume")) {
resume();
} else if (action.equalsIgnoreCase("next")) {
next();
} else if (action.equalsIgnoreCase("previous")) {
previous();
} else if (action.equalsIgnoreCase("volume")) {
if (request->hasParam("volume", true)) {
int volumeValue = request->getParam("volume", true)->value().toInt();
setVolume(static_cast<uint8_t>(std::max(0, std::min(static_cast<int>(MAX_VOLUME), volumeValue))));
} else {
ok = false;
}
} else if (action.equalsIgnoreCase("loop")) {
if (request->hasParam("loop", true)) {
String loopValue = request->getParam("loop", true)->value();
bool enabled = loopValue.equalsIgnoreCase("true") || loopValue == "1";
setLoop(enabled);
} else {
ok = false;
}
} else {
ok = false;
}
StaticJsonDocument<256> resp;
resp["success"] = ok;
resp["ready"] = m_playerReady;
resp["volume"] = static_cast<int>(m_volume);
resp["loop"] = m_loopEnabled;
if (!ok) {
resp["message"] = "Invalid action";
}
String json;
serializeJson(resp, json);
request->send(ok ? 200 : 400, "application/json", json);
}
void MultiMatrixService::publishEvent(const char* action) {
StaticJsonDocument<192> doc;
doc["action"] = action;
doc["volume"] = static_cast<int>(m_volume);
doc["loop"] = m_loopEnabled;
String payload;
serializeJson(doc, payload);
m_ctx.fire(EVENT_TOPIC, &payload);
}

View File

@@ -0,0 +1,53 @@
#pragma once
#include <Arduino.h>
#include <algorithm>
#include <memory>
#include <SoftwareSerial.h>
#include <DFRobotDFPlayerMini.h>
#include "spore/Service.h"
#include "spore/core/TaskManager.h"
#include "spore/core/NodeContext.h"
class ApiServer;
class AsyncWebServerRequest;
class MultiMatrixService : public Service {
public:
MultiMatrixService(NodeContext& ctx, TaskManager& taskManager, uint8_t rxPin, uint8_t txPin, uint8_t potentiometerPin);
void registerEndpoints(ApiServer& api) override;
void registerTasks(TaskManager& taskManager) override;
const char* getName() const override { return "MultiMatrixAudio"; }
bool isReady() const;
uint8_t getVolume() const;
bool isLoopEnabled() const;
void play();
void stop();
void pause();
void resume();
void next();
void previous();
void setVolume(uint8_t volume);
void setLoop(bool enabled);
private:
static constexpr uint16_t POTENTIOMETER_SAMPLE_INTERVAL_MS = 200;
static constexpr uint8_t MAX_VOLUME = 30;
static constexpr uint8_t POT_VOLUME_EPSILON = 2;
void pollPotentiometer();
void handleStatusRequest(AsyncWebServerRequest* request);
void handleControlRequest(AsyncWebServerRequest* request);
uint8_t calculateVolumeFromPotentiometer(uint16_t rawValue) const;
void applyVolume(uint8_t targetVolume);
void publishEvent(const char* action);
NodeContext& m_ctx;
TaskManager& m_taskManager;
std::unique_ptr<SoftwareSerial> m_serial;
DFRobotDFPlayerMini m_player;
uint8_t m_potentiometerPin;
uint8_t m_volume;
bool m_playerReady;
bool m_loopEnabled;
};

View File

@@ -0,0 +1,19 @@
# Multi-Matrix
This example combines different capabilities:
- Spore base stack
- use PixelStreamController in Matrix mode 16x16
- DFRobotDFPlayerMini audio playback
- analog potentiometer to controll audio volume
- API and web interface to control the audio player (start, stop, pause, next / previous track, volume)
## MCU
- Wemos D1 Mini
## Pin Configuration
```
#define MP3PLAYER_PIN_RX D3
#define MP3PLAYER_PIN_TX D4
#define MATRIX_PIN D2
#define POTI_PIN A0
```

View File

@@ -0,0 +1,508 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Multi-Matrix Audio Player</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
.player-container {
background: rgba(255, 255, 255, 0.98);
border-radius: 24px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
padding: 3rem 2.5rem;
max-width: 420px;
width: 100%;
backdrop-filter: blur(10px);
}
.player-header {
text-align: center;
margin-bottom: 2rem;
}
.player-title {
font-size: 1.75rem;
font-weight: 700;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 0.5rem;
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.4rem 1rem;
background: rgba(102, 126, 234, 0.1);
border-radius: 20px;
font-size: 0.875rem;
color: #667eea;
font-weight: 500;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #10b981;
animation: pulse 2s infinite;
}
.status-dot.inactive {
background: #ef4444;
animation: none;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.main-controls {
display: flex;
align-items: center;
justify-content: center;
gap: 1.5rem;
margin: 2.5rem 0;
}
.control-btn {
width: 52px;
height: 52px;
border-radius: 50%;
border: none;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
font-size: 1.2rem;
}
.control-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
}
.control-btn:active {
transform: translateY(0);
}
.play-btn {
width: 72px;
height: 72px;
font-size: 1.5rem;
box-shadow: 0 6px 25px rgba(102, 126, 234, 0.5);
}
.secondary-controls {
display: flex;
gap: 0.75rem;
margin-bottom: 2rem;
justify-content: center;
}
.secondary-btn {
padding: 0.6rem 1.25rem;
border: 2px solid #667eea;
background: white;
color: #667eea;
border-radius: 12px;
cursor: pointer;
font-size: 0.875rem;
font-weight: 600;
transition: all 0.3s ease;
}
.secondary-btn:hover {
background: #667eea;
color: white;
transform: translateY(-1px);
}
.secondary-btn:active {
transform: translateY(0);
}
.volume-section {
margin: 2rem 0;
}
.volume-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
font-size: 0.875rem;
color: #64748b;
font-weight: 600;
}
.volume-value {
color: #667eea;
font-weight: 700;
font-size: 1rem;
}
.volume-slider {
-webkit-appearance: none;
width: 100%;
height: 6px;
border-radius: 3px;
background: linear-gradient(to right, #667eea 0%, #764ba2 100%);
outline: none;
opacity: 0.7;
transition: opacity 0.2s;
}
.volume-slider:hover {
opacity: 1;
}
.volume-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: white;
cursor: pointer;
box-shadow: 0 2px 10px rgba(102, 126, 234, 0.5);
border: 2px solid #667eea;
}
.volume-slider::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: white;
cursor: pointer;
box-shadow: 0 2px 10px rgba(102, 126, 234, 0.5);
border: 2px solid #667eea;
}
.loop-toggle {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.25rem;
background: rgba(102, 126, 234, 0.05);
border-radius: 12px;
margin-top: 1.5rem;
}
.loop-label {
font-size: 0.95rem;
font-weight: 600;
color: #475569;
display: flex;
align-items: center;
gap: 0.5rem;
}
.toggle-switch {
position: relative;
width: 52px;
height: 28px;
background: #cbd5e1;
border-radius: 14px;
cursor: pointer;
transition: background 0.3s ease;
}
.toggle-switch.active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.toggle-slider {
position: absolute;
top: 3px;
left: 3px;
width: 22px;
height: 22px;
background: white;
border-radius: 50%;
transition: transform 0.3s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.toggle-switch.active .toggle-slider {
transform: translateX(24px);
}
.icon {
display: inline-block;
}
@media (max-width: 480px) {
.player-container {
padding: 2rem 1.5rem;
}
.player-title {
font-size: 1.5rem;
}
.control-btn {
width: 48px;
height: 48px;
}
.play-btn {
width: 64px;
height: 64px;
}
}
</style>
</head>
<body>
<div class="player-container">
<div class="player-header">
<h1 class="player-title">Multi-Matrix Audio</h1>
<div class="status-badge">
<span class="status-dot" id="statusDot"></span>
<span id="statusText">Connecting...</span>
</div>
</div>
<div class="main-controls">
<button class="control-btn" onclick="sendAction('previous')" title="Previous">
<span class="icon"></span>
</button>
<button class="control-btn play-btn" id="playBtn" onclick="togglePlayPause()" title="Play/Pause">
<span class="icon" id="playIcon"></span>
</button>
<button class="control-btn" onclick="sendAction('next')" title="Next">
<span class="icon"></span>
</button>
</div>
<div class="secondary-controls">
<button class="secondary-btn" onclick="sendAction('stop')">⏹ Stop</button>
<button class="secondary-btn" onclick="sendAction('resume')">▶ Resume</button>
</div>
<div class="volume-section">
<div class="volume-header">
<span>🔊 Volume</span>
<span class="volume-value" id="volumeDisplay">15</span>
</div>
<input type="range"
class="volume-slider"
id="volumeSlider"
min="0"
max="30"
value="15"
oninput="updateVolumeDisplay(this.value)"
onchange="setVolume(this.value)">
</div>
<div class="loop-toggle">
<div class="loop-label">
<span>🔁 Loop Mode</span>
</div>
<div class="toggle-switch" id="loopToggle" onclick="toggleLoop()">
<div class="toggle-slider"></div>
</div>
</div>
</div>
<script>
let isPlaying = false;
let loopEnabled = false;
async function fetchStatus() {
try {
const response = await fetch('/api/audio/status');
if (!response.ok) {
throw new Error('Status request failed');
}
const status = await response.json();
updateUIStatus(status);
} catch (error) {
document.getElementById('statusText').textContent = 'Error';
document.getElementById('statusDot').className = 'status-dot inactive';
console.error(error);
}
}
function updateUIStatus(status) {
const statusDot = document.getElementById('statusDot');
const statusText = document.getElementById('statusText');
if (status.ready) {
statusDot.className = 'status-dot';
statusText.textContent = 'Ready';
} else {
statusDot.className = 'status-dot inactive';
statusText.textContent = 'Not Ready';
}
if ('volume' in status) {
document.getElementById('volumeSlider').value = status.volume;
document.getElementById('volumeDisplay').textContent = status.volume;
}
if ('loop' in status) {
loopEnabled = status.loop;
const loopToggle = document.getElementById('loopToggle');
if (loopEnabled) {
loopToggle.classList.add('active');
} else {
loopToggle.classList.remove('active');
}
}
}
async function sendAction(action, params = {}) {
try {
const formData = new URLSearchParams({ action, ...params });
const response = await fetch('/api/audio', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: formData
});
const result = await response.json();
updateUIStatus(result);
if (action === 'play' || action === 'resume') {
isPlaying = true;
updatePlayButton();
} else if (action === 'pause' || action === 'stop') {
isPlaying = false;
updatePlayButton();
}
if (!result.success) {
alert(result.message || 'Action failed');
}
} catch (error) {
alert('Request failed: ' + error.message);
}
}
function togglePlayPause() {
if (isPlaying) {
sendAction('pause');
} else {
sendAction('play');
}
}
function updatePlayButton() {
const playIcon = document.getElementById('playIcon');
playIcon.textContent = isPlaying ? '⏸' : '▶';
}
function updateVolumeDisplay(value) {
document.getElementById('volumeDisplay').textContent = value;
}
function setVolume(value) {
sendAction('volume', { volume: value });
}
function toggleLoop() {
loopEnabled = !loopEnabled;
const loopToggle = document.getElementById('loopToggle');
if (loopEnabled) {
loopToggle.classList.add('active');
} else {
loopToggle.classList.remove('active');
}
sendAction('loop', { loop: loopEnabled });
}
fetchStatus();
setInterval(fetchStatus, 5000);
function setupWebSocket() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const ws = new WebSocket(protocol + '//' + window.location.host + '/ws');
ws.onmessage = (event) => {
try {
const payload = JSON.parse(event.data);
if (payload.event === 'audio/player' && payload.payload) {
const data = JSON.parse(payload.payload);
// Skip debug messages
if (data.action === 'pot_debug') {
return;
}
if ('volume' in data) {
document.getElementById('volumeSlider').value = data.volume;
document.getElementById('volumeDisplay').textContent = data.volume;
}
if ('loop' in data) {
loopEnabled = data.loop;
const loopToggle = document.getElementById('loopToggle');
if (loopEnabled) {
loopToggle.classList.add('active');
} else {
loopToggle.classList.remove('active');
}
}
if (data.action) {
const statusText = document.getElementById('statusText');
const statusDot = document.getElementById('statusDot');
if (data.action === 'play' || data.action === 'resume' || data.action === 'next' || data.action === 'previous') {
statusText.textContent = 'Playing';
statusDot.className = 'status-dot';
isPlaying = true;
updatePlayButton();
} else if (data.action === 'pause') {
statusText.textContent = 'Paused';
statusDot.className = 'status-dot';
isPlaying = false;
updatePlayButton();
} else if (data.action === 'stop') {
statusText.textContent = 'Stopped';
statusDot.className = 'status-dot inactive';
isPlaying = false;
updatePlayButton();
} else if (data.action === 'ready') {
statusText.textContent = 'Ready';
statusDot.className = 'status-dot';
}
}
}
} catch (err) {
console.error('WebSocket parse error', err);
}
};
ws.onclose = () => {
setTimeout(setupWebSocket, 3000);
};
}
setupWebSocket();
</script>
</body>
</html>

View File

@@ -0,0 +1,54 @@
#include <Arduino.h>
#include <memory>
#include "spore/Spore.h"
#include "spore/util/Logging.h"
#include "../pixelstream/PixelStreamController.h"
#include "MultiMatrixService.h"
namespace {
constexpr uint8_t MATRIX_PIN = D2;
constexpr uint16_t MATRIX_PIXEL_COUNT = 256;
constexpr uint8_t MATRIX_BRIGHTNESS = 40;
constexpr uint16_t MATRIX_WIDTH = 16;
constexpr bool MATRIX_SERPENTINE = false;
constexpr neoPixelType MATRIX_PIXEL_TYPE = NEO_GRB + NEO_KHZ800;
constexpr uint8_t MP3PLAYER_PIN_RX = D3;
constexpr uint8_t MP3PLAYER_PIN_TX = D4;
constexpr uint8_t POTENTIOMETER_PIN = A0;
}
Spore spore({
{"app", "multimatrix"},
{"role", "media"},
{"matrix", String(MATRIX_PIXEL_COUNT)}
});
std::unique_ptr<PixelStreamController> pixelController;
std::shared_ptr<MultiMatrixService> audioService;
void setup() {
spore.setup();
PixelStreamConfig config{
MATRIX_PIN,
MATRIX_PIXEL_COUNT,
MATRIX_BRIGHTNESS,
MATRIX_WIDTH,
MATRIX_SERPENTINE,
MATRIX_PIXEL_TYPE
};
pixelController = std::make_unique<PixelStreamController>(spore.getContext(), config);
pixelController->begin();
audioService = std::make_shared<MultiMatrixService>(spore.getContext(), spore.getTaskManager(), MP3PLAYER_PIN_RX, MP3PLAYER_PIN_TX, POTENTIOMETER_PIN);
spore.registerService(audioService);
spore.begin();
LOG_INFO("MultiMatrix", "Setup complete");
}
void loop() {
spore.loop();
}

View File

@@ -1,470 +1,361 @@
/** #include "NeoPattern.h"
* Original NeoPattern code by Bill Earl
* https://learn.adafruit.com/multi-tasking-the-arduino-part-3/overview
*
* TODO
* - cleanup the mess
* - fnc table for patterns to replace switch case
*
* Custom modifications by 0x1d:
* - default OnComplete callback that sets pattern to reverse
* - separate animation update from timer; Update now updates directly, UpdateScheduled uses timer
*/
#ifndef __NeoPattern_INCLUDED__
#define __NeoPattern_INCLUDED__
#include <Adafruit_NeoPixel.h> // Constructor - calls base-class constructor to initialize strip
NeoPattern::NeoPattern(uint16_t pixels, uint8_t pin, uint8_t type, void (*callback)(int))
using namespace std; : Adafruit_NeoPixel(pixels, pin, type)
// Pattern types supported:
enum pattern
{ {
NONE = 0, OnComplete = callback;
RAINBOW_CYCLE = 1, TotalSteps = numPixels();
THEATER_CHASE = 2, begin();
COLOR_WIPE = 3, }
SCANNER = 4,
FADE = 5, NeoPattern::NeoPattern(uint16_t pixels, uint8_t pin, uint8_t type)
FIRE = 6 : Adafruit_NeoPixel(pixels, pin, type)
};
// Patern directions supported:
enum direction
{ {
FORWARD, TotalSteps = numPixels();
REVERSE begin();
}; }
// NeoPattern Class - derived from the Adafruit_NeoPixel class NeoPattern::~NeoPattern() {
class NeoPattern : public Adafruit_NeoPixel // No frameBuffer to clean up
}
// Removed unused handleStream and drawFrameBuffer functions
void NeoPattern::onCompleteDefault(int pixels)
{ {
public: //Serial.println("onCompleteDefault");
// Member Variables: // FIXME no specific code
pattern ActivePattern = RAINBOW_CYCLE; // which pattern is running if (ActivePattern == THEATER_CHASE || ActivePattern == RAINBOW_CYCLE)
direction Direction = FORWARD; // direction to run the pattern
unsigned long Interval = 150; // milliseconds between updates
unsigned long lastUpdate = 0; // last update of position
uint32_t Color1 = 0;
uint32_t Color2 = 0; // What colors are in use
uint16_t TotalSteps = 32; // total number of steps in the pattern
uint16_t Index; // current step within the pattern
uint16_t completed = 0;
// FIXME return current NeoPatternState
void (*OnComplete)(int); // Callback on completion of pattern
uint8_t *frameBuffer;
int bufferSize = 0;
// Constructor - calls base-class constructor to initialize strip
NeoPattern(uint16_t pixels, uint8_t pin, uint8_t type, void (*callback)(int))
: Adafruit_NeoPixel(pixels, pin, type)
{ {
frameBuffer = (uint8_t *)malloc(768); return;
OnComplete = callback;
TotalSteps = numPixels();
begin();
} }
Reverse();
//Serial.println("pattern completed");
}
NeoPattern(uint16_t pixels, uint8_t pin, uint8_t type) // Increment the Index and reset at the end
: Adafruit_NeoPixel(pixels, pin, type) void NeoPattern::Increment()
{
completed = 0;
if (Direction == FORWARD)
{ {
frameBuffer = (uint8_t *)malloc(768); Index++;
TotalSteps = numPixels(); if (Index >= TotalSteps)
begin();
}
void handleStream(uint8_t *data, size_t len)
{
//const uint16_t *data16 = (uint16_t *)data;
bufferSize = len;
memcpy(frameBuffer, data, len);
}
void drawFrameBuffer(int w, uint8_t *frame, int length)
{
for (int i = 0; i < length; i++)
{ {
uint8_t r = frame[i];
uint8_t g = frame[i + 1];
uint8_t b = frame[i + 2];
setPixelColor(i, r, g, b);
}
}
void onCompleteDefault(int pixels)
{
//Serial.println("onCompleteDefault");
// FIXME no specific code
if (ActivePattern == THEATER_CHASE)
{
return;
}
Reverse();
//Serial.println("pattern completed");
}
// Update the pattern
void Update()
{
switch (ActivePattern)
{
case RAINBOW_CYCLE:
RainbowCycleUpdate();
break;
case THEATER_CHASE:
TheaterChaseUpdate();
break;
case COLOR_WIPE:
ColorWipeUpdate();
break;
case SCANNER:
ScannerUpdate();
break;
case FADE:
FadeUpdate();
break;
case FIRE:
Fire(50, 120);
break;
default:
if (bufferSize > 0)
{
drawFrameBuffer(TotalSteps, frameBuffer, bufferSize);
}
break;
}
}
void UpdateScheduled()
{
if ((millis() - lastUpdate) > Interval) // time to update
{
lastUpdate = millis();
Update();
}
}
// Increment the Index and reset at the end
void Increment()
{
completed = 0;
if (Direction == FORWARD)
{
Index++;
if (Index >= TotalSteps)
{
Index = 0;
completed = 1;
if (OnComplete != NULL)
{
OnComplete(numPixels()); // call the comlpetion callback
}
else
{
onCompleteDefault(numPixels());
}
}
}
else // Direction == REVERSE
{
--Index;
if (Index <= 0)
{
Index = TotalSteps - 1;
completed = 1;
if (OnComplete != NULL)
{
OnComplete(numPixels()); // call the comlpetion callback
}
else
{
onCompleteDefault(numPixels());
}
}
}
}
// Reverse pattern direction
void Reverse()
{
if (Direction == FORWARD)
{
Direction = REVERSE;
Index = TotalSteps - 1;
}
else
{
Direction = FORWARD;
Index = 0; Index = 0;
} completed = 1;
} if (OnComplete != NULL)
// Initialize for a RainbowCycle
void RainbowCycle(uint8_t interval, direction dir = FORWARD)
{
ActivePattern = RAINBOW_CYCLE;
Interval = interval;
TotalSteps = 255;
Index = 0;
Direction = dir;
}
// Update the Rainbow Cycle Pattern
void RainbowCycleUpdate()
{
for (int i = 0; i < numPixels(); i++)
{
setPixelColor(i, Wheel(((i * 256 / numPixels()) + Index) & 255));
}
show();
Increment();
}
// Initialize for a Theater Chase
void TheaterChase(uint32_t color1, uint32_t color2, uint16_t interval, direction dir = FORWARD)
{
ActivePattern = THEATER_CHASE;
Interval = interval;
TotalSteps = numPixels();
Color1 = color1;
Color2 = color2;
Index = 0;
Direction = dir;
}
// Update the Theater Chase Pattern
void TheaterChaseUpdate()
{
for (int i = 0; i < numPixels(); i++)
{
if ((i + Index) % 3 == 0)
{ {
setPixelColor(i, Color1); OnComplete(numPixels()); // call the comlpetion callback
} }
else else
{ {
setPixelColor(i, Color2); onCompleteDefault(numPixels());
} }
} }
show();
Increment();
} }
else // Direction == REVERSE
// Initialize for a ColorWipe
void ColorWipe(uint32_t color, uint8_t interval, direction dir = FORWARD)
{ {
ActivePattern = COLOR_WIPE; --Index;
Interval = interval; if (Index <= 0)
TotalSteps = numPixels();
Color1 = color;
Index = 0;
Direction = dir;
}
// Update the Color Wipe Pattern
void ColorWipeUpdate()
{
setPixelColor(Index, Color1);
show();
Increment();
}
// Initialize for a SCANNNER
void Scanner(uint32_t color1, uint8_t interval)
{
ActivePattern = SCANNER;
Interval = interval;
TotalSteps = (numPixels() - 1) * 2;
Color1 = color1;
Index = 0;
}
// Update the Scanner Pattern
void ScannerUpdate()
{
for (int i = 0; i < numPixels(); i++)
{ {
if (i == Index) // Scan Pixel to the right Index = TotalSteps - 1;
completed = 1;
if (OnComplete != NULL)
{ {
setPixelColor(i, Color1); OnComplete(numPixels()); // call the comlpetion callback
}
else if (i == TotalSteps - Index) // Scan Pixel to the left
{
setPixelColor(i, Color1);
}
else // Fading tail
{
setPixelColor(i, DimColor(getPixelColor(i)));
}
}
show();
Increment();
}
// Initialize for a Fade
void Fade(uint32_t color1, uint32_t color2, uint16_t steps, uint8_t interval, direction dir = FORWARD)
{
ActivePattern = FADE;
Interval = interval;
TotalSteps = steps;
Color1 = color1;
Color2 = color2;
Index = 0;
Direction = dir;
}
// Update the Fade Pattern
void FadeUpdate()
{
// Calculate linear interpolation between Color1 and Color2
// Optimise order of operations to minimize truncation error
uint8_t red = ((Red(Color1) * (TotalSteps - Index)) + (Red(Color2) * Index)) / TotalSteps;
uint8_t green = ((Green(Color1) * (TotalSteps - Index)) + (Green(Color2) * Index)) / TotalSteps;
uint8_t blue = ((Blue(Color1) * (TotalSteps - Index)) + (Blue(Color2) * Index)) / TotalSteps;
ColorSet(Color(red, green, blue));
show();
Increment();
}
// Calculate 50% dimmed version of a color (used by ScannerUpdate)
uint32_t DimColor(uint32_t color)
{
// Shift R, G and B components one bit to the right
uint32_t dimColor = Color(Red(color) >> 1, Green(color) >> 1, Blue(color) >> 1);
return dimColor;
}
// Set all pixels to a color (synchronously)
void ColorSet(uint32_t color)
{
for (int i = 0; i < numPixels(); i++)
{
setPixelColor(i, color);
}
show();
}
// Returns the Red component of a 32-bit color
uint8_t Red(uint32_t color)
{
return (color >> 16) & 0xFF;
}
// Returns the Green component of a 32-bit color
uint8_t Green(uint32_t color)
{
return (color >> 8) & 0xFF;
}
// Returns the Blue component of a 32-bit color
uint8_t Blue(uint32_t color)
{
return color & 0xFF;
}
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(uint8_t WheelPos)
{
//if(WheelPos == 0) return Color(0,0,0);
WheelPos = 255 - WheelPos;
if (WheelPos < 85)
{
return Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
else if (WheelPos < 170)
{
WheelPos -= 85;
return Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
else
{
WheelPos -= 170;
return Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
}
/**
* Effects from https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/
*/
void Fire(int Cooling, int Sparking)
{
uint8_t heat[numPixels()];
int cooldown;
// Step 1. Cool down every cell a little
for (int i = 0; i < numPixels(); i++)
{
cooldown = random(0, ((Cooling * 10) / numPixels()) + 2);
if (cooldown > heat[i])
{
heat[i] = 0;
} }
else else
{ {
heat[i] = heat[i] - cooldown; onCompleteDefault(numPixels());
} }
} }
// Step 2. Heat from each cell drifts 'up' and diffuses a little
for (int k = numPixels() - 1; k >= 2; k--)
{
heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2]) / 3;
}
// Step 3. Randomly ignite new 'sparks' near the bottom
if (random(255) < Sparking)
{
int y = random(7);
heat[y] = heat[y] + random(160, 255);
//heat[y] = random(160,255);
}
// Step 4. Convert heat to LED colors
for (int j = 0; j < numPixels(); j++)
{
setPixelHeatColor(j, heat[j]);
}
showStrip();
} }
}
void setPixelHeatColor(int Pixel, uint8_t temperature) // Reverse pattern direction
void NeoPattern::Reverse()
{
if (Direction == FORWARD)
{ {
// Scale 'heat' down from 0-255 to 0-191 Direction = REVERSE;
uint8_t t192 = round((temperature / 255.0) * 191); Index = TotalSteps - 1;
}
else
{
Direction = FORWARD;
Index = 0;
}
}
// calculate ramp up from // Initialize for a RainbowCycle
uint8_t heatramp = t192 & 0x3F; // 0..63 void NeoPattern::RainbowCycle(uint8_t interval, direction dir)
heatramp <<= 2; // scale up to 0..252 {
ActivePattern = RAINBOW_CYCLE;
Interval = interval;
TotalSteps = 255;
Index = 0;
Direction = dir;
}
// figure out which third of the spectrum we're in: // Update the Rainbow Cycle Pattern
if (t192 > 0x80) void NeoPattern::RainbowCycleUpdate()
{ // hottest {
setPixel(Pixel, 255, 255, heatramp); for (int i = 0; i < numPixels(); i++)
} {
else if (t192 > 0x40) setPixelColor(i, Wheel(((i * 256 / numPixels()) + Index) & 255));
{ // middle }
setPixel(Pixel, 255, heatramp, 0); show();
// RainbowCycle is continuous, just increment Index
Index++;
if (Index >= 255)
{
Index = 0;
}
}
// Initialize for a Theater Chase
void NeoPattern::TheaterChase(uint32_t color1, uint32_t color2, uint16_t interval, direction dir)
{
ActivePattern = THEATER_CHASE;
Interval = interval;
TotalSteps = numPixels();
Color1 = color1;
Color2 = color2;
Index = 0;
Direction = dir;
}
// Update the Theater Chase Pattern
void NeoPattern::TheaterChaseUpdate()
{
for (int i = 0; i < numPixels(); i++)
{
if ((i + Index) % 3 == 0)
{
setPixelColor(i, Color1);
} }
else else
{ // coolest {
setPixel(Pixel, heatramp, 0, 0); setPixelColor(i, Color2);
}
}
show();
Increment();
}
// Initialize for a ColorWipe
void NeoPattern::ColorWipe(uint32_t color, uint8_t interval, direction dir)
{
ActivePattern = COLOR_WIPE;
Interval = interval;
TotalSteps = numPixels();
Color1 = color;
Index = 0;
Direction = dir;
}
// Update the Color Wipe Pattern
void NeoPattern::ColorWipeUpdate()
{
setPixelColor(Index, Color1);
show();
Increment();
}
// Initialize for a SCANNNER
void NeoPattern::Scanner(uint32_t color1, uint8_t interval)
{
ActivePattern = SCANNER;
Interval = interval;
TotalSteps = (numPixels() - 1) * 2;
Color1 = color1;
Index = 0;
}
// Update the Scanner Pattern
void NeoPattern::ScannerUpdate()
{
for (int i = 0; i < numPixels(); i++)
{
if (i == Index) // Scan Pixel to the right
{
setPixelColor(i, Color1);
}
else if (i == TotalSteps - Index) // Scan Pixel to the left
{
setPixelColor(i, Color1);
}
else // Fading tail
{
setPixelColor(i, DimColor(getPixelColor(i)));
}
}
show();
Increment();
}
// Initialize for a Fade
void NeoPattern::Fade(uint32_t color1, uint32_t color2, uint16_t steps, uint8_t interval, direction dir)
{
ActivePattern = FADE;
Interval = interval;
TotalSteps = steps;
Color1 = color1;
Color2 = color2;
Index = 0;
Direction = dir;
}
// Update the Fade Pattern
void NeoPattern::FadeUpdate()
{
// Calculate linear interpolation between Color1 and Color2
// Optimise order of operations to minimize truncation error
uint8_t red = ((Red(Color1) * (TotalSteps - Index)) + (Red(Color2) * Index)) / TotalSteps;
uint8_t green = ((Green(Color1) * (TotalSteps - Index)) + (Green(Color2) * Index)) / TotalSteps;
uint8_t blue = ((Blue(Color1) * (TotalSteps - Index)) + (Blue(Color2) * Index)) / TotalSteps;
ColorSet(Color(red, green, blue));
show();
Increment();
}
// Calculate 50% dimmed version of a color (used by ScannerUpdate)
uint32_t NeoPattern::DimColor(uint32_t color)
{
// Shift R, G and B components one bit to the right
uint32_t dimColor = Color(Red(color) >> 1, Green(color) >> 1, Blue(color) >> 1);
return dimColor;
}
// Set all pixels to a color (synchronously)
void NeoPattern::ColorSet(uint32_t color)
{
for (int i = 0; i < numPixels(); i++)
{
setPixelColor(i, color);
}
show();
}
// Returns the Red component of a 32-bit color
uint8_t NeoPattern::Red(uint32_t color)
{
return (color >> 16) & 0xFF;
}
// Returns the Green component of a 32-bit color
uint8_t NeoPattern::Green(uint32_t color)
{
return (color >> 8) & 0xFF;
}
// Returns the Blue component of a 32-bit color
uint8_t NeoPattern::Blue(uint32_t color)
{
return color & 0xFF;
}
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t NeoPattern::Wheel(uint8_t WheelPos)
{
//if(WheelPos == 0) return Color(0,0,0);
WheelPos = 255 - WheelPos;
if (WheelPos < 85)
{
return Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
else if (WheelPos < 170)
{
WheelPos -= 85;
return Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
else
{
WheelPos -= 170;
return Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
}
/**
* Effects from https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/
*/
void NeoPattern::Fire(int Cooling, int Sparking)
{
uint8_t heat[numPixels()];
int cooldown;
// Step 1. Cool down every cell a little
for (int i = 0; i < numPixels(); i++)
{
cooldown = random(0, ((Cooling * 10) / numPixels()) + 2);
if (cooldown > heat[i])
{
heat[i] = 0;
}
else
{
heat[i] = heat[i] - cooldown;
} }
} }
void setPixel(int Pixel, uint8_t red, uint8_t green, uint8_t blue) // Step 2. Heat from each cell drifts 'up' and diffuses a little
for (int k = numPixels() - 1; k >= 2; k--)
{ {
setPixelColor(Pixel, Color(red, green, blue)); heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2]) / 3;
} }
void showStrip()
{
show();
}
};
#endif // Step 3. Randomly ignite new 'sparks' near the bottom
if (random(255) < Sparking)
{
int y = random(7);
heat[y] = heat[y] + random(160, 255);
//heat[y] = random(160,255);
}
// Step 4. Convert heat to LED colors
for (int j = 0; j < numPixels(); j++)
{
setPixelHeatColor(j, heat[j]);
}
showStrip();
}
void NeoPattern::setPixelHeatColor(int Pixel, uint8_t temperature)
{
// Scale 'heat' down from 0-255 to 0-191
uint8_t t192 = round((temperature / 255.0) * 191);
// calculate ramp up from
uint8_t heatramp = t192 & 0x3F; // 0..63
heatramp <<= 2; // scale up to 0..252
// figure out which third of the spectrum we're in:
if (t192 > 0x80)
{ // hottest
setPixel(Pixel, 255, 255, heatramp);
}
else if (t192 > 0x40)
{ // middle
setPixel(Pixel, 255, heatramp, 0);
}
else
{ // coolest
setPixel(Pixel, heatramp, 0, 0);
}
}
void NeoPattern::setPixel(int Pixel, uint8_t red, uint8_t green, uint8_t blue)
{
setPixelColor(Pixel, Color(red, green, blue));
}
void NeoPattern::showStrip()
{
show();
}

View File

@@ -0,0 +1,104 @@
/**
* Original NeoPattern code by Bill Earl
* https://learn.adafruit.com/multi-tasking-the-arduino-part-3/overview
*
* Custom modifications by 0x1d:
* - default OnComplete callback that sets pattern to reverse
* - separate animation update from timer; Update now updates directly, UpdateScheduled uses timer
*/
#ifndef __NeoPattern_INCLUDED__
#define __NeoPattern_INCLUDED__
#include <Adafruit_NeoPixel.h>
using namespace std;
// Pattern types supported:
enum pattern
{
NONE = 0,
RAINBOW_CYCLE = 1,
THEATER_CHASE = 2,
COLOR_WIPE = 3,
SCANNER = 4,
FADE = 5,
FIRE = 6
};
// Pattern directions supported:
enum direction
{
FORWARD,
REVERSE
};
// NeoPattern Class - derived from the Adafruit_NeoPixel class
class NeoPattern : public Adafruit_NeoPixel
{
public:
// Member Variables:
pattern ActivePattern = RAINBOW_CYCLE; // which pattern is running
direction Direction = FORWARD; // direction to run the pattern
unsigned long Interval = 150; // milliseconds between updates
unsigned long lastUpdate = 0; // last update of position
uint32_t Color1 = 0;
uint32_t Color2 = 0; // What colors are in use
uint16_t TotalSteps = 32; // total number of steps in the pattern
uint16_t Index; // current step within the pattern
uint16_t completed = 0;
// Callback on completion of pattern
void (*OnComplete)(int);
// Constructor - calls base-class constructor to initialize strip
NeoPattern(uint16_t pixels, uint8_t pin, uint8_t type, void (*callback)(int));
NeoPattern(uint16_t pixels, uint8_t pin, uint8_t type);
~NeoPattern();
// Stream handling functions removed
// Pattern completion
void onCompleteDefault(int pixels);
// Pattern control
void Increment();
void Reverse();
// Rainbow Cycle
void RainbowCycle(uint8_t interval, direction dir = FORWARD);
void RainbowCycleUpdate();
// Theater Chase
void TheaterChase(uint32_t color1, uint32_t color2, uint16_t interval, direction dir = FORWARD);
void TheaterChaseUpdate();
// Color Wipe
void ColorWipe(uint32_t color, uint8_t interval, direction dir = FORWARD);
void ColorWipeUpdate();
// Scanner
void Scanner(uint32_t color1, uint8_t interval);
void ScannerUpdate();
// Fade
void Fade(uint32_t color1, uint32_t color2, uint16_t steps, uint8_t interval, direction dir = FORWARD);
void FadeUpdate();
// Fire effect
void Fire(int Cooling, int Sparking);
void setPixelHeatColor(int Pixel, uint8_t temperature);
void setPixel(int Pixel, uint8_t red, uint8_t green, uint8_t blue);
void showStrip();
// Utility methods
uint32_t DimColor(uint32_t color);
void ColorSet(uint32_t color);
uint8_t Red(uint32_t color);
uint8_t Green(uint32_t color);
uint8_t Blue(uint32_t color);
uint32_t Wheel(uint8_t WheelPos);
};
#endif

View File

@@ -1,56 +1,100 @@
#include "NeoPatternService.h" #include "NeoPatternService.h"
#include "spore/core/ApiServer.h" #include "spore/core/ApiServer.h"
#include "spore/util/Logging.h"
#include "spore/internal/Globals.h"
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
NeoPatternService::NeoPatternService(TaskManager& taskMgr, uint16_t numPixels, uint8_t pin, uint8_t type) NeoPatternService::NeoPatternService(NodeContext& ctx, TaskManager& taskMgr, const NeoPixelConfig& config)
: taskManager(taskMgr), : taskManager(taskMgr),
pixels(numPixels, pin, type), ctx(ctx),
updateIntervalMs(100), config(config),
brightness(48) { activePattern(NeoPatternType::RAINBOW_CYCLE),
pixels.setBrightness(brightness); direction(NeoDirection::FORWARD),
pixels.show(); updateIntervalMs(config.updateInterval),
lastUpdateMs(0),
initialized(false) {
// Initialize NeoPattern
neoPattern = new NeoPattern(config.length, config.pin, NEO_GRB + NEO_KHZ800);
neoPattern->setBrightness(config.brightness);
// Initialize state
currentState.pattern = static_cast<uint>(activePattern);
currentState.color = 0xFF0000; // Red
currentState.color2 = 0x0000FF; // Blue
currentState.totalSteps = 16;
currentState.brightness = config.brightness;
// Set initial pattern
neoPattern->Color1 = currentState.color;
neoPattern->Color2 = currentState.color2;
neoPattern->TotalSteps = currentState.totalSteps;
neoPattern->ActivePattern = static_cast<::pattern>(activePattern);
neoPattern->Direction = static_cast<::direction>(direction);
registerPatterns();
registerEventHandlers();
initialized = true;
LOG_INFO("NeoPattern", "Service initialized");
}
registerTasks(); NeoPatternService::~NeoPatternService() {
setPatternByName("rainbow_cycle"); if (neoPattern) {
delete neoPattern;
}
} }
void NeoPatternService::registerEndpoints(ApiServer& api) { void NeoPatternService::registerEndpoints(ApiServer& api) {
api.addEndpoint("/api/neopattern/status", HTTP_GET, // Status endpoint
api.registerEndpoint("/api/neopattern/status", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleStatusRequest(request); }, [this](AsyncWebServerRequest* request) { handleStatusRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
api.addEndpoint("/api/neopattern/patterns", HTTP_GET, // Patterns list endpoint
api.registerEndpoint("/api/neopattern/patterns", HTTP_GET,
[this](AsyncWebServerRequest* request) { handlePatternsRequest(request); }, [this](AsyncWebServerRequest* request) { handlePatternsRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
api.addEndpoint("/api/neopattern", HTTP_POST, // Control endpoint
api.registerEndpoint("/api/neopattern", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleControlRequest(request); }, [this](AsyncWebServerRequest* request) { handleControlRequest(request); },
std::vector<ParamSpec>{ std::vector<ParamSpec>{
ParamSpec{String("pattern"), false, String("body"), String("string"), patternNamesVector()}, ParamSpec{String("pattern"), false, String("body"), String("string"), patternNamesVector()},
ParamSpec{String("interval_ms"), false, String("body"), String("number"), {}, String("100")},
ParamSpec{String("brightness"), false, String("body"), String("number"), {}, String("50")},
ParamSpec{String("color"), false, String("body"), String("color"), {}}, ParamSpec{String("color"), false, String("body"), String("color"), {}},
ParamSpec{String("color2"), false, String("body"), String("color"), {}}, ParamSpec{String("color2"), false, String("body"), String("color"), {}},
ParamSpec{String("r"), false, String("body"), String("number"), {}}, ParamSpec{String("brightness"), false, String("body"), String("numberRange"), {}, String("80")},
ParamSpec{String("g"), false, String("body"), String("number"), {}}, ParamSpec{String("total_steps"), false, String("body"), String("numberRange"), {}, String("16")},
ParamSpec{String("b"), false, String("body"), String("number"), {}}, ParamSpec{String("direction"), false, String("body"), String("string"), {String("forward"), String("reverse")}},
ParamSpec{String("r2"), false, String("body"), String("number"), {}}, ParamSpec{String("interval"), false, String("body"), String("number"), {}, String("100")},
ParamSpec{String("g2"), false, String("body"), String("number"), {}}, ParamSpec{String("broadcast"), false, String("body"), String("boolean"), {}}
ParamSpec{String("b2"), false, String("body"), String("number"), {}},
ParamSpec{String("total_steps"), false, String("body"), String("number"), {}},
ParamSpec{String("direction"), false, String("body"), String("string"), {String("forward"), String("reverse")}}
}); });
// State endpoint for complex state updates
api.registerEndpoint("/api/neopattern/state", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleStateRequest(request); },
std::vector<ParamSpec>{});
} }
void NeoPatternService::handleStatusRequest(AsyncWebServerRequest* request) { void NeoPatternService::handleStatusRequest(AsyncWebServerRequest* request) {
JsonDocument doc; JsonDocument doc;
doc["pin"] = pixels.getPin(); doc["pin"] = config.pin;
doc["count"] = pixels.numPixels(); doc["length"] = config.length;
doc["interval_ms"] = updateIntervalMs; doc["brightness"] = currentState.brightness;
doc["brightness"] = brightness;
doc["pattern"] = currentPatternName(); doc["pattern"] = currentPatternName();
doc["total_steps"] = pixels.TotalSteps; doc["color"] = String(currentState.color, HEX);
doc["color1"] = pixels.Color1; doc["color2"] = String(currentState.color2, HEX);
doc["color2"] = pixels.Color2; doc["total_steps"] = currentState.totalSteps;
doc["direction"] = (direction == NeoDirection::FORWARD) ? "forward" : "reverse";
doc["interval"] = updateIntervalMs;
doc["active"] = initialized;
// Add pattern metadata
String currentPattern = currentPatternName();
doc["pattern_description"] = getPatternDescription(currentPattern);
doc["pattern_requires_color2"] = patternRequiresColor2(currentPattern);
doc["pattern_supports_direction"] = patternSupportsDirection(currentPattern);
String json; String json;
serializeJson(doc, json); serializeJson(doc, json);
@@ -60,7 +104,17 @@ void NeoPatternService::handleStatusRequest(AsyncWebServerRequest* request) {
void NeoPatternService::handlePatternsRequest(AsyncWebServerRequest* request) { void NeoPatternService::handlePatternsRequest(AsyncWebServerRequest* request) {
JsonDocument doc; JsonDocument doc;
JsonArray arr = doc.to<JsonArray>(); JsonArray arr = doc.to<JsonArray>();
for (auto& kv : patternSetters) arr.add(kv.first);
// Get all patterns from registry and include metadata
auto patterns = patternRegistry.getAllPatterns();
for (const auto& pattern : patterns) {
JsonObject patternObj = arr.add<JsonObject>();
patternObj["name"] = pattern.name;
patternObj["type"] = pattern.type;
patternObj["description"] = pattern.description;
patternObj["requires_color2"] = pattern.requiresColor2;
patternObj["supports_direction"] = pattern.supportsDirection;
}
String json; String json;
serializeJson(doc, json); serializeJson(doc, json);
@@ -68,120 +122,477 @@ void NeoPatternService::handlePatternsRequest(AsyncWebServerRequest* request) {
} }
void NeoPatternService::handleControlRequest(AsyncWebServerRequest* request) { void NeoPatternService::handleControlRequest(AsyncWebServerRequest* request) {
if (request->hasParam("pattern", true)) { bool updated = false;
String name = request->getParam("pattern", true)->value(); bool broadcast = false;
setPatternByName(name);
if (request->hasParam("broadcast", true)) {
String b = request->getParam("broadcast", true)->value();
broadcast = b.equalsIgnoreCase("true") || b == "1";
} }
if (request->hasParam("interval_ms", true)) { // Build JSON payload from provided params (single source of truth)
unsigned long v = request->getParam("interval_ms", true)->value().toInt(); JsonDocument payload;
if (v < 1) v = 1; bool any = false;
updateIntervalMs = v; if (request->hasParam("pattern", true)) { payload["pattern"] = request->getParam("pattern", true)->value(); any = true; }
taskManager.setTaskInterval("neopattern_update", updateIntervalMs); if (request->hasParam("color", true)) { payload["color"] = request->getParam("color", true)->value(); any = true; }
if (request->hasParam("color2", true)) { payload["color2"] = request->getParam("color2", true)->value(); any = true; }
if (request->hasParam("brightness", true)) { payload["brightness"] = request->getParam("brightness", true)->value(); any = true; }
if (request->hasParam("total_steps", true)) { payload["total_steps"] = request->getParam("total_steps", true)->value(); any = true; }
if (request->hasParam("direction", true)) { payload["direction"] = request->getParam("direction", true)->value(); any = true; }
if (request->hasParam("interval", true)) { payload["interval"] = request->getParam("interval", true)->value(); any = true; }
String payloadStr;
serializeJson(payload, payloadStr);
// Always apply locally via event so we have a single codepath for updates
if (any) {
std::string ev = "api/neopattern";
String localData = payloadStr;
LOG_INFO("NeoPattern", String("Applying local api/neopattern via event payloadLen=") + String(payloadStr.length()));
ctx.fire(ev, &localData);
updated = true;
} }
if (request->hasParam("brightness", true)) { // Broadcast to peers if requested (delegate to core broadcast handler)
int b = request->getParam("brightness", true)->value().toInt(); if (broadcast && any) {
if (b < 0) b = 0; if (b > 255) b = 255; JsonDocument eventDoc;
setBrightness((uint8_t)b); eventDoc["event"] = "api/neopattern";
} eventDoc["data"] = payloadStr; // data is JSON string
// Accept packed color ints or r,g,b triplets String eventJson;
if (request->hasParam("color", true)) { serializeJson(eventDoc, eventJson);
pixels.Color1 = (uint32_t)strtoul(request->getParam("color", true)->value().c_str(), nullptr, 0);
} LOG_INFO("NeoPattern", String("Submitting cluster/broadcast for api/neopattern payloadLen=") + String(payloadStr.length()));
if (request->hasParam("color2", true)) { std::string ev = "cluster/broadcast";
pixels.Color2 = (uint32_t)strtoul(request->getParam("color2", true)->value().c_str(), nullptr, 0); String eventStr = eventJson;
} ctx.fire(ev, &eventStr);
if (request->hasParam("r", true) || request->hasParam("g", true) || request->hasParam("b", true)) {
int r = request->hasParam("r", true) ? request->getParam("r", true)->value().toInt() : 0;
int g = request->hasParam("g", true) ? request->getParam("g", true)->value().toInt() : 0;
int b = request->hasParam("b", true) ? request->getParam("b", true)->value().toInt() : 0;
pixels.Color1 = pixels.Color(r, g, b);
}
if (request->hasParam("r2", true) || request->hasParam("g2", true) || request->hasParam("b2", true)) {
int r = request->hasParam("r2", true) ? request->getParam("r2", true)->value().toInt() : 0;
int g = request->hasParam("g2", true) ? request->getParam("g2", true)->value().toInt() : 0;
int b = request->hasParam("b2", true) ? request->getParam("b2", true)->value().toInt() : 0;
pixels.Color2 = pixels.Color(r, g, b);
}
if (request->hasParam("total_steps", true)) {
pixels.TotalSteps = request->getParam("total_steps", true)->value().toInt();
}
if (request->hasParam("direction", true)) {
String dir = request->getParam("direction", true)->value();
if (dir.equalsIgnoreCase("forward")) pixels.Direction = FORWARD;
else if (dir.equalsIgnoreCase("reverse")) pixels.Direction = REVERSE;
} }
// Return current state
JsonDocument resp; JsonDocument resp;
resp["ok"] = true; resp["ok"] = true;
resp["pattern"] = currentPatternName(); resp["pattern"] = currentPatternName();
resp["interval_ms"] = updateIntervalMs; resp["color"] = String(currentState.color, HEX);
resp["brightness"] = brightness; resp["color2"] = String(currentState.color2, HEX);
resp["brightness"] = currentState.brightness;
resp["total_steps"] = currentState.totalSteps;
resp["direction"] = (direction == NeoDirection::FORWARD) ? "forward" : "reverse";
resp["interval"] = updateIntervalMs;
resp["updated"] = updated;
String json; String json;
serializeJson(resp, json); serializeJson(resp, json);
request->send(200, "application/json", json); request->send(200, "application/json", json);
} }
void NeoPatternService::registerEventHandlers() {
ctx.on("api/neopattern", [this](void* dataPtr) {
String* jsonStr = static_cast<String*>(dataPtr);
if (!jsonStr) {
LOG_WARN("NeoPattern", "Received api/neopattern with null dataPtr");
return;
}
LOG_INFO("NeoPattern", String("Received api/neopattern event dataLen=") + String(jsonStr->length()));
JsonDocument doc;
DeserializationError err = deserializeJson(doc, *jsonStr);
if (err) {
LOG_WARN("NeoPattern", String("Failed to parse cluster/event data: ") + err.c_str());
return;
}
JsonObject obj = doc.as<JsonObject>();
bool applied = applyControlParams(obj);
if (applied) {
LOG_INFO("NeoPattern", "Applied control from cluster/event");
}
});
void NeoPatternService::setBrightness(uint8_t b) { // Solid color event: sets all pixels to the same color
brightness = b; ctx.on("api/neopattern/color", [this](void* dataPtr) {
pixels.setBrightness(brightness); String* jsonStr = static_cast<String*>(dataPtr);
pixels.show(); if (!jsonStr) {
LOG_WARN("NeoPattern", "Received api/neopattern/color with null dataPtr");
return;
}
JsonDocument doc;
DeserializationError err = deserializeJson(doc, *jsonStr);
if (err) {
LOG_WARN("NeoPattern", String("Failed to parse color event data: ") + err.c_str());
return;
}
JsonObject obj = doc.as<JsonObject>();
// color can be string or number
String colorStr;
if (obj["color"].is<const char*>() || obj["color"].is<String>()) {
colorStr = obj["color"].as<String>();
} else if (obj["color"].is<long>() || obj["color"].is<int>()) {
colorStr = String(obj["color"].as<long>());
} else {
LOG_WARN("NeoPattern", "api/neopattern/color missing 'color'");
return;
}
// Optional brightness
if (obj["brightness"].is<int>() || obj["brightness"].is<long>()) {
int b = obj["brightness"].as<int>();
if (b < 0) b = 0; if (b > 255) b = 255;
setBrightness(static_cast<uint8_t>(b));
}
uint32_t color = parseColor(colorStr);
setPattern(NeoPatternType::NONE);
setColor(color);
LOG_INFO("NeoPattern", String("Set solid color ") + colorStr);
});
} }
void NeoPatternService::registerTasks() { bool NeoPatternService::applyControlParams(const JsonObject& obj) {
bool updated = false;
if (obj["pattern"].is<const char*>() || obj["pattern"].is<String>()) {
String name = obj["pattern"].as<String>();
if (isValidPattern(name)) {
setPatternByName(name);
updated = true;
}
}
if (obj["color"].is<const char*>() || obj["color"].is<String>() || obj["color"].is<long>() || obj["color"].is<int>()) {
String colorStr;
if (obj["color"].is<long>() || obj["color"].is<int>()) {
colorStr = String(obj["color"].as<long>());
} else {
colorStr = obj["color"].as<String>();
}
uint32_t color = parseColor(colorStr);
setColor(color);
updated = true;
}
if (obj["color2"].is<const char*>() || obj["color2"].is<String>() || obj["color2"].is<long>() || obj["color2"].is<int>()) {
String colorStr;
if (obj["color2"].is<long>() || obj["color2"].is<int>()) {
colorStr = String(obj["color2"].as<long>());
} else {
colorStr = obj["color2"].as<String>();
}
uint32_t color = parseColor(colorStr);
setColor2(color);
updated = true;
}
if (obj["brightness"].is<int>() || obj["brightness"].is<long>() || obj["brightness"].is<const char*>() || obj["brightness"].is<String>()) {
int b = 0;
if (obj["brightness"].is<int>() || obj["brightness"].is<long>()) {
b = obj["brightness"].as<int>();
} else {
b = String(obj["brightness"].as<String>()).toInt();
}
if (b < 0) {
b = 0;
}
if (b > 255) {
b = 255;
}
setBrightness(static_cast<uint8_t>(b));
updated = true;
}
if (obj["total_steps"].is<int>() || obj["total_steps"].is<long>() || obj["total_steps"].is<const char*>() || obj["total_steps"].is<String>()) {
int steps = 0;
if (obj["total_steps"].is<int>() || obj["total_steps"].is<long>()) {
steps = obj["total_steps"].as<int>();
} else {
steps = String(obj["total_steps"].as<String>()).toInt();
}
if (steps > 0) { setTotalSteps(static_cast<uint16_t>(steps)); updated = true; }
}
if (obj["direction"].is<const char*>() || obj["direction"].is<String>()) {
String dirStr = obj["direction"].as<String>();
NeoDirection dir = (dirStr.equalsIgnoreCase("reverse")) ? NeoDirection::REVERSE : NeoDirection::FORWARD;
setDirection(dir);
updated = true;
}
if (obj["interval"].is<int>() || obj["interval"].is<long>() || obj["interval"].is<const char*>() || obj["interval"].is<String>()) {
unsigned long interval = 0;
if (obj["interval"].is<int>() || obj["interval"].is<long>()) {
interval = obj["interval"].as<unsigned long>();
} else {
interval = String(obj["interval"].as<String>()).toInt();
}
if (interval > 0) { setUpdateInterval(interval); updated = true; }
}
return updated;
}
void NeoPatternService::handleStateRequest(AsyncWebServerRequest* request) {
if (request->contentType() != "application/json") {
request->send(400, "application/json", "{\"error\":\"Content-Type must be application/json\"}");
return;
}
// Note: AsyncWebServerRequest doesn't have getBody() method
// For now, we'll skip JSON body parsing and use form parameters
request->send(400, "application/json", "{\"error\":\"JSON body parsing not implemented\"}");
return;
// JSON body parsing not implemented for AsyncWebServerRequest
// This would need to be implemented differently
request->send(501, "application/json", "{\"error\":\"Not implemented\"}");
}
void NeoPatternService::setPattern(NeoPatternType pattern) {
activePattern = pattern;
currentState.pattern = static_cast<uint>(pattern);
neoPattern->ActivePattern = static_cast<::pattern>(pattern);
resetStateForPattern(pattern);
// Set up pattern-specific parameters
switch (pattern) {
case NeoPatternType::RAINBOW_CYCLE:
neoPattern->RainbowCycle(updateIntervalMs, static_cast<::direction>(direction));
break;
case NeoPatternType::THEATER_CHASE:
neoPattern->TheaterChase(currentState.color, currentState.color2, updateIntervalMs, static_cast<::direction>(direction));
break;
case NeoPatternType::COLOR_WIPE:
neoPattern->ColorWipe(currentState.color, updateIntervalMs, static_cast<::direction>(direction));
break;
case NeoPatternType::SCANNER:
neoPattern->Scanner(currentState.color, updateIntervalMs);
break;
case NeoPatternType::FADE:
neoPattern->Fade(currentState.color, currentState.color2, currentState.totalSteps, updateIntervalMs, static_cast<::direction>(direction));
break;
case NeoPatternType::FIRE:
// Fire pattern doesn't need setup
break;
case NeoPatternType::NONE:
// None pattern doesn't need setup
break;
}
}
void NeoPatternService::setPatternByName(const String& name) {
NeoPatternType pattern = nameToPattern(name);
setPattern(pattern);
}
void NeoPatternService::setColor(uint32_t color) {
currentState.color = color;
neoPattern->Color1 = color;
if (activePattern == NeoPatternType::NONE) {
neoPattern->ColorSet(color);
}
}
void NeoPatternService::setColor2(uint32_t color) {
currentState.color2 = color;
neoPattern->Color2 = color;
}
void NeoPatternService::setBrightness(uint8_t brightness) {
currentState.brightness = brightness;
neoPattern->setBrightness(brightness);
neoPattern->show();
}
void NeoPatternService::setTotalSteps(uint16_t steps) {
currentState.totalSteps = steps;
neoPattern->TotalSteps = steps;
}
void NeoPatternService::setDirection(NeoDirection dir) {
direction = dir;
neoPattern->Direction = static_cast<::direction>(dir);
}
void NeoPatternService::setUpdateInterval(unsigned long interval) {
updateIntervalMs = interval;
taskManager.setTaskInterval("neopattern_update", interval);
}
void NeoPatternService::setState(const NeoPatternState& state) {
currentState = state;
// Apply state to NeoPattern
neoPattern->Index = 0;
neoPattern->Color1 = state.color;
neoPattern->Color2 = state.color2;
neoPattern->TotalSteps = state.totalSteps;
neoPattern->ActivePattern = static_cast<::pattern>(state.pattern);
neoPattern->Direction = FORWARD;
setBrightness(state.brightness);
setPattern(static_cast<NeoPatternType>(state.pattern));
}
NeoPatternState NeoPatternService::getState() const {
return currentState;
}
void NeoPatternService::registerTasks(TaskManager& taskManager) {
taskManager.registerTask("neopattern_update", updateIntervalMs, [this]() { update(); }); taskManager.registerTask("neopattern_update", updateIntervalMs, [this]() { update(); });
taskManager.registerTask("neopattern_status_print", 10000, [this]() { taskManager.registerTask("neopattern_status_print", 10000, [this]() {
Serial.printf("[NeoPattern] pattern=%s interval=%lu ms brightness=%u\n", LOG_INFO("NeoPattern", "Status update");
currentPatternName().c_str(), updateIntervalMs, brightness);
}); });
} }
void NeoPatternService::registerPatterns() { void NeoPatternService::registerPatterns() {
patternSetters["off"] = [this]() { pixels.ActivePattern = NONE; }; // Register all patterns with their metadata and callbacks
patternSetters["rainbow_cycle"] = [this]() { pixels.RainbowCycle(updateIntervalMs); }; patternRegistry.registerPattern(
patternSetters["theater_chase"] = [this]() { pixels.TheaterChase(pixels.Color1 ? pixels.Color1 : pixels.Color(127,127,127), pixels.Color2, updateIntervalMs); }; "none",
patternSetters["color_wipe"] = [this]() { pixels.ColorWipe(pixels.Color1 ? pixels.Color1 : pixels.Color(255,0,0), updateIntervalMs); }; static_cast<uint8_t>(NeoPatternType::NONE),
patternSetters["scanner"] = [this]() { pixels.Scanner(pixels.Color1 ? pixels.Color1 : pixels.Color(0,0,255), updateIntervalMs); }; "No pattern - solid color",
patternSetters["fade"] = [this]() { pixels.Fade(pixels.Color1, pixels.Color2, pixels.TotalSteps ? pixels.TotalSteps : 32, updateIntervalMs); }; [this]() { /* No initialization needed */ },
patternSetters["fire"] = [this]() { pixels.ActivePattern = FIRE; pixels.Interval = updateIntervalMs; }; [this]() { updateNone(); },
false, // doesn't require color2
false // doesn't support direction
);
patternRegistry.registerPattern(
"rainbow_cycle",
static_cast<uint8_t>(NeoPatternType::RAINBOW_CYCLE),
"Rainbow cycle pattern",
nullptr, // No initializer needed, state is set up in setPattern
[this]() { updateRainbowCycle(); },
false, // doesn't require color2
true // supports direction
);
patternRegistry.registerPattern(
"theater_chase",
static_cast<uint8_t>(NeoPatternType::THEATER_CHASE),
"Theater chase pattern",
nullptr, // No initializer needed, state is set up in setPattern
[this]() { updateTheaterChase(); },
true, // requires color2
true // supports direction
);
patternRegistry.registerPattern(
"color_wipe",
static_cast<uint8_t>(NeoPatternType::COLOR_WIPE),
"Color wipe pattern",
nullptr, // No initializer needed, state is set up in setPattern
[this]() { updateColorWipe(); },
false, // doesn't require color2
true // supports direction
);
patternRegistry.registerPattern(
"scanner",
static_cast<uint8_t>(NeoPatternType::SCANNER),
"Scanner pattern",
nullptr, // No initializer needed, state is set up in setPattern
[this]() { updateScanner(); },
false, // doesn't require color2
false // doesn't support direction
);
patternRegistry.registerPattern(
"fade",
static_cast<uint8_t>(NeoPatternType::FADE),
"Fade pattern",
nullptr, // No initializer needed, state is set up in setPattern
[this]() { updateFade(); },
true, // requires color2
true // supports direction
);
patternRegistry.registerPattern(
"fire",
static_cast<uint8_t>(NeoPatternType::FIRE),
"Fire effect pattern",
nullptr, // No initializer needed, state is set up in setPattern
[this]() { updateFire(); },
false, // doesn't require color2
false // doesn't support direction
);
} }
std::vector<String> NeoPatternService::patternNamesVector() { std::vector<String> NeoPatternService::patternNamesVector() const {
if (patternSetters.empty()) registerPatterns(); return patternRegistry.getAllPatternNames();
std::vector<String> v;
v.reserve(patternSetters.size());
for (const auto& kv : patternSetters) v.push_back(kv.first);
return v;
} }
String NeoPatternService::currentPatternName() { String NeoPatternService::currentPatternName() const {
switch (pixels.ActivePattern) { return patternRegistry.getPatternName(static_cast<uint8_t>(activePattern));
case NONE: return String("off"); }
case RAINBOW_CYCLE: return String("rainbow_cycle");
case THEATER_CHASE: return String("theater_chase"); NeoPatternService::NeoPatternType NeoPatternService::nameToPattern(const String& name) const {
case COLOR_WIPE: return String("color_wipe"); uint8_t type = patternRegistry.getPatternType(name);
case SCANNER: return String("scanner"); return static_cast<NeoPatternType>(type);
case FADE: return String("fade"); }
case FIRE: return String("fire");
void NeoPatternService::resetStateForPattern(NeoPatternType pattern) {
neoPattern->Index = 0;
neoPattern->Direction = static_cast<::direction>(direction);
neoPattern->completed = 0;
// Don't reset lastUpdateMs to 0, keep the current timing
}
uint32_t NeoPatternService::parseColor(const String& colorStr) const {
if (colorStr.startsWith("#")) {
return strtoul(colorStr.substring(1).c_str(), nullptr, 16);
} else if (colorStr.startsWith("0x") || colorStr.startsWith("0X")) {
return strtoul(colorStr.c_str(), nullptr, 16);
} else {
return strtoul(colorStr.c_str(), nullptr, 10);
} }
return String("off");
} }
void NeoPatternService::setPatternByName(const String& name) { bool NeoPatternService::isValidPattern(const String& name) const {
if (patternSetters.empty()) registerPatterns(); return patternRegistry.isValidPattern(name);
auto it = patternSetters.find(name); }
if (it != patternSetters.end()) {
pixels.Index = 0; bool NeoPatternService::isValidPattern(NeoPatternType type) const {
pixels.Direction = FORWARD; return patternRegistry.isValidPattern(static_cast<uint8_t>(type));
it->second(); }
}
bool NeoPatternService::patternRequiresColor2(const String& name) const {
const PatternInfo* info = patternRegistry.getPattern(name);
return info ? info->requiresColor2 : false;
}
bool NeoPatternService::patternSupportsDirection(const String& name) const {
const PatternInfo* info = patternRegistry.getPattern(name);
return info ? info->supportsDirection : false;
}
String NeoPatternService::getPatternDescription(const String& name) const {
const PatternInfo* info = patternRegistry.getPattern(name);
return info ? info->description : "";
} }
void NeoPatternService::update() { void NeoPatternService::update() {
pixels.Update(); if (!initialized) return;
unsigned long now = millis();
if (now - lastUpdateMs < updateIntervalMs) return;
lastUpdateMs = now;
// Use pattern registry to execute the current pattern
patternRegistry.executePattern(static_cast<uint8_t>(activePattern));
}
void NeoPatternService::updateRainbowCycle() {
neoPattern->RainbowCycleUpdate();
}
void NeoPatternService::updateTheaterChase() {
neoPattern->TheaterChaseUpdate();
}
void NeoPatternService::updateColorWipe() {
neoPattern->ColorWipeUpdate();
}
void NeoPatternService::updateScanner() {
neoPattern->ScannerUpdate();
}
void NeoPatternService::updateFade() {
neoPattern->FadeUpdate();
}
void NeoPatternService::updateFire() {
neoPattern->Fire(50, 120);
}
void NeoPatternService::updateNone() {
// For NONE pattern, just show the current color
neoPattern->ColorSet(neoPattern->Color1);
} }

View File

@@ -1,34 +1,99 @@
#pragma once #pragma once
#include "spore/Service.h" #include "spore/Service.h"
#include "spore/core/TaskManager.h" #include "spore/core/TaskManager.h"
#include "NeoPattern.cpp" #include "spore/core/NodeContext.h"
#include "NeoPattern.h"
#include "NeoPatternState.h"
#include "NeoPixelConfig.h"
#include "PatternRegistry.h"
#include <map> #include <map>
#include <vector> #include <functional>
class NeoPatternService : public Service { class NeoPatternService : public Service {
public: public:
NeoPatternService(TaskManager& taskMgr, uint16_t numPixels, uint8_t pin, uint8_t type); enum class NeoPatternType {
NONE = 0,
RAINBOW_CYCLE = 1,
THEATER_CHASE = 2,
COLOR_WIPE = 3,
SCANNER = 4,
FADE = 5,
FIRE = 6
};
enum class NeoDirection {
FORWARD,
REVERSE
};
NeoPatternService(NodeContext& ctx, TaskManager& taskMgr, const NeoPixelConfig& config);
~NeoPatternService();
void registerEndpoints(ApiServer& api) override; void registerEndpoints(ApiServer& api) override;
void registerTasks(TaskManager& taskManager) override;
const char* getName() const override { return "NeoPattern"; } const char* getName() const override { return "NeoPattern"; }
void setBrightness(uint8_t b); // Pattern control methods
void setPattern(NeoPatternType pattern);
void setPatternByName(const String& name); void setPatternByName(const String& name);
void setColor(uint32_t color);
void setColor2(uint32_t color2);
void setBrightness(uint8_t brightness);
void setTotalSteps(uint16_t steps);
void setDirection(NeoDirection direction);
void setUpdateInterval(unsigned long interval);
// State management
void setState(const NeoPatternState& state);
NeoPatternState getState() const;
private: private:
void registerTasks();
void registerPatterns(); void registerPatterns();
std::vector<String> patternNamesVector();
String currentPatternName();
void update(); void update();
void registerEventHandlers();
bool applyControlParams(const JsonObject& obj);
// Pattern updaters
void updateRainbowCycle();
void updateTheaterChase();
void updateColorWipe();
void updateScanner();
void updateFade();
void updateFire();
void updateNone();
// Handlers // API handlers
void handleStatusRequest(AsyncWebServerRequest* request); void handleStatusRequest(AsyncWebServerRequest* request);
void handlePatternsRequest(AsyncWebServerRequest* request); void handlePatternsRequest(AsyncWebServerRequest* request);
void handleControlRequest(AsyncWebServerRequest* request); void handleControlRequest(AsyncWebServerRequest* request);
void handleStateRequest(AsyncWebServerRequest* request);
// Utility methods
std::vector<String> patternNamesVector() const;
String currentPatternName() const;
NeoPatternType nameToPattern(const String& name) const;
void resetStateForPattern(NeoPatternType pattern);
uint32_t parseColor(const String& colorStr) const;
// Pattern validation methods
bool isValidPattern(const String& name) const;
bool isValidPattern(NeoPatternType type) const;
bool patternRequiresColor2(const String& name) const;
bool patternSupportsDirection(const String& name) const;
String getPatternDescription(const String& name) const;
TaskManager& taskManager; TaskManager& taskManager;
NeoPattern pixels; NodeContext& ctx;
NeoPattern* neoPattern;
NeoPixelConfig config;
NeoPatternState currentState;
// Pattern registry for centralized pattern management
PatternRegistry patternRegistry;
NeoPatternType activePattern;
NeoDirection direction;
unsigned long updateIntervalMs; unsigned long updateIntervalMs;
uint8_t brightness; unsigned long lastUpdateMs;
std::map<String, std::function<void()>> patternSetters; bool initialized;
}; };

View File

@@ -0,0 +1,59 @@
#ifndef __NEOPATTERN_STATE__
#define __NEOPATTERN_STATE__
#include <ArduinoJson.h>
struct NeoPatternState {
// Default values
static constexpr uint DEFAULT_PATTERN = 0;
static constexpr uint DEFAULT_COLOR = 0xFF0000; // Red
static constexpr uint DEFAULT_COLOR2 = 0x0000FF; // Blue
static constexpr uint DEFAULT_TOTAL_STEPS = 16;
static constexpr uint DEFAULT_BRIGHTNESS = 64;
uint pattern = DEFAULT_PATTERN;
uint color = DEFAULT_COLOR;
uint color2 = DEFAULT_COLOR2;
uint totalSteps = DEFAULT_TOTAL_STEPS;
uint brightness = DEFAULT_BRIGHTNESS;
NeoPatternState() = default;
NeoPatternState(uint p, uint c, uint c2, uint steps, uint b)
: pattern(p), color(c), color2(c2), totalSteps(steps), brightness(b) {}
void mapJsonObject(JsonObject& root) const {
root["pattern"] = pattern;
root["color"] = color;
root["color2"] = color2;
root["totalSteps"] = totalSteps;
root["brightness"] = brightness;
}
void fromJsonObject(JsonObject& json) {
pattern = json["pattern"] | pattern;
color = json["color"] | color;
color2 = json["color2"] | color2;
totalSteps = json["totalSteps"] | totalSteps;
brightness = json["brightness"] | brightness;
}
// Helper methods for JSON string conversion
String toJsonString() const {
JsonDocument doc;
JsonObject root = doc.to<JsonObject>();
mapJsonObject(root);
String result;
serializeJson(doc, result);
return result;
}
void fromJsonString(const String& jsonStr) {
JsonDocument doc;
deserializeJson(doc, jsonStr);
JsonObject root = doc.as<JsonObject>();
fromJsonObject(root);
}
};
#endif

View File

@@ -0,0 +1,45 @@
#ifndef __NEOPIXEL_CONFIG__
#define __NEOPIXEL_CONFIG__
#include <ArduinoJson.h>
struct NeoPixelConfig
{
// Configuration constants
static constexpr int DEFAULT_PIN = 2;
static constexpr int DEFAULT_LENGTH = 8;
static constexpr int DEFAULT_BRIGHTNESS = 100;
static constexpr int DEFAULT_UPDATE_INTERVAL = 100;
static constexpr int DEFAULT_COLOR = 0xFF0000; // Red
int pin = DEFAULT_PIN;
int length = DEFAULT_LENGTH;
int brightness = DEFAULT_BRIGHTNESS;
int updateInterval = DEFAULT_UPDATE_INTERVAL;
int defaultColor = DEFAULT_COLOR;
NeoPixelConfig() = default;
NeoPixelConfig(int p, int l, int b, int interval, int color = DEFAULT_COLOR)
: pin(p), length(l), brightness(b), updateInterval(interval), defaultColor(color) {}
void mapJsonObject(JsonObject &root) const
{
root["pin"] = pin;
root["length"] = length;
root["brightness"] = brightness;
root["updateInterval"] = updateInterval;
root["defaultColor"] = defaultColor;
}
void fromJsonObject(JsonObject &json)
{
pin = json["pin"] | pin;
length = json["length"] | length;
brightness = json["brightness"] | brightness;
updateInterval = json["updateInterval"] | updateInterval;
defaultColor = json["defaultColor"] | defaultColor;
}
};
#endif

View File

@@ -0,0 +1,101 @@
#include "PatternRegistry.h"
PatternRegistry::PatternRegistry() {
// Constructor - patterns will be registered by the service
}
void PatternRegistry::registerPattern(const PatternInfo& pattern) {
patternMap_[pattern.name] = pattern;
typeToNameMap_[pattern.type] = pattern.name;
}
void PatternRegistry::registerPattern(const String& name, uint8_t type, const String& description,
std::function<void()> initializer, std::function<void()> updater,
bool requiresColor2, bool supportsDirection) {
PatternInfo info(name, type, description, initializer, updater, requiresColor2, supportsDirection);
registerPattern(info);
}
const PatternInfo* PatternRegistry::getPattern(const String& name) const {
auto it = patternMap_.find(name);
return (it != patternMap_.end()) ? &it->second : nullptr;
}
const PatternInfo* PatternRegistry::getPattern(uint8_t type) const {
auto typeIt = typeToNameMap_.find(type);
if (typeIt == typeToNameMap_.end()) {
return nullptr;
}
return getPattern(typeIt->second);
}
String PatternRegistry::getPatternName(uint8_t type) const {
auto it = typeToNameMap_.find(type);
return (it != typeToNameMap_.end()) ? it->second : "";
}
uint8_t PatternRegistry::getPatternType(const String& name) const {
const PatternInfo* info = getPattern(name);
return info ? info->type : 0;
}
std::vector<String> PatternRegistry::getAllPatternNames() const {
std::vector<String> names;
names.reserve(patternMap_.size());
for (const auto& pair : patternMap_) {
names.push_back(pair.first);
}
return names;
}
std::vector<PatternInfo> PatternRegistry::getAllPatterns() const {
std::vector<PatternInfo> patterns;
patterns.reserve(patternMap_.size());
for (const auto& pair : patternMap_) {
patterns.push_back(pair.second);
}
return patterns;
}
bool PatternRegistry::isValidPattern(const String& name) const {
return patternMap_.find(name) != patternMap_.end();
}
bool PatternRegistry::isValidPattern(uint8_t type) const {
return typeToNameMap_.find(type) != typeToNameMap_.end();
}
void PatternRegistry::executePattern(const String& name) const {
const PatternInfo* info = getPattern(name);
if (info && info->updater) {
info->updater();
}
}
void PatternRegistry::executePattern(uint8_t type) const {
const PatternInfo* info = getPattern(type);
if (info && info->updater) {
info->updater();
}
}
void PatternRegistry::initializePattern(const String& name) const {
const PatternInfo* info = getPattern(name);
if (info && info->initializer) {
info->initializer();
}
}
void PatternRegistry::initializePattern(uint8_t type) const {
const PatternInfo* info = getPattern(type);
if (info && info->initializer) {
info->initializer();
}
}
void PatternRegistry::updateTypeMap() {
typeToNameMap_.clear();
for (const auto& pair : patternMap_) {
typeToNameMap_[pair.second.type] = pair.first;
}
}

View File

@@ -0,0 +1,73 @@
#pragma once
#include <map>
#include <functional>
#include <vector>
#include <Arduino.h>
/**
* PatternInfo structure containing all information needed for a pattern
*/
struct PatternInfo {
String name;
uint8_t type;
String description;
std::function<void()> updater;
std::function<void()> initializer;
bool requiresColor2;
bool supportsDirection;
// Default constructor for std::map compatibility
PatternInfo() : type(0), requiresColor2(false), supportsDirection(false) {}
// Parameterized constructor
PatternInfo(const String& n, uint8_t t, const String& desc,
std::function<void()> initFunc, std::function<void()> updateFunc = nullptr,
bool needsColor2 = false, bool supportsDir = true)
: name(n), type(t), description(desc), updater(updateFunc),
initializer(initFunc), requiresColor2(needsColor2), supportsDirection(supportsDir) {}
};
/**
* PatternRegistry class for centralized pattern management
*/
class PatternRegistry {
public:
using PatternMap = std::map<String, PatternInfo>;
using PatternTypeMap = std::map<uint8_t, String>;
PatternRegistry();
~PatternRegistry() = default;
// Pattern registration
void registerPattern(const PatternInfo& pattern);
void registerPattern(const String& name, uint8_t type, const String& description,
std::function<void()> initializer, std::function<void()> updater = nullptr,
bool requiresColor2 = false, bool supportsDirection = true);
// Pattern lookup
const PatternInfo* getPattern(const String& name) const;
const PatternInfo* getPattern(uint8_t type) const;
String getPatternName(uint8_t type) const;
uint8_t getPatternType(const String& name) const;
// Pattern enumeration
std::vector<String> getAllPatternNames() const;
std::vector<PatternInfo> getAllPatterns() const;
const PatternMap& getPatternMap() const { return patternMap_; }
// Pattern validation
bool isValidPattern(const String& name) const;
bool isValidPattern(uint8_t type) const;
// Pattern execution
void executePattern(const String& name) const;
void executePattern(uint8_t type) const;
void initializePattern(const String& name) const;
void initializePattern(uint8_t type) const;
private:
PatternMap patternMap_;
PatternTypeMap typeToNameMap_;
void updateTypeMap();
};

View File

@@ -0,0 +1,131 @@
# NeoPattern Service for Spore Framework
This example demonstrates how to integrate a NeoPixel pattern service with the Spore framework. It provides a comprehensive LED strip control system with multiple animation patterns and REST API endpoints.
## Features
- **Multiple Animation Patterns**: Rainbow cycle, theater chase, color wipe, scanner, fade, and fire effects
- **REST API Control**: Full HTTP API for pattern control and configuration
- **Real-time Updates**: Smooth pattern transitions and real-time parameter changes
- **State Management**: Persistent state with JSON serialization
- **Spore Integration**: Built on the Spore framework for networking and task management
## Hardware Requirements
- ESP32 or compatible microcontroller
- NeoPixel LED strip (WS2812B or compatible)
- Appropriate power supply for your LED strip
## Configuration
Edit `NeoPixelConfig.h` to configure your setup:
```cpp
static constexpr int DEFAULT_PIN = 2;
static constexpr int DEFAULT_LENGTH = 8;
static constexpr int DEFAULT_BRIGHTNESS = 100;
static constexpr int DEFAULT_UPDATE_INTERVAL = 100;
static constexpr int DEFAULT_COLOR = 0xFF0000; // Red
```
## API Endpoints
### Status Information
- `GET /api/neopattern/status` - Get current status and configuration
- `GET /api/neopattern/patterns` - List available patterns
### Pattern Control
- `POST /api/neopattern` - Control pattern parameters
- `pattern`: Pattern name (none, rainbow_cycle, theater_chase, color_wipe, scanner, fade, fire)
- `color`: Primary color (hex string like "#FF0000" or "0xFF0000")
- `color2`: Secondary color for two-color patterns
- `brightness`: Brightness level (0-255)
- `total_steps`: Number of steps for patterns
- `direction`: Pattern direction (forward, reverse)
- `interval`: Update interval in milliseconds
### State Management
- `POST /api/neopattern/state` - Set complete state via JSON
## Example API Usage
### Set a rainbow cycle pattern
```bash
curl -X POST http://esp32-ip/api/neopattern \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "pattern=rainbow_cycle&interval=50"
```
### Set custom colors for theater chase
```bash
curl -X POST http://esp32-ip/api/neopattern \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "pattern=theater_chase&color=0xFF0000&color2=0x0000FF&brightness=150"
```
### Set complete state via JSON
```bash
curl -X POST http://esp32-ip/api/neopattern/state \
-H "Content-Type: application/json" \
-d '{
"pattern": 3,
"color": 16711680,
"color2": 255,
"totalSteps": 32,
"brightness": 128
}'
```
## Available Patterns
1. **none** - Static color display
2. **rainbow_cycle** - Smooth rainbow cycling
3. **theater_chase** - Moving dots with two colors
4. **color_wipe** - Progressive color filling
5. **scanner** - Scanning light effect
6. **fade** - Smooth color transition
7. **fire** - Fire simulation effect
## Building and Flashing
1. Install PlatformIO
2. Configure your `platformio.ini` to include the Spore framework
3. Build and upload:
```bash
pio run -t upload
```
## Integration with Spore Framework
This service integrates with the Spore framework by:
- Extending the `Service` base class
- Using `TaskManager` for pattern updates
- Registering REST API endpoints with `ApiServer`
- Following Spore's logging and configuration patterns
The service automatically registers itself with the Spore framework and provides all functionality through the standard Spore API infrastructure.
## Customization
To add new patterns:
1. Add the pattern enum to `NeoPatternService.h`
2. Implement the pattern logic in `NeoPattern.cpp`
3. Add the pattern updater to `registerPatterns()` in `NeoPatternService.cpp`
4. Update the pattern mapping in `nameToPatternMap`
## Troubleshooting
- **LEDs not lighting**: Check wiring and power supply
- **Patterns not updating**: Verify the update interval and task registration
- **API not responding**: Check network configuration and Spore framework setup
- **Memory issues**: Reduce LED count or pattern complexity
## Dependencies
- Spore Framework
- Adafruit NeoPixel library
- ArduinoJson
- ESP32 Arduino Core

View File

@@ -1,31 +0,0 @@
#ifndef __DEVICE_CONFIG__
#define __DEVICE_CONFIG__
// Scheduler config
#define _TASK_SLEEP_ON_IDLE_RUN
#define _TASK_STD_FUNCTION
#define _TASK_PRIORITY
// Chip config
#define SPROCKET_TYPE "SPROCKET"
#define SERIAL_BAUD_RATE 115200
#define STARTUP_DELAY 1000
// network config
#define SPROCKET_MODE 1
#define WIFI_CHANNEL 11
#define AP_SSID "sprocket"
#define AP_PASSWORD "th3r31sn0sp00n"
#define STATION_SSID "MyAP"
#define STATION_PASSWORD "th3r31sn0sp00n"
#define HOSTNAME "sprocket"
#define CONNECT_TIMEOUT 10000
// NeoPixel conig
#define LED_STRIP_PIN D2
#define LED_STRIP_LENGTH 8
#define LED_STRIP_BRIGHTNESS 48
#define LED_STRIP_UPDATE_INTERVAL 200
#define LED_STRIP_DEFAULT_COLOR 100
#endif

View File

@@ -1,25 +1,32 @@
#include <Arduino.h> #include <Arduino.h>
#include "spore/Spore.h" #include "spore/Spore.h"
#include "spore/util/Logging.h"
#include "NeoPatternService.h" #include "NeoPatternService.h"
#include "NeoPixelConfig.h"
#ifndef LED_STRIP_PIN // Configuration constants
#define LED_STRIP_PIN 2 #ifndef NEOPIXEL_PIN
#define NEOPIXEL_PIN 2
#endif #endif
#ifndef LED_STRIP_LENGTH #ifndef NEOPIXEL_LENGTH
#define LED_STRIP_LENGTH 8 #define NEOPIXEL_LENGTH 16
#endif #endif
#ifndef LED_STRIP_TYPE #ifndef NEOPIXEL_BRIGHTNESS
#define LED_STRIP_TYPE (NEO_GRB + NEO_KHZ800) #define NEOPIXEL_BRIGHTNESS 100
#endif
#ifndef NEOPIXEL_UPDATE_INTERVAL
#define NEOPIXEL_UPDATE_INTERVAL 100
#endif #endif
// Create Spore instance with custom labels // Create Spore instance with custom labels
Spore spore({ Spore spore({
{"app", "neopattern"}, {"app", "neopattern"},
{"device", "light"}, {"role", "led"},
{"pixels", String(LED_STRIP_LENGTH)}, {"pixels", String(NEOPIXEL_LENGTH)},
{"pin", String(LED_STRIP_PIN)} {"pin", String(NEOPIXEL_PIN)}
}); });
// Create custom service // Create custom service
@@ -29,17 +36,25 @@ void setup() {
// Initialize the Spore framework // Initialize the Spore framework
spore.setup(); spore.setup();
// Create configuration
NeoPixelConfig config(
NEOPIXEL_PIN,
NEOPIXEL_LENGTH,
NEOPIXEL_BRIGHTNESS,
NEOPIXEL_UPDATE_INTERVAL
);
// Create and add custom service // Create and add custom service
neoPatternService = new NeoPatternService(spore.getTaskManager(), LED_STRIP_LENGTH, LED_STRIP_PIN, LED_STRIP_TYPE); neoPatternService = new NeoPatternService(spore.getContext(), spore.getTaskManager(), config);
spore.addService(neoPatternService); spore.registerService(neoPatternService);
// Start the API server and complete initialization // Start the API server and complete initialization
spore.begin(); spore.begin();
Serial.println("[Main] NeoPattern service registered and ready!"); LOG_INFO("Main", "NeoPattern service registered and ready!");
} }
void loop() { void loop() {
// Run the Spore framework loop // Run the Spore framework loop
spore.loop(); spore.loop();
} }

View File

@@ -1,291 +0,0 @@
#include "NeoPixelService.h"
#include "spore/core/ApiServer.h"
// Wheel helper: map 0-255 to RGB rainbow
static uint32_t colorWheel(Adafruit_NeoPixel& strip, uint8_t pos) {
pos = 255 - pos;
if (pos < 85) {
return strip.Color(255 - pos * 3, 0, pos * 3);
}
if (pos < 170) {
pos -= 85;
return strip.Color(0, pos * 3, 255 - pos * 3);
}
pos -= 170;
return strip.Color(pos * 3, 255 - pos * 3, 0);
}
NeoPixelService::NeoPixelService(TaskManager& taskMgr, uint16_t numPixels, uint8_t pin, neoPixelType type)
: taskManager(taskMgr),
strip(numPixels, pin, type),
currentPattern(Pattern::Off),
updateIntervalMs(20),
lastUpdateMs(0),
wipeIndex(0),
wipeColor(strip.Color(255, 0, 0)),
rainbowJ(0),
cycleJ(0),
chaseJ(0),
chaseQ(0),
chasePhaseOn(true),
chaseColor(strip.Color(127, 127, 127)),
brightness(50) {
strip.begin();
strip.setBrightness(brightness);
strip.show();
registerPatterns();
registerTasks();
}
void NeoPixelService::registerEndpoints(ApiServer& api) {
api.addEndpoint("/api/neopixel/status", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleStatusRequest(request); },
std::vector<ParamSpec>{});
api.addEndpoint("/api/neopixel/patterns", HTTP_GET,
[this](AsyncWebServerRequest* request) { handlePatternsRequest(request); },
std::vector<ParamSpec>{});
api.addEndpoint("/api/neopixel", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleControlRequest(request); },
std::vector<ParamSpec>{
ParamSpec{String("pattern"), false, String("body"), String("string"), patternNamesVector()},
ParamSpec{String("interval_ms"), false, String("body"), String("number"), {}, String("100")},
ParamSpec{String("brightness"), false, String("body"), String("number"), {}, String("50")},
ParamSpec{String("color"), false, String("body"), String("color"), {}},
ParamSpec{String("color2"), false, String("body"), String("color"), {}},
ParamSpec{String("r"), false, String("body"), String("number"), {}},
ParamSpec{String("g"), false, String("body"), String("number"), {}},
ParamSpec{String("b"), false, String("body"), String("number"), {}},
ParamSpec{String("r2"), false, String("body"), String("number"), {}},
ParamSpec{String("g2"), false, String("body"), String("number"), {}},
ParamSpec{String("b2"), false, String("body"), String("number"), {}},
ParamSpec{String("total_steps"), false, String("body"), String("number"), {}},
ParamSpec{String("direction"), false, String("body"), String("string"), {String("forward"), String("reverse")}}
});
}
void NeoPixelService::handleStatusRequest(AsyncWebServerRequest* request) {
JsonDocument doc;
doc["pin"] = strip.getPin();
doc["count"] = strip.numPixels();
doc["interval_ms"] = updateIntervalMs;
doc["brightness"] = brightness;
doc["pattern"] = currentPatternName();
String json;
serializeJson(doc, json);
request->send(200, "application/json", json);
}
void NeoPixelService::handlePatternsRequest(AsyncWebServerRequest* request) {
JsonDocument doc;
JsonArray arr = doc.to<JsonArray>();
for (auto& kv : patternUpdaters) arr.add(kv.first);
String json;
serializeJson(doc, json);
request->send(200, "application/json", json);
}
void NeoPixelService::handleControlRequest(AsyncWebServerRequest* request) {
if (request->hasParam("pattern", true)) {
String name = request->getParam("pattern", true)->value();
setPatternByName(name);
}
if (request->hasParam("interval_ms", true)) {
unsigned long v = request->getParam("interval_ms", true)->value().toInt();
if (v < 1) v = 1;
updateIntervalMs = v;
taskManager.setTaskInterval("neopixel_update", updateIntervalMs);
}
if (request->hasParam("brightness", true)) {
int b = request->getParam("brightness", true)->value().toInt();
if (b < 0) b = 0; if (b > 255) b = 255;
setBrightness((uint8_t)b);
}
// Accept packed color ints or r,g,b triplets
if (request->hasParam("color", true)) {
wipeColor = (uint32_t)strtoul(request->getParam("color", true)->value().c_str(), nullptr, 0);
chaseColor = wipeColor;
}
if (request->hasParam("r", true) || request->hasParam("g", true) || request->hasParam("b", true)) {
int r = request->hasParam("r", true) ? request->getParam("r", true)->value().toInt() : 0;
int g = request->hasParam("g", true) ? request->getParam("g", true)->value().toInt() : 0;
int b = request->hasParam("b", true) ? request->getParam("b", true)->value().toInt() : 0;
wipeColor = strip.Color(r, g, b);
chaseColor = strip.Color(r / 2, g / 2, b / 2); // dimmer for chase
}
JsonDocument resp;
resp["ok"] = true;
resp["pattern"] = currentPatternName();
resp["interval_ms"] = updateIntervalMs;
resp["brightness"] = brightness;
String json;
serializeJson(resp, json);
request->send(200, "application/json", json);
}
void NeoPixelService::setBrightness(uint8_t b) {
brightness = b;
strip.setBrightness(brightness);
strip.show();
}
void NeoPixelService::registerTasks() {
taskManager.registerTask("neopixel_update", updateIntervalMs, [this]() { update(); });
taskManager.registerTask("neopixel_status_print", 10000, [this]() {
Serial.printf("[NeoPixel] pattern=%s interval=%lu ms brightness=%u\n",
currentPatternName().c_str(), updateIntervalMs, brightness);
});
}
void NeoPixelService::registerPatterns() {
patternUpdaters["off"] = [this]() { updateOff(); };
patternUpdaters["color_wipe"] = [this]() { updateColorWipe(); };
patternUpdaters["rainbow"] = [this]() { updateRainbow(); };
patternUpdaters["rainbow_cycle"] = [this]() { updateRainbowCycle(); };
patternUpdaters["theater_chase"] = [this]() { updateTheaterChase(); };
patternUpdaters["theater_chase_rainbow"] = [this]() { updateTheaterChaseRainbow(); };
}
std::vector<String> NeoPixelService::patternNamesVector() const {
std::vector<String> v;
v.reserve(patternUpdaters.size());
for (const auto& kv : patternUpdaters) v.push_back(kv.first);
return v;
}
String NeoPixelService::currentPatternName() const {
switch (currentPattern) {
case Pattern::Off: return String("off");
case Pattern::ColorWipe: return String("color_wipe");
case Pattern::Rainbow: return String("rainbow");
case Pattern::RainbowCycle: return String("rainbow_cycle");
case Pattern::TheaterChase: return String("theater_chase");
case Pattern::TheaterChaseRainbow: return String("theater_chase_rainbow");
}
return String("off");
}
NeoPixelService::Pattern NeoPixelService::nameToPattern(const String& name) const {
if (name.equalsIgnoreCase("color_wipe")) return Pattern::ColorWipe;
if (name.equalsIgnoreCase("rainbow")) return Pattern::Rainbow;
if (name.equalsIgnoreCase("rainbow_cycle")) return Pattern::RainbowCycle;
if (name.equalsIgnoreCase("theater_chase")) return Pattern::TheaterChase;
if (name.equalsIgnoreCase("theater_chase_rainbow")) return Pattern::TheaterChaseRainbow;
return Pattern::Off;
}
void NeoPixelService::setPatternByName(const String& name) {
Pattern p = nameToPattern(name);
resetStateForPattern(p);
}
void NeoPixelService::resetStateForPattern(Pattern p) {
strip.clear();
strip.show();
wipeIndex = 0;
rainbowJ = 0;
cycleJ = 0;
chaseJ = 0;
chaseQ = 0;
chasePhaseOn = true;
lastUpdateMs = 0;
currentPattern = p;
}
void NeoPixelService::update() {
unsigned long now = millis();
if (now - lastUpdateMs < updateIntervalMs) return;
lastUpdateMs = now;
const String name = currentPatternName();
auto it = patternUpdaters.find(name);
if (it != patternUpdaters.end()) {
it->second();
} else {
updateOff();
}
}
void NeoPixelService::updateOff() {
strip.clear();
strip.show();
}
void NeoPixelService::updateColorWipe() {
if (wipeIndex < strip.numPixels()) {
strip.setPixelColor(wipeIndex, wipeColor);
++wipeIndex;
strip.show();
} else {
strip.clear();
wipeIndex = 0;
}
}
void NeoPixelService::updateRainbow() {
for (uint16_t i = 0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, colorWheel(strip, (i + rainbowJ) & 255));
}
strip.show();
rainbowJ = (rainbowJ + 1) & 0xFF;
}
void NeoPixelService::updateRainbowCycle() {
for (uint16_t i = 0; i < strip.numPixels(); i++) {
uint8_t pos = ((i * 256 / strip.numPixels()) + cycleJ) & 0xFF;
strip.setPixelColor(i, colorWheel(strip, pos));
}
strip.show();
cycleJ = (cycleJ + 1) & 0xFF;
}
void NeoPixelService::updateTheaterChase() {
if (chasePhaseOn) {
for (uint16_t i = 0; i < strip.numPixels(); i += 3) {
uint16_t idx = i + chaseQ;
if (idx < strip.numPixels()) strip.setPixelColor(idx, chaseColor);
}
strip.show();
chasePhaseOn = false;
} else {
for (uint16_t i = 0; i < strip.numPixels(); i += 3) {
uint16_t idx = i + chaseQ;
if (idx < strip.numPixels()) strip.setPixelColor(idx, 0);
}
strip.show();
chasePhaseOn = true;
chaseQ = (chaseQ + 1) % 3;
chaseJ = (chaseJ + 1) % 10;
}
}
void NeoPixelService::updateTheaterChaseRainbow() {
if (chasePhaseOn) {
for (uint16_t i = 0; i < strip.numPixels(); i += 3) {
uint16_t idx = i + chaseQ;
if (idx < strip.numPixels()) strip.setPixelColor(idx, colorWheel(strip, (idx + chaseJ) % 255));
}
strip.show();
chasePhaseOn = false;
} else {
for (uint16_t i = 0; i < strip.numPixels(); i += 3) {
uint16_t idx = i + chaseQ;
if (idx < strip.numPixels()) strip.setPixelColor(idx, 0);
}
strip.show();
chasePhaseOn = true;
chaseQ = (chaseQ + 1) % 3;
chaseJ = (chaseJ + 1) & 0xFF;
}
}

View File

@@ -1,67 +0,0 @@
#pragma once
#include "spore/Service.h"
#include "spore/core/TaskManager.h"
#include <Adafruit_NeoPixel.h>
#include <map>
#include <vector>
class NeoPixelService : public Service {
public:
enum class Pattern {
Off,
ColorWipe,
Rainbow,
RainbowCycle,
TheaterChase,
TheaterChaseRainbow
};
NeoPixelService(TaskManager& taskMgr, uint16_t numPixels, uint8_t pin, neoPixelType type);
void registerEndpoints(ApiServer& api) override;
const char* getName() const override { return "NeoPixel"; }
void setPatternByName(const String& name);
void setBrightness(uint8_t b);
private:
void registerTasks();
void registerPatterns();
std::vector<String> patternNamesVector() const;
String currentPatternName() const;
Pattern nameToPattern(const String& name) const;
void resetStateForPattern(Pattern p);
void update();
// Pattern updaters
void updateOff();
void updateColorWipe();
void updateRainbow();
void updateRainbowCycle();
void updateTheaterChase();
void updateTheaterChaseRainbow();
// Handlers
void handleStatusRequest(AsyncWebServerRequest* request);
void handlePatternsRequest(AsyncWebServerRequest* request);
void handleControlRequest(AsyncWebServerRequest* request);
TaskManager& taskManager;
Adafruit_NeoPixel strip;
std::map<String, std::function<void()>> patternUpdaters;
Pattern currentPattern;
unsigned long updateIntervalMs;
unsigned long lastUpdateMs;
// State for patterns
uint16_t wipeIndex;
uint32_t wipeColor;
uint8_t rainbowJ;
uint8_t cycleJ;
int chaseJ;
int chaseQ;
bool chasePhaseOn;
uint32_t chaseColor;
uint8_t brightness;
};

View File

@@ -1,45 +0,0 @@
#include <Arduino.h>
#include "spore/Spore.h"
#include "NeoPixelService.h"
#ifndef NEOPIXEL_PIN
#define NEOPIXEL_PIN 2
#endif
#ifndef NEOPIXEL_COUNT
#define NEOPIXEL_COUNT 16
#endif
#ifndef NEOPIXEL_TYPE
#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800)
#endif
// Create Spore instance with custom labels
Spore spore({
{"app", "neopixel"},
{"device", "light"},
{"pixels", String(NEOPIXEL_COUNT)},
{"pin", String(NEOPIXEL_PIN)}
});
// Create custom service
NeoPixelService* neoPixelService = nullptr;
void setup() {
// Initialize the Spore framework
spore.setup();
// Create and add custom service
neoPixelService = new NeoPixelService(spore.getTaskManager(), NEOPIXEL_COUNT, NEOPIXEL_PIN, NEOPIXEL_TYPE);
spore.addService(neoPixelService);
// Start the API server and complete initialization
spore.begin();
Serial.println("[Main] NeoPixel service registered and ready!");
}
void loop() {
// Run the Spore framework loop
spore.loop();
}

View File

@@ -0,0 +1,127 @@
#include "PixelStreamController.h"
namespace {
constexpr int COMPONENTS_PER_PIXEL = 3;
}
PixelStreamController::PixelStreamController(NodeContext& ctxRef, const PixelStreamConfig& cfg)
: ctx(ctxRef), config(cfg), pixels(cfg.pixelCount, cfg.pin, cfg.pixelType) {
}
void PixelStreamController::begin() {
pixels.begin();
pixels.setBrightness(config.brightness);
// Default all pixels to green so we can verify hardware before streaming frames
for (uint16_t i = 0; i < config.pixelCount; ++i) {
pixels.setPixelColor(i, pixels.Color(0, 255, 0));
}
pixels.show();
ctx.on("udp/raw", [this](void* data) {
this->handleEvent(data);
});
LOG_INFO("PixelStream", String("PixelStreamController ready on pin ") + String(config.pin) + " with " + String(config.pixelCount) + " pixels");
}
void PixelStreamController::handleEvent(void* data) {
if (data == nullptr) {
return;
}
String* payload = static_cast<String*>(data);
if (!payload) {
return;
}
if (!applyFrame(*payload)) {
LOG_WARN("PixelStream", String("Ignoring RAW payload with invalid length (") + String(payload->length()) + ")");
}
}
bool PixelStreamController::applyFrame(const String& payload) {
static constexpr std::size_t frameWidth = COMPONENTS_PER_PIXEL * 2;
const std::size_t payloadLength = static_cast<std::size_t>(payload.length());
if (payloadLength == 0 || (payloadLength % frameWidth) != 0) {
LOG_WARN("PixelStream", String("Payload size ") + String(payloadLength) + " is not a multiple of " + String(frameWidth));
return false;
}
const uint16_t framesProvided = static_cast<uint16_t>(payloadLength / frameWidth);
const uint16_t pixelsToUpdate = std::min(config.pixelCount, framesProvided);
for (uint16_t index = 0; index < pixelsToUpdate; ++index) {
const std::size_t base = static_cast<std::size_t>(index) * frameWidth;
FrameComponents components{};
if (!tryParsePixel(payload, base, components)) {
LOG_WARN("PixelStream", String("Invalid hex data at pixel index ") + String(index));
return false;
}
const uint16_t hardwareIndex = mapPixelIndex(index);
pixels.setPixelColor(hardwareIndex, pixels.Color(components.red, components.green, components.blue));
}
// Clear any remaining pixels so stale data is removed when fewer frames are provided
for (uint16_t index = pixelsToUpdate; index < config.pixelCount; ++index) {
const uint16_t hardwareIndex = mapPixelIndex(index);
pixels.setPixelColor(hardwareIndex, 0);
}
pixels.show();
return true;
}
uint16_t PixelStreamController::mapPixelIndex(uint16_t logicalIndex) const {
if (config.matrixWidth == 0) {
return logicalIndex;
}
const uint16_t row = logicalIndex / config.matrixWidth;
const uint16_t col = logicalIndex % config.matrixWidth;
if (!config.matrixSerpentine || (row % 2 == 0)) {
return row * config.matrixWidth + col;
}
const uint16_t reversedCol = (config.matrixWidth - 1) - col;
return row * config.matrixWidth + reversedCol;
}
int PixelStreamController::hexToNibble(char c) {
if (c >= '0' && c <= '9') {
return c - '0';
}
if (c >= 'a' && c <= 'f') {
return 10 + c - 'a';
}
if (c >= 'A' && c <= 'F') {
return 10 + c - 'A';
}
return -1;
}
bool PixelStreamController::tryParsePixel(const String& payload, std::size_t startIndex, FrameComponents& components) const {
static constexpr std::size_t frameWidth = COMPONENTS_PER_PIXEL * 2;
if (startIndex + frameWidth > static_cast<std::size_t>(payload.length())) {
return false;
}
const int rHi = hexToNibble(payload[startIndex]);
const int rLo = hexToNibble(payload[startIndex + 1]);
const int gHi = hexToNibble(payload[startIndex + 2]);
const int gLo = hexToNibble(payload[startIndex + 3]);
const int bHi = hexToNibble(payload[startIndex + 4]);
const int bLo = hexToNibble(payload[startIndex + 5]);
if (rHi < 0 || rLo < 0 || gHi < 0 || gLo < 0 || bHi < 0 || bLo < 0) {
return false;
}
components.red = static_cast<uint8_t>((rHi << 4) | rLo);
components.green = static_cast<uint8_t>((gHi << 4) | gLo);
components.blue = static_cast<uint8_t>((bHi << 4) | bLo);
return true;
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include <Arduino.h>
#include <Adafruit_NeoPixel.h>
#include <algorithm>
#include <cstddef>
#include "spore/core/NodeContext.h"
#include "spore/util/Logging.h"
struct PixelStreamConfig {
uint8_t pin;
uint16_t pixelCount;
uint8_t brightness;
uint16_t matrixWidth;
bool matrixSerpentine;
neoPixelType pixelType;
};
class PixelStreamController {
public:
PixelStreamController(NodeContext& ctx, const PixelStreamConfig& config);
void begin();
private:
struct FrameComponents {
uint8_t red;
uint8_t green;
uint8_t blue;
};
bool tryParsePixel(const String& payload, std::size_t startIndex, FrameComponents& components) const;
void handleEvent(void* data);
bool applyFrame(const String& payload);
uint16_t mapPixelIndex(uint16_t logicalIndex) const;
static int hexToNibble(char c);
NodeContext& ctx;
PixelStreamConfig config;
Adafruit_NeoPixel pixels;
};

View File

@@ -0,0 +1,202 @@
#include "PixelStreamService.h"
#include "spore/util/Logging.h"
#include <Adafruit_NeoPixel.h>
PixelStreamService::PixelStreamService(NodeContext& ctx, ApiServer& apiServer, PixelStreamController* controller)
: ctx(ctx), apiServer(apiServer), controller(controller) {}
void PixelStreamService::registerEndpoints(ApiServer& api) {
// Config endpoint for setting pixelstream configuration
api.registerEndpoint("/api/pixelstream/config", HTTP_PUT,
[this](AsyncWebServerRequest* request) { handleConfigRequest(request); },
std::vector<ParamSpec>{
ParamSpec{String("pin"), false, String("body"), String("number"), {}, String("")},
ParamSpec{String("pixel_count"), false, String("body"), String("number"), {}, String("")},
ParamSpec{String("brightness"), false, String("body"), String("number"), {}, String("")},
ParamSpec{String("matrix_width"), false, String("body"), String("number"), {}, String("")},
ParamSpec{String("matrix_serpentine"), false, String("body"), String("boolean"), {}, String("")},
ParamSpec{String("pixel_type"), false, String("body"), String("number"), {}, String("")}
});
// Config endpoint for getting pixelstream configuration
api.registerEndpoint("/api/pixelstream/config", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleGetConfigRequest(request); },
std::vector<ParamSpec>{});
}
void PixelStreamService::registerTasks(TaskManager& taskManager) {
// PixelStreamService doesn't register any tasks itself
}
PixelStreamConfig PixelStreamService::loadConfig() {
// Initialize with proper defaults
PixelStreamConfig config;
config.pin = 2;
config.pixelCount = 16;
config.brightness = 80;
config.matrixWidth = 16;
config.matrixSerpentine = false;
config.pixelType = NEO_GRB + NEO_KHZ800;
if (!LittleFS.begin()) {
LOG_WARN("PixelStream", "Failed to initialize LittleFS, using defaults");
return config;
}
if (!LittleFS.exists(CONFIG_FILE())) {
LOG_INFO("PixelStream", "No pixelstream config file found, using defaults");
// Save defaults
saveConfig(config);
return config;
}
File file = LittleFS.open(CONFIG_FILE(), "r");
if (!file) {
LOG_ERROR("PixelStream", "Failed to open config file for reading");
return config;
}
JsonDocument doc;
DeserializationError error = deserializeJson(doc, file);
file.close();
if (error) {
LOG_ERROR("PixelStream", "Failed to parse config file: " + String(error.c_str()));
return config;
}
if (doc["pin"].is<uint8_t>()) config.pin = doc["pin"].as<uint8_t>();
if (doc["pixel_count"].is<uint16_t>()) config.pixelCount = doc["pixel_count"].as<uint16_t>();
if (doc["brightness"].is<uint8_t>()) config.brightness = doc["brightness"].as<uint8_t>();
if (doc["matrix_width"].is<uint16_t>()) config.matrixWidth = doc["matrix_width"].as<uint16_t>();
if (doc["matrix_serpentine"].is<bool>()) config.matrixSerpentine = doc["matrix_serpentine"].as<bool>();
if (doc["pixel_type"].is<uint8_t>()) config.pixelType = static_cast<neoPixelType>(doc["pixel_type"].as<uint8_t>());
LOG_INFO("PixelStream", "Configuration loaded from " + String(CONFIG_FILE()));
return config;
}
bool PixelStreamService::saveConfig(const PixelStreamConfig& config) {
if (!LittleFS.begin()) {
LOG_ERROR("PixelStream", "LittleFS not initialized, cannot save config");
return false;
}
File file = LittleFS.open(CONFIG_FILE(), "w");
if (!file) {
LOG_ERROR("PixelStream", "Failed to open config file for writing");
return false;
}
JsonDocument doc;
doc["pin"] = config.pin;
doc["pixel_count"] = config.pixelCount;
doc["brightness"] = config.brightness;
doc["matrix_width"] = config.matrixWidth;
doc["matrix_serpentine"] = config.matrixSerpentine;
doc["pixel_type"] = static_cast<uint8_t>(config.pixelType);
size_t bytesWritten = serializeJson(doc, file);
file.close();
if (bytesWritten > 0) {
LOG_INFO("PixelStream", "Configuration saved to " + String(CONFIG_FILE()) + " (" + String(bytesWritten) + " bytes)");
return true;
} else {
LOG_ERROR("PixelStream", "Failed to write configuration to file");
return false;
}
}
void PixelStreamService::handleConfigRequest(AsyncWebServerRequest* request) {
// Load current config from file
PixelStreamConfig config = loadConfig();
bool updated = false;
// Handle individual form parameters
if (request->hasParam("pin", true)) {
String pinStr = request->getParam("pin", true)->value();
if (pinStr.length() > 0) {
int pinValue = pinStr.toInt();
if (pinValue >= 0 && pinValue <= 255) {
config.pin = static_cast<uint8_t>(pinValue);
updated = true;
}
}
}
if (request->hasParam("pixel_count", true)) {
String countStr = request->getParam("pixel_count", true)->value();
if (countStr.length() > 0) {
int countValue = countStr.toInt();
if (countValue > 0 && countValue <= 65535) {
config.pixelCount = static_cast<uint16_t>(countValue);
updated = true;
}
}
}
if (request->hasParam("brightness", true)) {
String brightnessStr = request->getParam("brightness", true)->value();
if (brightnessStr.length() > 0) {
int brightnessValue = brightnessStr.toInt();
if (brightnessValue >= 0 && brightnessValue <= 255) {
config.brightness = static_cast<uint8_t>(brightnessValue);
updated = true;
}
}
}
if (request->hasParam("matrix_width", true)) {
String widthStr = request->getParam("matrix_width", true)->value();
if (widthStr.length() > 0) {
int widthValue = widthStr.toInt();
if (widthValue > 0 && widthValue <= 65535) {
config.matrixWidth = static_cast<uint16_t>(widthValue);
updated = true;
}
}
}
if (request->hasParam("matrix_serpentine", true)) {
String serpentineStr = request->getParam("matrix_serpentine", true)->value();
config.matrixSerpentine = (serpentineStr.equalsIgnoreCase("true") || serpentineStr == "1");
updated = true;
}
if (request->hasParam("pixel_type", true)) {
String typeStr = request->getParam("pixel_type", true)->value();
if (typeStr.length() > 0) {
int typeValue = typeStr.toInt();
config.pixelType = static_cast<neoPixelType>(typeValue);
updated = true;
}
}
if (!updated) {
request->send(400, "application/json", "{\"error\":\"No valid configuration fields provided\"}");
return;
}
// Save config to file
if (saveConfig(config)) {
LOG_INFO("PixelStreamService", "Configuration updated and saved to pixelstream.json");
request->send(200, "application/json", "{\"status\":\"success\",\"message\":\"Configuration updated and saved\"}");
} else {
LOG_ERROR("PixelStreamService", "Failed to save configuration to file");
request->send(500, "application/json", "{\"error\":\"Failed to save configuration\"}");
}
}
void PixelStreamService::handleGetConfigRequest(AsyncWebServerRequest* request) {
PixelStreamConfig config = loadConfig();
JsonDocument doc;
doc["pin"] = config.pin;
doc["pixel_count"] = config.pixelCount;
doc["brightness"] = config.brightness;
doc["matrix_width"] = config.matrixWidth;
doc["matrix_serpentine"] = config.matrixSerpentine;
doc["pixel_type"] = static_cast<uint8_t>(config.pixelType);
String json;
serializeJson(doc, json);
request->send(200, "application/json", json);
}

View File

@@ -0,0 +1,33 @@
#pragma once
#include "spore/Service.h"
#include "spore/core/NodeContext.h"
#include "PixelStreamController.h"
#include <ArduinoJson.h>
#include <LittleFS.h>
#include "spore/util/Logging.h"
// PixelStreamConfig is defined in PixelStreamController.h
class PixelStreamService : public Service {
public:
PixelStreamService(NodeContext& ctx, ApiServer& apiServer, PixelStreamController* controller);
void registerEndpoints(ApiServer& api) override;
void registerTasks(TaskManager& taskManager) override;
const char* getName() const override { return "PixelStream"; }
// Config management
PixelStreamConfig loadConfig();
bool saveConfig(const PixelStreamConfig& config);
void setController(PixelStreamController* ctrl) { controller = ctrl; }
private:
NodeContext& ctx;
ApiServer& apiServer;
PixelStreamController* controller;
void handleConfigRequest(AsyncWebServerRequest* request);
void handleGetConfigRequest(AsyncWebServerRequest* request);
static const char* CONFIG_FILE() { return "/pixelstream.json"; }
};

View File

@@ -0,0 +1,33 @@
# PixelStream Example
This example demonstrates how to consume the `udp/raw` cluster event and drive a NeoPixel strip or matrix directly from streamed RGB data. Frames are provided as hex encoded byte triplets (`RRGGBB` per pixel).
## Features
- Subscribes to `udp/raw` via `NodeContext::on`.
- Converts incoming frames into pixel colors for strips or matrices.
- Supports serpentine (zig-zag) matrix wiring.
## Payload Format
Each packet is expected to be `RAW:` followed by `pixelCount * 3 * 2` hexadecimal characters. For example, for 8 pixels:
```
RAW:FF0000FF0000FF0000FF0000FF0000FF0000FF0000FF0000FF0000
```
## Usage
### Strip Mode
Upload the example with `PIXEL_MATRIX_WIDTH` set to 0 (default). Send frames containing `PIXEL_COUNT * 3` bytes as hex.
### Matrix Mode
Set `PIXEL_MATRIX_WIDTH` to the number of columns. The controller remaps even/odd rows to support serpentine wiring.
## Configuration
Adjust `PIXEL_PIN`, `PIXEL_COUNT`, `PIXEL_BRIGHTNESS`, `PIXEL_MATRIX_WIDTH`, `PIXEL_MATRIX_SERPENTINE`, and `PIXEL_TYPE` through build defines or editing `main.cpp`.

View File

@@ -0,0 +1,70 @@
#include <Arduino.h>
#include "spore/Spore.h"
#include "spore/util/Logging.h"
#include "PixelStreamController.h"
#include "PixelStreamService.h"
#include <Adafruit_NeoPixel.h>
// Defaults are now loaded from config.json on LittleFS
// Can still be overridden with preprocessor defines if needed
#ifndef PIXEL_PIN
#define PIXEL_PIN 2
#endif
#ifndef PIXEL_COUNT
#define PIXEL_COUNT 16
#endif
#ifndef PIXEL_BRIGHTNESS
#define PIXEL_BRIGHTNESS 80
#endif
#ifndef PIXEL_MATRIX_WIDTH
#define PIXEL_MATRIX_WIDTH 16
#endif
#ifndef PIXEL_MATRIX_SERPENTINE
#define PIXEL_MATRIX_SERPENTINE 0
#endif
#ifndef PIXEL_TYPE
#define PIXEL_TYPE NEO_GRB + NEO_KHZ800
#endif
Spore spore({
{"app", "pixelstream"},
{"role", "led"},
{"pixels", String(PIXEL_COUNT)}
});
PixelStreamController* controller = nullptr;
PixelStreamService* service = nullptr;
void setup() {
spore.setup();
// Create service first (need it to load config)
service = new PixelStreamService(spore.getContext(), spore.getApiServer(), nullptr);
// Load pixelstream config from LittleFS (pixelstream.json) or use defaults
PixelStreamConfig config = service->loadConfig();
// Create controller with loaded config
controller = new PixelStreamController(spore.getContext(), config);
controller->begin();
// Update service with the actual controller
service->setController(controller);
// Register service
spore.registerService(service);
// Start the API server
spore.begin();
}
void loop() {
spore.loop();
}

View File

@@ -1,6 +1,13 @@
# Relay Service Example # Relay Service Example
A minimal example that demonstrates the Spore framework with a custom RelayService. The Spore framework automatically handles all core functionality (WiFi, clustering, API server, task management) while allowing easy registration of custom services. A minimal example that demonstrates the Spore framework with a custom RelayService and web interface. The Spore framework automatically handles all core functionality (WiFi, clustering, API server, task management) while allowing easy registration of custom services.
## Features
- **API Control**: RESTful API endpoints for programmatic control
- **Web Interface**: Beautiful web UI for manual control at `http://<device-ip>/relay.html`
- **Real-time Status**: Live status updates and visual feedback
- **Toggle Functionality**: One-click toggle between ON/OFF states
- Default relay pin: `GPIO0` (ESP-01). Override with `-DRELAY_PIN=<pin>`. - Default relay pin: `GPIO0` (ESP-01). Override with `-DRELAY_PIN=<pin>`.
- WiFi and API port are configured in `src/Config.cpp`. - WiFi and API port are configured in `src/Config.cpp`.
@@ -25,8 +32,8 @@ RelayService* relayService = nullptr;
void setup() { void setup() {
spore.setup(); spore.setup();
relayService = new RelayService(spore.getTaskManager(), RELAY_PIN); relayService = new RelayService(spore.getContext(), spore.getTaskManager(), RELAY_PIN);
spore.addService(relayService); spore.registerService(relayService);
spore.begin(); spore.begin();
} }
@@ -42,6 +49,7 @@ The Spore framework automatically provides:
- REST API server with core endpoints - REST API server with core endpoints
- Task scheduling and execution - Task scheduling and execution
- Node status monitoring - Node status monitoring
- Static file serving for web interfaces (core service)
## Build & Upload ## Build & Upload
@@ -61,6 +69,20 @@ pio device monitor -b 115200
Assume the device IP is 192.168.1.50 below (replace with your device's IP shown in serial output). Assume the device IP is 192.168.1.50 below (replace with your device's IP shown in serial output).
## Web Interface
The web interface is located in the `data/` folder and will be served by the core StaticFileService.
Access the web interface at: `http://192.168.1.50/relay.html`
The web interface provides:
- Visual status indicator (red for OFF, green for ON)
- Turn ON/OFF buttons
- Toggle button for quick switching
- Real-time status updates every 2 seconds
- Uptime display
- Error handling and user feedback
## Relay API ## Relay API
- Get relay status - Get relay status

View File

@@ -1,20 +1,20 @@
#include "RelayService.h" #include "RelayService.h"
#include "spore/core/ApiServer.h" #include "spore/core/ApiServer.h"
#include "spore/util/Logging.h"
RelayService::RelayService(TaskManager& taskMgr, int pin) RelayService::RelayService(NodeContext& ctx, TaskManager& taskMgr, int pin)
: taskManager(taskMgr), relayPin(pin), relayOn(false) { : ctx(ctx), taskManager(taskMgr), relayPin(pin), relayOn(false) {
pinMode(relayPin, OUTPUT); pinMode(relayPin, OUTPUT);
// Many relay modules are active LOW. Start in OFF state (relay de-energized). // Many relay modules are active LOW. Start in OFF state (relay de-energized).
digitalWrite(relayPin, HIGH); digitalWrite(relayPin, HIGH);
registerTasks();
} }
void RelayService::registerEndpoints(ApiServer& api) { void RelayService::registerEndpoints(ApiServer& api) {
api.addEndpoint("/api/relay/status", HTTP_GET, api.registerEndpoint("/api/relay/status", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleStatusRequest(request); }, [this](AsyncWebServerRequest* request) { handleStatusRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
api.addEndpoint("/api/relay", HTTP_POST, api.registerEndpoint("/api/relay", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleControlRequest(request); }, [this](AsyncWebServerRequest* request) { handleControlRequest(request); },
std::vector<ParamSpec>{ std::vector<ParamSpec>{
ParamSpec{String("state"), true, String("body"), String("string"), ParamSpec{String("state"), true, String("body"), String("string"),
@@ -64,13 +64,13 @@ void RelayService::turnOn() {
relayOn = true; relayOn = true;
// Active LOW relay // Active LOW relay
digitalWrite(relayPin, LOW); digitalWrite(relayPin, LOW);
Serial.println("[RelayService] Relay ON"); LOG_INFO("RelayService", "Relay ON");
} }
void RelayService::turnOff() { void RelayService::turnOff() {
relayOn = false; relayOn = false;
digitalWrite(relayPin, HIGH); digitalWrite(relayPin, HIGH);
Serial.println("[RelayService] Relay OFF"); LOG_INFO("RelayService", "Relay OFF");
} }
void RelayService::toggle() { void RelayService::toggle() {
@@ -81,9 +81,8 @@ void RelayService::toggle() {
} }
} }
void RelayService::registerTasks() { void RelayService::registerTasks(TaskManager& taskManager) {
taskManager.registerTask("relay_status_print", 5000, [this]() { taskManager.registerTask("relay_status_print", 5000, [this]() {
Serial.printf("[RelayService] Status - pin: %d, state: %s\n", LOG_INFO("RelayService", "Status - pin: " + String(relayPin) + ", state: " + (relayOn ? "ON" : "OFF"));
relayPin, relayOn ? "ON" : "OFF");
}); });
} }

View File

@@ -1,12 +1,14 @@
#pragma once #pragma once
#include "spore/Service.h" #include "spore/Service.h"
#include "spore/core/TaskManager.h" #include "spore/core/TaskManager.h"
#include "spore/core/NodeContext.h"
#include <ArduinoJson.h> #include <ArduinoJson.h>
class RelayService : public Service { class RelayService : public Service {
public: public:
RelayService(TaskManager& taskMgr, int pin); RelayService(NodeContext& ctx, TaskManager& taskMgr, int pin);
void registerEndpoints(ApiServer& api) override; void registerEndpoints(ApiServer& api) override;
void registerTasks(TaskManager& taskManager) override;
const char* getName() const override { return "Relay"; } const char* getName() const override { return "Relay"; }
void turnOn(); void turnOn();
@@ -14,8 +16,7 @@ public:
void toggle(); void toggle();
private: private:
void registerTasks(); NodeContext& ctx;
TaskManager& taskManager; TaskManager& taskManager;
int relayPin; int relayPin;
bool relayOn; bool relayOn;

View File

@@ -0,0 +1,305 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Relay Control - Spore</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
color: white;
}
.container {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 40px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
text-align: center;
max-width: 400px;
width: 90%;
}
h1 {
margin-bottom: 30px;
font-size: 2.2em;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
.relay-status {
margin-bottom: 30px;
}
.status-indicator {
width: 120px;
height: 120px;
border-radius: 50%;
margin: 0 auto 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2em;
font-weight: bold;
transition: all 0.3s ease;
border: 4px solid rgba(255, 255, 255, 0.3);
}
.status-indicator.off {
background: rgba(255, 0, 0, 0.3);
color: #ff6b6b;
}
.status-indicator.on {
background: rgba(0, 255, 0, 0.3);
color: #51cf66;
box-shadow: 0 0 20px rgba(81, 207, 102, 0.5);
}
.status-text {
font-size: 1.5em;
margin-bottom: 10px;
}
.pin-info {
font-size: 0.9em;
opacity: 0.8;
}
.controls {
display: flex;
gap: 15px;
justify-content: center;
flex-wrap: wrap;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 25px;
font-size: 1em;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
min-width: 100px;
text-transform: uppercase;
letter-spacing: 1px;
}
.btn-primary {
background: rgba(255, 255, 255, 0.2);
color: white;
border: 2px solid rgba(255, 255, 255, 0.3);
}
.btn-primary:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.btn-primary:active {
transform: translateY(0);
}
.btn-success {
background: rgba(81, 207, 102, 0.8);
color: white;
border: 2px solid rgba(81, 207, 102, 1);
}
.btn-success:hover {
background: rgba(81, 207, 102, 1);
transform: translateY(-2px);
}
.btn-danger {
background: rgba(255, 107, 107, 0.8);
color: white;
border: 2px solid rgba(255, 107, 107, 1);
}
.btn-danger:hover {
background: rgba(255, 107, 107, 1);
transform: translateY(-2px);
}
.loading {
opacity: 0.6;
pointer-events: none;
}
.error {
color: #ff6b6b;
margin-top: 15px;
font-size: 0.9em;
}
.success {
color: #51cf66;
margin-top: 15px;
font-size: 0.9em;
}
.uptime {
margin-top: 20px;
font-size: 0.8em;
opacity: 0.7;
}
</style>
</head>
<body>
<div class="container">
<h1>🔌 Relay Control</h1>
<div class="relay-status">
<div class="status-indicator off" id="statusIndicator">
OFF
</div>
<div class="status-text" id="statusText">Relay is OFF</div>
<div class="pin-info" id="pinInfo">Pin: Loading...</div>
</div>
<div class="controls">
<button class="btn btn-success" id="turnOnBtn" onclick="controlRelay('on')">
Turn ON
</button>
<button class="btn btn-danger" id="turnOffBtn" onclick="controlRelay('off')">
Turn OFF
</button>
<button class="btn btn-primary" id="toggleBtn" onclick="controlRelay('toggle')">
Toggle
</button>
</div>
<div id="message"></div>
<div class="uptime" id="uptime"></div>
</div>
<script>
let currentState = 'off';
let relayPin = '';
// Load initial relay status
async function loadRelayStatus() {
try {
const response = await fetch('/api/relay/status');
if (!response.ok) {
throw new Error('Failed to fetch relay status');
}
const data = await response.json();
currentState = data.state;
relayPin = data.pin;
updateUI();
updateUptime(data.uptime);
} catch (error) {
console.error('Error loading relay status:', error);
showMessage('Error loading relay status: ' + error.message, 'error');
}
}
// Control relay
async function controlRelay(action) {
const buttons = document.querySelectorAll('.btn');
buttons.forEach(btn => btn.classList.add('loading'));
try {
const response = await fetch('/api/relay', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `state=${action}`
});
const data = await response.json();
if (data.success) {
currentState = data.state;
updateUI();
showMessage(`Relay turned ${data.state.toUpperCase()}`, 'success');
} else {
showMessage(data.message || 'Failed to control relay', 'error');
}
} catch (error) {
console.error('Error controlling relay:', error);
showMessage('Error controlling relay: ' + error.message, 'error');
} finally {
buttons.forEach(btn => btn.classList.remove('loading'));
}
}
// Update UI based on current state
function updateUI() {
const statusIndicator = document.getElementById('statusIndicator');
const statusText = document.getElementById('statusText');
const pinInfo = document.getElementById('pinInfo');
if (currentState === 'on') {
statusIndicator.className = 'status-indicator on';
statusIndicator.textContent = 'ON';
statusText.textContent = 'Relay is ON';
} else {
statusIndicator.className = 'status-indicator off';
statusIndicator.textContent = 'OFF';
statusText.textContent = 'Relay is OFF';
}
if (relayPin) {
pinInfo.textContent = `Pin: ${relayPin}`;
}
}
// Update uptime display
function updateUptime(uptime) {
const uptimeElement = document.getElementById('uptime');
if (uptime) {
const seconds = Math.floor(uptime / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
let uptimeText = `Uptime: ${seconds}s`;
if (hours > 0) {
uptimeText = `Uptime: ${hours}h ${minutes % 60}m ${seconds % 60}s`;
} else if (minutes > 0) {
uptimeText = `Uptime: ${minutes}m ${seconds % 60}s`;
}
uptimeElement.textContent = uptimeText;
}
}
// Show message to user
function showMessage(message, type) {
const messageElement = document.getElementById('message');
messageElement.textContent = message;
messageElement.className = type;
// Clear message after 3 seconds
setTimeout(() => {
messageElement.textContent = '';
messageElement.className = '';
}, 3000);
}
// Load status on page load
loadRelayStatus();
// Refresh status every 2 seconds
setInterval(loadRelayStatus, 2000);
</script>
</body>
</html>

View File

@@ -0,0 +1,177 @@
// Only initialize if not already done
if (!window.relayControlInitialized) {
class RelayControl {
constructor() {
this.currentState = 'off';
this.relayPin = '';
this.init();
}
init() {
this.createComponent();
this.loadRelayStatus();
this.setupEventListeners();
// Refresh status every 2 seconds
setInterval(() => this.loadRelayStatus(), 2000);
}
createComponent() {
// Create the main container
const container = document.createElement('div');
container.className = 'relay-component';
container.innerHTML = `
<h1>🔌 Relay Control</h1>
<div class="relay-status">
<div class="status-indicator off" id="statusIndicator">
OFF
</div>
<div class="status-text" id="statusText">Relay is OFF</div>
<div class="pin-info" id="pinInfo">Pin: Loading...</div>
</div>
<div class="controls">
<button class="btn btn-success" id="turnOnBtn">
Turn ON
</button>
<button class="btn btn-danger" id="turnOffBtn">
Turn OFF
</button>
<button class="btn btn-primary" id="toggleBtn">
Toggle
</button>
</div>
<div id="message"></div>
<div class="uptime" id="uptime"></div>
`;
// Append to component div
const componentDiv = document.getElementById('component');
if (componentDiv) {
componentDiv.appendChild(container);
} else {
// Fallback to body if component div not found
document.body.appendChild(container);
}
}
setupEventListeners() {
document.getElementById('turnOnBtn').addEventListener('click', () => this.controlRelay('on'));
document.getElementById('turnOffBtn').addEventListener('click', () => this.controlRelay('off'));
document.getElementById('toggleBtn').addEventListener('click', () => this.controlRelay('toggle'));
}
// Load initial relay status
async loadRelayStatus() {
try {
const response = await fetch('/api/relay/status');
if (!response.ok) {
throw new Error('Failed to fetch relay status');
}
const data = await response.json();
this.currentState = data.state;
this.relayPin = data.pin;
this.updateUI();
this.updateUptime(data.uptime);
} catch (error) {
console.error('Error loading relay status:', error);
this.showMessage('Error loading relay status: ' + error.message, 'error');
}
}
// Control relay
async controlRelay(action) {
const buttons = document.querySelectorAll('.btn');
buttons.forEach(btn => btn.classList.add('loading'));
try {
const response = await fetch('/api/relay', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `state=${action}`
});
const data = await response.json();
if (data.success) {
this.currentState = data.state;
this.updateUI();
this.showMessage(`Relay turned ${data.state.toUpperCase()}`, 'success');
} else {
this.showMessage(data.message || 'Failed to control relay', 'error');
}
} catch (error) {
console.error('Error controlling relay:', error);
this.showMessage('Error controlling relay: ' + error.message, 'error');
} finally {
buttons.forEach(btn => btn.classList.remove('loading'));
}
}
// Update UI based on current state
updateUI() {
const statusIndicator = document.getElementById('statusIndicator');
const statusText = document.getElementById('statusText');
const pinInfo = document.getElementById('pinInfo');
if (this.currentState === 'on') {
statusIndicator.className = 'status-indicator on';
statusIndicator.textContent = 'ON';
statusText.textContent = 'Relay is ON';
} else {
statusIndicator.className = 'status-indicator off';
statusIndicator.textContent = 'OFF';
statusText.textContent = 'Relay is OFF';
}
if (this.relayPin) {
pinInfo.textContent = `Pin: ${this.relayPin}`;
}
}
// Update uptime display
updateUptime(uptime) {
const uptimeElement = document.getElementById('uptime');
if (uptime) {
const seconds = Math.floor(uptime / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
let uptimeText = `Uptime: ${seconds}s`;
if (hours > 0) {
uptimeText = `Uptime: ${hours}h ${minutes % 60}m ${seconds % 60}s`;
} else if (minutes > 0) {
uptimeText = `Uptime: ${minutes}m ${seconds % 60}s`;
}
uptimeElement.textContent = uptimeText;
}
}
// Show message to user
showMessage(message, type) {
const messageElement = document.getElementById('message');
messageElement.textContent = message;
messageElement.className = type;
// Clear message after 3 seconds
setTimeout(() => {
messageElement.textContent = '';
messageElement.className = '';
}, 3000);
}
}
// Initialize the relay control when the page loads
document.addEventListener('DOMContentLoaded', () => {
new RelayControl();
});
window.relayControlInitialized = true;
}

View File

@@ -0,0 +1,141 @@
.relay-component {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 40px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
text-align: center;
max-width: 400px;
width: 100%;
color: white;
box-sizing: border-box;
}
.relay-component h1 {
margin: 0 0 30px 0;
font-size: 2.2em;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
.relay-component .relay-status {
margin-bottom: 30px;
}
.relay-component .status-indicator {
width: 120px;
height: 120px;
border-radius: 50%;
margin: 0 auto 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2em;
font-weight: bold;
transition: all 0.3s ease;
border: 4px solid rgba(255, 255, 255, 0.3);
}
.relay-component .status-indicator.off {
background: rgba(255, 0, 0, 0.3);
color: #ff6b6b;
}
.relay-component .status-indicator.on {
background: rgba(0, 255, 0, 0.3);
color: #51cf66;
box-shadow: 0 0 20px rgba(81, 207, 102, 0.5);
}
.relay-component .status-text {
font-size: 1.5em;
margin-bottom: 10px;
}
.relay-component .pin-info {
font-size: 0.9em;
opacity: 0.8;
}
.relay-component .controls {
display: flex;
gap: 15px;
justify-content: center;
flex-wrap: wrap;
}
.relay-component .btn {
padding: 12px 24px;
border: none;
border-radius: 25px;
font-size: 1em;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
min-width: 100px;
text-transform: uppercase;
letter-spacing: 1px;
}
.relay-component .btn-primary {
background: rgba(255, 255, 255, 0.2);
color: #333;
border: 2px solid rgba(0, 0, 0, 0.8);
font-weight: bold;
}
.relay-component .btn-primary:hover {
background: rgba(255, 255, 255, 0.4);
color: #222;
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.relay-component .btn-primary:active {
transform: translateY(0);
}
.relay-component .btn-success {
background: rgba(81, 207, 102, 0.8);
color: white;
border: 2px solid rgba(81, 207, 102, 1);
}
.relay-component .btn-success:hover {
background: rgba(81, 207, 102, 1);
transform: translateY(-2px);
}
.relay-component .btn-danger {
background: rgba(255, 107, 107, 0.8);
color: white;
border: 2px solid rgba(255, 107, 107, 1);
}
.relay-component .btn-danger:hover {
background: rgba(255, 107, 107, 1);
transform: translateY(-2px);
}
.relay-component .loading {
opacity: 0.6;
pointer-events: none;
}
.relay-component .error {
color: #ff6b6b;
margin-top: 15px;
font-size: 0.9em;
}
.relay-component .success {
color: #51cf66;
margin-top: 15px;
font-size: 0.9em;
}
.relay-component .uptime {
margin-top: 20px;
font-size: 0.8em;
opacity: 0.7;
}

View File

@@ -22,13 +22,14 @@ void setup() {
spore.setup(); spore.setup();
// Create and add custom service // Create and add custom service
relayService = new RelayService(spore.getTaskManager(), RELAY_PIN); relayService = new RelayService(spore.getContext(), spore.getTaskManager(), RELAY_PIN);
spore.addService(relayService); spore.registerService(relayService);
// Start the API server and complete initialization // Start the API server and complete initialization
spore.begin(); spore.begin();
Serial.println("[Main] Relay service registered and ready!"); LOG_INFO("Main", "Relay service registered and ready!");
LOG_INFO("Main", "Web interface available at http://<node-ip>/relay.html");
} }
void loop() { void loop() {

View File

@@ -1,82 +0,0 @@
# Static Web Server Example
This example demonstrates how to serve static HTML files from LittleFS using the Spore framework.
## Features
- Serves static files from LittleFS filesystem
- Beautiful web interface with real-time status updates
- Responsive design that works on mobile and desktop
- Automatic API endpoint discovery and status monitoring
## Files
- `main.cpp` - Main application entry point
- `data/index.html` - Web interface (served from LittleFS)
## Web Interface
The web interface provides:
- **Node Status** - Shows uptime and system information
- **Network Status** - Displays WiFi connection status
- **Task Status** - Shows active background tasks
- **Cluster Status** - Displays cluster membership
- **API Links** - Quick access to all available API endpoints
## Building and Flashing
### Build for D1 Mini (recommended for web interface)
```bash
# Build the firmware
pio run -e d1_mini_static_web
# Flash to device
pio run -e d1_mini_static_web -t upload
```
### Upload Files to LittleFS
After flashing the firmware, you need to upload the HTML files to the LittleFS filesystem:
```bash
# Upload data directory to LittleFS
pio run -e d1_mini_static_web -t uploadfs
```
## Usage
1. Flash the firmware to your ESP8266 device
2. Upload the data files to LittleFS
3. Connect to the device's WiFi network
4. Open a web browser and navigate to `http://<device-ip>/`
5. The web interface will load and display real-time status information
## API Endpoints
The web interface automatically discovers and displays links to all available API endpoints:
- `/api/node/status` - Node status and system information
- `/api/network/status` - Network and WiFi status
- `/api/tasks/status` - Task management status
- `/api/cluster/members` - Cluster membership
- `/api/node/endpoints` - Complete API endpoint list
## Customization
To customize the web interface:
1. Modify `data/index.html` with your desired content
2. Add additional static files (CSS, JS, images) to the `data/` directory
3. Re-upload the files using `pio run -e d1_mini_static_web -t uploadfs`
## File Structure
```
data/
├── index.html # Main web interface
└── (other static files) # Additional web assets
```
The StaticFileService automatically serves files from the LittleFS root directory, so any files placed in the `data/` directory will be accessible via HTTP.

View File

@@ -1,21 +0,0 @@
#include <Arduino.h>
#include "spore/Spore.h"
// Create Spore instance
Spore spore;
void setup() {
// Initialize Spore framework
spore.setup();
// Start the framework (this will start the API server with static file serving)
spore.begin();
Serial.println("Static web server started!");
Serial.println("Visit http://<node-ip>/ to see the web interface");
}
void loop() {
// Let Spore handle the main loop
spore.loop();
}

View File

@@ -1,9 +1,12 @@
#pragma once #pragma once
#include "spore/core/ApiServer.h" #include "spore/core/ApiServer.h"
class TaskManager;
class Service { class Service {
public: public:
virtual ~Service() = default; virtual ~Service() = default;
virtual void registerEndpoints(ApiServer& api) = 0; virtual void registerEndpoints(ApiServer& api) = 0;
virtual void registerTasks(TaskManager& taskManager) = 0;
virtual const char* getName() const = 0; virtual const char* getName() const = 0;
}; };

View File

@@ -10,6 +10,7 @@
#include "core/ApiServer.h" #include "core/ApiServer.h"
#include "core/TaskManager.h" #include "core/TaskManager.h"
#include "Service.h" #include "Service.h"
#include "util/Logging.h"
class Spore { class Spore {
public: public:
@@ -23,8 +24,8 @@ public:
void loop(); void loop();
// Service management // Service management
void addService(std::shared_ptr<Service> service); void registerService(std::shared_ptr<Service> service);
void addService(Service* service); void registerService(Service* service);
// Access to core components // Access to core components
NodeContext& getContext() { return ctx; } NodeContext& getContext() { return ctx; }
@@ -33,7 +34,6 @@ public:
ClusterManager& getCluster() { return cluster; } ClusterManager& getCluster() { return cluster; }
ApiServer& getApiServer() { return apiServer; } ApiServer& getApiServer() { return apiServer; }
private: private:
void initializeCore(); void initializeCore();
void registerCoreServices(); void registerCoreServices();

View File

@@ -2,6 +2,7 @@
#include <Arduino.h> #include <Arduino.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <AsyncWebSocket.h>
#include <Updater.h> #include <Updater.h>
#include <functional> #include <functional>
#include <vector> #include <vector>
@@ -18,17 +19,20 @@ class ApiServer {
public: public:
ApiServer(NodeContext& ctx, TaskManager& taskMgr, uint16_t port = 80); ApiServer(NodeContext& ctx, TaskManager& taskMgr, uint16_t port = 80);
void begin(); void begin();
void addService(Service& service); void registerService(Service& service);
void addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler); void registerEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler);
void addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler, void registerEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler,
std::function<void(AsyncWebServerRequest*, const String&, size_t, uint8_t*, size_t, bool)> uploadHandler); std::function<void(AsyncWebServerRequest*, const String&, size_t, uint8_t*, size_t, bool)> uploadHandler);
void addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler, void registerEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler,
const std::vector<ParamSpec>& params); const std::vector<ParamSpec>& params);
void addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler, void registerEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler,
std::function<void(AsyncWebServerRequest*, const String&, size_t, uint8_t*, size_t, bool)> uploadHandler, std::function<void(AsyncWebServerRequest*, const String&, size_t, uint8_t*, size_t, bool)> uploadHandler,
const std::vector<ParamSpec>& params); const std::vector<ParamSpec>& params);
// Static file serving
void serveStatic(const String& uri, fs::FS& fs, const String& path, const String& cache_header = "");
static const char* methodToStr(int method); static const char* methodToStr(int method);
// Access to endpoints for endpoints endpoint // Access to endpoints for endpoints endpoint
@@ -36,13 +40,18 @@ public:
private: private:
AsyncWebServer server; AsyncWebServer server;
AsyncWebSocket ws{ "/ws" };
NodeContext& ctx; NodeContext& ctx;
TaskManager& taskManager; TaskManager& taskManager;
std::vector<std::reference_wrapper<Service>> services; std::vector<std::reference_wrapper<Service>> services;
std::vector<EndpointInfo> endpoints; // Single source of truth for endpoints std::vector<EndpointInfo> endpoints; // Single source of truth for endpoints
std::vector<AsyncWebSocketClient*> wsClients;
// Internal helpers // Internal helpers
void registerEndpoint(const String& uri, int method, void registerEndpoint(const String& uri, int method,
const std::vector<ParamSpec>& params, const std::vector<ParamSpec>& params,
const String& serviceName); const String& serviceName);
// WebSocket helpers
void setupWebSocket();
}; };

View File

@@ -7,23 +7,42 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <ESP8266HTTPClient.h> #include <ESP8266HTTPClient.h>
#include <map> #include <map>
#include <vector>
#include <functional>
class ClusterManager { class ClusterManager {
public: public:
ClusterManager(NodeContext& ctx, TaskManager& taskMgr); ClusterManager(NodeContext& ctx, TaskManager& taskMgr);
void registerTasks(); void registerTasks();
void sendDiscovery(); void listen();
void listenForDiscovery();
void addOrUpdateNode(const String& nodeHost, IPAddress nodeIP); void addOrUpdateNode(const String& nodeHost, IPAddress nodeIP);
void updateAllNodeStatuses(); void updateAllNodeStatuses();
void removeDeadNodes(); void removeDeadNodes();
void printMemberList(); void printMemberList();
const std::map<String, NodeInfo>& getMemberList() const { return *ctx.memberList; } size_t getMemberCount() const { return ctx.memberList->getMemberCount(); }
void fetchNodeInfo(const IPAddress& ip); void updateLocalNodeResources(NodeInfo& node);
void updateLocalNodeResources();
void heartbeatTaskCallback(); void heartbeatTaskCallback();
void updateAllMembersInfoTaskCallback(); void updateAllMembersInfoTaskCallback();
void broadcastNodeUpdate();
private: private:
NodeContext& ctx; NodeContext& ctx;
TaskManager& taskManager; TaskManager& taskManager;
struct MessageHandler {
bool (*predicate)(const char*);
std::function<void(const char*)> handle;
const char* name;
};
void initMessageHandlers();
void handleIncomingMessage(const char* incoming);
static bool isHeartbeatMsg(const char* msg);
static bool isNodeUpdateMsg(const char* msg);
static bool isClusterEventMsg(const char* msg);
static bool isRawMsg(const char* msg);
void onHeartbeat(const char* msg);
void onNodeUpdate(const char* msg);
void onClusterEvent(const char* msg);
void onRawMessage(const char* msg);
void sendNodeInfo(const String& hostname, const IPAddress& targetIP);
unsigned long lastHeartbeatSentAt = 0;
std::vector<MessageHandler> messageHandlers;
}; };

View File

@@ -0,0 +1,133 @@
#pragma once
#include <Arduino.h>
#include <map>
#include <string>
#include <optional>
#include <functional>
#include "spore/types/NodeInfo.h"
/**
* @brief Manages the list of cluster members.
*
* The Memberlist class maintains a collection of cluster members, where each member
* is identified by its IP address and associated with a NodeInfo object. It provides
* methods to add, update, and remove members, as well as handle node status changes
* (stale and dead nodes).
*/
class Memberlist {
public:
/**
* @brief Default constructor.
*/
Memberlist();
/**
* @brief Destructor.
*/
~Memberlist();
/**
* @brief Adds or updates a member in the list.
*
* If the member already exists, updates its information. Otherwise, adds a new member.
* @param ip The IP address of the member (as string).
* @param node The NodeInfo object containing member details.
* @return True if the member was added or updated, false otherwise.
*/
bool addOrUpdateMember(const std::string& ip, const NodeInfo& node);
/**
* @brief Adds a new member to the list.
*
* @param ip The IP address of the member (as string).
* @param node The NodeInfo object containing member details.
* @return True if the member was added, false if it already exists.
*/
bool addMember(const std::string& ip, const NodeInfo& node);
/**
* @brief Updates an existing member in the list.
*
* @param ip The IP address of the member (as string).
* @param node The updated NodeInfo object.
* @return True if the member was updated, false if it doesn't exist.
*/
bool updateMember(const std::string& ip, const NodeInfo& node);
/**
* @brief Removes a member from the list.
*
* @param ip The IP address of the member to remove (as string).
* @return True if the member was removed, false if it doesn't exist.
*/
bool removeMember(const std::string& ip);
/**
* @brief Retrieves a member by IP address.
*
* @param ip The IP address of the member (as string).
* @return Optional containing the NodeInfo if found, or std::nullopt if not found.
*/
std::optional<NodeInfo> getMember(const std::string& ip) const;
/**
* @brief Iterates over all members and calls the provided callback for each.
*
* @param callback Function to call for each member. Receives (ip, node) as parameters.
*/
void forEachMember(std::function<void(const std::string&, const NodeInfo&)> callback) const;
/**
* @brief Iterates over all members and calls the provided callback for each.
*
* @param callback Function to call for each member. Receives (ip, node) as parameters.
* If callback returns false, iteration stops.
* @return True if all members were processed, false if iteration was stopped early.
*/
bool forEachMemberUntil(std::function<bool(const std::string&, const NodeInfo&)> callback) const;
/**
* @brief Gets the number of members in the list.
*
* @return The number of members.
*/
size_t getMemberCount() const;
/**
* @brief Updates the status of all members based on current time and thresholds.
*
* Marks nodes as stale or dead based on their last seen time.
* @param currentTime The current time in milliseconds.
* @param staleThresholdMs Threshold for marking a node as stale (milliseconds).
* @param deadThresholdMs Threshold for marking a node as dead (milliseconds).
* @param onStatusChange Optional callback fired when a node's status changes.
*/
void updateAllNodeStatuses(unsigned long currentTime,
unsigned long staleThresholdMs,
unsigned long deadThresholdMs,
std::function<void(const std::string&, NodeInfo::Status, NodeInfo::Status)> onStatusChange = nullptr);
/**
* @brief Removes all dead members from the list.
*
* @return The number of members removed.
*/
size_t removeDeadMembers();
/**
* @brief Checks if a member exists in the list.
*
* @param ip The IP address of the member (as string).
* @return True if the member exists, false otherwise.
*/
bool hasMember(const std::string& ip) const;
/**
* @brief Clears all members from the list.
*/
void clear();
private:
std::map<std::string, NodeInfo> m_members; ///< Internal map holding the members.
};

View File

@@ -27,6 +27,8 @@ public:
void setWiFiConfig(const String& ssid, const String& password, void setWiFiConfig(const String& ssid, const String& password,
uint32_t connect_timeout_ms = 10000, uint32_t connect_timeout_ms = 10000,
uint32_t retry_delay_ms = 500); uint32_t retry_delay_ms = 500);
bool saveConfig();
void restartNode();
// Network status methods // Network status methods
bool isConnected() const { return WiFi.isConnected(); } bool isConnected() const { return WiFi.isConnected(); }

View File

@@ -2,12 +2,14 @@
#include <WiFiUdp.h> #include <WiFiUdp.h>
#include <map> #include <map>
#include "spore/types/NodeInfo.h"
#include <functional> #include <functional>
#include <string> #include <string>
#include <initializer_list> #include <initializer_list>
#include <memory>
#include "spore/types/NodeInfo.h"
#include "spore/types/Config.h" #include "spore/types/Config.h"
#include "spore/types/ApiTypes.h" #include "spore/types/ApiTypes.h"
#include "spore/core/Memberlist.h"
class NodeContext { class NodeContext {
public: public:
@@ -18,12 +20,17 @@ public:
String hostname; String hostname;
IPAddress localIP; IPAddress localIP;
NodeInfo self; NodeInfo self;
std::map<String, NodeInfo>* memberList; std::unique_ptr<Memberlist> memberList;
Config config; ::Config config;
std::map<String, String> constructorLabels; // Labels passed to constructor (not persisted)
using EventCallback = std::function<void(void*)>; using EventCallback = std::function<void(void*)>;
std::map<std::string, std::vector<EventCallback>> eventRegistry; std::map<std::string, std::vector<EventCallback>> eventRegistry;
using AnyEventCallback = std::function<void(const std::string&, void*)>;
std::vector<AnyEventCallback> anyEventSubscribers;
void on(const std::string& event, EventCallback cb); void on(const std::string& event, EventCallback cb);
void fire(const std::string& event, void* data); void fire(const std::string& event, void* data);
void onAny(AnyEventCallback cb);
void rebuildLabels(); // Rebuild self.labels from constructorLabels + config.labels
}; };

View File

@@ -5,20 +5,20 @@
// Cluster protocol and API constants // Cluster protocol and API constants
namespace ClusterProtocol { namespace ClusterProtocol {
constexpr const char* DISCOVERY_MSG = "CLUSTER_DISCOVERY"; constexpr const char* HEARTBEAT_MSG = "cluster/heartbeat";
constexpr const char* RESPONSE_MSG = "CLUSTER_RESPONSE"; constexpr const char* NODE_UPDATE_MSG = "node/update";
constexpr const char* CLUSTER_EVENT_MSG = "cluster/event";
constexpr const char* RAW_MSG = "RAW";
constexpr uint16_t UDP_PORT = 4210; constexpr uint16_t UDP_PORT = 4210;
constexpr size_t UDP_BUF_SIZE = 64; // Increased buffer to accommodate larger RAW pixel streams and node info JSON over UDP
constexpr size_t UDP_BUF_SIZE = 2048;
constexpr const char* API_NODE_STATUS = "/api/node/status"; constexpr const char* API_NODE_STATUS = "/api/node/status";
} }
namespace TaskIntervals { namespace TaskIntervals {
constexpr unsigned long SEND_DISCOVERY = 1000;
constexpr unsigned long LISTEN_FOR_DISCOVERY = 100;
constexpr unsigned long UPDATE_STATUS = 1000; constexpr unsigned long UPDATE_STATUS = 1000;
constexpr unsigned long PRINT_MEMBER_LIST = 5000; constexpr unsigned long PRINT_MEMBER_LIST = 5000;
constexpr unsigned long HEARTBEAT = 2000; constexpr unsigned long HEARTBEAT = 2000;
constexpr unsigned long UPDATE_ALL_MEMBERS_INFO = 10000;
} }
constexpr unsigned long NODE_ACTIVE_THRESHOLD = 10000; constexpr unsigned long NODE_ACTIVE_THRESHOLD = 10000;

View File

@@ -7,6 +7,7 @@ class ClusterService : public Service {
public: public:
ClusterService(NodeContext& ctx); ClusterService(NodeContext& ctx);
void registerEndpoints(ApiServer& api) override; void registerEndpoints(ApiServer& api) override;
void registerTasks(TaskManager& taskManager) override;
const char* getName() const override { return "Cluster"; } const char* getName() const override { return "Cluster"; }
private: private:

View File

@@ -0,0 +1,43 @@
#pragma once
#include "spore/Service.h"
#include <functional>
class MonitoringService : public Service {
public:
MonitoringService();
void registerEndpoints(ApiServer& api) override;
void registerTasks(TaskManager& taskManager) override;
const char* getName() const override { return "Monitoring"; }
// System resource information
struct SystemResources {
// CPU information
float currentCpuUsage;
float averageCpuUsage;
unsigned long measurementCount;
bool isMeasuring;
// Memory information
size_t freeHeap;
size_t totalHeap;
// Filesystem information
size_t totalBytes;
size_t usedBytes;
size_t freeBytes;
float usagePercent;
// System uptime
unsigned long uptimeMs;
unsigned long uptimeSeconds;
};
// Get current system resources
SystemResources getSystemResources() const;
private:
void handleResourcesRequest(AsyncWebServerRequest* request);
// Helper methods
void getFilesystemInfo(size_t& totalBytes, size_t& usedBytes) const;
};

View File

@@ -7,6 +7,7 @@ class NetworkService : public Service {
public: public:
NetworkService(NetworkManager& networkManager); NetworkService(NetworkManager& networkManager);
void registerEndpoints(ApiServer& api) override; void registerEndpoints(ApiServer& api) override;
void registerTasks(TaskManager& taskManager) override;
const char* getName() const override { return "Network"; } const char* getName() const override { return "Network"; }
private: private:

View File

@@ -8,6 +8,7 @@ class NodeService : public Service {
public: public:
NodeService(NodeContext& ctx, ApiServer& apiServer); NodeService(NodeContext& ctx, ApiServer& apiServer);
void registerEndpoints(ApiServer& api) override; void registerEndpoints(ApiServer& api) override;
void registerTasks(TaskManager& taskManager) override;
const char* getName() const override { return "Node"; } const char* getName() const override { return "Node"; }
private: private:
@@ -19,4 +20,6 @@ private:
void handleUpdateUpload(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final); void handleUpdateUpload(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final);
void handleRestartRequest(AsyncWebServerRequest* request); void handleRestartRequest(AsyncWebServerRequest* request);
void handleEndpointsRequest(AsyncWebServerRequest* request); void handleEndpointsRequest(AsyncWebServerRequest* request);
void handleConfigRequest(AsyncWebServerRequest* request);
void handleGetConfigRequest(AsyncWebServerRequest* request);
}; };

View File

@@ -9,16 +9,11 @@ class StaticFileService : public Service {
public: public:
StaticFileService(NodeContext& ctx, ApiServer& apiServer); StaticFileService(NodeContext& ctx, ApiServer& apiServer);
void registerEndpoints(ApiServer& api) override; void registerEndpoints(ApiServer& api) override;
void registerTasks(TaskManager& taskManager) override;
const char* getName() const override { return name.c_str(); } const char* getName() const override { return name.c_str(); }
private: private:
static const String name; static const String name;
NodeContext& ctx; NodeContext& ctx;
ApiServer& apiServer; ApiServer& apiServer;
void handleRootRequest(AsyncWebServerRequest* request);
void handleStaticFileRequest(AsyncWebServerRequest* request);
void handleNotFound(AsyncWebServerRequest* request);
String getContentType(const String& filename);
bool fileExists(const String& path);
}; };

View File

@@ -7,6 +7,7 @@ class TaskService : public Service {
public: public:
TaskService(TaskManager& taskManager); TaskService(TaskManager& taskManager);
void registerEndpoints(ApiServer& api) override; void registerEndpoints(ApiServer& api) override;
void registerTasks(TaskManager& taskManager) override;
const char* getName() const override { return "Task"; } const char* getName() const override { return "Task"; }
private: private:

View File

@@ -1,9 +1,33 @@
#pragma once #pragma once
#include <Arduino.h> #include <Arduino.h>
#include <LittleFS.h>
#include <ArduinoJson.h>
#include <map>
class Config { class Config {
public: public:
// Default Configuration Constants
static constexpr const char* DEFAULT_WIFI_SSID = "shroud";
static constexpr const char* DEFAULT_WIFI_PASSWORD = "th3r31sn0sp00n";
static constexpr uint16_t DEFAULT_UDP_PORT = 4210;
static constexpr uint16_t DEFAULT_API_SERVER_PORT = 80;
static constexpr unsigned long DEFAULT_CLUSTER_LISTEN_INTERVAL_MS = 10;
static constexpr unsigned long DEFAULT_HEARTBEAT_INTERVAL_MS = 5000;
static constexpr unsigned long DEFAULT_STATUS_UPDATE_INTERVAL_MS = 1000;
static constexpr unsigned long DEFAULT_NODE_UPDATE_BROADCAST_INTERVAL_MS = 5000;
static constexpr unsigned long DEFAULT_PRINT_INTERVAL_MS = 5000;
static constexpr unsigned long DEFAULT_NODE_ACTIVE_THRESHOLD_MS = 10000;
static constexpr unsigned long DEFAULT_NODE_INACTIVE_THRESHOLD_MS = 60000;
static constexpr unsigned long DEFAULT_NODE_DEAD_THRESHOLD_MS = 120000;
static constexpr unsigned long DEFAULT_WIFI_CONNECT_TIMEOUT_MS = 15000;
static constexpr unsigned long DEFAULT_WIFI_RETRY_DELAY_MS = 500;
static constexpr unsigned long DEFAULT_RESTART_DELAY_MS = 10;
static constexpr uint16_t DEFAULT_JSON_DOC_SIZE = 1024;
static constexpr uint32_t DEFAULT_LOW_MEMORY_THRESHOLD_BYTES = 10000;
static constexpr uint32_t DEFAULT_CRITICAL_MEMORY_THRESHOLD_BYTES = 5000;
static constexpr size_t DEFAULT_MAX_CONCURRENT_HTTP_REQUESTS = 3;
// WiFi Configuration // WiFi Configuration
String wifi_ssid; String wifi_ssid;
String wifi_password; String wifi_password;
@@ -13,11 +37,9 @@ public:
uint16_t api_server_port; uint16_t api_server_port;
// Cluster Configuration // Cluster Configuration
unsigned long discovery_interval_ms;
unsigned long heartbeat_interval_ms; unsigned long heartbeat_interval_ms;
unsigned long cluster_listen_interval_ms;
unsigned long status_update_interval_ms; unsigned long status_update_interval_ms;
unsigned long member_info_update_interval_ms;
unsigned long print_interval_ms;
// Node Status Thresholds // Node Status Thresholds
unsigned long node_active_threshold_ms; unsigned long node_active_threshold_ms;
@@ -32,6 +54,22 @@ public:
unsigned long restart_delay_ms; unsigned long restart_delay_ms;
uint16_t json_doc_size; uint16_t json_doc_size;
// Memory Management
uint32_t low_memory_threshold_bytes;
uint32_t critical_memory_threshold_bytes;
size_t max_concurrent_http_requests;
// Custom Labels
std::map<String, String> labels;
// Constructor // Constructor
Config(); Config();
// Persistence methods
bool saveToFile(const String& filename = "/config.json");
bool loadFromFile(const String& filename = "/config.json");
void setDefaults();
private:
static const char* CONFIG_FILE_PATH;
}; };

View File

@@ -9,6 +9,7 @@ struct NodeInfo {
String hostname; String hostname;
IPAddress ip; IPAddress ip;
unsigned long lastSeen; unsigned long lastSeen;
unsigned long uptime = 0; // milliseconds since node started
enum Status { ACTIVE, INACTIVE, DEAD } status; enum Status { ACTIVE, INACTIVE, DEAD } status;
struct Resources { struct Resources {
uint32_t freeHeap = 0; uint32_t freeHeap = 0;
@@ -17,7 +18,7 @@ struct NodeInfo {
uint32_t cpuFreqMHz = 0; uint32_t cpuFreqMHz = 0;
uint32_t flashChipSize = 0; uint32_t flashChipSize = 0;
} resources; } resources;
unsigned long latency = 0; // ms since lastSeen unsigned long latency = 0; // ms from heartbeat broadcast to NODE_UPDATE receipt
std::vector<EndpointInfo> endpoints; // List of registered endpoints std::vector<EndpointInfo> endpoints; // List of registered endpoints
std::map<String, String> labels; // Arbitrary node labels (key -> value) std::map<String, String> labels; // Arbitrary node labels (key -> value)
}; };

View File

@@ -0,0 +1,29 @@
#pragma once
#include <Arduino.h>
enum class LogLevel {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3
};
// Global log level - can be changed at runtime
extern LogLevel g_logLevel;
// Simple logging functions
void logMessage(LogLevel level, const String& component, const String& message);
void logDebug(const String& component, const String& message);
void logInfo(const String& component, const String& message);
void logWarn(const String& component, const String& message);
void logError(const String& component, const String& message);
// Set global log level
void setLogLevel(LogLevel level);
// Convenience macros - no context needed
#define LOG_DEBUG(component, message) logDebug(component, message)
#define LOG_INFO(component, message) logInfo(component, message)
#define LOG_WARN(component, message) logWarn(component, message)
#define LOG_ERROR(component, message) logError(component, message)

Binary file not shown.

View File

@@ -1,6 +0,0 @@
# Name, Type, Subtype, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x6000,
otadata, data, ota, 0xf000, 0x2000,
app0, app, factory, 0x11000, 0x30000, # 192KB
app1, app, ota_0, 0x41000, 0x30000, # 192KB
littlefs, data, littlefs, 0x71000, 0x9000 # 36KB
1 # Name, Type, Subtype, Offset, Size, Flags
2 nvs, data, nvs, 0x9000, 0x6000,
3 otadata, data, ota, 0xf000, 0x2000,
4 app0, app, factory, 0x11000, 0x30000, # 192 KB
5 app1, app, ota_0, 0x41000, 0x30000, # 192 KB
6 littlefs, data, littlefs, 0x71000, 0x9000 # 36 KB

View File

@@ -1,6 +0,0 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, , 0x4000,
otadata, data, ota, , 0x2000,
app0, app, ota_0, , 0x3E000,
app1, app, ota_1, , 0x3E000,
littlefs, data, littlefs, , 0x1C000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x4000
3 otadata data ota 0x2000
4 app0 app ota_0 0x3E000
5 app1 app ota_1 0x3E000
6 littlefs data littlefs 0x1C000

View File

@@ -9,16 +9,26 @@
; https://docs.platformio.org/page/projectconf.html ; https://docs.platformio.org/page/projectconf.html
[platformio] [platformio]
default_envs = esp01_1m ;default_envs = base
src_dir = . src_dir = .
data_dir = ${PROJECT_DIR}/examples/${PIOENV}/data
[common] [common]
monitor_speed = 115200 monitor_speed = 115200
lib_deps = lib_deps =
esp32async/ESPAsyncWebServer@^3.8.0 esp32async/ESPAsyncWebServer@^3.8.0
bblanchon/ArduinoJson@^7.4.2 bblanchon/ArduinoJson@^7.4.2
build_flags =
-Os ; Optimize for size
-ffunction-sections ; Place each function in its own section
-fdata-sections ; Place data in separate sections
-Wl,--gc-sections ; Remove unused sections at link time
-DNDEBUG ; Disable debug assertions
-DVTABLES_IN_FLASH ; Move virtual tables to flash
-fno-exceptions ; Disable C++ exceptions
-fno-rtti ; Disable runtime type information
[env:esp01_1m] [env:base]
platform = platformio/espressif8266@^4.2.1 platform = platformio/espressif8266@^4.2.1
board = esp01_1m board = esp01_1m
framework = arduino framework = arduino
@@ -30,12 +40,14 @@ board_build.filesystem = littlefs
; note: somehow partition table is not working, so we need to use the ldscript ; note: somehow partition table is not working, so we need to use the ldscript
board_build.ldscript = eagle.flash.1m64.ld ; 64KB -> FS Size board_build.ldscript = eagle.flash.1m64.ld ; 64KB -> FS Size
lib_deps = ${common.lib_deps} lib_deps = ${common.lib_deps}
build_flags = ${common.build_flags}
build_src_filter = build_src_filter =
+<examples/base/*.cpp> +<examples/base/*.cpp>
+<src/spore/*.cpp> +<src/spore/*.cpp>
+<src/spore/core/*.cpp> +<src/spore/core/*.cpp>
+<src/spore/services/*.cpp> +<src/spore/services/*.cpp>
+<src/spore/types/*.cpp> +<src/spore/types/*.cpp>
+<src/spore/util/*.cpp>
+<src/internal/*.cpp> +<src/internal/*.cpp>
[env:d1_mini] [env:d1_mini]
@@ -49,15 +61,17 @@ board_build.flash_mode = dio ; D1 Mini uses DIO on 4 Mbit flash
board_build.flash_size = 4M board_build.flash_size = 4M
board_build.ldscript = eagle.flash.4m1m.ld board_build.ldscript = eagle.flash.4m1m.ld
lib_deps = ${common.lib_deps} lib_deps = ${common.lib_deps}
build_flags = ${common.build_flags}
build_src_filter = build_src_filter =
+<examples/base/*.cpp> +<examples/base/*.cpp>
+<src/spore/*.cpp> +<src/spore/*.cpp>
+<src/spore/core/*.cpp> +<src/spore/core/*.cpp>
+<src/spore/services/*.cpp> +<src/spore/services/*.cpp>
+<src/spore/types/*.cpp> +<src/spore/types/*.cpp>
+<src/spore/util/*.cpp>
+<src/internal/*.cpp> +<src/internal/*.cpp>
[env:esp01_1m_relay] [env:relay]
platform = platformio/espressif8266@^4.2.1 platform = platformio/espressif8266@^4.2.1
board = esp01_1m board = esp01_1m
framework = arduino framework = arduino
@@ -67,15 +81,18 @@ board_build.filesystem = littlefs
board_build.flash_mode = dout board_build.flash_mode = dout
board_build.ldscript = eagle.flash.1m64.ld board_build.ldscript = eagle.flash.1m64.ld
lib_deps = ${common.lib_deps} lib_deps = ${common.lib_deps}
;data_dir = examples/relay/data
build_flags = ${common.build_flags}
build_src_filter = build_src_filter =
+<examples/relay/*.cpp> +<examples/relay/*.cpp>
+<src/spore/*.cpp> +<src/spore/*.cpp>
+<src/spore/core/*.cpp> +<src/spore/core/*.cpp>
+<src/spore/services/*.cpp> +<src/spore/services/*.cpp>
+<src/spore/types/*.cpp> +<src/spore/types/*.cpp>
+<src/spore/util/*.cpp>
+<src/internal/*.cpp> +<src/internal/*.cpp>
[env:esp01_1m_neopixel] [env:neopattern]
platform = platformio/espressif8266@^4.2.1 platform = platformio/espressif8266@^4.2.1
board = esp01_1m board = esp01_1m
framework = arduino framework = arduino
@@ -86,35 +103,17 @@ board_build.flash_mode = dout
board_build.ldscript = eagle.flash.1m64.ld board_build.ldscript = eagle.flash.1m64.ld
lib_deps = ${common.lib_deps} lib_deps = ${common.lib_deps}
adafruit/Adafruit NeoPixel@^1.15.1 adafruit/Adafruit NeoPixel@^1.15.1
build_flags = -DLED_STRIP_PIN=2 ;${common.build_flags}
build_src_filter = build_src_filter =
+<examples/neopixel/*.cpp> +<examples/neopattern/*.cpp>
+<src/spore/*.cpp> +<src/spore/*.cpp>
+<src/spore/core/*.cpp> +<src/spore/core/*.cpp>
+<src/spore/services/*.cpp> +<src/spore/services/*.cpp>
+<src/spore/types/*.cpp> +<src/spore/types/*.cpp>
+<src/spore/util/*.cpp>
+<src/internal/*.cpp> +<src/internal/*.cpp>
[env:d1_mini_neopixel] [env:pixelstream]
platform = platformio/espressif8266@^4.2.1
board = d1_mini
framework = arduino
upload_speed = 115200
monitor_speed = 115200
board_build.filesystem = littlefs
board_build.flash_mode = dio
board_build.flash_size = 4M
board_build.ldscript = eagle.flash.4m1m.ld
lib_deps = ${common.lib_deps}
adafruit/Adafruit NeoPixel@^1.15.1
build_src_filter =
+<examples/neopixel/*.cpp>
+<src/spore/*.cpp>
+<src/spore/core/*.cpp>
+<src/spore/services/*.cpp>
+<src/spore/types/*.cpp>
+<src/internal/*.cpp>
[env:esp01_1m_neopattern]
platform = platformio/espressif8266@^4.2.1 platform = platformio/espressif8266@^4.2.1
board = esp01_1m board = esp01_1m
framework = arduino framework = arduino
@@ -125,16 +124,38 @@ board_build.flash_mode = dout
board_build.ldscript = eagle.flash.1m64.ld board_build.ldscript = eagle.flash.1m64.ld
lib_deps = ${common.lib_deps} lib_deps = ${common.lib_deps}
adafruit/Adafruit NeoPixel@^1.15.1 adafruit/Adafruit NeoPixel@^1.15.1
build_flags = -DLED_STRIP_PIN=2 build_flags = ${common.build_flags}
build_src_filter = build_src_filter =
+<examples/neopattern/*.cpp> +<examples/pixelstream/*.cpp>
+<src/spore/*.cpp> +<src/spore/*.cpp>
+<src/spore/core/*.cpp> +<src/spore/core/*.cpp>
+<src/spore/services/*.cpp> +<src/spore/services/*.cpp>
+<src/spore/types/*.cpp> +<src/spore/types/*.cpp>
+<src/spore/util/*.cpp>
+<src/internal/*.cpp> +<src/internal/*.cpp>
[env:d1_mini_neopattern] [env:pixelstream_d1]
platform = platformio/espressif8266@^4.2.1
board = d1_mini
framework = arduino
upload_speed = 115200
monitor_speed = 115200
board_build.filesystem = littlefs
board_build.flash_mode = dout
board_build.ldscript = eagle.flash.4m1m.ld
lib_deps = ${common.lib_deps}
adafruit/Adafruit NeoPixel@^1.15.1
build_flags = -DPIXEL_PIN=TX -DPIXEL_COUNT=256 -DMATRIX_WIDTH=16 ${common.build_flags}
build_src_filter =
+<examples/pixelstream/*.cpp>
+<src/spore/*.cpp>
+<src/spore/core/*.cpp>
+<src/spore/services/*.cpp>
+<src/spore/types/*.cpp>
+<src/spore/util/*.cpp>
+<src/internal/*.cpp>
[env:multimatrix]
platform = platformio/espressif8266@^4.2.1 platform = platformio/espressif8266@^4.2.1
board = d1_mini board = d1_mini
framework = arduino framework = arduino
@@ -146,29 +167,14 @@ board_build.flash_size = 4M
board_build.ldscript = eagle.flash.4m1m.ld board_build.ldscript = eagle.flash.4m1m.ld
lib_deps = ${common.lib_deps} lib_deps = ${common.lib_deps}
adafruit/Adafruit NeoPixel@^1.15.1 adafruit/Adafruit NeoPixel@^1.15.1
dfrobot/DFRobotDFPlayerMini@^1.0.6
build_flags = ${common.build_flags}
build_src_filter = build_src_filter =
+<examples/neopattern/*.cpp> +<examples/multimatrix/*.cpp>
+<src/spore/*.cpp> +<examples/pixelstream/PixelStreamController.cpp>
+<src/spore/core/*.cpp>
+<src/spore/services/*.cpp>
+<src/spore/types/*.cpp>
+<src/internal/*.cpp>
[env:d1_mini_static_web]
platform = platformio/espressif8266@^4.2.1
board = d1_mini
framework = arduino
upload_speed = 115200
monitor_speed = 115200
board_build.flash_mode = dio
board_build.flash_size = 4M
board_build.filesystem = littlefs
board_build.ldscript = eagle.flash.4m1m.ld
lib_deps = ${common.lib_deps}
build_src_filter =
+<examples/static_web/*.cpp>
+<src/spore/*.cpp> +<src/spore/*.cpp>
+<src/spore/core/*.cpp> +<src/spore/core/*.cpp>
+<src/spore/services/*.cpp> +<src/spore/services/*.cpp>
+<src/spore/types/*.cpp> +<src/spore/types/*.cpp>
+<src/spore/util/*.cpp>
+<src/internal/*.cpp> +<src/internal/*.cpp>

View File

@@ -4,17 +4,24 @@
#include "spore/services/ClusterService.h" #include "spore/services/ClusterService.h"
#include "spore/services/TaskService.h" #include "spore/services/TaskService.h"
#include "spore/services/StaticFileService.h" #include "spore/services/StaticFileService.h"
#include "spore/services/MonitoringService.h"
#include <Arduino.h> #include <Arduino.h>
Spore::Spore() : ctx(), network(ctx), taskManager(ctx), cluster(ctx, taskManager), Spore::Spore() : ctx(), network(ctx), taskManager(ctx), cluster(ctx, taskManager),
apiServer(ctx, taskManager, ctx.config.api_server_port), apiServer(ctx, taskManager, ctx.config.api_server_port),
initialized(false), apiServerStarted(false) { initialized(false), apiServerStarted(false) {
// Rebuild labels from constructor + config labels
ctx.rebuildLabels();
} }
Spore::Spore(std::initializer_list<std::pair<String, String>> initialLabels) Spore::Spore(std::initializer_list<std::pair<String, String>> initialLabels)
: ctx(initialLabels), network(ctx), taskManager(ctx), cluster(ctx, taskManager), : ctx(initialLabels), network(ctx), taskManager(ctx), cluster(ctx, taskManager),
apiServer(ctx, taskManager, ctx.config.api_server_port), apiServer(ctx, taskManager, ctx.config.api_server_port),
initialized(false), apiServerStarted(false) { initialized(false), apiServerStarted(false) {
// Rebuild labels from constructor + config labels (config takes precedence)
ctx.rebuildLabels();
} }
Spore::~Spore() { Spore::~Spore() {
@@ -23,12 +30,12 @@ Spore::~Spore() {
void Spore::setup() { void Spore::setup() {
if (initialized) { if (initialized) {
Serial.println("[Spore] Already initialized, skipping setup"); LOG_INFO("Spore", "Already initialized, skipping setup");
return; return;
} }
Serial.begin(115200); Serial.begin(115200);
Serial.println("[Spore] Starting Spore framework..."); LOG_INFO("Spore", "Starting Spore framework...");
// Initialize core components // Initialize core components
initializeCore(); initializeCore();
@@ -37,21 +44,21 @@ void Spore::setup() {
registerCoreServices(); registerCoreServices();
initialized = true; initialized = true;
Serial.println("[Spore] Framework setup complete - call begin() to start API server"); LOG_INFO("Spore", "Framework setup complete - call begin() to start API server");
} }
void Spore::begin() { void Spore::begin() {
if (!initialized) { if (!initialized) {
Serial.println("[Spore] Framework not initialized, call setup() first"); LOG_ERROR("Spore", "Framework not initialized, call setup() first");
return; return;
} }
if (apiServerStarted) { if (apiServerStarted) {
Serial.println("[Spore] API server already started"); LOG_WARN("Spore", "API server already started");
return; return;
} }
Serial.println("[Spore] Starting API server..."); LOG_INFO("Spore", "Starting API server...");
// Start API server // Start API server
startApiServer(); startApiServer();
@@ -59,22 +66,25 @@ void Spore::begin() {
// Print initial task status // Print initial task status
taskManager.printTaskStatus(); taskManager.printTaskStatus();
Serial.println("[Spore] Framework ready!"); LOG_INFO("Spore", "Framework ready!");
} }
void Spore::loop() { void Spore::loop() {
if (!initialized) { if (!initialized) {
Serial.println("[Spore] Framework not initialized, call setup() first"); LOG_ERROR("Spore", "Framework not initialized, call setup() first");
return; return;
} }
// Execute main tasks
taskManager.execute(); taskManager.execute();
// Yield to allow other tasks to run
yield(); yield();
} }
void Spore::addService(std::shared_ptr<Service> service) { void Spore::registerService(std::shared_ptr<Service> service) {
if (!service) { if (!service) {
Serial.println("[Spore] Warning: Attempted to add null service"); LOG_WARN("Spore", "Attempted to add null service");
return; return;
} }
@@ -82,26 +92,27 @@ void Spore::addService(std::shared_ptr<Service> service) {
if (apiServerStarted) { if (apiServerStarted) {
// If API server is already started, register the service immediately // If API server is already started, register the service immediately
apiServer.addService(*service); apiServer.registerService(*service);
Serial.printf("[Spore] Added service '%s' to running API server\n", service->getName()); service->registerTasks(taskManager);
LOG_INFO("Spore", "Added service '" + String(service->getName()) + "' to running API server and task manager");
} else { } else {
Serial.printf("[Spore] Registered service '%s' (will be added to API server when begin() is called)\n", service->getName()); LOG_INFO("Spore", "Registered service '" + String(service->getName()) + "' (will be added to API server when begin() is called)");
} }
} }
void Spore::addService(Service* service) { void Spore::registerService(Service* service) {
if (!service) { if (!service) {
Serial.println("[Spore] Warning: Attempted to add null service"); LOG_WARN("Spore", "Attempted to add null service");
return; return;
} }
// Wrap raw pointer in shared_ptr with no-op deleter to avoid double-delete // Wrap raw pointer in shared_ptr with no-op deleter to avoid double-delete
addService(std::shared_ptr<Service>(service, [](Service*){})); registerService(std::shared_ptr<Service>(service, [](Service*){}));
} }
void Spore::initializeCore() { void Spore::initializeCore() {
Serial.println("[Spore] Initializing core components..."); LOG_INFO("Spore", "Initializing core components...");
// Setup WiFi first // Setup WiFi first
network.setupWiFi(); network.setupWiFi();
@@ -109,11 +120,11 @@ void Spore::initializeCore() {
// Initialize task manager // Initialize task manager
taskManager.initialize(); taskManager.initialize();
Serial.println("[Spore] Core components initialized"); LOG_INFO("Spore", "Core components initialized");
} }
void Spore::registerCoreServices() { void Spore::registerCoreServices() {
Serial.println("[Spore] Registering core services..."); LOG_INFO("Spore", "Registering core services...");
// Create core services // Create core services
auto nodeService = std::make_shared<NodeService>(ctx, apiServer); auto nodeService = std::make_shared<NodeService>(ctx, apiServer);
@@ -121,6 +132,7 @@ void Spore::registerCoreServices() {
auto clusterService = std::make_shared<ClusterService>(ctx); auto clusterService = std::make_shared<ClusterService>(ctx);
auto taskService = std::make_shared<TaskService>(taskManager); auto taskService = std::make_shared<TaskService>(taskManager);
auto staticFileService = std::make_shared<StaticFileService>(ctx, apiServer); auto staticFileService = std::make_shared<StaticFileService>(ctx, apiServer);
auto monitoringService = std::make_shared<MonitoringService>();
// Add to services list // Add to services list
services.push_back(nodeService); services.push_back(nodeService);
@@ -128,23 +140,25 @@ void Spore::registerCoreServices() {
services.push_back(clusterService); services.push_back(clusterService);
services.push_back(taskService); services.push_back(taskService);
services.push_back(staticFileService); services.push_back(staticFileService);
services.push_back(monitoringService);
Serial.println("[Spore] Core services registered"); LOG_INFO("Spore", "Core services registered");
} }
void Spore::startApiServer() { void Spore::startApiServer() {
if (apiServerStarted) { if (apiServerStarted) {
Serial.println("[Spore] API server already started"); LOG_WARN("Spore", "API server already started");
return; return;
} }
Serial.println("[Spore] Starting API server..."); LOG_INFO("Spore", "Starting API server...");
// Register all services with API server // Register all services with API server and task manager
for (auto& service : services) { for (auto& service : services) {
if (service) { if (service) {
apiServer.addService(*service); apiServer.registerService(*service);
Serial.printf("[Spore] Added service '%s' to API server\n", service->getName()); service->registerTasks(taskManager);
LOG_INFO("Spore", "Added service '" + String(service->getName()) + "' to API server and task manager");
} }
} }
@@ -152,5 +166,5 @@ void Spore::startApiServer() {
apiServer.begin(); apiServer.begin();
apiServerStarted = true; apiServerStarted = true;
Serial.println("[Spore] API server started"); LOG_INFO("Spore", "API server started");
} }

View File

@@ -1,5 +1,6 @@
#include "spore/core/ApiServer.h" #include "spore/core/ApiServer.h"
#include "spore/Service.h" #include "spore/Service.h"
#include "spore/util/Logging.h"
#include <algorithm> #include <algorithm>
const char* ApiServer::methodToStr(int method) { const char* ApiServer::methodToStr(int method) {
@@ -22,15 +23,16 @@ void ApiServer::registerEndpoint(const String& uri, int method,
endpoints.push_back(EndpointInfo{uri, method, params, serviceName, true}); endpoints.push_back(EndpointInfo{uri, method, params, serviceName, true});
// Update cluster if needed // Update cluster if needed
if (ctx.memberList && !ctx.memberList->empty()) { String localIPStr = ctx.localIP.toString();
auto it = ctx.memberList->find(ctx.hostname); auto member = ctx.memberList->getMember(localIPStr.c_str());
if (it != ctx.memberList->end()) { if (member) {
it->second.endpoints.push_back(EndpointInfo{uri, method, params, serviceName, true}); NodeInfo updatedNode = *member;
} updatedNode.endpoints.push_back(EndpointInfo{uri, method, params, serviceName, true});
ctx.memberList->updateMember(localIPStr.c_str(), updatedNode);
} }
} }
void ApiServer::addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler) { void ApiServer::registerEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler) {
// Get current service name if available // Get current service name if available
String serviceName = "unknown"; String serviceName = "unknown";
if (!services.empty()) { if (!services.empty()) {
@@ -40,7 +42,7 @@ void ApiServer::addEndpoint(const String& uri, int method, std::function<void(As
server.on(uri.c_str(), method, requestHandler); server.on(uri.c_str(), method, requestHandler);
} }
void ApiServer::addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler, void ApiServer::registerEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler,
std::function<void(AsyncWebServerRequest*, const String&, size_t, uint8_t*, size_t, bool)> uploadHandler) { std::function<void(AsyncWebServerRequest*, const String&, size_t, uint8_t*, size_t, bool)> uploadHandler) {
// Get current service name if available // Get current service name if available
String serviceName = "unknown"; String serviceName = "unknown";
@@ -52,7 +54,7 @@ void ApiServer::addEndpoint(const String& uri, int method, std::function<void(As
} }
// Overloads that also record minimal capability specs // Overloads that also record minimal capability specs
void ApiServer::addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler, void ApiServer::registerEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler,
const std::vector<ParamSpec>& params) { const std::vector<ParamSpec>& params) {
// Get current service name if available // Get current service name if available
String serviceName = "unknown"; String serviceName = "unknown";
@@ -63,7 +65,7 @@ void ApiServer::addEndpoint(const String& uri, int method, std::function<void(As
server.on(uri.c_str(), method, requestHandler); server.on(uri.c_str(), method, requestHandler);
} }
void ApiServer::addEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler, void ApiServer::registerEndpoint(const String& uri, int method, std::function<void(AsyncWebServerRequest*)> requestHandler,
std::function<void(AsyncWebServerRequest*, const String&, size_t, uint8_t*, size_t, bool)> uploadHandler, std::function<void(AsyncWebServerRequest*, const String&, size_t, uint8_t*, size_t, bool)> uploadHandler,
const std::vector<ParamSpec>& params) { const std::vector<ParamSpec>& params) {
// Get current service name if available // Get current service name if available
@@ -75,17 +77,118 @@ void ApiServer::addEndpoint(const String& uri, int method, std::function<void(As
server.on(uri.c_str(), method, requestHandler, uploadHandler); server.on(uri.c_str(), method, requestHandler, uploadHandler);
} }
void ApiServer::addService(Service& service) { void ApiServer::registerService(Service& service) {
services.push_back(service); services.push_back(service);
Serial.printf("[API] Added service: %s\n", service.getName()); LOG_INFO("API", "Added service: " + String(service.getName()));
}
void ApiServer::serveStatic(const String& uri, fs::FS& fs, const String& path, const String& cache_header) {
server.serveStatic(uri.c_str(), fs, path.c_str(), cache_header.c_str()).setDefaultFile("index.html");
LOG_INFO("API", "Registered static file serving: " + uri + " -> " + path);
} }
void ApiServer::begin() { void ApiServer::begin() {
// Setup streaming API (WebSocket)
setupWebSocket();
server.addHandler(&ws);
// Register all service endpoints // Register all service endpoints
for (auto& service : services) { for (auto& service : services) {
service.get().registerEndpoints(*this); service.get().registerEndpoints(*this);
Serial.printf("[API] Registered endpoints for service: %s\n", service.get().getName()); LOG_INFO("API", "Registered endpoints for service: " + String(service.get().getName()));
} }
server.begin(); server.begin();
} }
void ApiServer::setupWebSocket() {
ws.onEvent([this](AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) {
if (type == WS_EVT_DATA) {
AwsFrameInfo* info = (AwsFrameInfo*)arg;
if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
// Parse directly from the raw buffer with explicit length
JsonDocument doc;
DeserializationError err = deserializeJson(doc, (const char*)data, len);
if (!err) {
LOG_DEBUG("API", "Received event: " + String(doc["event"].as<String>()));
String eventName = doc["event"].as<String>();
String payloadStr;
if (doc["payload"].is<const char*>()) {
payloadStr = doc["payload"].as<const char*>();
} else if (!doc["payload"].isNull()) {
// If payload is an object/array, serialize it
String tmp; serializeJson(doc["payload"], tmp); payloadStr = tmp;
}
// Allow empty payload; services may treat it as defaults
if (eventName.length() > 0) {
// Inject origin tag into payload JSON if possible
String enriched = payloadStr;
if (payloadStr.length() > 0) {
JsonDocument pd;
if (!deserializeJson(pd, payloadStr)) {
pd["_origin"] = String("ws:") + String(client->id());
String tmp; serializeJson(pd, tmp); enriched = tmp;
} else {
// If payload is plain string, leave as-is (no origin)
}
}
std::string ev = eventName.c_str();
ctx.fire(ev, &enriched);
// Acknowledge
client->text("{\"ok\":true}");
} else {
client->text("{\"error\":\"Missing 'event'\"}");
}
} else {
client->text("{\"error\":\"Invalid JSON\"}");
}
}
} else if (type == WS_EVT_CONNECT) {
client->text("{\"hello\":\"ws connected\"}");
wsClients.push_back(client);
} else if (type == WS_EVT_DISCONNECT) {
wsClients.erase(std::remove(wsClients.begin(), wsClients.end(), client), wsClients.end());
}
});
// Subscribe to all local events and forward to websocket clients
ctx.onAny([this](const std::string& event, void* dataPtr) {
// Ignore raw UDP frames
if (event == "udp/raw") {
return;
}
String* payloadStrPtr = static_cast<String*>(dataPtr);
String payloadStr = payloadStrPtr ? *payloadStrPtr : String("");
// Extract and strip origin if present
String origin;
String cleanedPayload = payloadStr;
if (payloadStr.length() > 0) {
JsonDocument pd;
if (!deserializeJson(pd, payloadStr)) {
if (pd["_origin"].is<const char*>()) {
origin = pd["_origin"].as<const char*>();
pd.remove("_origin");
String tmp; serializeJson(pd, tmp); cleanedPayload = tmp;
}
}
}
JsonDocument outDoc;
outDoc["event"] = event.c_str();
outDoc["payload"] = cleanedPayload;
String out; serializeJson(outDoc, out);
if (origin.startsWith("ws:")) {
uint32_t originId = (uint32_t)origin.substring(3).toInt();
for (auto* c : wsClients) {
if (c && c->id() != originId) {
c->text(out);
}
}
} else {
ws.textAll(out);
}
});
}

View File

@@ -1,220 +1,493 @@
#include "spore/core/ClusterManager.h" #include "spore/core/ClusterManager.h"
#include "spore/internal/Globals.h" #include "spore/internal/Globals.h"
#include "spore/util/Logging.h"
#include "spore/types/NodeInfo.h"
ClusterManager::ClusterManager(NodeContext& ctx, TaskManager& taskMgr) : ctx(ctx), taskManager(taskMgr) { ClusterManager::ClusterManager(NodeContext& ctx, TaskManager& taskMgr) : ctx(ctx), taskManager(taskMgr) {
// Register callback for node_discovered event // Register callback for node/discovered event - this fires when network is ready
ctx.on("node_discovered", [this](void* data) { ctx.on("node/discovered", [this](void* data) {
NodeInfo* node = static_cast<NodeInfo*>(data); NodeInfo* node = static_cast<NodeInfo*>(data);
this->addOrUpdateNode(node->hostname, node->ip); this->addOrUpdateNode(node->hostname, node->ip);
}); });
// Centralized broadcast handler: services fire 'cluster/broadcast' with cluster/event JSON payload
ctx.on("cluster/broadcast", [this](void* data) {
String* jsonStr = static_cast<String*>(data);
if (!jsonStr) {
LOG_WARN("Cluster", "cluster/broadcast called with null data");
return;
}
// Subnet-directed broadcast (more reliable than 255.255.255.255 on some networks)
IPAddress ip = WiFi.localIP();
IPAddress mask = WiFi.subnetMask();
IPAddress bcast(ip[0] | ~mask[0], ip[1] | ~mask[1], ip[2] | ~mask[2], ip[3] | ~mask[3]);
LOG_DEBUG("Cluster", String("Broadcasting cluster/event to ") + bcast.toString() + " len=" + String(jsonStr->length()));
this->ctx.udp->beginPacket(bcast, this->ctx.config.udp_port);
String msg = String(ClusterProtocol::CLUSTER_EVENT_MSG) + ":" + *jsonStr;
this->ctx.udp->write(msg.c_str());
this->ctx.udp->endPacket();
});
// Handler for node update broadcasts: services fire 'cluster/node/update' when their node info changes
ctx.on("cluster/node/update", [this](void* data) {
// Trigger immediate NODE_UPDATE broadcast when node info changes
broadcastNodeUpdate();
});
// Handler for memberlist changes: print memberlist when it changes
ctx.on("cluster/memberlist/changed", [this](void* data) {
printMemberList();
});
// Register tasks // Register tasks
registerTasks(); registerTasks();
initMessageHandlers();
} }
void ClusterManager::registerTasks() { void ClusterManager::registerTasks() {
taskManager.registerTask("discovery_send", ctx.config.discovery_interval_ms, [this]() { sendDiscovery(); }); taskManager.registerTask("cluster_listen", ctx.config.cluster_listen_interval_ms, [this]() { listen(); });
taskManager.registerTask("discovery_listen", ctx.config.discovery_interval_ms / 10, [this]() { listenForDiscovery(); });
taskManager.registerTask("status_update", ctx.config.status_update_interval_ms, [this]() { updateAllNodeStatuses(); removeDeadNodes(); }); taskManager.registerTask("status_update", ctx.config.status_update_interval_ms, [this]() { updateAllNodeStatuses(); removeDeadNodes(); });
taskManager.registerTask("print_members", ctx.config.print_interval_ms, [this]() { printMemberList(); });
taskManager.registerTask("heartbeat", ctx.config.heartbeat_interval_ms, [this]() { heartbeatTaskCallback(); }); taskManager.registerTask("heartbeat", ctx.config.heartbeat_interval_ms, [this]() { heartbeatTaskCallback(); });
taskManager.registerTask("update_members_info", ctx.config.member_info_update_interval_ms, [this]() { updateAllMembersInfoTaskCallback(); }); LOG_INFO("ClusterManager", "Registered all cluster tasks");
Serial.println("[ClusterManager] Registered all cluster tasks");
} }
void ClusterManager::sendDiscovery() { // Discovery functionality removed - using heartbeat-only approach
//Serial.println("[Cluster] Sending discovery packet...");
ctx.udp->beginPacket("255.255.255.255", ctx.config.udp_port);
ctx.udp->write(ClusterProtocol::DISCOVERY_MSG);
ctx.udp->endPacket();
}
void ClusterManager::listenForDiscovery() { void ClusterManager::listen() {
int packetSize = ctx.udp->parsePacket(); int packetSize = ctx.udp->parsePacket();
if (packetSize) { if (!packetSize) {
char incoming[ClusterProtocol::UDP_BUF_SIZE];
int len = ctx.udp->read(incoming, ClusterProtocol::UDP_BUF_SIZE);
if (len > 0) {
incoming[len] = 0;
}
//Serial.printf("[UDP] Packet received: %s\n", incoming);
if (strcmp(incoming, ClusterProtocol::DISCOVERY_MSG) == 0) {
//Serial.printf("[UDP] Discovery request from: %s\n", ctx.udp->remoteIP().toString().c_str());
ctx.udp->beginPacket(ctx.udp->remoteIP(), ctx.config.udp_port);
String response = String(ClusterProtocol::RESPONSE_MSG) + ":" + ctx.hostname;
ctx.udp->write(response.c_str());
ctx.udp->endPacket();
//Serial.printf("[UDP] Sent response with hostname: %s\n", ctx.hostname.c_str());
} else if (strncmp(incoming, ClusterProtocol::RESPONSE_MSG, strlen(ClusterProtocol::RESPONSE_MSG)) == 0) {
char* hostPtr = incoming + strlen(ClusterProtocol::RESPONSE_MSG) + 1;
String nodeHost = String(hostPtr);
addOrUpdateNode(nodeHost, ctx.udp->remoteIP());
}
}
}
void ClusterManager::addOrUpdateNode(const String& nodeHost, IPAddress nodeIP) {
auto& memberList = *ctx.memberList;
// O(1) lookup instead of O(n) search
auto it = memberList.find(nodeHost);
if (it != memberList.end()) {
// Update existing node
it->second.ip = nodeIP;
it->second.lastSeen = millis();
//fetchNodeInfo(nodeIP); // Do not fetch here, handled by periodic task
return; return;
} }
// Add new node
NodeInfo newNode;
newNode.hostname = nodeHost;
newNode.ip = nodeIP;
newNode.lastSeen = millis();
updateNodeStatus(newNode, newNode.lastSeen, ctx.config.node_inactive_threshold_ms, ctx.config.node_dead_threshold_ms);
memberList[nodeHost] = newNode;
Serial.printf("[Cluster] Added node: %s @ %s | Status: %s | last update: 0\n",
nodeHost.c_str(),
newNode.ip.toString().c_str(),
statusToStr(newNode.status));
//fetchNodeInfo(nodeIP); // Do not fetch here, handled by periodic task
}
void ClusterManager::fetchNodeInfo(const IPAddress& ip) { char incoming[ClusterProtocol::UDP_BUF_SIZE];
if(ip == ctx.localIP) { int len = ctx.udp->read(incoming, ClusterProtocol::UDP_BUF_SIZE);
Serial.println("[Cluster] Skipping fetch for local node"); if (len <= 0) {
return; return;
} }
unsigned long requestStart = millis(); if (len >= (int)ClusterProtocol::UDP_BUF_SIZE) {
HTTPClient http; incoming[ClusterProtocol::UDP_BUF_SIZE - 1] = 0;
WiFiClient client; } else {
String url = "http://" + ip.toString() + ClusterProtocol::API_NODE_STATUS; incoming[len] = 0;
http.begin(client, url); }
int httpCode = http.GET(); handleIncomingMessage(incoming);
unsigned long requestEnd = millis(); }
unsigned long requestDuration = requestEnd - requestStart;
if (httpCode == 200) { void ClusterManager::initMessageHandlers() {
String payload = http.getString(); messageHandlers.clear();
JsonDocument doc; messageHandlers.push_back({ &ClusterManager::isRawMsg, [this](const char* msg){ this->onRawMessage(msg); }, "RAW" });
DeserializationError err = deserializeJson(doc, payload); messageHandlers.push_back({ &ClusterManager::isHeartbeatMsg, [this](const char* msg){ this->onHeartbeat(msg); }, "HEARTBEAT" });
if (!err) { messageHandlers.push_back({ &ClusterManager::isNodeUpdateMsg, [this](const char* msg){ this->onNodeUpdate(msg); }, "NODE_UPDATE" });
auto& memberList = *ctx.memberList; messageHandlers.push_back({ &ClusterManager::isClusterEventMsg, [this](const char* msg){ this->onClusterEvent(msg); }, "CLUSTER_EVENT" });
// Still need to iterate since we're searching by IP, not hostname }
for (auto& pair : memberList) {
NodeInfo& node = pair.second; void ClusterManager::handleIncomingMessage(const char* incoming) {
if (node.ip == ip) { for (const auto& h : messageHandlers) {
node.resources.freeHeap = doc["freeHeap"]; if (h.predicate(incoming)) {
node.resources.chipId = doc["chipId"]; h.handle(incoming);
node.resources.sdkVersion = (const char*)doc["sdkVersion"]; return;
node.resources.cpuFreqMHz = doc["cpuFreqMHz"]; }
node.resources.flashChipSize = doc["flashChipSize"]; }
node.status = NodeInfo::ACTIVE; // Unknown message - log first token
node.latency = requestDuration; const char* colon = strchr(incoming, ':');
node.lastSeen = millis(); String head;
node.endpoints.clear(); if (colon) {
if (doc["api"].is<JsonArray>()) { head = String(incoming).substring(0, colon - incoming);
JsonArray apiArr = doc["api"].as<JsonArray>(); } else {
for (JsonObject apiObj : apiArr) { head = String(incoming);
String uri = (const char*)apiObj["uri"]; }
int method = apiObj["method"]; LOG_DEBUG("Cluster", String("Unknown cluster message: ") + head);
// Create basic EndpointInfo without params for cluster nodes }
EndpointInfo endpoint;
endpoint.uri = uri; bool ClusterManager::isHeartbeatMsg(const char* msg) {
endpoint.method = method; return strncmp(msg, ClusterProtocol::HEARTBEAT_MSG, strlen(ClusterProtocol::HEARTBEAT_MSG)) == 0;
endpoint.isLocal = false; }
endpoint.serviceName = "remote";
node.endpoints.push_back(endpoint); bool ClusterManager::isNodeUpdateMsg(const char* msg) {
} return strncmp(msg, ClusterProtocol::NODE_UPDATE_MSG, strlen(ClusterProtocol::NODE_UPDATE_MSG)) == 0;
} }
// Parse labels if present
node.labels.clear(); bool ClusterManager::isClusterEventMsg(const char* msg) {
if (doc["labels"].is<JsonObject>()) { return strncmp(msg, ClusterProtocol::CLUSTER_EVENT_MSG, strlen(ClusterProtocol::CLUSTER_EVENT_MSG)) == 0;
JsonObject labelsObj = doc["labels"].as<JsonObject>(); }
for (JsonPair kvp : labelsObj) {
String k = String(kvp.key().c_str()); bool ClusterManager::isRawMsg(const char* msg) {
String v = String(labelsObj[kvp.key()]); // RAW frames must be "RAW:<payload>"; enforce the delimiter so we skip things like "RAW_HEARTBEAT".
node.labels[k] = v; const std::size_t prefixLen = strlen(ClusterProtocol::RAW_MSG);
} if (strncmp(msg, ClusterProtocol::RAW_MSG, prefixLen) != 0) {
} return false;
Serial.printf("[Cluster] Fetched info for node: %s @ %s\n", node.hostname.c_str(), ip.toString().c_str()); }
break; return msg[prefixLen] == ':';
} }
// Discovery functionality removed - using heartbeat-only approach
void ClusterManager::onHeartbeat(const char* msg) {
// Extract hostname from heartbeat message: "cluster/heartbeat:hostname"
const char* colon = strchr(msg, ':');
if (!colon) {
LOG_WARN("Cluster", "Invalid heartbeat message format");
return;
}
String hostname = String(colon + 1);
IPAddress senderIP = ctx.udp->remoteIP();
// Update memberlist with the heartbeat
addOrUpdateNode(hostname, senderIP);
// Respond with minimal node info (hostname, ip, uptime, labels)
sendNodeInfo(hostname, senderIP);
}
void ClusterManager::onNodeUpdate(const char* msg) {
// Message format: "node/update:hostname:{json}"
const char* firstColon = strchr(msg, ':');
if (!firstColon) {
LOG_WARN("Cluster", "Invalid NODE_UPDATE message format");
return;
}
const char* secondColon = strchr(firstColon + 1, ':');
if (!secondColon) {
LOG_WARN("Cluster", "Invalid NODE_UPDATE message format");
return;
}
String hostnamePart = String(firstColon + 1);
String hostname = hostnamePart.substring(0, secondColon - firstColon - 1);
const char* jsonCStr = secondColon + 1;
JsonDocument doc;
DeserializationError err = deserializeJson(doc, jsonCStr);
if (err) {
LOG_WARN("Cluster", String("Failed to parse NODE_UPDATE JSON from ") + ctx.udp->remoteIP().toString());
return;
}
// The NODE_UPDATE contains info about the target node (hostname from message)
// but is sent FROM the responding node (ctx.udp->remoteIP())
// We need to find the responding node in the memberlist, not the target node
IPAddress respondingNodeIP = ctx.udp->remoteIP();
String respondingIPStr = respondingNodeIP.toString();
// Find the responding node by IP address
auto respondingMember = ctx.memberList->getMember(respondingIPStr.c_str());
if (!respondingMember) {
LOG_WARN("Cluster", String("Received NODE_UPDATE from unknown node: ") + respondingNodeIP.toString());
return;
}
// Calculate latency only if we recently sent a heartbeat (within last 1 second)
unsigned long latency = 0;
unsigned long now = millis();
if (lastHeartbeatSentAt != 0 && (now - lastHeartbeatSentAt) < 1000) { // 1 second window
latency = now - lastHeartbeatSentAt;
lastHeartbeatSentAt = 0; // Reset for next calculation
}
// Create updated node info
NodeInfo updatedNode = *respondingMember;
bool hostnameChanged = false;
bool labelsChanged = false;
// Update hostname if provided
if (doc["hostname"].is<const char*>()) {
String newHostname = doc["hostname"].as<const char*>();
if (updatedNode.hostname != newHostname) {
updatedNode.hostname = newHostname;
hostnameChanged = true;
}
}
// Update uptime if provided
if (doc["uptime"].is<unsigned long>()) {
updatedNode.uptime = doc["uptime"];
}
// Update labels if provided
if (doc["labels"].is<JsonObject>()) {
JsonObject labelsObj = doc["labels"].as<JsonObject>();
std::map<String, String> newLabels;
for (JsonPair kvp : labelsObj) {
const char* key = kvp.key().c_str();
const char* value = labelsObj[kvp.key()];
newLabels[key] = String(value);
}
// Compare with existing labels
if (newLabels != updatedNode.labels) {
labelsChanged = true;
updatedNode.labels = newLabels;
}
}
// Update timing and status
updatedNode.lastSeen = now;
updatedNode.status = NodeInfo::ACTIVE;
// Update latency if we calculated it (preserve existing value if not)
if (latency > 0) {
updatedNode.latency = latency;
}
// Persist the updated node info to the memberlist
ctx.memberList->updateMember(respondingIPStr.c_str(), updatedNode);
// Check if any fields changed that require broadcasting
bool nodeInfoChanged = hostnameChanged || labelsChanged;
if (nodeInfoChanged) {
// Fire cluster/node/update event to trigger broadcast
ctx.fire("cluster/node/update", nullptr);
}
LOG_DEBUG("Cluster", String("Updated responding node ") + updatedNode.hostname + " @ " + respondingNodeIP.toString() +
" | hostname: " + (hostnameChanged ? "changed" : "unchanged") +
" | labels: " + (labelsChanged ? "changed" : "unchanged") +
" | latency: " + (latency > 0 ? String(latency) + "ms" : "not calculated"));
}
void ClusterManager::sendNodeInfo(const String& targetHostname, const IPAddress& targetIP) {
JsonDocument doc;
// Get our node info for the response (we're the responding node)
String localIPStr = ctx.localIP.toString();
auto member = ctx.memberList->getMember(localIPStr.c_str());
if (member) {
const NodeInfo& node = *member;
// Response contains info about ourselves (the responding node)
doc["hostname"] = node.hostname;
doc["ip"] = node.ip.toString();
doc["uptime"] = node.uptime;
// Add labels if present
if (!node.labels.empty()) {
JsonObject labelsObj = doc["labels"].to<JsonObject>();
for (const auto& kv : node.labels) {
labelsObj[kv.first.c_str()] = kv.second;
} }
} }
} else { } else {
Serial.printf("[Cluster] Failed to fetch info for node @ %s, HTTP code: %d\n", ip.toString().c_str(), httpCode); // Fallback to basic info if not in memberlist
doc["hostname"] = ctx.hostname;
doc["ip"] = ctx.localIP.toString();
doc["uptime"] = millis();
}
String json;
serializeJson(doc, json);
// Send NODE_UPDATE:targetHostname:{json about responding node}
ctx.udp->beginPacket(targetIP, ctx.config.udp_port);
String msg = String(ClusterProtocol::NODE_UPDATE_MSG) + ":" + targetHostname + ":" + json;
ctx.udp->write(msg.c_str());
ctx.udp->endPacket();
LOG_DEBUG("Cluster", String("Sent NODE_UPDATE response to ") + targetHostname + " @ " + targetIP.toString());
}
void ClusterManager::onClusterEvent(const char* msg) {
// Message format: cluster/event:{"event":"...","data":"<json string>"}
const char* jsonStart = msg + strlen(ClusterProtocol::CLUSTER_EVENT_MSG) + 1; // skip prefix and ':'
if (*jsonStart == '\0') {
LOG_DEBUG("Cluster", "cluster/event received with empty payload");
return;
}
LOG_DEBUG("Cluster", String("cluster/event raw from ") + ctx.udp->remoteIP().toString() + " len=" + String(strlen(jsonStart)));
JsonDocument doc;
DeserializationError err = deserializeJson(doc, jsonStart);
if (err) {
LOG_ERROR("Cluster", String("Failed to parse cluster/event JSON from ") + ctx.udp->remoteIP().toString());
return;
}
// Robust extraction of event and data
String eventStr;
if (doc["event"].is<const char*>()) {
eventStr = doc["event"].as<const char*>();
} else if (doc["event"].is<String>()) {
eventStr = doc["event"].as<String>();
}
String data;
if (doc["data"].is<const char*>()) {
data = doc["data"].as<const char*>();
} else if (doc["data"].is<JsonVariantConst>()) {
// If data is a nested JSON object/array, serialize it back to string
String tmp;
serializeJson(doc["data"], tmp);
data = tmp;
}
if (eventStr.length() == 0 || data.length() == 0) {
String dbg;
serializeJson(doc, dbg);
LOG_WARN("Cluster", String("cluster/event missing 'event' or 'data' | payload=") + dbg);
return;
}
std::string eventKey(eventStr.c_str());
LOG_DEBUG("Cluster", String("Firing event '") + eventStr + "' with dataLen=" + String(data.length()));
ctx.fire(eventKey, &data);
}
void ClusterManager::onRawMessage(const char* msg) {
const std::size_t prefixLen = strlen(ClusterProtocol::RAW_MSG);
if (msg[prefixLen] != ':') {
LOG_WARN("Cluster", "RAW message received without payload delimiter");
return;
}
const char* payloadStart = msg + prefixLen + 1;
if (*payloadStart == '\0') {
LOG_WARN("Cluster", "RAW message received with empty payload");
return;
}
String payload(payloadStart);
ctx.fire("udp/raw", &payload);
}
void ClusterManager::addOrUpdateNode(const String& nodeHost, IPAddress nodeIP) {
bool memberlistChanged = false;
String ipStr = nodeIP.toString();
// Check if member exists
auto existingMember = ctx.memberList->getMember(ipStr.c_str());
if (existingMember) {
// Update existing node - preserve all existing field values
NodeInfo updatedNode = *existingMember;
if (updatedNode.ip != nodeIP) {
updatedNode.ip = nodeIP;
memberlistChanged = true;
}
updatedNode.lastSeen = millis();
ctx.memberList->updateMember(ipStr.c_str(), updatedNode);
} else {
// Add new node
NodeInfo newNode;
newNode.hostname = nodeHost;
newNode.ip = nodeIP;
newNode.lastSeen = millis();
updateNodeStatus(newNode, newNode.lastSeen, ctx.config.node_inactive_threshold_ms, ctx.config.node_dead_threshold_ms);
// Initialize static resources if this is the local node being added for the first time
if (nodeIP == ctx.localIP && nodeHost == ctx.hostname) {
newNode.resources.chipId = ESP.getChipId();
newNode.resources.sdkVersion = String(ESP.getSdkVersion());
newNode.resources.cpuFreqMHz = ESP.getCpuFreqMHz();
newNode.resources.flashChipSize = ESP.getFlashChipSize();
LOG_DEBUG("Cluster", "Initialized static resources for local node");
}
ctx.memberList->addMember(ipStr.c_str(), newNode);
memberlistChanged = true;
LOG_INFO("Cluster", "Added node: " + nodeHost + " @ " + newNode.ip.toString() + " | Status: " + statusToStr(newNode.status) + " | last update: 0");
}
// Fire event if memberlist changed
if (memberlistChanged) {
ctx.fire("cluster/memberlist/changed", nullptr);
} }
http.end();
} }
void ClusterManager::heartbeatTaskCallback() { void ClusterManager::heartbeatTaskCallback() {
auto& memberList = *ctx.memberList; // Update local node resources and lastSeen since we're actively sending heartbeats
auto it = memberList.find(ctx.hostname); String localIPStr = ctx.localIP.toString();
if (it != memberList.end()) { auto member = ctx.memberList->getMember(localIPStr.c_str());
NodeInfo& node = it->second; if (member) {
node.lastSeen = millis(); NodeInfo node = *member;
node.status = NodeInfo::ACTIVE; updateLocalNodeResources(node);
updateLocalNodeResources(); node.lastSeen = millis(); // Update lastSeen since we're actively participating
ctx.fire("node_discovered", &node); ctx.memberList->updateMember(localIPStr.c_str(), node);
} }
// Broadcast heartbeat - peers will respond with NODE_UPDATE
lastHeartbeatSentAt = millis();
ctx.udp->beginPacket("255.255.255.255", ctx.config.udp_port);
String hb = String(ClusterProtocol::HEARTBEAT_MSG) + ":" + ctx.hostname;
ctx.udp->write(hb.c_str());
ctx.udp->endPacket();
LOG_DEBUG("Cluster", String("Sent heartbeat: ") + ctx.hostname);
} }
void ClusterManager::updateAllMembersInfoTaskCallback() { void ClusterManager::updateAllMembersInfoTaskCallback() {
auto& memberList = *ctx.memberList; // HTTP-based member info fetching disabled; node info is provided via UDP responses to heartbeats
for (auto& pair : memberList) { // No-op to reduce network and memory usage
const NodeInfo& node = pair.second; }
if (node.ip != ctx.localIP) {
fetchNodeInfo(node.ip); void ClusterManager::broadcastNodeUpdate() {
// Broadcast our current node info as NODE_UPDATE to all cluster members
String localIPStr = ctx.localIP.toString();
auto member = ctx.memberList->getMember(localIPStr.c_str());
if (!member) {
return;
}
const NodeInfo& node = *member;
JsonDocument doc;
doc["hostname"] = node.hostname;
doc["uptime"] = node.uptime;
// Add labels if present
if (!node.labels.empty()) {
JsonObject labelsObj = doc["labels"].to<JsonObject>();
for (const auto& kv : node.labels) {
labelsObj[kv.first.c_str()] = kv.second;
} }
} }
String json;
serializeJson(doc, json);
// Broadcast to all cluster members
ctx.udp->beginPacket("255.255.255.255", ctx.config.udp_port);
String msg = String(ClusterProtocol::NODE_UPDATE_MSG) + ":" + ctx.hostname + ":" + json;
ctx.udp->write(msg.c_str());
ctx.udp->endPacket();
LOG_DEBUG("Cluster", String("Broadcasted NODE_UPDATE for ") + ctx.hostname);
} }
void ClusterManager::updateAllNodeStatuses() { void ClusterManager::updateAllNodeStatuses() {
auto& memberList = *ctx.memberList;
unsigned long now = millis(); unsigned long now = millis();
for (auto& pair : memberList) { ctx.memberList->updateAllNodeStatuses(now, ctx.config.node_inactive_threshold_ms, ctx.config.node_dead_threshold_ms);
NodeInfo& node = pair.second;
updateNodeStatus(node, now, ctx.config.node_inactive_threshold_ms, ctx.config.node_dead_threshold_ms);
}
} }
void ClusterManager::removeDeadNodes() { void ClusterManager::removeDeadNodes() {
auto& memberList = *ctx.memberList; size_t removedCount = ctx.memberList->removeDeadMembers();
unsigned long now = millis(); if (removedCount > 0) {
LOG_INFO("Cluster", String("Removed ") + removedCount + " dead nodes");
// Use iterator to safely remove elements from map ctx.fire("cluster/memberlist/changed", nullptr);
for (auto it = memberList.begin(); it != memberList.end(); ) {
unsigned long diff = now - it->second.lastSeen;
if (it->second.status == NodeInfo::DEAD && diff > ctx.config.node_dead_threshold_ms) {
Serial.printf("[Cluster] Removing node: %s\n", it->second.hostname.c_str());
it = memberList.erase(it);
} else {
++it;
}
} }
} }
void ClusterManager::printMemberList() { void ClusterManager::printMemberList() {
auto& memberList = *ctx.memberList; size_t count = ctx.memberList->getMemberCount();
if (memberList.empty()) { if (count == 0) {
Serial.println("[Cluster] Member List: empty"); LOG_INFO("Cluster", "Member List: empty");
return; return;
} }
Serial.println("[Cluster] Member List:"); LOG_INFO("Cluster", "Member List:");
for (const auto& pair : memberList) { ctx.memberList->forEachMember([](const std::string& ip, const NodeInfo& node) {
const NodeInfo& node = pair.second; LOG_INFO("Cluster", " " + node.hostname + " @ " + node.ip.toString() + " | Status: " + statusToStr(node.status) + " | last seen: " + String(millis() - node.lastSeen));
Serial.printf(" %s @ %s | Status: %s | last seen: %lu\n", node.hostname.c_str(), node.ip.toString().c_str(), statusToStr(node.status), millis() - node.lastSeen); });
}
} }
void ClusterManager::updateLocalNodeResources() { void ClusterManager::updateLocalNodeResources(NodeInfo& node) {
auto& memberList = *ctx.memberList; // Update node status and timing
auto it = memberList.find(ctx.hostname); node.lastSeen = millis();
if (it != memberList.end()) { node.status = NodeInfo::ACTIVE;
NodeInfo& node = it->second; node.uptime = millis();
node.resources.freeHeap = ESP.getFreeHeap();
node.resources.chipId = ESP.getChipId(); // Update dynamic resources (always updated)
node.resources.sdkVersion = String(ESP.getSdkVersion()); uint32_t freeHeap = ESP.getFreeHeap();
node.resources.cpuFreqMHz = ESP.getCpuFreqMHz(); node.resources.freeHeap = freeHeap;
node.resources.flashChipSize = ESP.getFlashChipSize();
// Log memory warnings if heap is getting low
if (freeHeap < ctx.config.low_memory_threshold_bytes) {
LOG_WARN("Cluster", "Low memory warning: " + String(freeHeap) + " bytes free");
} else if (freeHeap < ctx.config.critical_memory_threshold_bytes) {
LOG_ERROR("Cluster", "Critical memory warning: " + String(freeHeap) + " bytes free");
} }
} }

View File

@@ -0,0 +1,114 @@
#include "spore/core/Memberlist.h"
#include <algorithm>
Memberlist::Memberlist() = default;
Memberlist::~Memberlist() = default;
bool Memberlist::addOrUpdateMember(const std::string& ip, const NodeInfo& node) {
auto it = m_members.find(ip);
if (it != m_members.end()) {
// Update existing member
it->second = node;
it->second.lastSeen = millis(); // Update last seen time
return true;
} else {
// Add new member
NodeInfo newNode = node;
newNode.lastSeen = millis();
m_members[ip] = newNode;
return true;
}
}
bool Memberlist::addMember(const std::string& ip, const NodeInfo& node) {
if (m_members.find(ip) != m_members.end()) {
return false; // Member already exists
}
NodeInfo newNode = node;
newNode.lastSeen = millis();
m_members[ip] = newNode;
return true;
}
bool Memberlist::updateMember(const std::string& ip, const NodeInfo& node) {
auto it = m_members.find(ip);
if (it == m_members.end()) {
return false; // Member doesn't exist
}
it->second = node;
it->second.lastSeen = millis(); // Update last seen time
return true;
}
bool Memberlist::removeMember(const std::string& ip) {
auto it = m_members.find(ip);
if (it == m_members.end()) {
return false; // Member doesn't exist
}
m_members.erase(it);
return true;
}
std::optional<NodeInfo> Memberlist::getMember(const std::string& ip) const {
auto it = m_members.find(ip);
if (it != m_members.end()) {
return it->second;
}
return std::nullopt;
}
void Memberlist::forEachMember(std::function<void(const std::string&, const NodeInfo&)> callback) const {
for (const auto& pair : m_members) {
callback(pair.first, pair.second);
}
}
bool Memberlist::forEachMemberUntil(std::function<bool(const std::string&, const NodeInfo&)> callback) const {
for (const auto& pair : m_members) {
if (!callback(pair.first, pair.second)) {
return false;
}
}
return true;
}
size_t Memberlist::getMemberCount() const {
return m_members.size();
}
void Memberlist::updateAllNodeStatuses(unsigned long currentTime,
unsigned long staleThresholdMs,
unsigned long deadThresholdMs,
std::function<void(const std::string&, NodeInfo::Status, NodeInfo::Status)> onStatusChange) {
for (auto& [ip, node] : m_members) {
NodeInfo::Status oldStatus = node.status;
updateNodeStatus(node, currentTime, staleThresholdMs, deadThresholdMs);
if (oldStatus != node.status && onStatusChange) {
onStatusChange(ip, oldStatus, node.status);
}
}
}
size_t Memberlist::removeDeadMembers() {
size_t removedCount = 0;
auto it = m_members.begin();
while (it != m_members.end()) {
if (it->second.status == NodeInfo::Status::DEAD) {
it = m_members.erase(it);
++removedCount;
} else {
++it;
}
}
return removedCount;
}
bool Memberlist::hasMember(const std::string& ip) const {
return m_members.find(ip) != m_members.end();
}
void Memberlist::clear() {
m_members.clear();
}

View File

@@ -1,26 +1,27 @@
#include "spore/core/NetworkManager.h" #include "spore/core/NetworkManager.h"
#include "spore/util/Logging.h"
// SSID and password are now configured via Config class // SSID and password are now configured via Config class
void NetworkManager::scanWifi() { void NetworkManager::scanWifi() {
if (!isScanning) { if (!isScanning) {
isScanning = true; isScanning = true;
Serial.println("[WiFi] Starting WiFi scan..."); LOG_INFO("WiFi", "Starting WiFi scan...");
// Start async WiFi scan // Start async WiFi scan
WiFi.scanNetworksAsync([this](int networksFound) { WiFi.scanNetworksAsync([this](int networksFound) {
Serial.printf("[WiFi] Scan completed, found %d networks\n", networksFound); LOG_INFO("WiFi", "Scan completed, found " + String(networksFound) + " networks");
this->processAccessPoints(); this->processAccessPoints();
this->isScanning = false; this->isScanning = false;
}, true); }, true);
} else { } else {
Serial.println("[WiFi] Scan already in progress..."); LOG_WARN("WiFi", "Scan already in progress...");
} }
} }
void NetworkManager::processAccessPoints() { void NetworkManager::processAccessPoints() {
int numNetworks = WiFi.scanComplete(); int numNetworks = WiFi.scanComplete();
if (numNetworks <= 0) { if (numNetworks <= 0) {
Serial.println("[WiFi] No networks found or scan not complete"); LOG_WARN("WiFi", "No networks found or scan not complete");
return; return;
} }
@@ -43,8 +44,7 @@ void NetworkManager::processAccessPoints() {
accessPoints.push_back(ap); accessPoints.push_back(ap);
Serial.printf("[WiFi] Found network %d: %s, Ch: %d, RSSI: %d\n", LOG_DEBUG("WiFi", "Found network " + String(i + 1) + ": " + ap.ssid + ", Ch: " + String(ap.channel) + ", RSSI: " + String(ap.rssi));
i + 1, ap.ssid.c_str(), ap.channel, ap.rssi);
} }
// Free the memory used by the scan // Free the memory used by the scan
@@ -65,6 +65,16 @@ void NetworkManager::setWiFiConfig(const String& ssid, const String& password,
ctx.config.wifi_retry_delay_ms = retry_delay_ms; ctx.config.wifi_retry_delay_ms = retry_delay_ms;
} }
bool NetworkManager::saveConfig() {
return ctx.config.saveToFile();
}
void NetworkManager::restartNode() {
LOG_INFO("NetworkManager", "Restarting node after WiFi configuration change...");
delay(100); // Give time for response to be sent
ESP.restart();
}
void NetworkManager::setHostnameFromMac() { void NetworkManager::setHostnameFromMac() {
uint8_t mac[6]; uint8_t mac[6];
WiFi.macAddress(mac); WiFi.macAddress(mac);
@@ -77,31 +87,26 @@ void NetworkManager::setHostnameFromMac() {
void NetworkManager::setupWiFi() { void NetworkManager::setupWiFi() {
WiFi.mode(WIFI_STA); WiFi.mode(WIFI_STA);
WiFi.begin(ctx.config.wifi_ssid.c_str(), ctx.config.wifi_password.c_str()); WiFi.begin(ctx.config.wifi_ssid.c_str(), ctx.config.wifi_password.c_str());
Serial.println("[WiFi] Connecting to AP..."); LOG_INFO("WiFi", "Connecting to AP...");
unsigned long startAttemptTime = millis(); unsigned long startAttemptTime = millis();
while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < ctx.config.wifi_connect_timeout_ms) { while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < ctx.config.wifi_connect_timeout_ms) {
delay(ctx.config.wifi_retry_delay_ms); delay(ctx.config.wifi_retry_delay_ms);
Serial.print("."); // Progress dots handled by delay, no logging needed
} }
if (WiFi.status() == WL_CONNECTED) { if (WiFi.status() == WL_CONNECTED) {
Serial.println(); LOG_INFO("WiFi", "Connected to AP, IP: " + WiFi.localIP().toString());
Serial.print("[WiFi] Connected to AP, IP: ");
Serial.println(WiFi.localIP());
} else { } else {
Serial.println(); LOG_WARN("WiFi", "Failed to connect to AP. Creating AP...");
Serial.println("[WiFi] Failed to connect to AP. Creating AP...");
WiFi.mode(WIFI_AP); WiFi.mode(WIFI_AP);
WiFi.softAP(ctx.config.wifi_ssid.c_str(), ctx.config.wifi_password.c_str()); WiFi.softAP(ctx.config.wifi_ssid.c_str(), ctx.config.wifi_password.c_str());
Serial.print("[WiFi] AP created, IP: "); LOG_INFO("WiFi", "AP created, IP: " + WiFi.softAPIP().toString());
Serial.println(WiFi.softAPIP());
} }
setHostnameFromMac(); setHostnameFromMac();
ctx.udp->begin(ctx.config.udp_port); ctx.udp->begin(ctx.config.udp_port);
ctx.localIP = WiFi.localIP(); ctx.localIP = WiFi.localIP();
ctx.hostname = WiFi.hostname(); ctx.hostname = WiFi.hostname();
Serial.print("[WiFi] Hostname set to: "); LOG_INFO("WiFi", "Hostname set to: " + ctx.hostname);
Serial.println(ctx.hostname); LOG_INFO("WiFi", "UDP listening on port " + String(ctx.config.udp_port));
Serial.printf("[WiFi] UDP listening on port %d\n", ctx.config.udp_port);
// Populate self NodeInfo // Populate self NodeInfo
ctx.self.hostname = ctx.hostname; ctx.self.hostname = ctx.hostname;
@@ -112,17 +117,11 @@ void NetworkManager::setupWiFi() {
} }
ctx.self.lastSeen = millis(); ctx.self.lastSeen = millis();
ctx.self.status = NodeInfo::ACTIVE; ctx.self.status = NodeInfo::ACTIVE;
ctx.self.labels["hostname"] = ctx.hostname;
// Ensure member list has an entry for this node // Ensure member list has an entry for this node
auto &memberList = *ctx.memberList; String localIPStr = ctx.localIP.toString();
auto existing = memberList.find(ctx.hostname); ctx.memberList->addOrUpdateMember(localIPStr.c_str(), ctx.self);
if (existing == memberList.end()) {
memberList[ctx.hostname] = ctx.self;
} else {
existing->second = ctx.self;
}
// Notify listeners that the node is (re)discovered // Notify listeners that the node is (re)discovered
ctx.fire("node_discovered", &ctx.self); ctx.fire("node/discovered", &ctx.self);
} }

View File

@@ -2,7 +2,7 @@
NodeContext::NodeContext() { NodeContext::NodeContext() {
udp = new WiFiUDP(); udp = new WiFiUDP();
memberList = new std::map<String, NodeInfo>(); memberList = std::make_unique<Memberlist>();
hostname = ""; hostname = "";
self.hostname = ""; self.hostname = "";
self.ip = IPAddress(); self.ip = IPAddress();
@@ -12,13 +12,14 @@ NodeContext::NodeContext() {
NodeContext::NodeContext(std::initializer_list<std::pair<String, String>> initialLabels) : NodeContext() { NodeContext::NodeContext(std::initializer_list<std::pair<String, String>> initialLabels) : NodeContext() {
for (const auto& kv : initialLabels) { for (const auto& kv : initialLabels) {
constructorLabels[kv.first] = kv.second;
self.labels[kv.first] = kv.second; self.labels[kv.first] = kv.second;
} }
} }
NodeContext::~NodeContext() { NodeContext::~NodeContext() {
delete udp; delete udp;
delete memberList; // memberList is a unique_ptr, so no need to delete manually
} }
void NodeContext::on(const std::string& event, EventCallback cb) { void NodeContext::on(const std::string& event, EventCallback cb) {
@@ -29,4 +30,26 @@ void NodeContext::fire(const std::string& event, void* data) {
for (auto& cb : eventRegistry[event]) { for (auto& cb : eventRegistry[event]) {
cb(data); cb(data);
} }
for (auto& acb : anyEventSubscribers) {
acb(event, data);
}
}
void NodeContext::onAny(AnyEventCallback cb) {
anyEventSubscribers.push_back(cb);
}
void NodeContext::rebuildLabels() {
// Clear current labels
self.labels.clear();
// Add constructor labels first
for (const auto& kv : constructorLabels) {
self.labels[kv.first] = kv.second;
}
// Add config labels (these override constructor labels if same key)
for (const auto& kv : config.labels) {
self.labels[kv.first] = kv.second;
}
} }

View File

@@ -1,4 +1,5 @@
#include "spore/core/TaskManager.h" #include "spore/core/TaskManager.h"
#include "spore/util/Logging.h"
#include <Arduino.h> #include <Arduino.h>
TaskManager::TaskManager(NodeContext& ctx) : ctx(ctx) {} TaskManager::TaskManager(NodeContext& ctx) : ctx(ctx) {}
@@ -31,9 +32,9 @@ void TaskManager::enableTask(const std::string& name) {
int idx = findTaskIndex(name); int idx = findTaskIndex(name);
if (idx >= 0) { if (idx >= 0) {
taskDefinitions[idx].enabled = true; taskDefinitions[idx].enabled = true;
Serial.printf("[TaskManager] Enabled task: %s\n", name.c_str()); LOG_INFO("TaskManager", "Enabled task: " + String(name.c_str()));
} else { } else {
Serial.printf("[TaskManager] Warning: Task not found: %s\n", name.c_str()); LOG_WARN("TaskManager", "Task not found: " + String(name.c_str()));
} }
} }
@@ -41,9 +42,9 @@ void TaskManager::disableTask(const std::string& name) {
int idx = findTaskIndex(name); int idx = findTaskIndex(name);
if (idx >= 0) { if (idx >= 0) {
taskDefinitions[idx].enabled = false; taskDefinitions[idx].enabled = false;
Serial.printf("[TaskManager] Disabled task: %s\n", name.c_str()); LOG_INFO("TaskManager", "Disabled task: " + String(name.c_str()));
} else { } else {
Serial.printf("[TaskManager] Warning: Task not found: %s\n", name.c_str()); LOG_WARN("TaskManager", "Task not found: " + String(name.c_str()));
} }
} }
@@ -51,9 +52,9 @@ void TaskManager::setTaskInterval(const std::string& name, unsigned long interva
int idx = findTaskIndex(name); int idx = findTaskIndex(name);
if (idx >= 0) { if (idx >= 0) {
taskDefinitions[idx].interval = interval; taskDefinitions[idx].interval = interval;
Serial.printf("[TaskManager] Set interval for task %s: %lu ms\n", name.c_str(), interval); LOG_INFO("TaskManager", "Set interval for task " + String(name.c_str()) + ": " + String(interval) + " ms");
} else { } else {
Serial.printf("[TaskManager] Warning: Task not found: %s\n", name.c_str()); LOG_WARN("TaskManager", "Task not found: " + String(name.c_str()));
} }
} }
@@ -84,27 +85,24 @@ void TaskManager::enableAllTasks() {
for (auto& taskDef : taskDefinitions) { for (auto& taskDef : taskDefinitions) {
taskDef.enabled = true; taskDef.enabled = true;
} }
Serial.println("[TaskManager] Enabled all tasks"); LOG_INFO("TaskManager", "Enabled all tasks");
} }
void TaskManager::disableAllTasks() { void TaskManager::disableAllTasks() {
for (auto& taskDef : taskDefinitions) { for (auto& taskDef : taskDefinitions) {
taskDef.enabled = false; taskDef.enabled = false;
} }
Serial.println("[TaskManager] Disabled all tasks"); LOG_INFO("TaskManager", "Disabled all tasks");
} }
void TaskManager::printTaskStatus() const { void TaskManager::printTaskStatus() const {
Serial.println("\n[TaskManager] Task Status:"); LOG_INFO("TaskManager", "\nTask Status:");
Serial.println("=========================="); LOG_INFO("TaskManager", "==========================");
for (const auto& taskDef : taskDefinitions) { for (const auto& taskDef : taskDefinitions) {
Serial.printf(" %s: %s (interval: %lu ms)\n", LOG_INFO("TaskManager", " " + String(taskDef.name.c_str()) + ": " + (taskDef.enabled ? "ENABLED" : "DISABLED") + " (interval: " + String(taskDef.interval) + " ms)");
taskDef.name.c_str(),
taskDef.enabled ? "ENABLED" : "DISABLED",
taskDef.interval);
} }
Serial.println("==========================\n"); LOG_INFO("TaskManager", "==========================\n");
} }
void TaskManager::execute() { void TaskManager::execute() {

View File

@@ -4,28 +4,49 @@
ClusterService::ClusterService(NodeContext& ctx) : ctx(ctx) {} ClusterService::ClusterService(NodeContext& ctx) : ctx(ctx) {}
void ClusterService::registerEndpoints(ApiServer& api) { void ClusterService::registerEndpoints(ApiServer& api) {
api.addEndpoint("/api/cluster/members", HTTP_GET, api.registerEndpoint("/api/cluster/members", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleMembersRequest(request); }, [this](AsyncWebServerRequest* request) { handleMembersRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
// Generic cluster broadcast endpoint
api.registerEndpoint("/api/cluster/event", HTTP_POST,
[this](AsyncWebServerRequest* request) {
if (!request->hasParam("event", true) || !request->hasParam("payload", true)) {
request->send(400, "application/json", "{\"error\":\"Missing 'event' or 'payload'\"}");
return;
}
String eventName = request->getParam("event", true)->value();
String payloadStr = request->getParam("payload", true)->value();
JsonDocument envelope;
envelope["event"] = eventName;
envelope["data"] = payloadStr; // pass payload as JSON string
String eventJson;
serializeJson(envelope, eventJson);
std::string ev = "cluster/broadcast";
ctx.fire(ev, &eventJson);
request->send(200, "application/json", "{\"ok\":true}");
},
std::vector<ParamSpec>{
ParamSpec{String("event"), true, String("body"), String("string"), {}},
ParamSpec{String("payload"), true, String("body"), String("string"), {}}
});
}
void ClusterService::registerTasks(TaskManager& taskManager) {
// ClusterService doesn't register any tasks itself
} }
void ClusterService::handleMembersRequest(AsyncWebServerRequest* request) { void ClusterService::handleMembersRequest(AsyncWebServerRequest* request) {
JsonDocument doc; JsonDocument doc;
JsonArray arr = doc["members"].to<JsonArray>(); JsonArray arr = doc["members"].to<JsonArray>();
for (const auto& pair : *ctx.memberList) { ctx.memberList->forEachMember([&arr](const std::string& ip, const NodeInfo& node) {
const NodeInfo& node = pair.second;
JsonObject obj = arr.add<JsonObject>(); JsonObject obj = arr.add<JsonObject>();
obj["hostname"] = node.hostname; obj["hostname"] = node.hostname;
obj["ip"] = node.ip.toString(); obj["ip"] = node.ip.toString();
obj["lastSeen"] = node.lastSeen; obj["lastSeen"] = node.lastSeen;
obj["latency"] = node.latency; obj["latency"] = node.latency;
obj["status"] = statusToStr(node.status); obj["status"] = statusToStr(node.status);
obj["resources"]["freeHeap"] = node.resources.freeHeap;
obj["resources"]["chipId"] = node.resources.chipId;
obj["resources"]["sdkVersion"] = node.resources.sdkVersion;
obj["resources"]["cpuFreqMHz"] = node.resources.cpuFreqMHz;
obj["resources"]["flashChipSize"] = node.resources.flashChipSize;
// Add labels if present // Add labels if present
if (!node.labels.empty()) { if (!node.labels.empty()) {
@@ -34,7 +55,7 @@ void ClusterService::handleMembersRequest(AsyncWebServerRequest* request) {
labelsObj[kv.first.c_str()] = kv.second; labelsObj[kv.first.c_str()] = kv.second;
} }
} }
} });
String json; String json;
serializeJson(doc, json); serializeJson(doc, json);

View File

@@ -0,0 +1,99 @@
#include "spore/services/MonitoringService.h"
#include "spore/core/ApiServer.h"
#include "spore/util/Logging.h"
#include <Arduino.h>
#include <FS.h>
#include <LittleFS.h>
MonitoringService::MonitoringService() {
}
void MonitoringService::registerEndpoints(ApiServer& api) {
api.registerEndpoint("/api/monitoring/resources", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleResourcesRequest(request); },
std::vector<ParamSpec>{});
}
void MonitoringService::registerTasks(TaskManager& taskManager) {
// MonitoringService doesn't register any tasks itself
}
MonitoringService::SystemResources MonitoringService::getSystemResources() const {
SystemResources resources;
// CPU information - sending fixed value of 100
resources.currentCpuUsage = 100.0f;
resources.averageCpuUsage = 100.0f;
resources.measurementCount = 0;
resources.isMeasuring = false;
// Memory information - ESP8266 compatible
resources.freeHeap = ESP.getFreeHeap();
resources.totalHeap = 81920; // ESP8266 has ~80KB RAM
// Filesystem information
getFilesystemInfo(resources.totalBytes, resources.usedBytes);
resources.freeBytes = resources.totalBytes - resources.usedBytes;
resources.usagePercent = resources.totalBytes > 0 ?
(float)resources.usedBytes / (float)resources.totalBytes * 100.0f : 0.0f;
// System uptime
resources.uptimeMs = millis();
resources.uptimeSeconds = resources.uptimeMs / 1000;
return resources;
}
void MonitoringService::handleResourcesRequest(AsyncWebServerRequest* request) {
SystemResources resources = getSystemResources();
JsonDocument doc;
// CPU section
JsonObject cpu = doc["cpu"].to<JsonObject>();
cpu["current_usage"] = resources.currentCpuUsage;
cpu["average_usage"] = resources.averageCpuUsage;
cpu["measurement_count"] = resources.measurementCount;
cpu["is_measuring"] = resources.isMeasuring;
// Memory section
JsonObject memory = doc["memory"].to<JsonObject>();
memory["free_heap"] = resources.freeHeap;
memory["total_heap"] = resources.totalHeap;
memory["heap_usage_percent"] = resources.totalHeap > 0 ?
(float)(resources.totalHeap - resources.freeHeap) / (float)resources.totalHeap * 100.0f : 0.0f;
// Filesystem section
JsonObject filesystem = doc["filesystem"].to<JsonObject>();
filesystem["total_bytes"] = resources.totalBytes;
filesystem["used_bytes"] = resources.usedBytes;
filesystem["free_bytes"] = resources.freeBytes;
filesystem["usage_percent"] = resources.usagePercent;
// System section
JsonObject system = doc["system"].to<JsonObject>();
system["uptime_ms"] = resources.uptimeMs;
system["uptime_seconds"] = resources.uptimeSeconds;
system["uptime_formatted"] = String(resources.uptimeSeconds / 3600) + "h " +
String((resources.uptimeSeconds % 3600) / 60) + "m " +
String(resources.uptimeSeconds % 60) + "s";
String json;
serializeJson(doc, json);
request->send(200, "application/json", json);
}
void MonitoringService::getFilesystemInfo(size_t& totalBytes, size_t& usedBytes) const {
totalBytes = 0;
usedBytes = 0;
if (LittleFS.begin()) {
FSInfo fsInfo;
if (LittleFS.info(fsInfo)) {
totalBytes = fsInfo.totalBytes;
usedBytes = fsInfo.usedBytes;
}
LittleFS.end();
}
}

View File

@@ -1,4 +1,5 @@
#include "spore/services/NetworkService.h" #include "spore/services/NetworkService.h"
#include "spore/util/Logging.h"
#include <ArduinoJson.h> #include <ArduinoJson.h>
NetworkService::NetworkService(NetworkManager& networkManager) NetworkService::NetworkService(NetworkManager& networkManager)
@@ -6,20 +7,20 @@ NetworkService::NetworkService(NetworkManager& networkManager)
void NetworkService::registerEndpoints(ApiServer& api) { void NetworkService::registerEndpoints(ApiServer& api) {
// WiFi scanning endpoints // WiFi scanning endpoints
api.addEndpoint("/api/network/wifi/scan", HTTP_POST, api.registerEndpoint("/api/network/wifi/scan", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleWifiScanRequest(request); }, [this](AsyncWebServerRequest* request) { handleWifiScanRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
api.addEndpoint("/api/network/wifi/scan", HTTP_GET, api.registerEndpoint("/api/network/wifi/scan", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleGetWifiNetworks(request); }, [this](AsyncWebServerRequest* request) { handleGetWifiNetworks(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
// Network status and configuration endpoints // Network status and configuration endpoints
api.addEndpoint("/api/network/status", HTTP_GET, api.registerEndpoint("/api/network/status", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleNetworkStatus(request); }, [this](AsyncWebServerRequest* request) { handleNetworkStatus(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
api.addEndpoint("/api/network/wifi/config", HTTP_POST, api.registerEndpoint("/api/network/wifi/config", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleSetWifiConfig(request); }, [this](AsyncWebServerRequest* request) { handleSetWifiConfig(request); },
std::vector<ParamSpec>{ std::vector<ParamSpec>{
ParamSpec{String("ssid"), true, String("body"), String("string"), {}, String("")}, ParamSpec{String("ssid"), true, String("body"), String("string"), {}, String("")},
@@ -29,6 +30,10 @@ void NetworkService::registerEndpoints(ApiServer& api) {
}); });
} }
void NetworkService::registerTasks(TaskManager& taskManager) {
// NetworkService doesn't register any tasks itself
}
void NetworkService::handleWifiScanRequest(AsyncWebServerRequest* request) { void NetworkService::handleWifiScanRequest(AsyncWebServerRequest* request) {
networkManager.scanWifi(); networkManager.scanWifi();
@@ -112,21 +117,34 @@ void NetworkService::handleSetWifiConfig(AsyncWebServerRequest* request) {
retry_delay_ms = request->getParam("retry_delay_ms", true)->value().toInt(); retry_delay_ms = request->getParam("retry_delay_ms", true)->value().toInt();
} }
// Update configuration // Update configuration in memory
networkManager.setWiFiConfig(ssid, password, connect_timeout_ms, retry_delay_ms); networkManager.setWiFiConfig(ssid, password, connect_timeout_ms, retry_delay_ms);
// Attempt to connect with new settings // Save configuration to persistent storage
networkManager.setupWiFi(); bool configSaved = networkManager.saveConfig();
if (!configSaved) {
LOG_WARN("NetworkService", "Failed to save WiFi configuration to persistent storage");
}
// Prepare response
JsonDocument doc; JsonDocument doc;
doc["status"] = "success"; doc["status"] = "success";
doc["message"] = "WiFi configuration updated"; doc["message"] = "WiFi configuration updated and saved";
doc["connected"] = WiFi.isConnected(); doc["config_saved"] = configSaved;
if (WiFi.isConnected()) { doc["restarting"] = true;
doc["ip"] = WiFi.localIP().toString();
}
String json; String json;
serializeJson(doc, json); serializeJson(doc, json);
request->send(200, "application/json", json);
// Send response before restarting
AsyncWebServerResponse* response = request->beginResponse(200, "application/json", json);
response->addHeader("Connection", "close");
request->send(response);
// Restart the node to apply new WiFi settings
request->onDisconnect([this]() {
LOG_INFO("NetworkService", "Restarting node to apply WiFi configuration...");
delay(100); // Give time for response to be sent
networkManager.restartNode();
});
} }

View File

@@ -1,16 +1,17 @@
#include "spore/services/NodeService.h" #include "spore/services/NodeService.h"
#include "spore/core/ApiServer.h" #include "spore/core/ApiServer.h"
#include "spore/util/Logging.h"
NodeService::NodeService(NodeContext& ctx, ApiServer& apiServer) : ctx(ctx), apiServer(apiServer) {} NodeService::NodeService(NodeContext& ctx, ApiServer& apiServer) : ctx(ctx), apiServer(apiServer) {}
void NodeService::registerEndpoints(ApiServer& api) { void NodeService::registerEndpoints(ApiServer& api) {
// Status endpoint // Status endpoint
api.addEndpoint("/api/node/status", HTTP_GET, api.registerEndpoint("/api/node/status", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleStatusRequest(request); }, [this](AsyncWebServerRequest* request) { handleStatusRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
// Update endpoint with file upload // Update endpoint with file upload
api.addEndpoint("/api/node/update", HTTP_POST, api.registerEndpoint("/api/node/update", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleUpdateRequest(request); }, [this](AsyncWebServerRequest* request) { handleUpdateRequest(request); },
[this](AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final) { [this](AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final) {
handleUpdateUpload(request, filename, index, data, len, final); handleUpdateUpload(request, filename, index, data, len, final);
@@ -20,14 +21,48 @@ void NodeService::registerEndpoints(ApiServer& api) {
}); });
// Restart endpoint // Restart endpoint
api.addEndpoint("/api/node/restart", HTTP_POST, api.registerEndpoint("/api/node/restart", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleRestartRequest(request); }, [this](AsyncWebServerRequest* request) { handleRestartRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
// Endpoints endpoint // Endpoints endpoint
api.addEndpoint("/api/node/endpoints", HTTP_GET, api.registerEndpoint("/api/node/endpoints", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleEndpointsRequest(request); }, [this](AsyncWebServerRequest* request) { handleEndpointsRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
// Config endpoint for setting labels
api.registerEndpoint("/api/node/config", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleConfigRequest(request); },
std::vector<ParamSpec>{
ParamSpec{String("labels"), true, String("body"), String("json"), {}, String("")}
});
// Config endpoint for getting node configuration (without WiFi password)
api.registerEndpoint("/api/node/config", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleGetConfigRequest(request); },
std::vector<ParamSpec>{});
// Generic local event endpoint
api.registerEndpoint("/api/node/event", HTTP_POST,
[this](AsyncWebServerRequest* request) {
if (!request->hasParam("event", true) || !request->hasParam("payload", true)) {
request->send(400, "application/json", "{\"error\":\"Missing 'event' or 'payload'\"}");
return;
}
String eventName = request->getParam("event", true)->value();
String payloadStr = request->getParam("payload", true)->value();
std::string ev = eventName.c_str();
ctx.fire(ev, &payloadStr);
request->send(200, "application/json", "{\"ok\":true}");
},
std::vector<ParamSpec>{
ParamSpec{String("event"), true, String("body"), String("string"), {}},
ParamSpec{String("payload"), true, String("body"), String("string"), {}}
});
}
void NodeService::registerTasks(TaskManager& taskManager) {
// NodeService doesn't register any tasks itself
} }
void NodeService::handleStatusRequest(AsyncWebServerRequest* request) { void NodeService::handleStatusRequest(AsyncWebServerRequest* request) {
@@ -39,20 +74,18 @@ void NodeService::handleStatusRequest(AsyncWebServerRequest* request) {
doc["flashChipSize"] = ESP.getFlashChipSize(); doc["flashChipSize"] = ESP.getFlashChipSize();
// Include local node labels if present // Include local node labels if present
if (ctx.memberList) { auto member = ctx.memberList->getMember(ctx.hostname.c_str());
auto it = ctx.memberList->find(ctx.hostname); if (member) {
if (it != ctx.memberList->end()) { JsonObject labelsObj = doc["labels"].to<JsonObject>();
JsonObject labelsObj = doc["labels"].to<JsonObject>(); for (const auto& kv : member->labels) {
for (const auto& kv : it->second.labels) { labelsObj[kv.first.c_str()] = kv.second;
labelsObj[kv.first.c_str()] = kv.second; }
} } else if (!ctx.self.labels.empty()) {
} else if (!ctx.self.labels.empty()) {
JsonObject labelsObj = doc["labels"].to<JsonObject>(); JsonObject labelsObj = doc["labels"].to<JsonObject>();
for (const auto& kv : ctx.self.labels) { for (const auto& kv : ctx.self.labels) {
labelsObj[kv.first.c_str()] = kv.second; labelsObj[kv.first.c_str()] = kv.second;
} }
} }
}
String json; String json;
serializeJson(doc, json); serializeJson(doc, json);
@@ -65,8 +98,8 @@ void NodeService::handleUpdateRequest(AsyncWebServerRequest* request) {
success ? "{\"status\": \"OK\"}" : "{\"status\": \"FAIL\"}"); success ? "{\"status\": \"OK\"}" : "{\"status\": \"FAIL\"}");
response->addHeader("Connection", "close"); response->addHeader("Connection", "close");
request->send(response); request->send(response);
request->onDisconnect([]() { request->onDisconnect([this]() {
Serial.println("[API] Restart device"); LOG_INFO("API", "Restart device");
delay(10); delay(10);
ESP.restart(); ESP.restart();
}); });
@@ -75,14 +108,13 @@ void NodeService::handleUpdateRequest(AsyncWebServerRequest* request) {
void NodeService::handleUpdateUpload(AsyncWebServerRequest* request, const String& filename, void NodeService::handleUpdateUpload(AsyncWebServerRequest* request, const String& filename,
size_t index, uint8_t* data, size_t len, bool final) { size_t index, uint8_t* data, size_t len, bool final) {
if (!index) { if (!index) {
Serial.print("[OTA] Update Start "); LOG_INFO("OTA", "Update Start " + String(filename));
Serial.println(filename);
Update.runAsync(true); Update.runAsync(true);
if(!Update.begin(request->contentLength(), U_FLASH)) { if(!Update.begin(request->contentLength(), U_FLASH)) {
Serial.println("[OTA] Update failed: not enough space"); LOG_ERROR("OTA", "Update failed: not enough space");
Update.printError(Serial); Update.printError(Serial);
AsyncWebServerResponse* response = request->beginResponse(500, "application/json", AsyncWebServerResponse* response = request->beginResponse(500, "application/json",
"{\"status\": \"FAIL\"}"); "{\"status\": \"FAIL\", \"message\": \"Update failed: not enough space\"}");
response->addHeader("Connection", "close"); response->addHeader("Connection", "close");
request->send(response); request->send(response);
return; return;
@@ -96,14 +128,12 @@ void NodeService::handleUpdateUpload(AsyncWebServerRequest* request, const Strin
if (final) { if (final) {
if (Update.end(true)) { if (Update.end(true)) {
if(Update.isFinished()) { if(Update.isFinished()) {
Serial.print("[OTA] Update Success with "); LOG_INFO("OTA", "Update Success with " + String(index + len) + "B");
Serial.print(index + len);
Serial.println("B");
} else { } else {
Serial.println("[OTA] Update not finished"); LOG_WARN("OTA", "Update not finished");
} }
} else { } else {
Serial.print("[OTA] Update failed: "); LOG_ERROR("OTA", "Update failed: " + String(Update.getError()));
Update.printError(Serial); Update.printError(Serial);
} }
} }
@@ -114,8 +144,8 @@ void NodeService::handleRestartRequest(AsyncWebServerRequest* request) {
"{\"status\": \"restarting\"}"); "{\"status\": \"restarting\"}");
response->addHeader("Connection", "close"); response->addHeader("Connection", "close");
request->send(response); request->send(response);
request->onDisconnect([]() { request->onDisconnect([this]() {
Serial.println("[API] Restart device"); LOG_INFO("API", "Restart device");
delay(10); delay(10);
ESP.restart(); ESP.restart();
}); });
@@ -155,3 +185,108 @@ void NodeService::handleEndpointsRequest(AsyncWebServerRequest* request) {
serializeJson(doc, json); serializeJson(doc, json);
request->send(200, "application/json", json); request->send(200, "application/json", json);
} }
void NodeService::handleConfigRequest(AsyncWebServerRequest* request) {
if (!request->hasParam("labels", true)) {
request->send(400, "application/json", "{\"error\":\"Missing 'labels' parameter\"}");
return;
}
String labelsJson = request->getParam("labels", true)->value();
// Parse the JSON
JsonDocument doc;
DeserializationError error = deserializeJson(doc, labelsJson);
if (error) {
request->send(400, "application/json", "{\"error\":\"Invalid JSON format: " + String(error.c_str()) + "\"}");
return;
}
// Update config labels
ctx.config.labels.clear();
if (doc.is<JsonObject>()) {
JsonObject labelsObj = doc.as<JsonObject>();
for (JsonPair kv : labelsObj) {
ctx.config.labels[kv.key().c_str()] = kv.value().as<String>();
}
}
// Rebuild self.labels from constructor + config labels
ctx.rebuildLabels();
// Update the member list entry for the local node if it exists
String localIPStr = ctx.localIP.toString();
auto member = ctx.memberList->getMember(localIPStr.c_str());
if (member) {
// Update the labels in the member list entry
NodeInfo updatedNode = *member;
updatedNode.labels.clear();
for (const auto& kv : ctx.self.labels) {
updatedNode.labels[kv.first] = kv.second;
}
ctx.memberList->updateMember(localIPStr.c_str(), updatedNode);
}
// Save config to file
if (ctx.config.saveToFile()) {
LOG_INFO("NodeService", "Labels updated and saved to config");
request->send(200, "application/json", "{\"status\":\"success\",\"message\":\"Labels updated and saved\"}");
} else {
LOG_ERROR("NodeService", "Failed to save labels to config file");
request->send(500, "application/json", "{\"error\":\"Failed to save configuration\"}");
}
}
void NodeService::handleGetConfigRequest(AsyncWebServerRequest* request) {
JsonDocument doc;
// WiFi Configuration (excluding password for security)
JsonObject wifiObj = doc["wifi"].to<JsonObject>();
wifiObj["ssid"] = ctx.config.wifi_ssid;
wifiObj["connect_timeout_ms"] = ctx.config.wifi_connect_timeout_ms;
wifiObj["retry_delay_ms"] = ctx.config.wifi_retry_delay_ms;
// Network Configuration
JsonObject networkObj = doc["network"].to<JsonObject>();
networkObj["udp_port"] = ctx.config.udp_port;
networkObj["api_server_port"] = ctx.config.api_server_port;
// Cluster Configuration
JsonObject clusterObj = doc["cluster"].to<JsonObject>();
clusterObj["heartbeat_interval_ms"] = ctx.config.heartbeat_interval_ms;
clusterObj["cluster_listen_interval_ms"] = ctx.config.cluster_listen_interval_ms;
clusterObj["status_update_interval_ms"] = ctx.config.status_update_interval_ms;
// Node Status Thresholds
JsonObject thresholdsObj = doc["thresholds"].to<JsonObject>();
thresholdsObj["node_active_threshold_ms"] = ctx.config.node_active_threshold_ms;
thresholdsObj["node_inactive_threshold_ms"] = ctx.config.node_inactive_threshold_ms;
thresholdsObj["node_dead_threshold_ms"] = ctx.config.node_dead_threshold_ms;
// System Configuration
JsonObject systemObj = doc["system"].to<JsonObject>();
systemObj["restart_delay_ms"] = ctx.config.restart_delay_ms;
systemObj["json_doc_size"] = ctx.config.json_doc_size;
// Memory Management
JsonObject memoryObj = doc["memory"].to<JsonObject>();
memoryObj["low_memory_threshold_bytes"] = ctx.config.low_memory_threshold_bytes;
memoryObj["critical_memory_threshold_bytes"] = ctx.config.critical_memory_threshold_bytes;
memoryObj["max_concurrent_http_requests"] = ctx.config.max_concurrent_http_requests;
// Custom Labels
if (!ctx.config.labels.empty()) {
JsonObject labelsObj = doc["labels"].to<JsonObject>();
for (const auto& kv : ctx.config.labels) {
labelsObj[kv.first.c_str()] = kv.second;
}
}
// Add metadata
doc["version"] = "1.0";
doc["retrieved_at"] = millis();
String json;
serializeJson(doc, json);
request->send(200, "application/json", json);
}

View File

@@ -1,4 +1,5 @@
#include "spore/services/StaticFileService.h" #include "spore/services/StaticFileService.h"
#include "spore/util/Logging.h"
#include <Arduino.h> #include <Arduino.h>
const String StaticFileService::name = "StaticFileService"; const String StaticFileService::name = "StaticFileService";
@@ -10,100 +11,16 @@ StaticFileService::StaticFileService(NodeContext& ctx, ApiServer& apiServer)
void StaticFileService::registerEndpoints(ApiServer& api) { void StaticFileService::registerEndpoints(ApiServer& api) {
// Initialize LittleFS // Initialize LittleFS
if (!LittleFS.begin()) { if (!LittleFS.begin()) {
Serial.println("[StaticFileService] LittleFS Mount Failed"); LOG_ERROR("StaticFileService", "LittleFS Mount Failed");
return; return;
} }
Serial.println("[StaticFileService] LittleFS mounted successfully"); LOG_INFO("StaticFileService", "LittleFS mounted successfully");
// Root endpoint - serve index.html // Use the built-in static file serving from ESPAsyncWebServer
api.addEndpoint("/", HTTP_GET, api.serveStatic("/", LittleFS, "/public", "max-age=3600");
[this](AsyncWebServerRequest* request) { handleRootRequest(request); },
std::vector<ParamSpec>{});
// Static file serving for any path
api.addEndpoint("/*", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleStaticFileRequest(request); },
std::vector<ParamSpec>{});
} }
void StaticFileService::handleRootRequest(AsyncWebServerRequest* request) { void StaticFileService::registerTasks(TaskManager& taskManager) {
// Serve index.html from root // StaticFileService doesn't register any tasks itself
String path = "/index.html";
if (!fileExists(path)) {
request->send(404, "text/plain", "File not found");
return;
}
File file = LittleFS.open(path, "r");
if (!file) {
request->send(500, "text/plain", "Failed to open file");
return;
}
String contentType = getContentType(path);
request->send(LittleFS, path, contentType);
file.close();
} }
void StaticFileService::handleStaticFileRequest(AsyncWebServerRequest* request) {
String path = request->url();
// Remove leading slash for LittleFS path
if (path.startsWith("/")) {
path = path.substring(1);
}
// If path is empty or just "/", serve index.html
if (path.isEmpty() || path == "/") {
path = "index.html";
}
// Check if file exists
if (!fileExists("/" + path)) {
request->send(404, "text/plain", "File not found: " + path);
return;
}
String contentType = getContentType(path);
request->send(LittleFS, "/" + path, contentType);
}
void StaticFileService::handleNotFound(AsyncWebServerRequest* request) {
// Try to serve index.html as fallback
if (fileExists("/index.html")) {
request->send(LittleFS, "/index.html", "text/html");
} else {
request->send(404, "text/plain", "Not found");
}
}
String StaticFileService::getContentType(const String& filename) {
if (filename.endsWith(".html") || filename.endsWith(".htm")) {
return "text/html";
} else if (filename.endsWith(".css")) {
return "text/css";
} else if (filename.endsWith(".js")) {
return "application/javascript";
} else if (filename.endsWith(".json")) {
return "application/json";
} else if (filename.endsWith(".png")) {
return "image/png";
} else if (filename.endsWith(".jpg") || filename.endsWith(".jpeg")) {
return "image/jpeg";
} else if (filename.endsWith(".gif")) {
return "image/gif";
} else if (filename.endsWith(".svg")) {
return "image/svg+xml";
} else if (filename.endsWith(".ico")) {
return "image/x-icon";
} else if (filename.endsWith(".txt")) {
return "text/plain";
} else {
return "application/octet-stream";
}
}
bool StaticFileService::fileExists(const String& path) {
return LittleFS.exists(path);
}

View File

@@ -5,11 +5,11 @@
TaskService::TaskService(TaskManager& taskManager) : taskManager(taskManager) {} TaskService::TaskService(TaskManager& taskManager) : taskManager(taskManager) {}
void TaskService::registerEndpoints(ApiServer& api) { void TaskService::registerEndpoints(ApiServer& api) {
api.addEndpoint("/api/tasks/status", HTTP_GET, api.registerEndpoint("/api/tasks/status", HTTP_GET,
[this](AsyncWebServerRequest* request) { handleStatusRequest(request); }, [this](AsyncWebServerRequest* request) { handleStatusRequest(request); },
std::vector<ParamSpec>{}); std::vector<ParamSpec>{});
api.addEndpoint("/api/tasks/control", HTTP_POST, api.registerEndpoint("/api/tasks/control", HTTP_POST,
[this](AsyncWebServerRequest* request) { handleControlRequest(request); }, [this](AsyncWebServerRequest* request) { handleControlRequest(request); },
std::vector<ParamSpec>{ std::vector<ParamSpec>{
ParamSpec{ ParamSpec{
@@ -31,6 +31,10 @@ void TaskService::registerEndpoints(ApiServer& api) {
}); });
} }
void TaskService::registerTasks(TaskManager& taskManager) {
// TaskService doesn't register any tasks itself - it manages other tasks
}
void TaskService::handleStatusRequest(AsyncWebServerRequest* request) { void TaskService::handleStatusRequest(AsyncWebServerRequest* request) {
JsonDocument scratch; JsonDocument scratch;
auto taskStatuses = taskManager.getAllTaskStatuses(scratch); auto taskStatuses = taskManager.getAllTaskStatuses(scratch);

View File

@@ -1,31 +1,194 @@
#include "spore/types/Config.h" #include "spore/types/Config.h"
#include "spore/util/Logging.h"
const char* Config::CONFIG_FILE_PATH = "/config.json";
Config::Config() { Config::Config() {
// Initialize LittleFS
if (!LittleFS.begin()) {
LOG_WARN("Config", "Failed to initialize LittleFS, using defaults");
setDefaults();
return;
}
// Try to load configuration from file
if (!loadFromFile()) {
LOG_INFO("Config", "No config file found, using defaults");
setDefaults();
// Save defaults to file for future use
saveToFile();
} else {
LOG_INFO("Config", "Configuration loaded from file");
}
}
void Config::setDefaults() {
// WiFi Configuration // WiFi Configuration
wifi_ssid = "shroud"; wifi_ssid = DEFAULT_WIFI_SSID;
wifi_password = "th3r31sn0sp00n"; wifi_password = DEFAULT_WIFI_PASSWORD;
// Network Configuration // Network Configuration
udp_port = 4210; udp_port = DEFAULT_UDP_PORT;
api_server_port = 80; api_server_port = DEFAULT_API_SERVER_PORT;
// Cluster Configuration // Cluster Configuration
discovery_interval_ms = 1000; cluster_listen_interval_ms = DEFAULT_CLUSTER_LISTEN_INTERVAL_MS;
heartbeat_interval_ms = 2000; heartbeat_interval_ms = DEFAULT_HEARTBEAT_INTERVAL_MS;
status_update_interval_ms = 1000; status_update_interval_ms = DEFAULT_STATUS_UPDATE_INTERVAL_MS;
member_info_update_interval_ms = 10000;
print_interval_ms = 5000;
// Node Status Thresholds // Node Status Thresholds
node_active_threshold_ms = 10000; node_active_threshold_ms = DEFAULT_NODE_ACTIVE_THRESHOLD_MS;
node_inactive_threshold_ms = 60000; node_inactive_threshold_ms = DEFAULT_NODE_INACTIVE_THRESHOLD_MS;
node_dead_threshold_ms = 120000; node_dead_threshold_ms = DEFAULT_NODE_DEAD_THRESHOLD_MS;
// WiFi Connection // WiFi Connection
wifi_connect_timeout_ms = 15000; wifi_connect_timeout_ms = DEFAULT_WIFI_CONNECT_TIMEOUT_MS;
wifi_retry_delay_ms = 500; wifi_retry_delay_ms = DEFAULT_WIFI_RETRY_DELAY_MS;
// System Configuration // System Configuration
restart_delay_ms = 10; restart_delay_ms = DEFAULT_RESTART_DELAY_MS;
json_doc_size = 1024; json_doc_size = DEFAULT_JSON_DOC_SIZE;
// Memory Management
low_memory_threshold_bytes = DEFAULT_LOW_MEMORY_THRESHOLD_BYTES; // 10KB
critical_memory_threshold_bytes = DEFAULT_CRITICAL_MEMORY_THRESHOLD_BYTES; // 5KB
max_concurrent_http_requests = DEFAULT_MAX_CONCURRENT_HTTP_REQUESTS;
// Custom Labels - start empty by default
labels.clear();
}
bool Config::saveToFile(const String& filename) {
if (!LittleFS.begin()) {
LOG_ERROR("Config", "LittleFS not initialized, cannot save config");
return false;
}
File file = LittleFS.open(filename, "w");
if (!file) {
LOG_ERROR("Config", "Failed to open config file for writing: " + filename);
return false;
}
JsonDocument doc;
// WiFi Configuration
doc["wifi"]["ssid"] = wifi_ssid;
doc["wifi"]["password"] = wifi_password;
doc["wifi"]["connect_timeout_ms"] = wifi_connect_timeout_ms;
doc["wifi"]["retry_delay_ms"] = wifi_retry_delay_ms;
// Network Configuration
doc["network"]["udp_port"] = udp_port;
doc["network"]["api_server_port"] = api_server_port;
// Cluster Configuration
doc["cluster"]["heartbeat_interval_ms"] = heartbeat_interval_ms;
doc["cluster"]["cluster_listen_interval_ms"] = cluster_listen_interval_ms;
doc["cluster"]["status_update_interval_ms"] = status_update_interval_ms;
// Node Status Thresholds
doc["thresholds"]["node_active_threshold_ms"] = node_active_threshold_ms;
doc["thresholds"]["node_inactive_threshold_ms"] = node_inactive_threshold_ms;
doc["thresholds"]["node_dead_threshold_ms"] = node_dead_threshold_ms;
// System Configuration
doc["system"]["restart_delay_ms"] = restart_delay_ms;
doc["system"]["json_doc_size"] = json_doc_size;
// Memory Management
doc["memory"]["low_memory_threshold_bytes"] = low_memory_threshold_bytes;
doc["memory"]["critical_memory_threshold_bytes"] = critical_memory_threshold_bytes;
doc["memory"]["max_concurrent_http_requests"] = max_concurrent_http_requests;
// Custom Labels
JsonObject labelsObj = doc["labels"].to<JsonObject>();
for (const auto& kv : labels) {
labelsObj[kv.first] = kv.second;
}
// Add metadata
doc["_meta"]["version"] = "1.0";
doc["_meta"]["saved_at"] = millis();
size_t bytesWritten = serializeJson(doc, file);
file.close();
if (bytesWritten > 0) {
LOG_INFO("Config", "Configuration saved to " + filename + " (" + String(bytesWritten) + " bytes)");
return true;
} else {
LOG_ERROR("Config", "Failed to write configuration to file");
return false;
}
}
bool Config::loadFromFile(const String& filename) {
if (!LittleFS.begin()) {
LOG_ERROR("Config", "LittleFS not initialized, cannot load config");
return false;
}
if (!LittleFS.exists(filename)) {
LOG_DEBUG("Config", "Config file does not exist: " + filename);
return false;
}
File file = LittleFS.open(filename, "r");
if (!file) {
LOG_ERROR("Config", "Failed to open config file for reading: " + filename);
return false;
}
JsonDocument doc;
DeserializationError error = deserializeJson(doc, file);
file.close();
if (error) {
LOG_ERROR("Config", "Failed to parse config file: " + String(error.c_str()));
return false;
}
// Load WiFi Configuration with defaults
wifi_ssid = doc["wifi"]["ssid"] | DEFAULT_WIFI_SSID;
wifi_password = doc["wifi"]["password"] | DEFAULT_WIFI_PASSWORD;
wifi_connect_timeout_ms = doc["wifi"]["connect_timeout_ms"] | DEFAULT_WIFI_CONNECT_TIMEOUT_MS;
wifi_retry_delay_ms = doc["wifi"]["retry_delay_ms"] | DEFAULT_WIFI_RETRY_DELAY_MS;
// Load Network Configuration with defaults
udp_port = doc["network"]["udp_port"] | DEFAULT_UDP_PORT;
api_server_port = doc["network"]["api_server_port"] | DEFAULT_API_SERVER_PORT;
// Load Cluster Configuration with defaults
heartbeat_interval_ms = doc["cluster"]["heartbeat_interval_ms"] | DEFAULT_HEARTBEAT_INTERVAL_MS;
cluster_listen_interval_ms = doc["cluster"]["cluster_listen_interval_ms"] | DEFAULT_CLUSTER_LISTEN_INTERVAL_MS;
status_update_interval_ms = doc["cluster"]["status_update_interval_ms"] | DEFAULT_STATUS_UPDATE_INTERVAL_MS;
// Load Node Status Thresholds with defaults
node_active_threshold_ms = doc["thresholds"]["node_active_threshold_ms"] | DEFAULT_NODE_ACTIVE_THRESHOLD_MS;
node_inactive_threshold_ms = doc["thresholds"]["node_inactive_threshold_ms"] | DEFAULT_NODE_INACTIVE_THRESHOLD_MS;
node_dead_threshold_ms = doc["thresholds"]["node_dead_threshold_ms"] | DEFAULT_NODE_DEAD_THRESHOLD_MS;
// Load System Configuration with defaults
restart_delay_ms = doc["system"]["restart_delay_ms"] | DEFAULT_RESTART_DELAY_MS;
json_doc_size = doc["system"]["json_doc_size"] | DEFAULT_JSON_DOC_SIZE;
// Load Memory Management with defaults
low_memory_threshold_bytes = doc["memory"]["low_memory_threshold_bytes"] | DEFAULT_LOW_MEMORY_THRESHOLD_BYTES;
critical_memory_threshold_bytes = doc["memory"]["critical_memory_threshold_bytes"] | DEFAULT_CRITICAL_MEMORY_THRESHOLD_BYTES;
max_concurrent_http_requests = doc["memory"]["max_concurrent_http_requests"] | DEFAULT_MAX_CONCURRENT_HTTP_REQUESTS;
// Load Custom Labels
labels.clear();
if (doc["labels"].is<JsonObject>()) {
JsonObject labelsObj = doc["labels"].as<JsonObject>();
for (JsonPair kv : labelsObj) {
labels[kv.key().c_str()] = kv.value().as<String>();
}
}
LOG_DEBUG("Config", "Loaded WiFi SSID: " + wifi_ssid);
LOG_DEBUG("Config", "Config file version: " + String(doc["_meta"]["version"] | "unknown"));
return true;
} }

View File

@@ -0,0 +1,47 @@
#include "spore/util/Logging.h"
// Global log level - defaults to INFO
LogLevel g_logLevel = LogLevel::INFO;
void logMessage(LogLevel level, const String& component, const String& message) {
// Skip if below current log level
if (level < g_logLevel) {
return;
}
// Format: [timestamp] [level] [component] message
String timestamp = String(millis());
String levelStr;
switch (level) {
case LogLevel::DEBUG: levelStr = "DEBUG"; break;
case LogLevel::INFO: levelStr = "INFO"; break;
case LogLevel::WARN: levelStr = "WARN"; break;
case LogLevel::ERROR: levelStr = "ERROR"; break;
default: levelStr = "UNKNOWN"; break;
}
String formatted = "[" + timestamp + "] [" + levelStr + "] [" + component + "] " + message;
Serial.println(formatted);
}
void logDebug(const String& component, const String& message) {
logMessage(LogLevel::DEBUG, component, message);
}
void logInfo(const String& component, const String& message) {
logMessage(LogLevel::INFO, component, message);
}
void logWarn(const String& component, const String& message) {
logMessage(LogLevel::WARN, component, message);
}
void logError(const String& component, const String& message) {
logMessage(LogLevel::ERROR, component, message);
}
void setLogLevel(LogLevel level) {
g_logLevel = level;
logInfo("Logging", "Log level set to " + String((int)level));
}

1
test/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules/

View File

@@ -1,11 +1,64 @@
# Test Scripts
This directory is intended for PlatformIO Test Runner and project tests. This directory contains JavaScript test scripts to interact with the Spore device, primarily for testing cluster event broadcasting.
Unit Testing is a software testing method by which individual units of ## Prerequisites
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to These scripts require [Node.js](https://nodejs.org/) to be installed on your system.
determine whether they are fit for use. Unit testing finds problems early
in the development cycle. ## How to Run
### 1. HTTP Cluster Broadcast Color (`test/http-cluster-broadcast-color.js`)
This script sends HTTP POST requests to the `/api/cluster/event` endpoint on your Spore device. It broadcasts NeoPattern color changes across the cluster every 5 seconds.
**Usage:**
```
node test/http-cluster-broadcast-color.js <device-ip>
```
Example:
```
node test/http-cluster-broadcast-color.js 10.0.1.53
```
This will broadcast `{ event: "api/neopattern/color", data: { color: "#RRGGBB", brightness: 128 } }` every 5 seconds to the cluster via `/api/cluster/event`.
### 2. WS Local Color Setter (`test/ws-color-client.js`)
Connects to the device WebSocket (`/ws`) and sets a solid color locally (non-broadcast) every 5 seconds by firing `api/neopattern/color`.
**Usage:**
```
node test/ws-color-client.js ws://<device-ip>/ws
```
Example:
```
node test/ws-color-client.js ws://10.0.1.53/ws
```
### 3. WS Cluster Broadcast Color (`test/ws-cluster-broadcast-color.js`)
Connects to the device WebSocket (`/ws`) and broadcasts a color change to all peers every 5 seconds by firing `cluster/broadcast` with the proper envelope.
**Usage:**
```
node test/ws-cluster-broadcast-color.js ws://<device-ip>/ws
```
Example:
```
node test/ws-cluster-broadcast-color.js ws://10.0.1.53/ws
```
### 4. WS Cluster Broadcast Rainbow (`test/ws-cluster-broadcast-rainbow.js`)
Broadcasts a smooth rainbow color transition over WebSocket using `cluster/broadcast` and the `api/neopattern/color` event. Update rate defaults to `UPDATE_RATE` in the script (e.g., 100 ms).
**Usage:**
```
node test/ws-cluster-broadcast-rainbow.js ws://<device-ip>/ws
```
Example:
```
node test/ws-cluster-broadcast-rainbow.js ws://10.0.1.53/ws
```
Note: Very fast update intervals (e.g., 10 ms) may saturate links or the device.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html

View File

@@ -0,0 +1,52 @@
// Simple HTTP client to broadcast a neopattern color change to the cluster
// Usage: node cluster-broadcast-color.js 10.0.1.53
const http = require('http');
const host = process.argv[2] || '127.0.0.1';
const port = 80;
const colors = ['#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF'];
let idx = 0;
function postClusterEvent(event, payloadObj) {
const payload = encodeURIComponent(JSON.stringify(payloadObj));
const body = `event=${encodeURIComponent(event)}&payload=${payload}`;
const options = {
host,
port,
path: '/api/cluster/event',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(body)
}
};
const req = http.request(options, (res) => {
let data = '';
res.on('data', (chunk) => (data += chunk));
res.on('end', () => {
console.log('Response:', res.statusCode, data);
});
});
req.on('error', (err) => {
console.error('Request error:', err.message);
});
req.write(body);
req.end();
}
console.log(`Broadcasting color changes to http://${host}/api/cluster/event ...`);
setInterval(() => {
const color = colors[idx % colors.length];
idx++;
const payload = { color, brightness: 80 };
console.log('Broadcasting color:', payload);
postClusterEvent('api/neopattern/color', payload);
}, 5000);

View File

@@ -0,0 +1,46 @@
// WebSocket client to broadcast neopattern color changes across the cluster
// Usage: node ws-cluster-broadcast-color.js ws://<device-ip>/ws
const WebSocket = require('ws');
const url = process.argv[2] || 'ws://127.0.0.1/ws';
const ws = new WebSocket(url);
const colors = ['#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF'];
let idx = 0;
ws.on('open', () => {
console.log('Connected to', url);
// Broadcast color change every 5 seconds via cluster/broadcast
setInterval(() => {
const color = colors[idx % colors.length];
idx++;
const payload = { color, brightness: 80 };
const envelope = {
event: 'api/neopattern/color',
data: payload // server will serialize object payloads
};
const msg = { event: 'cluster/broadcast', payload: envelope };
ws.send(JSON.stringify(msg));
console.log('Broadcasted color event', payload);
}, 5000);
});
ws.on('message', (data) => {
try {
const msg = JSON.parse(data.toString());
console.log('Received:', msg);
} catch (e) {
console.log('Received raw:', data.toString());
}
});
ws.on('error', (err) => {
console.error('WebSocket error:', err.message);
});
ws.on('close', () => {
console.log('WebSocket closed');
});

View File

@@ -0,0 +1,71 @@
// WebSocket client to broadcast smooth rainbow color changes across the cluster
// Usage: node ws-cluster-broadcast-rainbow.js ws://<device-ip>/ws
const WebSocket = require('ws');
const url = process.argv[2] || 'ws://127.0.0.1/ws';
const ws = new WebSocket(url);
function hsvToRgb(h, s, v) {
const c = v * s;
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
const m = v - c;
let r = 0, g = 0, b = 0;
if (h < 60) { r = c; g = x; b = 0; }
else if (h < 120) { r = x; g = c; b = 0; }
else if (h < 180) { r = 0; g = c; b = x; }
else if (h < 240) { r = 0; g = x; b = c; }
else if (h < 300) { r = x; g = 0; b = c; }
else { r = c; g = 0; b = x; }
const R = Math.round((r + m) * 255);
const G = Math.round((g + m) * 255);
const B = Math.round((b + m) * 255);
return { r: R, g: G, b: B };
}
function toHex({ r, g, b }) {
const h = (n) => n.toString(16).padStart(2, '0').toUpperCase();
return `#${h(r)}${h(g)}${h(b)}`;
}
let hue = 0;
const SAT = 1.0; // full saturation
const VAL = 1.0; // full value
const BRIGHTNESS = 80;
const UPDATE_RATE = 100; // ms
let timer = null;
ws.on('open', () => {
console.log('Connected to', url);
// UPDATE_RATE ms updates (10 Hz). Be aware this can saturate slow links.
timer = setInterval(() => {
const rgb = hsvToRgb(hue, SAT, VAL);
const color = toHex(rgb);
const envelope = {
event: 'api/neopattern/color',
data: { color, brightness: BRIGHTNESS }
};
const msg = { event: 'cluster/broadcast', payload: envelope };
try {
ws.send(JSON.stringify(msg));
} catch (_) {}
hue = (hue + 2) % 360; // advance hue (adjust for speed)
}, UPDATE_RATE);
});
ws.on('message', (data) => {
// Optionally throttle logs: comment out for quieter output
// console.log('WS:', data.toString());
});
ws.on('error', (err) => {
console.error('WebSocket error:', err.message);
});
ws.on('close', () => {
if (timer) clearInterval(timer);
console.log('WebSocket closed');
});

View File

@@ -0,0 +1,48 @@
// Simple WebSocket client to test streaming API color changes
// Usage: node ws-color-client.js ws://<device-ip>/ws
const WebSocket = require('ws');
const url = process.argv[2] || 'ws://127.0.0.1/ws';
const ws = new WebSocket(url);
const colors = [
'#FF0000', // red
'#00FF00', // green
'#0000FF', // blue
'#FFFF00', // yellow
'#FF00FF', // magenta
'#00FFFF' // cyan
];
let idx = 0;
ws.on('open', () => {
console.log('Connected to', url);
// Send a message every 5 seconds to set solid color
setInterval(() => {
const color = colors[idx % colors.length];
idx++;
const payload = { color, brightness: 80 };
// Send payload as an object (server supports string or object)
const msg = { event: 'api/neopattern/color', payload };
ws.send(JSON.stringify(msg));
console.log('Sent color event', payload);
}, 5000);
});
ws.on('message', (data) => {
try {
const msg = JSON.parse(data.toString());
console.log('Received:', msg);
} catch (e) {
console.log('Received raw:', data.toString());
}
});
ws.on('error', (err) => {
console.error('WebSocket error:', err.message);
});
ws.on('close', () => {
console.log('WebSocket closed');
});

33
test/package-lock.json generated Normal file
View File

@@ -0,0 +1,33 @@
{
"name": "test",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"ws": "^8.18.3"
}
},
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
}
}

21
test/package.json Normal file
View File

@@ -0,0 +1,21 @@
{
"dependencies": {
"ws": "^8.18.3"
},
"scripts": {
"pixelstream:fade-green-blue": "node pixelstream/fade-green-blue.js",
"pixelstream:bouncing-ball": "node pixelstream/bouncing-ball.js",
"pixelstream:rainbow": "node pixelstream/rainbow.js",
"pixelstream:lava-lamp": "node pixelstream/lava-lamp.js",
"pixelstream:meteor-rain": "node pixelstream/meteor-rain.js",
"pixelstream:spiral-bloom": "node pixelstream/spiral-bloom.js",
"pixelstream:ocean-glimmer": "node pixelstream/ocean-glimmer.js",
"pixelstream:nebula-drift": "node pixelstream/nebula-drift.js",
"pixelstream:voxel-fireflies": "node pixelstream/voxel-fireflies.js",
"pixelstream:wormhole-tunnel": "node pixelstream/wormhole-tunnel.js",
"pixelstream:circuit-pulse": "node pixelstream/circuit-pulse.js",
"pixelstream:aurora-curtains": "node pixelstream/aurora-curtains.js",
"pixelstream:snek": "npm start --prefix pixelstream/snek",
"pixelstream:tetris": "npm start --prefix pixelstream/tetris"
}
}

View File

@@ -0,0 +1,115 @@
const dgram = require('dgram');
const {
clamp,
createFrame,
frameToPayload,
hexToRgb,
samplePalette,
toIndex,
} = require('./shared-frame-utils');
const DEFAULT_PORT = 4210;
const DEFAULT_WIDTH = 16;
const DEFAULT_HEIGHT = 16;
const DEFAULT_INTERVAL_MS = 65;
const BAND_COUNT = 5;
const WAVE_SPEED = 0.35;
const HORIZONTAL_SWAY = 0.45;
const paletteStops = [
{ stop: 0.0, color: hexToRgb('01010a') },
{ stop: 0.2, color: hexToRgb('041332') },
{ stop: 0.4, color: hexToRgb('0c3857') },
{ stop: 0.65, color: hexToRgb('1aa07a') },
{ stop: 0.85, color: hexToRgb('68d284') },
{ stop: 1.0, color: hexToRgb('f4f5c6') },
];
const host = process.argv[2];
const port = parseInt(process.argv[3] || String(DEFAULT_PORT), 10);
const width = parseInt(process.argv[4] || String(DEFAULT_WIDTH), 10);
const height = parseInt(process.argv[5] || String(DEFAULT_HEIGHT), 10);
const intervalMs = parseInt(process.argv[6] || String(DEFAULT_INTERVAL_MS), 10);
if (!host) {
console.error('Usage: node aurora-curtains.js <device-ip> [port] [width] [height] [interval-ms]');
process.exit(1);
}
if (Number.isNaN(port) || Number.isNaN(width) || Number.isNaN(height) || Number.isNaN(intervalMs)) {
console.error('Invalid numeric argument. Expected integers for port, width, height, and interval-ms.');
process.exit(1);
}
if (width <= 0 || height <= 0) {
console.error('Matrix dimensions must be positive integers.');
process.exit(1);
}
const socket = dgram.createSocket('udp4');
const isBroadcast = host === '255.255.255.255' || host.endsWith('.255');
const frame = createFrame(width, height);
const bands = createBands(BAND_COUNT, width);
let timeSeconds = 0;
const frameTimeSeconds = intervalMs / 1000;
if (isBroadcast) {
socket.bind(() => {
socket.setBroadcast(true);
});
}
socket.on('error', (error) => {
console.error('Socket error:', error.message);
});
function createBands(count, matrixWidth) {
const generatedBands = [];
for (let index = 0; index < count; ++index) {
generatedBands.push({
center: Math.random() * (matrixWidth - 1),
phase: Math.random() * Math.PI * 2,
width: 1.2 + Math.random() * 1.8,
});
}
return generatedBands;
}
function generateFrame() {
timeSeconds += frameTimeSeconds;
for (let row = 0; row < height; ++row) {
const verticalRatio = row / Math.max(1, height - 1);
for (let col = 0; col < width; ++col) {
let intensity = 0;
bands.forEach((band, index) => {
const sway = Math.sin(timeSeconds * WAVE_SPEED + band.phase + verticalRatio * Math.PI * 2) * HORIZONTAL_SWAY;
const center = band.center + sway * (index % 2 === 0 ? 1 : -1);
const distance = Math.abs(col - center);
const blurred = Math.exp(-(distance * distance) / (2 * band.width * band.width));
intensity += blurred * (0.8 + Math.sin(timeSeconds * 0.4 + index) * 0.2);
});
const normalized = clamp(intensity / bands.length, 0, 1);
const gradientBlend = clamp((normalized * 0.7 + verticalRatio * 0.3), 0, 1);
frame[toIndex(col, row, width)] = samplePalette(paletteStops, gradientBlend);
}
}
return frameToPayload(frame);
}
function sendFrame() {
const payload = generateFrame();
const message = Buffer.from(payload, 'utf8');
socket.send(message, port, host);
}
setInterval(sendFrame, intervalMs);
console.log(
`Streaming aurora curtains to ${host}:${port} (${width}x${height}, interval=${intervalMs}ms)`
);

View File

@@ -0,0 +1,80 @@
const dgram = require('dgram');
const host = process.argv[2];
const port = parseInt(process.argv[3] || '4210', 10);
const pixels = parseInt(process.argv[4] || '64', 10);
const intervalMs = parseInt(process.argv[5] || '30', 10);
if (!host) {
console.error('Usage: node bouncing-ball.js <device-ip> [port] [pixels] [interval-ms]');
process.exit(1);
}
const socket = dgram.createSocket('udp4');
const isBroadcast = host === '255.255.255.255' || host.endsWith('.255');
let position = Math.random() * (pixels - 1);
let velocity = randomVelocity();
function randomVelocity() {
const min = 0.15;
const max = 0.4;
const sign = Math.random() < 0.5 ? -1 : 1;
return (min + Math.random() * (max - min)) * sign;
}
function rebound(sign) {
velocity = randomVelocity() * sign;
}
function mix(a, b, t) {
return a + (b - a) * t;
}
function generateFrame() {
const dt = intervalMs / 1000;
position += velocity * dt * 60; // scale velocity to 60 FPS reference
if (position < 0) {
position = -position;
rebound(1);
} else if (position > pixels - 1) {
position = (pixels - 1) - (position - (pixels - 1));
rebound(-1);
}
const activeIndex = Math.max(0, Math.min(pixels - 1, Math.round(position)));
let payload = 'RAW:';
for (let i = 0; i < pixels; i++) {
if (i === activeIndex) {
payload += 'ff8000';
continue;
}
const distance = Math.abs(i - position);
const intensity = Math.max(0, 1 - distance);
const green = Math.round(mix(20, 200, intensity)).toString(16).padStart(2, '0');
const blue = Math.round(mix(40, 255, intensity)).toString(16).padStart(2, '0');
payload += '00' + green + blue;
}
return payload;
}
function sendFrame() {
const payload = generateFrame();
const message = Buffer.from(payload, 'utf8');
socket.send(message, port, host);
}
setInterval(sendFrame, intervalMs);
if (isBroadcast) {
socket.bind(() => {
socket.setBroadcast(true);
});
}
console.log(`Streaming bouncing ball pattern to ${host}:${port} with ${pixels} pixels (interval=${intervalMs}ms)`);

View File

@@ -0,0 +1,166 @@
const dgram = require('dgram');
const {
addHexColor,
createFrame,
fadeFrame,
frameToPayload,
hexToRgb,
samplePalette,
toIndex,
} = require('./shared-frame-utils');
const DEFAULT_PORT = 4210;
const DEFAULT_WIDTH = 16;
const DEFAULT_HEIGHT = 16;
const DEFAULT_INTERVAL_MS = 50;
const PATH_FADE = 0.85;
const PULSE_LENGTH = 6;
const paletteStops = [
{ stop: 0.0, color: hexToRgb('020209') },
{ stop: 0.3, color: hexToRgb('023047') },
{ stop: 0.6, color: hexToRgb('115173') },
{ stop: 0.8, color: hexToRgb('1ca78f') },
{ stop: 1.0, color: hexToRgb('94fdf3') },
];
const accentColors = ['14f5ff', 'a7ff4d', 'ffcc3f'];
const host = process.argv[2];
const port = parseInt(process.argv[3] || String(DEFAULT_PORT), 10);
const width = parseInt(process.argv[4] || String(DEFAULT_WIDTH), 10);
const height = parseInt(process.argv[5] || String(DEFAULT_HEIGHT), 10);
const intervalMs = parseInt(process.argv[6] || String(DEFAULT_INTERVAL_MS), 10);
if (!host) {
console.error('Usage: node circuit-pulse.js <device-ip> [port] [width] [height] [interval-ms]');
process.exit(1);
}
if (Number.isNaN(port) || Number.isNaN(width) || Number.isNaN(height) || Number.isNaN(intervalMs)) {
console.error('Invalid numeric argument. Expected integers for port, width, height, and interval-ms.');
process.exit(1);
}
if (width <= 0 || height <= 0) {
console.error('Matrix dimensions must be positive integers.');
process.exit(1);
}
const socket = dgram.createSocket('udp4');
const isBroadcast = host === '255.255.255.255' || host.endsWith('.255');
const frame = createFrame(width, height);
const paths = createPaths(width, height);
const pulses = createPulses(paths.length);
const frameTimeSeconds = intervalMs / 1000;
if (isBroadcast) {
socket.bind(() => {
socket.setBroadcast(true);
});
}
socket.on('error', (error) => {
console.error('Socket error:', error.message);
});
function createPaths(matrixWidth, matrixHeight) {
const horizontalStep = Math.max(2, Math.floor(matrixHeight / 4));
const verticalStep = Math.max(2, Math.floor(matrixWidth / 4));
const generatedPaths = [];
for (let y = 1; y < matrixHeight; y += horizontalStep) {
const path = [];
for (let x = 0; x < matrixWidth; ++x) {
path.push({ x, y });
}
generatedPaths.push(path);
}
for (let x = 2; x < matrixWidth; x += verticalStep) {
const path = [];
for (let y = 0; y < matrixHeight; ++y) {
path.push({ x, y });
}
generatedPaths.push(path);
}
return generatedPaths;
}
function createPulses(count) {
const pulseList = [];
for (let index = 0; index < count; ++index) {
pulseList.push(spawnPulse(index));
}
return pulseList;
}
function spawnPulse(pathIndex) {
const color = accentColors[pathIndex % accentColors.length];
return {
pathIndex,
position: 0,
speed: 3 + Math.random() * 2,
color,
};
}
function updatePulse(pulse, deltaSeconds) {
pulse.position += pulse.speed * deltaSeconds;
const path = paths[pulse.pathIndex];
if (!path || path.length === 0) {
return;
}
if (pulse.position >= path.length + PULSE_LENGTH) {
Object.assign(pulse, spawnPulse(pulse.pathIndex));
pulse.position = 0;
}
}
function renderPulse(pulse) {
const path = paths[pulse.pathIndex];
if (!path) {
return;
}
for (let offset = 0; offset < PULSE_LENGTH; ++offset) {
const index = Math.floor(pulse.position) - offset;
if (index < 0 || index >= path.length) {
continue;
}
const { x, y } = path[index];
const intensity = Math.max(0, 1 - offset / PULSE_LENGTH);
const baseColor = samplePalette(paletteStops, intensity);
frame[toIndex(x, y, width)] = baseColor;
addHexColor(frame, toIndex(x, y, width), pulse.color, intensity * 1.4);
}
}
function generateFrame() {
fadeFrame(frame, PATH_FADE);
pulses.forEach((pulse) => {
updatePulse(pulse, frameTimeSeconds);
renderPulse(pulse);
});
return frameToPayload(frame);
}
function sendFrame() {
const payload = generateFrame();
const message = Buffer.from(payload, 'utf8');
socket.send(message, port, host);
}
setInterval(sendFrame, intervalMs);
console.log(
`Streaming circuit pulse to ${host}:${port} (${width}x${height}, interval=${intervalMs}ms, paths=${paths.length})`
);

View File

@@ -0,0 +1,55 @@
const dgram = require('dgram');
const host = process.argv[2];
const port = parseInt(process.argv[3] || '4210', 10);
const pixels = parseInt(process.argv[4] || '64', 10);
const speed = parseFloat(process.argv[5] || '0.5'); // cycles per second
if (!host) {
console.error('Usage: node fade-green-blue.js <device-ip> [port] [pixels] [speed-hz]');
process.exit(1);
}
const socket = dgram.createSocket('udp4');
const intervalMs = 50;
let tick = 0;
const isBroadcast = host === '255.255.255.255' || host.endsWith('.255');
function generateFrame() {
const timeSeconds = (tick * intervalMs) / 1000;
const phase = timeSeconds * speed * Math.PI * 2;
const blend = (Math.sin(phase) + 1) * 0.5; // 0..1
const green = Math.round(255 * (1 - blend));
const blue = Math.round(255 * blend);
let payload = 'RAW:';
const gHex = green.toString(16).padStart(2, '0');
const bHex = blue.toString(16).padStart(2, '0');
for (let i = 0; i < pixels; i++) {
payload += '00';
payload += gHex;
payload += bHex;
}
return payload;
}
function sendFrame() {
const payload = generateFrame();
const message = Buffer.from(payload, 'utf8');
socket.send(message, port, host);
tick += 1;
}
setInterval(sendFrame, intervalMs);
if (isBroadcast) {
socket.bind(() => {
socket.setBroadcast(true);
});
}
console.log(`Streaming green/blue fade to ${host}:${port} with ${pixels} pixels (speed=${speed}Hz)`);

View File

@@ -0,0 +1,171 @@
const dgram = require('dgram');
const {
clamp,
hexToRgb,
samplePalette: samplePaletteFromStops,
toIndex,
} = require('./shared-frame-utils');
const DEFAULT_PORT = 4210;
const DEFAULT_WIDTH = 16;
const DEFAULT_HEIGHT = 16;
const DEFAULT_INTERVAL_MS = 60;
const DEFAULT_BLOB_COUNT = 6;
const BASE_BLOB_SPEED = 0.18;
const PHASE_SPEED_MIN = 0.6;
const PHASE_SPEED_MAX = 1.2;
const host = process.argv[2];
const port = parseInt(process.argv[3] || String(DEFAULT_PORT), 10);
const width = parseInt(process.argv[4] || String(DEFAULT_WIDTH), 10);
const height = parseInt(process.argv[5] || String(DEFAULT_HEIGHT), 10);
const intervalMs = parseInt(process.argv[6] || String(DEFAULT_INTERVAL_MS), 10);
const blobCount = parseInt(process.argv[7] || String(DEFAULT_BLOB_COUNT), 10);
if (!host) {
console.error('Usage: node lava-lamp.js <device-ip> [port] [width] [height] [interval-ms] [blob-count]');
process.exit(1);
}
if (Number.isNaN(port) || Number.isNaN(width) || Number.isNaN(height) || Number.isNaN(intervalMs) || Number.isNaN(blobCount)) {
console.error('Invalid numeric argument. Expected integers for port, width, height, interval-ms, and blob-count.');
process.exit(1);
}
if (width <= 0 || height <= 0) {
console.error('Matrix dimensions must be positive integers.');
process.exit(1);
}
if (blobCount <= 0) {
console.error('Blob count must be a positive integer.');
process.exit(1);
}
const totalPixels = width * height;
const socket = dgram.createSocket('udp4');
const isBroadcast = host === '255.255.255.255' || host.endsWith('.255');
const maxAxis = Math.max(width, height);
const minBlobRadius = Math.max(3, maxAxis * 0.18);
const maxBlobRadius = Math.max(minBlobRadius + 1, maxAxis * 0.38);
const frameTimeSeconds = intervalMs / 1000;
const paletteStops = [
{ stop: 0.0, color: hexToRgb('050319') },
{ stop: 0.28, color: hexToRgb('2a0c4f') },
{ stop: 0.55, color: hexToRgb('8f1f73') },
{ stop: 0.75, color: hexToRgb('ff4a22') },
{ stop: 0.9, color: hexToRgb('ff9333') },
{ stop: 1.0, color: hexToRgb('fff7b0') },
];
const blobs = createBlobs(blobCount);
if (isBroadcast) {
socket.bind(() => {
socket.setBroadcast(true);
});
}
socket.on('error', (error) => {
console.error('Socket error:', error.message);
});
function createBlobs(count) {
const blobList = [];
for (let index = 0; index < count; ++index) {
const angle = Math.random() * Math.PI * 2;
const speed = BASE_BLOB_SPEED * (0.6 + Math.random() * 0.8);
blobList.push({
x: Math.random() * Math.max(1, width - 1),
y: Math.random() * Math.max(1, height - 1),
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
minRadius: minBlobRadius * (0.6 + Math.random() * 0.3),
maxRadius: maxBlobRadius * (0.8 + Math.random() * 0.4),
intensity: 0.8 + Math.random() * 0.7,
phase: Math.random() * Math.PI * 2,
phaseVelocity: PHASE_SPEED_MIN + Math.random() * (PHASE_SPEED_MAX - PHASE_SPEED_MIN),
});
}
return blobList;
}
function updateBlobs(deltaSeconds) {
const maxX = Math.max(0, width - 1);
const maxY = Math.max(0, height - 1);
blobs.forEach((blob) => {
blob.x += blob.vx * deltaSeconds;
blob.y += blob.vy * deltaSeconds;
if (blob.x < 0) {
blob.x = -blob.x;
blob.vx = Math.abs(blob.vx);
} else if (blob.x > maxX) {
blob.x = 2 * maxX - blob.x;
blob.vx = -Math.abs(blob.vx);
}
if (blob.y < 0) {
blob.y = -blob.y;
blob.vy = Math.abs(blob.vy);
} else if (blob.y > maxY) {
blob.y = 2 * maxY - blob.y;
blob.vy = -Math.abs(blob.vy);
}
blob.phase += blob.phaseVelocity * deltaSeconds;
});
}
function generateFrame() {
updateBlobs(frameTimeSeconds);
const frame = new Array(totalPixels);
for (let row = 0; row < height; ++row) {
for (let col = 0; col < width; ++col) {
const energy = calculateEnergyAt(col, row);
const color = samplePaletteFromStops(paletteStops, energy);
frame[toIndex(col, row, width)] = color;
}
}
return 'RAW:' + frame.join('');
}
function calculateEnergyAt(col, row) {
let energy = 0;
blobs.forEach((blob) => {
const radius = getBlobRadius(blob);
const dx = col - blob.x;
const dy = row - blob.y;
const distance = Math.hypot(dx, dy);
const falloff = Math.max(0, 1 - distance / radius);
energy += blob.intensity * falloff * falloff;
});
return clamp(energy / blobs.length, 0, 1);
}
function getBlobRadius(blob) {
const oscillation = (Math.sin(blob.phase) + 1) * 0.5;
return blob.minRadius + (blob.maxRadius - blob.minRadius) * oscillation;
}
function sendFrame() {
const payload = generateFrame();
const message = Buffer.from(payload, 'utf8');
socket.send(message, port, host);
}
setInterval(sendFrame, intervalMs);
console.log(
`Streaming lava lamp to ${host}:${port} (${width}x${height}, interval=${intervalMs}ms, blobs=${blobCount})`,
);

View File

@@ -0,0 +1,132 @@
const dgram = require('dgram');
const {
clamp,
createFrame,
fadeFrame,
frameToPayload,
hexToRgb,
samplePalette,
toIndex,
} = require('./shared-frame-utils');
const DEFAULT_PORT = 4210;
const DEFAULT_WIDTH = 16;
const DEFAULT_HEIGHT = 16;
const DEFAULT_INTERVAL_MS = 45;
const DEFAULT_METEOR_COUNT = 12;
const BASE_SPEED_MIN = 4;
const BASE_SPEED_MAX = 10;
const TRAIL_DECAY = 0.76;
const paletteStops = [
{ stop: 0.0, color: hexToRgb('0a0126') },
{ stop: 0.3, color: hexToRgb('123d8b') },
{ stop: 0.7, color: hexToRgb('21c7d9') },
{ stop: 1.0, color: hexToRgb('f7ffff') },
];
const host = process.argv[2];
const port = parseInt(process.argv[3] || String(DEFAULT_PORT), 10);
const width = parseInt(process.argv[4] || String(DEFAULT_WIDTH), 10);
const height = parseInt(process.argv[5] || String(DEFAULT_HEIGHT), 10);
const intervalMs = parseInt(process.argv[6] || String(DEFAULT_INTERVAL_MS), 10);
const meteorCount = parseInt(process.argv[7] || String(DEFAULT_METEOR_COUNT), 10);
if (!host) {
console.error('Usage: node meteor-rain.js <device-ip> [port] [width] [height] [interval-ms] [meteor-count]');
process.exit(1);
}
if (Number.isNaN(port) || Number.isNaN(width) || Number.isNaN(height) || Number.isNaN(intervalMs) || Number.isNaN(meteorCount)) {
console.error('Invalid numeric argument. Expected integers for port, width, height, interval-ms, and meteor-count.');
process.exit(1);
}
if (width <= 0 || height <= 0) {
console.error('Matrix dimensions must be positive integers.');
process.exit(1);
}
if (meteorCount <= 0) {
console.error('Meteor count must be a positive integer.');
process.exit(1);
}
const socket = dgram.createSocket('udp4');
const isBroadcast = host === '255.255.255.255' || host.endsWith('.255');
const frame = createFrame(width, height);
const meteors = createMeteors(meteorCount, width, height);
const frameTimeSeconds = intervalMs / 1000;
if (isBroadcast) {
socket.bind(() => {
socket.setBroadcast(true);
});
}
socket.on('error', (error) => {
console.error('Socket error:', error.message);
});
function createMeteors(count, matrixWidth, matrixHeight) {
const meteorList = [];
for (let index = 0; index < count; ++index) {
meteorList.push(spawnMeteor(matrixWidth, matrixHeight));
}
return meteorList;
}
function spawnMeteor(matrixWidth, matrixHeight) {
const angle = (Math.PI / 4) * (0.6 + Math.random() * 0.8);
const speed = BASE_SPEED_MIN + Math.random() * (BASE_SPEED_MAX - BASE_SPEED_MIN);
return {
x: Math.random() * matrixWidth,
y: -Math.random() * matrixHeight,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
};
}
function drawMeteor(meteor) {
const col = Math.round(meteor.x);
const row = Math.round(meteor.y);
if (col < 0 || col >= width || row < 0 || row >= height) {
return;
}
const energy = clamp(1.2 - Math.random() * 0.2, 0, 1);
frame[toIndex(col, row, width)] = samplePalette(paletteStops, energy);
}
function updateMeteors(deltaSeconds) {
meteors.forEach((meteor, index) => {
meteor.x += meteor.vx * deltaSeconds;
meteor.y += meteor.vy * deltaSeconds;
drawMeteor(meteor);
if (meteor.x > width + 1 || meteor.y > height + 1) {
meteors[index] = spawnMeteor(width, height);
}
});
}
function generateFrame() {
fadeFrame(frame, TRAIL_DECAY);
updateMeteors(frameTimeSeconds);
return frameToPayload(frame);
}
function sendFrame() {
const payload = generateFrame();
const message = Buffer.from(payload, 'utf8');
socket.send(message, port, host);
}
setInterval(sendFrame, intervalMs);
console.log(
`Streaming meteor rain to ${host}:${port} (${width}x${height}, interval=${intervalMs}ms, meteors=${meteorCount})`,
);

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