-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathplatformio.ini
More file actions
411 lines (402 loc) · 22.1 KB
/
Copy pathplatformio.ini
File metadata and controls
411 lines (402 loc) · 22.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
; SPDX-License-Identifier: MIT
; Heliograph — PlatformIO project configuration.
;
; NOTE ON THE PLATFORM URL: do not replace this with `platform = espressif32`.
; The official platformio/platform-espressif32 still ships Arduino core 2.0.17 on
; ESP-IDF 4.4.7 for `framework = arduino`, even in its latest release (v7.0.1,
; 2026-05-12). pioarduino is the maintained route to Arduino core 3.x / IDF 5.5.x.
; See docs/decisions.md.
[platformio]
default_envs = waveshare-rs485-can
src_dir = src
test_dir = test
[common]
; Regenerates src/drivers/modbus_profile/profiles_generated.cpp from profiles/*/*.toml
; before every build. A broken device profile fails the build with a validation message,
; never the running device. See docs/adding-a-device.md.
;
; build_web.py does the same for the web UI: it strips the comments out of
; src/web/assets/*.h and gzips each page into a PROGMEM array. Neither output is committed,
; for the same reason -- the authored file is the source of truth, and a stale generated copy
; in the tree is the drift the build step exists to prevent.
extra_scripts =
pre:tools/gen_profiles.py
pre:tools/build_web.py
build_flags =
-std=gnu++17
-Wall
-Wextra
; -iquote, not -I, and this matters. PlatformIO puts library include dirs ahead of ours,
; and espMqttClient ships src/Transport/Transport.h. On a case-insensitive filesystem
; (macOS APFS by default) our #include "transport/transport.h" then resolves to *theirs*.
; GCC searches -iquote paths before every -I path for quoted includes, so this puts our
; tree first without depending on filesystem case sensitivity or library ordering.
-iquote src
; ---------------------------------------------------------------------------------------
; Host-based tests. No ESP32 required.
;
; Only platform-independent sources are compiled in. If a file listed here ever needs an
; Arduino include, that is a design error, not a reason to widen this filter: the protocol
; core, the data model and the register map must stay testable on the host.
; ---------------------------------------------------------------------------------------
[env:native]
platform = native
extra_scripts = ${common.extra_scripts}
test_framework = unity
; Static analysis (pio check). Own code only: cppcheck cannot expand ArduinoJson's
; namespace macros and reports false preprocessor errors inside the library, even with
; skipped packages -- the header is pulled in through our includes, hence the targeted
; suppression on top.
check_tool = cppcheck
check_skip_packages = yes
check_flags =
cppcheck: --suppress=preprocessorErrorDirective:*/ArduinoJson/*
; Without this PlatformIO links the tests against nothing from src/.
test_build_src = yes
build_flags =
${common.build_flags}
-iquote test
; Clang-only diagnostic (GCC has no equivalent, so CI never sees these): every hit is a
; private field the ESP32 implementation uses and the host stub compiles out -- the
; split-implementation classes (MqttOutput, RestApi, WifiManager, NvsBackend) share one
; header between both builds by design. Genuinely dead fields still surface in review
; and cppcheck; this only silences the false positives on local macOS builds.
-Wno-unused-private-field
; Unity compiles without double support by default; the measurement model is all doubles.
-DUNITY_INCLUDE_DOUBLE
-DUNITY_DOUBLE_PRECISION=1e-12
-DENABLE_DRIVER_EVERSOLAR=1
-DENABLE_DRIVER_MODBUS_PROFILE=1
-DENABLE_DRIVER_SOLAX=1
-DENABLE_DRIVER_SUNSPEC=1
-DENABLE_DRIVER_SOLARMAX=1
-DENABLE_DRIVER_MOCK=1
build_src_filter =
+<device/>
+<transport/transport.cpp>
+<state/>
+<commands/>
+<relays/>
+<status/>
+<diagnostics/>
+<drivers/driver_registry.cpp>
+<drivers/discovery_engine.cpp>
+<app/device_plan.cpp>
+<app/discovery_runner.cpp>
+<app/capture_runner.cpp>
+<app/driver_capture_runner.cpp>
+<drivers/eversolar_legacy/>
+<drivers/modbus_profile/>
+<drivers/solax_x1/>
+<drivers/sunspec/>
+<drivers/solarmax/>
+<drivers/mock/>
+<protocols/modbus/>
+<protocols/pmu/>
+<protocols/maxtalk/>
+<outputs/modbus_tcp/register_map.cpp>
+<outputs/modbus_tcp/modbus_tcp_server.cpp>
+<outputs/mqtt/mqtt_payloads.cpp>
+<outputs/mqtt/home_assistant_discovery.cpp>
+<outputs/mqtt/publish_policy.cpp>
+<outputs/mqtt/mqtt_output.cpp>
+<outputs/rest/rest_payloads.cpp>
+<outputs/rest/capture_request.cpp>
+<outputs/prometheus/prometheus_metrics.cpp>
+<config/configuration.cpp>
+<config/configuration_store.cpp>
+<config/config_backup.cpp>
+<network/provisioning_policy.cpp>
+<network/ipv4.cpp>
+<network/rtc_time.cpp>
+<network/wifi_manager.cpp>
+<config/nvs_backend.cpp>
+<ota/ota_manager.cpp>
+<ota/sha256.cpp>
+<outputs/rest/rest_api.cpp>
lib_deps =
; ArduinoJson is plain C++ and needs no Arduino framework, so the payload builders and
; the Home Assistant discovery generator are tested on the host -- which is exactly where
; a wrong value_template or device_class would otherwise stay invisible until Home
; Assistant quietly shows nothing.
bblanchon/ArduinoJson@7.4.3
; ---------------------------------------------------------------------------------------
; Target firmware. One environment per supported board (the HELIOGRAPH_BOARD_* flag picks
; the header in src/boards/); everything else is shared through [esp32common].
;
; PINNED TO A VERSION, NOT TO `stable`. That URL is a moving target, and on 2026-07-31 it was
; found to have moved: this machine still had 55.03.39 (Arduino core 3.3.9, IDF 5.5.4) from the
; note below, while CI had picked up 55.03.311 (core 3.3.11, IDF 5.5.5) and had been building
; every release from it since 2026-07-24. The v0.25.0 images that went out to OTA were compiled
; by a toolchain nobody had booted on hardware, and nothing in the repo said so.
;
; A firmware that must run for years unattended cannot have its compiler and RTOS change
; underneath a release without a commit saying it did. Bumping is now a deliberate edit here,
; reviewable like any other, and the reason the official platformio/platform-espressif32 was
; rejected is untouched: it still ships Arduino core 2.0.17 on IDF 4.4.7 for framework=arduino.
;
; Pinned at 55.03.311 because that is what the released binaries already contain -- pinning back
; to 55.03.39 would have made the NEXT release differ from the last one in the other direction,
; with no more evidence behind it.
;
; VERIFIED ON HARDWARE: 55.03.311 (core 3.3.11 / IDF 5.5.5) boots and runs on the RS485-CAN,
; 2026-07-31 — v0.25.1 flashed over OTA onto the production bridge. First 76 minutes: 448 polls
; against a live EverSolar with one failure, poll duration ewma 102 ms and max 108 ms, no
; coredump, no MQTT publish failures, 8.38 MB PSRAM free (so qio_opi is right for this module).
; RS485 timing is the thing a toolchain change could plausibly have disturbed, and it did not.
;
; RE-CHECKED 2026-08-01 at 30.8 h and again 2026-08-02 at 46.6 h: boot_count still 4 -- no reboot
; since the OTA -- and no coredump either time. Poll duration ewma 103 ms, max 111 ms, min 97 ms
; (day one: 102 / 108); free heap 148324 and PSRAM free unchanged at 8378140, so no leak trend.
; The EverSolar came back on its own after each night and resumed publishing real
; ac.power.total / energy.today, so the driver's sunrise-recovery path is unaffected by this
; toolchain. RS485 timing is the thing a compiler change could have disturbed, and across two
; days it has not moved.
;
; ON THE FAILURE COUNT, because the raw numbers mislead. At 46.6 h: 10775 successful polls, 967
; failed, 1898 rs485_timeout_total, and zero checksum errors and zero invalid frames.
;
; The timeouts are NOT a subset of the failed polls -- 1898 is nearly twice 967, and they count
; different things. poll_failure_total counts POLLS; rs485_timeout_total accumulates the driver's
; own bus-error counter (device_context.cpp records the delta of driver_.busErrors().timeouts),
; which counts individual RS485 TRANSACTIONS, and one poll costs more than one. The ratio held at
; both samples (953/485 and 1898/967, both ~1.96). An earlier version of this note read "967
; failures (1898 of them timeouts)", which cannot be true of a subset.
;
; The cumulative failure share climbs with every night -- 0.22% after the first 76 daylight
; minutes, 5.7% at 30.8 h, 8.2% at 46.6 h -- because the inverter is dark and does not answer.
; That is the expected shape, not a regression: checksum errors and invalid frames are both still
; zero, consecutive_poll_failures is 0 by day, and it polls successfully every few seconds. What
; this does NOT establish is that the overnight pattern is exactly the expected one; the bridge
; exposes cumulative counters, not a per-hour breakdown, so that remains inferred rather than
; measured.
;
; VERDICT: the toolchain question is answered for this board. Two days, two nights, two unassisted
; sunrises, timing that has not moved. That is strictly more evidence than 55.03.39 ever had -- its
; own line below says "built clean and ran on hardware", one day, and it was called verified. It
; would be inconsistent to hold the replacement to a higher bar than the thing it replaced.
;
; (The eight-sunrise gate belongs to the EVERSOLAR DRIVER, not to a toolchain. That bug lived at
; the night-to-morning transition, so only sunrises could close it; it was met on 2026-07-29,
; before this pin existed. Do not import that number here -- different question, different risk.)
;
; WHAT IS STILL OPEN IS NOT TIME, IT IS COVERAGE. 3.3.11 has run on the RS485-CAN only, driving
; one inverter through one driver (eversolar_legacy). CI compiles all four environments, but
; nobody has booted it on the Relay-1CH or the Relay-6CH. The 6CH is the one to check: it is a
; different module with a different memory configuration (qio_qspi, N8, no PSRAM, against this
; board's qio_opi with 8 MB), and memory configuration is exactly where a compiler and RTOS change
; can interact.
;
; The driver gap is the smaller of the two: every driver runs on the same rs485Task and the same
; transport, so a timing regression would have surfaced on this path. But "verified" above means
; this board and this driver, not every board and every protocol. Waiting longer closes neither
; gap; flashing a board closes one of them.
;
; Earlier: 55.03.39 (core 3.3.9 / IDF 5.5.4) built clean and ran on hardware, 2026-07-16.
;
; VERSION FORMAT: the middle component is zero-padded -- 55.03.39, 55.03.311. Both the release tag
; and the `version` field in the platform's own platform.json use that form, so it is what the URL
; needs and what `pio pkg list` prints. Earlier notes here dropped the zero, which matches no tag
; and no package version; anyone pasting that into the URL above gets a 404. check_layering.sh
; rule 10 fails the build on the unpadded form, here and in the docs.
; ---------------------------------------------------------------------------------------
[esp32common]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.311/platform-espressif32.zip
board = esp32-s3-devkitc-1
framework = arduino
board_build.mcu = esp32s3
board_build.flash_mode = qio
monitor_speed = 115200
; A panic prints a backtrace of raw addresses. esp32_exception_decoder resolves them against
; the ELF that is already sitting in .pio/build, so the console shows file:line instead of
; hex -- the whole difference between a diagnosable 03:00 reboot and a shrug. `time` stamps
; every line, which matters because the firmware's own log timestamps only start once the RTC
; or NTP has a clock, and a panic can happen before that.
;
; Belongs here rather than in an editor setting: .vscode/extensions.json recommends an IDE
; decoder, but that does nothing for `pio device monitor` on the command line.
monitor_filters = esp32_exception_decoder, time
extra_scripts = ${common.extra_scripts}
; PSRAM is deliberately NOT set here: it is a per-module property, not a family one, and
; the boards this firmware targets genuinely differ. Declaring octal PSRAM for a module
; that has none makes the bootloader probe for a chip that is not there and log
; "octal_psram: PSRAM chip is not connected ... Bailing out." on every single boot
; (observed on the Relay-6CH, 2026-07-23). It boots on regardless, so this hides easily --
; each env states its own memory_type and BOARD_HAS_PSRAM below.
build_flags =
${common.build_flags}
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_MODE=1
; All hardware drivers in one image: the flash has room, discovery picks the right
; one, and one binary per BOARD beats one per brand.
-DENABLE_DRIVER_EVERSOLAR=1
-DENABLE_DRIVER_MODBUS_PROFILE=1
-DENABLE_DRIVER_SOLAX=1
-DENABLE_DRIVER_SUNSPEC=1
-DENABLE_DRIVER_SOLARMAX=1
-DENABLE_DRIVER_MOCK=1
-DENABLE_PROMETHEUS=1
lib_deps =
; FOUR BUILD WARNINGS COME FROM eModbus AND CANNOT BE FIXED HERE. Three are
; `AsyncClient::close(bool) is deprecated`, one is a `%u` format string against a uint32_t.
; None is in src/ -- checked per board, every board reports the same four and zero of our own.
;
; They are not a pin that has gone stale: as of 2026-08 every dependency below IS on its
; newest published release, verified against the registry and the GitHub releases rather than
; against `pio pkg outdated`, which respects these pins and therefore can never report an
; exactly-pinned package as behind. eModbus 1.7.4 is simply fourteen months old while the
; repository keeps committing, and the deprecation is not even reported upstream.
;
; Nor can they be silenced cleanly. build_src_filter does not reach libdeps, and lib_ignore
; drops a whole library we need. Vendoring eModbus to patch three lines would trade four
; warnings for a permanent maintenance burden. So: known, sourced, and left alone -- and three
; of the four come out of ModbusClientTCPasync.cpp, a file this firmware never uses at all
; (we run the SERVER, not the client).
;
; Pinned deliberately. eModbus's last tag is older than its last commit, so `latest`
; is not reproducible. ESPAsyncWebServer must come from the ESP32Async org: both the
; me-no-dev original and mathieucarbou's fork are archived.
;
; AsyncTCP comes from the registry, AS AN EXACT VERSION, and FIRST IN THIS LIST.
; All three matter: eModbus (^3.3.8) and ESPAsyncWebServer (^3.4.10) declare their own
; registry-spec AsyncTCP dependencies, and a git-installed copy (version `+sha.<hash>`)
; does not satisfy those -- the resolver then installs a second, newer AsyncTCP next to
; the pinned one and renames both with @src-<hash> suffixes: two copies, one pin
; defeated. A registry install of the same exact version satisfies both ranges, so
; everyone links against the one pinned copy.
;
; NOTE: the registry owner is `miq19`, not `eModbus` -- the GitHub org name and the
; PlatformIO package owner are different. `eModbus/eModbus` does not resolve.
;
; eModbus also drags in arduino-libraries/Ethernet (the AVR W5100-shield library).
; It stays, reluctantly: lib_ignore matches by library NAME, and the Arduino core's
; own ETH.h wrapper is ALSO named "Ethernet" -- ignoring the name breaks the core's
; Network library (missing esp_eth_driver.h include path), which is exactly what a
; future Ethernet-equipped board would need. The AVR copy is linker-stripped from
; every image; it only costs a few seconds of compile time.
;
; WARNING about lib_ignore on this platform generally: pioarduino implements it by
; EDITING pioarduino-build.py inside the shared framework-arduinoespressif32-libs
; package (component_manager.py), and removing the lib_ignore does NOT restore it --
; every later build on the machine keeps failing on the stripped includes until the
; package's pioarduino-build.py.<mcu> backup is copied back (learned 2026-07-22).
; BUMPED TOGETHER, not separately: ESPAsyncWebServer 3.12.0 requires AsyncTCP 3.5.0.
; Pinning one without the other gives the resolver two ranges to satisfy and the
; second-copy problem described above.
;
; AsyncTCP 3.5.0 changes abort() to run in the caller's task rather than queueing an
; event, so onDisconnect/onError now fire on that task. Checked before bumping: no code
; here calls AsyncClient::abort(), and neither does eModbus -- ModbusServerTCPasync uses
; close(true), which was already synchronous. That matters because it silences its own
; onDisconnect handler around a teardown to avoid a deadlock; had the changed path been
; the one it uses, this bump would have needed far more than a build.
;
; 3.12.0 also rewrites the WebSocket layer. Not a path here -- the dashboard uses SSE
; (AsyncEventSource), which IS touched, by two use-after-free fixes on the abort/close
; teardown. That is the reason to take this rather than sit on 3.11.2.
esp32async/AsyncTCP@3.5.0
https://github.com/ESP32Async/ESPAsyncWebServer#v3.12.0
miq19/eModbus@1.7.4
; EXACT, like everything above it. These two carried carets (^7.4.3, ^1.7.3), which is not
; a pin: it accepts any later minor. There is no lockfile -- .pio/ is gitignored -- so the
; resolved set lives only in whatever machine built last, and a CI runner with a cold cache
; could produce a different firmware from the same commit without anything saying so. That
; matters more here than in most projects: this builds an OTA image, and "the version that
; shipped" has to be reconstructible from the tag.
;
; Both were already resolving to the versions named here, and both are the latest release
; upstream (checked 2026-08-28), so this pins the current behaviour rather than changing it.
bblanchon/ArduinoJson@7.4.3
bertmelis/espMqttClient@1.7.3
; The board Tim runs in production (16 MB flash, PCF85063 RTC, no relays).
[env:waveshare-rs485-can]
extends = esp32common
board_upload.flash_size = 16MB
board_upload.maximum_size = 16777216
; N16R8: 16 MB flash + 8 MB OCTAL PSRAM. Verified on hardware -- this is the board in
; production and it boots without a PSRAM complaint.
board_build.arduino.memory_type = qio_opi
; Two app partitions so a failed OTA falls back to the previous image. Without this line
; PlatformIO silently uses the board default (~3.2 MB single app, no rollback).
board_build.partitions = partitions_16mb_ota.csv
build_flags =
${esp32common.build_flags}
-DBOARD_HAS_PSRAM
-DHELIOGRAPH_BOARD_RS485_CAN
; 16 MB flash, one relay (DRM0 actuator), PCF85063 RTC.
[env:waveshare-relay-1ch]
extends = esp32common
board_upload.flash_size = 16MB
board_upload.maximum_size = 16777216
board_build.partitions = partitions_16mb_ota.csv
; VERIFIED on hardware 2026-07-26 (board MAC 28:84:85:b2:e6:f0): the module IS an N16R8.
; esptool reports 16MB flash + 8MB embedded octal PSRAM, and the firmware boots clean with
; no "octal_psram: PSRAM chip is not connected" bailout -- which is exactly what the 6CH (a
; plain N8, no PSRAM) logged on every boot before its config was corrected. So qio flash +
; opi (octal) PSRAM is the right memory type here; these two lines stay.
board_build.arduino.memory_type = qio_opi
build_flags =
${esp32common.build_flags}
-DBOARD_HAS_PSRAM
-DHELIOGRAPH_BOARD_RELAY_1CH
; 8 MB flash (own partition table), six relays (DRM0-8 actuators), no RTC.
[env:waveshare-relay-6ch]
extends = esp32common
board_upload.flash_size = 8MB
board_upload.maximum_size = 8388608
board_build.partitions = partitions_8mb_ota.csv
; ESP32-S3-WROOM-1U-N8: 8 MB flash, NO PSRAM. Hence no memory_type override to qio_opi and
; no BOARD_HAS_PSRAM -- inheriting those (as this env did until 2026-07-23) made every boot
; log "octal_psram: PSRAM chip is not connected, or wrong PSRAM line mode / Bailing out."
; before continuing without it. Confirmed against the Waveshare product listing and the
; ESPHome device config for this board, and by the first boot of real hardware.
board_build.arduino.memory_type = qio_qspi
build_flags =
${esp32common.build_flags}
-DHELIOGRAPH_BOARD_RELAY_6CH
; Step-debugging over the S3's built-in USB-Serial-JTAG. Same board and flags as the production
; env, differing only in build type and debugger.
;
; A SEPARATE env on purpose. The shipping images must stay release builds -- `build_type = debug`
; drops the optimiser to -Og and keeps full symbols, which changes both size and timing, and RS485
; timing is the one thing this firmware cannot afford to have change underneath it silently.
;
; Deliberately NOT in the CI matrix (firmware.yml enumerates its four envs explicitly) and not in
; default_envs, so it costs nothing per PR and produces no release asset. The flip side is that
; nothing compiles it automatically: if it ever stops building, that is why.
;
; debug_tool is stated even though esp32-s3-devkitc-1.json already declares esp-builtin as its
; default_tool -- so it reads as a decision rather than an accident. No external probe is needed:
; the JTAG lives on the same USB-C connector as the CDC console, on a separate USB interface, so
; ARDUINO_USB_CDC_ON_BOOT=1 and debugging coexist.
;
; See docs/hardware.md for the workflow, including the watchdog caveat -- halting a core stops
; the task WDT being fed, so a breakpoint held longer than the timeout resets the board.
[env:debug-rs485-can]
extends = env:waveshare-rs485-can
build_type = debug
debug_tool = esp-builtin
; Mock-only build: no RS485 hardware needed, exercises the full output stack.
[env:mock]
extends = esp32common
board_upload.flash_size = 16MB
board_upload.maximum_size = 16777216
board_build.partitions = partitions_16mb_ota.csv
; Mirrors the RS485-CAN it stands in for (N16R8, octal PSRAM).
board_build.arduino.memory_type = qio_opi
build_flags =
${common.build_flags}
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_MODE=1
-DBOARD_HAS_PSRAM
-DHELIOGRAPH_BOARD_RS485_CAN
; Two virtual relays through the full MQTT/REST/HA stack, no pins touched: the whole
; relay feature is exercisable on a board without relay hardware.
-DHELIOGRAPH_MOCK_RELAYS=2
-DENABLE_DRIVER_EVERSOLAR=0
-DENABLE_DRIVER_MODBUS_PROFILE=0
-DENABLE_DRIVER_SOLAX=0
-DENABLE_DRIVER_SUNSPEC=0
-DENABLE_DRIVER_SOLARMAX=0
-DENABLE_DRIVER_MOCK=1
-DENABLE_PROMETHEUS=1