Repository navigation
Expand file tree
/
Copy pathplatformio.ini
More file actions
1176 lines (1123 loc) · 62.5 KB
/
Copy pathplatformio.ini
File metadata and controls
1176 lines (1123 loc) · 62.5 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
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
[platformio]
; The S3 1.28" (Kit S3) is the default SKU and builds by default (`pio run`); build
; another with `pio run -e <env>`.
default_envs = blipscope-s3-128
; Shared build_src_filter fragments so exclusions live in ONE place and can't drift.
; The editions are separate compile-time products and are NOT self-#ifdef-gated, so a
; sibling's TUs must be excluded from every other product or they compile and link into
; the shipped/OTA image. Composing from these fragments means adding edition N+1 touches
; only its own env (drop radar TUs + all editions, then re-include its own dir):
; radar env : +<*> ${filters.editions_off}
; edition env : +<*> ${filters.radar_off} ${filters.editions_off} +<its_dir/>
[filters]
; Radar-only translation units (dropped by every edition build).
radar_off = -<AircraftManager.cpp> -<MqttPublisher.cpp> -<SpecialAircraft.cpp> -<AircraftInfoFields.cpp> -<Logbook.cpp> -<IcaoCountry.cpp> -<models/>
; Every edition directory (dropped by the radar build and by every OTHER edition; each
; edition re-includes its own after this). src/astro/ is the shared ephemeris used only
; by Space, so it lives here and Space re-includes it.
editions_off = -<eam/> -<space/> -<astro/> -<seismic/> -<birding/> -<fishing/> -<claudescope/> -<speed/>
; The standalone hardware-test TU (src/hwtest_main.cpp) defines its own setup()/loop(),
; so it MUST be excluded from every product build or the link fails on duplicate symbols.
; Only [env:hwtest-s3-146] re-includes it.
hwtest_off = -<hwtest_main.cpp>
; The Missileer game bench harness (src/gametest_main.cpp) -- same rule as the
; hardware test above: it defines its own setup()/loop(), so it MUST be excluded
; from every product build or the link fails on duplicate symbols. Only
; [env:gametest-s3-128] re-includes it.
gametest_off = -<gametest_main.cpp>
; The Missileer flight-animation rig: its harness TU (own setup()/loop(), same
; duplicate-symbol rule as the two above) AND the animation module it drives.
;
; The MODULE is excluded for a different reason from the harness, and the
; distinction matters. src/anim/ is not test scaffolding -- it is the flight
; director the game will consume, written to be lifted (own namespace, draws
; into a caller-supplied sprite, no harness types in its surface). It is dropped
; here only because nothing in a shipping SKU calls it yet, and dead code in an
; image that is already tight on flash (min_spiffs, OTA needs the room) is a
; cost with no benefit today.
;
; THE DAY A PRODUCT CONSUMES IT, that env stops applying this fragment and
; re-includes +<anim/> -- exactly how the edition dirs work above. Nothing about
; the module has to change for that.
anim_off = -<anim/> -<animtest_main.cpp>
; THAT DAY IS 2026-08-27, and this is the fragment the radar env applies instead.
; Follow Mode's globe face (spec 9) consumes the module's projection and its
; coastline set, so the radar SKUs now compile src/anim/ while still dropping the
; harness TU. The editions are unaffected: each spells out ${filters.anim_off} in
; its own build_src_filter and keeps the module out.
;
; A SEPARATE POSITIVE FRAGMENT RATHER THAN "${filters.anim_off} +<anim/>". A
; build_src_filter is order-dependent, and this repo has already paid once for
; assuming a later token beats an earlier one -- the -U/-D asymmetry that built a
; bench env with no backend URL and compiled clean. Saying it once, positively,
; removes the question.
;
; The premise that dropped it -- "an image already tight on flash (min_spiffs)" --
; is stale for these SKUs anyway: [env:blipscope-s3-128] overrides the partition
; table. Measured cost of the re-include is in the commit that made it.
animtest_off = -<animtest_main.cpp>
; Settings shared by every Blipscope SKU. Each [env:*] below picks a board and a
; -DBLIPSCOPE_VARIANT_* flag; the matching header in include/variants/ supplies pins,
; the display/touch driver selection, and capability flags. Adding a SKU = add one
; variant header + one [env:*] here; no shared firmware changes.
[common]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.39/platform-espressif32.zip
framework = arduino
monitor_speed = 115200
upload_protocol = esptool
; Pre-build patches re-applied on every build so they survive a fresh library install (.pio/
; is gitignored): (1) the ASYNC_RESPONCE_BUFF_SIZE #ifndef guard on ESPAsyncWebServer so the
; -D override below takes effect; (2) restore the shared LCD_CAM interrupt flag in LovyanGFX's
; RGB bus so the S3 RGB panel's interrupt can be allocated (see the script for the full why).
; (3) boot_app0.bin is appended by the platform at a hardcoded 0xe000, which is
; inside the s3-128's enlarged nvs -- an ordinary upload would corrupt the device
; config it just wrote. Re-points it at the table's real otadata offset; a no-op
; on the stock tables, where otadata genuinely is at 0xe000.
; (4) the config-page gate runs here as well as in CI: a bench flash never
; touches CI, and a bench flash is how a blank config page would reach real
; hardware. See scripts/check_config_form_prebuild.py.
extra_scripts =
pre:scripts/check_config_form_prebuild.py
pre:scripts/patch_async_buff.py
pre:scripts/patch_wm_copy.py
pre:scripts/patch_lgfx_rgb_intr.py
post:scripts/fix_boot_app0_offset.py
build_unflags = -std=gnu++11
build_flags =
-std=gnu++17
-Wall
; BUILD IDENTITY. Here in [common] deliberately, so no env can opt out and a
; new env gets it for free. A wrong-env flash is indistinguishable from a
; hardware or upstream fault from the outside: flashing the non-cloud
; blipscope-s3-128 over a cloud board produced "no leaderboard submits",
; "stale data, sweep stops" and "photos never load" at once, and an hour went
; into diagnosing an airspace-capacity ceiling that was not there. The device
; must be able to say what it is, on the banner and on the config page,
; without anyone knowing which log lines exist in which build.
-DBUILD_ENV=\"$PIOENV\"
; Shrink ESPAsyncWebServer's per-send buffer from 2x TCP_MSS (2872 B) to 1 KB so it
; fits a fragmented heap on the tightest board; roomier boards are unaffected.
-DASYNC_RESPONCE_BUFF_SIZE=1024
; mbedTLS's large buffers go to PSRAM instead of the internal heap (#245).
;
; IN [common] FOR THE SAME REASON AS BUILD IDENTITY ABOVE: no env should be
; able to opt out, and a new env gets it for free. Every SKU is a dual-core
; S3 with PSRAM, so there is no board this is wrong for -- and TlsAllocator
; falls back to the internal heap per-allocation if PSRAM is ever refused,
; so it degrades rather than failing.
;
; PROMOTED FROM A BENCH ENV ON EVIDENCE, 2026-08-31. A 152.9 h matched A/B
; (COM119 stock vs COM16 with this flag) is in docs/tls-psram-ab-2026-08-31.md.
; The number that made it a launch item is not the heap: it is that the
; CONTROL ARM COMPLETED ONLY 3 OF 8 OTA CHECKS. The update check needs the
; same contiguous block the enrichment could not get, so a fragmented unit
; loses the only remote path by which it could be repaired.
-DBLIPSCOPE_TLS_PSRAM
; Default source set: the radar app. Every edition dir is dropped here (see [filters]);
; each edition env below re-includes only its own dir. Written as one shared exclusion
; list rather than per-env hand-maintained lists so adding edition N+1 can't silently
; leave a sibling's TUs linked into another product (they are not #ifdef-gated).
build_src_filter = +<*> ${filters.editions_off} ${filters.hwtest_off} ${filters.gametest_off} ${filters.animtest_off}
board_build.partitions = min_spiffs.csv
lib_deps =
lovyan03/LovyanGFX @ 1.2.21
tzapu/WiFiManager @ 2.0.17
ESP32Async/ESPAsyncWebServer @ 3.11.1
ESP32Async/AsyncTCP @ 3.4.10
bblanchon/ArduinoJson @ 7.4.3
knolleary/PubSubClient @ 2.8
; MIT, ~2 KB: Reed-Solomon + masking + format info is not worth hand-rolling.
; Pinned like every other dep -- CI installs libdeps fresh on every run.
ricmoo/QRCode @ 0.0.1
; ---- cloud backend, named exactly once per env -------------------------------
; CLOUD_FEED_BASE is defined in ONE place per env. The tempting alternative -- define
; production in the shipping env, then -U and redefine it in a bench env -- DOES NOT
; WORK under PlatformIO, and fails silently. -U lands in CCFLAGS while -D lands in
; CPPDEFINES, so the undefine wins whatever order this file puts them in; the build
; then falls through CloudFeed.h's `#ifndef CLOUD_FEED_BASE -> ""` and produces a radar
; with NO backend that compiles clean and boots to an empty screen. Measured 2026-08-08
; while making this very change: blipscope-s3-128-cloud built with an empty base URL.
;
; So bench envs compose from the URL-free *_hw sections plus one of these, rather than
; inheriting the shipping env's flags and trying to take the URL back out.
[cloud]
prod = -DFEATURE_CLOUD_FEED -DCLOUD_FEED_BASE=\"https://scopes.valarsystems.com\"
staging = -DFEATURE_CLOUD_FEED -DCLOUD_FEED_BASE=\"https://scopes-staging.valarsystems.com\"
; RUN 4 ONLY. TEST-NET-1 (RFC 5737) -- guaranteed unroutable on the public
; internet and NOT a private range, so it cannot accidentally reach anything on
; the bench LAN. Every blips and enrich request fails at the transport layer
; while the board stays fully associated, which is the state the reachability
; watchdog exists to detect.
;
; WHY NOT THE OTA FAULT INJECTION, which is what the original defect came out of:
; OTA_FAULT_AT_PCT only fires DURING a download, and the pinned pre-release
; advertises version 6 against a candidate on 10 -- `latest > current` is false,
; no download starts, and the fault never fires. It worked on 2026-09-03 because
; the board was on 5 at the time (the AE row reads `incomplete 5>6`).
;
; AND IT WOULD NOT HAVE TESTED THE LADDER ANYWAY: that injection is
; WiFi.disconnect(false,false), and rung 1 of the ladder is the same operation,
; so the fault does not survive its own remedy. This one does -- the destination
; is unroutable, so a reconnect and a radio reset both leave it failing, which is
; what lets rungs 2 and 3 be observed at all.
netfault = -DFEATURE_CLOUD_FEED -DCLOUD_FEED_BASE=\"https://192.0.2.1\"
; Board flags WITHOUT a backend, so each env picks its own exactly once. A shipping env
; is these plus ${cloud.prod}; a bench env is these plus ${cloud.staging}.
[s3128_hw]
build_flags =
${common.build_flags}
-DBLIPSCOPE_VARIANT_S3_128
-DBOARD_HAS_PSRAM
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1 ; assumed native USB-CDC (typical of these clones)
[s3146_hw]
build_flags =
${common.build_flags}
-DBLIPSCOPE_VARIANT_S3_146
-DBOARD_HAS_PSRAM
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1 ; native USB-CDC: upload + monitor over the USB-C port
; ===================== Blipscope Kit S3 (jxl 1.28" round, S3R8) ========
; THE DEFAULT SKU (default_envs above) and what the pilot fleet ships on: "jxl"-brand
; ESP32-S3R8 clone of the 1.28" GC9A01 + CST816 round board (PCF85063 RTC, QMI8658 IMU,
; spk/mic, TF, 2x WS2812, 5 buttons). Pin map VERIFIED on hardware 2026-07-09 (probe
; phase 2); graduated to a CI release row 2026-07-15.
;
; It began as the candidate replacement for the ESP32-C3 Kit, which was retired
; 2026-06-29, briefly revived bench-only for the cloud-feed program, and deleted
; outright 2026-08-09 -- variant header, envs and the wedge-bisection harness with it.
;
; This is the one SKU whose HARDWARE needs a per-batch acceptance gate before it ships:
; the touch IC's factory config and the u.FL-vs-chip antenna build are both silent,
; per-batch substitution risks. See INCOMING-INSPECTION.md.
[env:blipscope-s3-128]
extends = common
board = esp32-s3-devkitc-1
board_build.arduino.memory_type = qio_opi ; S3R8: 8 MB octal PSRAM (flash size TBD)
board_build.flash_size = 16MB
board_upload.flash_size = 16MB
; 84 KB NVS instead of the stock 20 KB -- see partitions-s3-16mb-bignvs.csv for the
; measurement that forced it (20 KB was full with the logbook at only 36% of usage).
; Factory-flash only: OTA never rewrites the partition table, so boards already in
; the field keep the stock layout and keep taking updates normally. Scoped to this
; SKU deliberately -- the other S3 envs still use default_16MB.csv.
board_build.partitions = partitions-s3-16mb-bignvs.csv
build_flags =
${s3128_hw.build_flags}
; Declares which table this image EXPECTS, so Logbook::reportCapacity() can
; check it against the partition the board actually booted. It must move with
; the line above: an env that takes bignvs without this flag simply loses the
; check, and one that sets it without the table warns on every boot.
; The failure it exists for is silent -- a differing table relocates NVS and
; the logbook, location, WiFi and device key all become unreachable at once,
; presenting as "my Blipscope forgot everything".
-DBLIPSCOPE_EXPECT_BIG_NVS
; ---- SHIPS AS A CLOUD RADAR ----------------------------------------------
; This env is what the CI release matrix builds and what devices OTA into, so it
; must BE the shipping image. It was not: the cloud feed lived only in a separate
; -prodburn env, so a board flashed for the pilot would take its first OTA and come
; back with the feed compiled out. Nothing reports that -- a non-cloud image simply
; stops contacting the proxy, which reads as a quiet device, not a fault.
; RULE: the env named in .github/workflows/firmware.yml IS the product. Anything the
; product needs belongs here, never in a sibling env CI does not build.
; scripts/check_release_envs.mjs enforces it.
${cloud.prod}
; FETCH-TRACE -- the shipping image plus fetch instrumentation, and NOTHING else.
;
; Exists because the two SOAK_TEST envs below cannot answer a question about an
; idle board. Both of them (a) point at ${cloud.staging} rather than production,
; and (b) arm SoakHarness, which synthesises taps (AircraftManager.cpp:3706). For
; the 2026-08-17 heap-fragmentation investigation each of those changes the thing
; being measured: the backend differs from the control board's, and the harness
; injects the exact variable the first (wrong) hypothesis blamed.
;
; So this env extends the SHIPPING env and adds one flag. build_flags is restated
; in full because PlatformIO REPLACES rather than appends -- including
; -DBLIPSCOPE_EXPECT_BIG_NVS, which must travel with board_build.partitions above
; or the image silently loses the table check it inherits the table for.
;
; Verify the artifact, not this comment: the ELF must contain the PRODUCTION host
; and must NOT contain SoakHarness. `bash scripts/check-fetchtrace.sh` does both.
; TLS-PSRAM -- the shipping image plus issue #245's fix, and NOTHING else.
;
; The A/B partner for a plain shipping board: same env, same backend, same sky,
; one flag apart. #245 kills enrichment after roughly a day of uptime because
; mbedTLS is pinned to internal RAM (CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y in the
; prebuilt Arduino libs) and its 16 KB record buffer can no longer find a
; contiguous internal block. -DBLIPSCOPE_TLS_PSRAM routes mbedTLS allocations at
; or above 4 KB to PSRAM instead, of which every SKU has ~8.2 MB free.
;
; build_flags is restated IN FULL for the same reason fetchtrace restates it:
; PlatformIO REPLACES rather than appends, and -DBLIPSCOPE_EXPECT_BIG_NVS must
; travel with board_build.partitions or the image silently loses the table check.
;
; Verify the artifact: the ELF must contain the tls-alloc INSTALLED string, and
; the board must print [tls-alloc] PSRAM routing INSTALLED at boot. A build that
; silently lost the flag prints the NOT built in line instead -- deliberately,
; so the control and the treatment are never confusable from a capture alone.
; THE CONTROL ARM, KEPT SO THE A/B CAN BE RUN AGAIN IN THE OTHER DIRECTION.
; This was env:blipscope-s3-128-tlspsram until 2026-08-31, when the flag became
; the default in [common] and the env became a duplicate of the shipping one.
; Inverted rather than deleted: #271's enrichment re-fit needs a board WITHOUT
; the fix to measure against, and a gate whose failing branch is unreachable is
; a gate nobody can check.
;
; VERIFY THIS ONE ON THE ARTIFACT, NOT THE INI. PlatformIO puts -U in CCFLAGS and
; -D in CPPDEFINES, so the undefine wins regardless of the order shown here --
; which is the behaviour wanted, and is exactly the asymmetry that once produced
; a bench env with no backend URL that compiled clean. The ELF carries two
; mutually exclusive strings, so one grep settles it:
; "PSRAM routing INSTALLED" <- shipping envs
; "PSRAM routing NOT built in" <- this one
; RUN 4 -- the reachability watchdog rehearsal. See docs/ota-control-plan.md.
;
; COMPOSED FROM s3128_hw + ${cloud.netfault}, NOT from env:blipscope-s3-128 with
; the URL taken back out. That is the rule at the top of the [cloud] section and
; it is not stylistic: -U lands in CCFLAGS and -D in CPPDEFINES, so an undefine
; beats a later redefine and the build silently gets NO backend at all.
;
; VERIFY ON THE ARTIFACT, NOT HERE. The ELF must contain "192.0.2.1" and must NOT
; contain "scopes.valarsystems.com". One grep settles it, and an env that quietly
; lost its backend looks identical to one that is pointed at an unroutable one --
; both produce a dark radar, and only one of them is the test.
; B3 ONLY -- the dark-room look at the quiet-hour reboot. Moves the reboot to a
; chosen evening hour so a person can watch it without being awake at 03:00.
;
; THE HOUR IS IN build_flags BELOW, and this comment used to claim the opposite
; -- "set on the command line, not here, so this env cannot silently carry a
; stale one" -- while the env carried -DBLIPSCOPE_QUIET_HOUR=4 three lines down.
; Corrected 2026-09-09. Prose does not run, and a comment asserting a safety
; property the file does not have is worse than no comment: it answers the
; question a reader came to ask, wrongly.
;
; The command-line form was tried and rejected. --project-option REPLACES
; build_flags rather than appending, so it must restate the whole list --
; including ${cloud.prod} -- and a mis-stated one silently yields a build with NO
; backend at all (the -U/-D trap in the [cloud] note above). Set the hour here,
; and READ IT BACK OFF THE BOARD: the `[quiet] armed:` line prints the hour and
; the override marker, which is the artifact. The ini is a statement of intent.
;
; NEVER SHIPS. The build announces itself in the [quiet] armed: line as
; "** BENCH OVERRIDE **", which is the thing that matters -- not that the number
; differs, but that a capture read weeks later cannot mistake a 20:00 reboot for
; a shipping defect. QUIET_HOUR_IS_OVERRIDE is true whenever the flag is defined,
; even if it is set to 3.
[env:blipscope-s3-128-quiethour]
extends = env:blipscope-s3-128
build_flags =
${s3128_hw.build_flags}
-DBLIPSCOPE_EXPECT_BIG_NVS
${cloud.prod}
; 22 = 22:00 local, the hour Daniel watches for B3 on 2026-09-09.
; Bench value; the shipping default is 3 (include/QuietHourPolicy.h).
;
; THIS NUMBER IS ONLY MEANINGFUL WITH THE BOARD'S tz-offset BESIDE IT, and it
; has now been wrong in both directions in one morning. With tz-offset="0"
; (UTC) it had to be 5, because 05:00Z is 22:00 PDT. With tz-offset=-7 it has
; to be 22. Same wall-clock target, different constant, because the offset is
; the input and the hour is not.
;
; So: READ `[quiet] armed:` OFF THE BOARD after any change to either, and do
; the arithmetic against the offset it actually reports. Do not infer the
; firing time from this line.
-DBLIPSCOPE_QUIET_HOUR=22
; B3 ON DEMAND -- the dark-room look at the quiet-hour reboot, run at a chosen
; hour in a darkened room instead of at 03:00 (or at 22:00, which failed once
; already because the board turned out not to be dimmed at all).
;
; THREE OVERRIDES, because B3 has three preconditions and all three are
; otherwise only true in the middle of the night:
;
; BLIPSCOPE_FORCE_NIGHT the panel must be DIM before the reboot. Forces
; the solar-night verdict only -- `autoDim && night`
; is untouched production logic, so a board with
; auto-dim off still will not dim and the run is
; invalid, exactly as a real night would be.
; BLIPSCOPE_QUIET_HOUR the local hour to fire at. Set to the NEXT hour
; boundary so the real edge trigger fires it; this
; is not a "reboot now" hook, it is the schedule.
; BLIPSCOPE_REBOOT_MIN_S the 24 h cap legitimately refuses a second reboot
; in one day, and an on-demand run needs the
; deferral ACCEPTED. Shortens the cap; does NOT
; bypass the check, so the same comparison, the same
; NVS stamp and the same refusal path all still run.
;
; NEVER SHIPS, and that is asserted on the artifact rather than trusted: the
; boot log announces `** BENCH OVERRIDE **`, and the release check greps the
; shipping ELF for that string expecting zero -- with a positive control against
; THIS env's ELF, because "0 occurrences" is equally consistent with "absent"
; and "my grep cannot read this file".
;
; The hour lives here rather than on the command line: --project-option REPLACES
; build_flags rather than appending, and a mis-stated one silently drops the
; backend URL (the -U/-D trap in the [cloud] note above). Read `[quiet] armed:`
; off the board and do the arithmetic against the offset it reports.
[env:blipscope-s3-128-b3]
extends = env:blipscope-s3-128
build_flags =
${s3128_hw.build_flags}
-DBLIPSCOPE_EXPECT_BIG_NVS
${cloud.prod}
-DBLIPSCOPE_QUIET_HOUR=13
-DBLIPSCOPE_FORCE_NIGHT
-DBLIPSCOPE_REBOOT_MIN_S=120
; WI-FI JOIN DIAGNOSTIC -- display-only, bench and field-visit use, NEVER SHIPS.
;
; Built to diagnose a board that joins at home and will not join an eero 6+ at
; another house, with no laptop present. It renders what WiFi.onEvent ALREADY
; receives plus one diagnostic scan, and changes nothing about how the device
; joins: no retry counts, no timeouts, no auth mode, no PMF, no fast-join logic.
;
; Announces itself on the glass ("** WIFI DIAG BUILD -- NOT SHIPPING **") and on
; serial ([jdiag] ... NEVER SHIPS), and the string must be provably ABSENT from
; the shipping ELF -- checked with a positive control against THIS env, because
; "0 occurrences" is equally consistent with "absent" and "my grep cannot read
; this file".
;
; THE BOARD CARRYING THIS GETS RE-FLASHED TO SHIPPING BEFORE IT GOES ANYWHERE
; NEAR A CUSTOMER. See INCOMING-INSPECTION.md section 6.
[env:blipscope-s3-128-joindiag]
extends = env:blipscope-s3-128
build_flags =
${s3128_hw.build_flags}
-DBLIPSCOPE_EXPECT_BIG_NVS
${cloud.prod}
-DBLIPSCOPE_JOIN_DIAG
; ALERT BENCH -- the shipping s3-128 image plus ONE synthetic emergency/military
; contact on serial keys (AircraftManager::PollAlertBench), so "an alert is never
; consumed unseen" is exercised on the glass without waiting for a real 7700.
; NOT in the CI matrix and never published: scripts/check-no-bench-hooks.sh fails
; any shipping build whose image contains the "[alert-bench]" marker.
[env:blipscope-s3-128-alertbench]
extends = env:blipscope-s3-128
build_flags =
${s3128_hw.build_flags}
-DBLIPSCOPE_EXPECT_BIG_NVS
${cloud.prod}
-DALERT_BENCH
[env:blipscope-s3-128-touchbench]
; BENCH ONLY: the touch-wedge reboot cap on a healthy board (TouchBench.h). The chip is
; DEAD from boot (serial `w` revives it); the touch idle is 20 s and the healthy reset
; 180 s instead of 10 min and 60 min. The overrides #error without TOUCH_WEDGE_BENCH, and
; check-no-bench-hooks.sh refuses its [touch-bench] marker in every shipping image.
extends = env:blipscope-s3-128
build_flags =
${s3128_hw.build_flags}
-DBLIPSCOPE_EXPECT_BIG_NVS
${cloud.prod}
-DTOUCH_WEDGE_BENCH
-DTOUCH_WD_REBOOT_IDLE_MS=20000UL
-DTOUCH_WD_HEALTHY_MS=180000UL
[env:blipscope-s3-128-netfault]
extends = env:blipscope-s3-128
build_flags =
${s3128_hw.build_flags}
-DBLIPSCOPE_EXPECT_BIG_NVS
${cloud.netfault}
[env:blipscope-s3-128-tlsheap]
extends = env:blipscope-s3-128
build_unflags = -DBLIPSCOPE_TLS_PSRAM
build_flags =
${s3128_hw.build_flags}
-DBLIPSCOPE_EXPECT_BIG_NVS
${cloud.prod}
[env:blipscope-s3-128-fetchtrace]
extends = env:blipscope-s3-128
build_flags =
${s3128_hw.build_flags}
-DBLIPSCOPE_EXPECT_BIG_NVS
${cloud.prod}
-DFETCH_TRACE
; Wedge soak: cloud feed + SOAK_TEST + the human-duty gesture script, with the touch
; watchdog active via the variant flag. The [health]/[soak] ledger is the same shape the
; retired C3 emitted, so bench-logs/c3-soak-baseline-2026-07-10.log stays readable
; side-by-side as the historical baseline (the board is gone; the comparison isn't).
[env:blipscope-s3-128-soak]
extends = env:blipscope-s3-128
build_flags =
; hw section + staging, NOT the parent's build_flags: the parent ships PRODUCTION
; now, and a macro cannot be taken back out (see the [cloud] note above).
${s3128_hw.build_flags}
${cloud.staging}
-DSOAK_TEST
; OTA pinned to the pre-gate tag (invisible to the fleet's /latest): the
; weekend gate's OTA-at-steady-state leg. version.txt is withheld from the
; release until the 24 h line -- uploading it + setting minFw=5 in staging
; KV arms the update mid-run without touching the device.
-DOTA_RELEASE_BASE=\"https://github.com/Valar-Systems/valar-scopes/releases/download/pregate-s3-128-v5\"
; DENSE-SKY SOAK -- the launch bar, not an edge case. Some of the 50 pilot units
; will live under LAX / JFK / ATL, and a dense sky is where the shared depth-1
; enrich queue saturates: the tracked set pins at MAX_AIRCRAFT and churns, so any
; request that is not preemptive loses every race. That is how the hourly
; leaderboard submit reached zero landings at LAX + 10 mi while nothing errored.
;
; Set the device's location to the coordinates below on the config page before
; running (location is NVS, deliberately not a build flag -- baking a position in
; would make the harness lie about which config it soaked):
;
; lat 33.943324 lon -118.409144 radius 10 mi ; LAX threshold
;
; GATE: every due submit must go away within LB_SUBMIT_GATE_MS of becoming due.
; The device prints "[leaderboard] submit away after N ms (worst M ms)" on each
; one, so the run is auditable from the serial log alone. 120 s is generous
; against a ~1-2 s enrich cycle -- it is a starvation detector, not a latency
; benchmark, and a gate that trips on a slow afternoon gets ignored.
[env:blipscope-s3-128-densesky]
extends = env:blipscope-s3-128
build_flags =
; hw section + staging, NOT the parent's build_flags: the parent ships PRODUCTION
; now, and a macro cannot be taken back out (see the [cloud] note above).
${s3128_hw.build_flags}
${cloud.staging}
-DSOAK_TEST
-DLB_SUBMIT_GATE_MS=120000
; The Kit S3 (1.28") with the cloud feed but WITHOUT SOAK_TEST -- a clean,
; manually-testable cloud build (the soak env above simulates taps via its duty
; script, which fights manual detail-card testing). Same STAGING base + no baked
; key as the s3-146 cloud env; paste the access key on the config page after
; flashing. Bench-only, like the other s3-128 cloud envs (no CI/release row).
[env:blipscope-s3-128-cloud]
extends = env:blipscope-s3-128
build_flags =
; hw section + staging, NOT the parent's build_flags: the parent ships PRODUCTION
; now, and a macro cannot be taken back out (see the [cloud] note above).
${s3128_hw.build_flags}
${cloud.staging}
; Incoming inspection (flash THIS first, before any firmware env): I2C census + CST816
; identity + the 0xFE DisAutoSleep accessibility experiment, over the same lgfx::i2c
; path as TouchWatchdog so ACK/NACK results compare 1:1 with the C3 finding. Only
; src/probe/TouchProbe.cpp compiles. Pin overrides: -DPROBE_TP_SDA/SCL/RST/INT/ADDR.
[env:probe-s3-128]
extends = common
board = esp32-s3-devkitc-1
board_build.arduino.memory_type = qio_opi
board_build.flash_size = 16MB
board_upload.flash_size = 16MB
build_src_filter = +<probe/TouchProbe.cpp>
build_flags =
${common.build_flags}
-DPROBE_SKETCH
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1
; Heap-metric probe (src/probe/HeapProbe.cpp). Answers one question: does
; ESP.getMaxAllocHeap() measure the memory a TLS handshake actually needs? The
; 54 h soak's OTA check failed a 16,717 B caps-0x804 alloc while the health line
; reported largest=31732, so one of those numbers is not what it is read as.
; See the file header; the verdict is the last PROBE, line it prints.
[env:probe-s3-128-heap]
extends = env:probe-s3-128
build_src_filter = +<probe/HeapProbe.cpp>
; Blit probe (src/probe/BlitProbe.cpp). Answers the one number the full-bleed
; 240x240 detail card was scoped on without measuring: what a 240^2 PSRAM sprite
; costs per frame versus today's 150x100, and whether the fillScreen it makes
; redundant pays for it. Also holds a 240^2 sprite + a 20 KB payload live and
; trial-allocates a real handshake against the remainder.
;
; NOT extended from env:probe-s3-128: that env carries no BLIPSCOPE_VARIANT_* and
; no PSRAM memory_type (TouchProbe talks to raw lgfx::i2c and needs neither). A
; blit measured with the sprite in internal RAM instead of PSRAM would be a
; confident number about a machine we do not ship. This env is the SHIPPING board
; config with the app swapped out -- same variant, same qio_opi PSRAM, same
; partition table so the bench board's NVS survives the round trip back to v6.
[env:probe-s3-128-blit]
extends = common
board = esp32-s3-devkitc-1
board_build.arduino.memory_type = qio_opi
board_build.flash_size = 16MB
board_upload.flash_size = 16MB
board_build.partitions = partitions-s3-16mb-bignvs.csv
build_src_filter = +<probe/BlitProbe.cpp>
build_flags =
${s3128_hw.build_flags}
-DPROBE_SKETCH
; LogbookProbe: the v5 caps, eviction and the v4->v5 operator migration.
;
; These paths are unreachable by using the product. Eviction needs a store at its
; cap, which took a week of GA-heavy sky at the OLD caps and is 2x further away at
; the new ones; the migration only fires against a store written by firmware that
; is no longer running. So they get driven directly instead of waited for.
;
; Same shape as probe-s3-128-blit: the SHIPPING board config with the app swapped
; out, so nothing measured here is a fact about a machine we do not ship. The probe
; never calls Begin() or MaybePersist(), so flashing it cannot harm the bench
; board's logbook -- but keep the partition table matching anyway, so the trip back
; to a radar build leaves NVS untouched.
[env:probe-s3-128-logbook]
extends = common
board = esp32-s3-devkitc-1
board_build.arduino.memory_type = qio_opi
board_build.flash_size = 16MB
board_upload.flash_size = 16MB
board_build.partitions = partitions-s3-16mb-bignvs.csv
build_src_filter = +<probe/LogbookProbe.cpp> +<Logbook.cpp>
build_flags =
${s3128_hw.build_flags}
-DPROBE_SKETCH
; Probe phase 2, on the bus the sweep verified (SDA=8 SCL=9): hunt the TP_INT pin
; (falling-edge counters on every safe unclaimed GPIO while a finger drags) and
; test the TP_RST=GPIO0 hypothesis (drive low -> CTP must NACK; release -> re-ACK
; after the 450 ms boot). Results land in include/variants/s3_128.h and arm the
; watchdog's hard rung + INT-gated touch reads before the ship-config soak.
[env:probe-s3-128-phase2]
extends = env:probe-s3-128
build_flags =
${env:probe-s3-128.build_flags}
-DPROBE_PHASE2
-DPROBE_TP_SDA=8
-DPROBE_TP_SCL=9
-DPROBE_TP_RST=0
; ===================== Blipscope Pro 2.1 (S3 test board) ==============
; Waveshare ESP32-S3-Touch-LCD-2.1: ESP32-S3R8 + 2.1" round ST7701 480x480 RGB LCD +
; CST820 touch + QMI8658 IMU + buzzer. First S3 SKU and first RGB-bus panel. The panel/
; touch reset and the ST7701 init chip-select sit on a TCA9554 I2C expander; variant::
; BoardPreInit() (src/board/board_s3_touch21.cpp) drives it before tft.init(). PSRAM +
; dual-core, so the variant sets BANDED_RENDER=false.
[env:blipscope-pro-s3-21]
extends = common
board = esp32-s3-devkitc-1 ; generic S3; flash/PSRAM pinned below
board_build.arduino.memory_type = qio_opi ; 16 MB QIO flash + 8 MB OCTAL PSRAM (the R8)
board_build.flash_size = 16MB
board_upload.flash_size = 16MB
board_build.partitions = default_16MB.csv ; the RGB build is larger than the C3's; 16 MB gives OTA room
build_flags =
${common.build_flags}
-DBLIPSCOPE_VARIANT_S3_21
-DBOARD_HAS_PSRAM
; ---- SHIPS AS A CLOUD RADAR ----------------------------------------------
; This env is what the CI release matrix builds and what devices OTA into, so it
; must BE the shipping image. It was not: the cloud feed lived only in a separate
; -prodburn env, so a board flashed for the pilot would take its first OTA and come
; back with the feed compiled out. Nothing reports that -- a non-cloud image simply
; stops contacting the proxy, which reads as a quiet device, not a fault.
; RULE: the env named in .github/workflows/firmware.yml IS the product. Anything the
; product needs belongs here, never in a sibling env CI does not build.
; scripts/check_release_envs.mjs enforces it.
${cloud.prod}
; NB: -DBLIPSCOPE_BRINGUP_DIAG (a ~10.5 s boot-time color-flash panel/PSRAM diagnostic,
; #ifdef block still in main.cpp) is intentionally OFF: this env ships in the CI release
; matrix, so the flag would run the diagnostic on every customer boot / OTA. Re-enable it
; only for a local bring-up build, never on a released env.
; Serial goes to UART0 (no ARDUINO_USB_CDC_ON_BOOT) -> use the board's CH343P UART USB port for
; upload + monitor; it auto-resets and doesn't drop on reboot, so it captures the full boot log.
; ===================== Blipscope 1.46 (S3 1.46" round) ================
; Waveshare ESP32-S3-Touch-LCD-1.46B: ESP32-S3R8 + 1.46" round 412x412 IPS (SPD2010 over QSPI) +
; SPD2010 touch + QMI8658 IMU + PCM5101 I2S speaker. The SPD2010 is a TDDI chip (display + touch in
; one), and LovyanGFX has neither driver, so both are custom: include/Panel_SPD2010.hpp (subclasses
; lgfx::Panel_AMOLED) and include/Touch_SPD2010.hpp (an lgfx::ITouch). Panel/touch reset sit on a
; TCA9554 expander driven by variant::BoardPreInit() (src/board/board_s3_touch146.cpp).
; LovyanGFX enables its QSPI bus automatically on the S3 (LGFX_USE_QSPI). This board uses native
; USB-CDC (303A:1001), so upload + monitor run over the USB-C port. PSRAM + dual-core:
; BANDED_RENDER=false.
[env:blipscope-s3-146]
extends = common
board = esp32-s3-devkitc-1
board_build.arduino.memory_type = qio_opi ; 16 MB QIO flash + 8 MB OCTAL PSRAM (the R8)
board_build.flash_size = 16MB
board_upload.flash_size = 16MB
board_build.partitions = default_16MB.csv ; 16 MB gives OTA room
build_flags =
${s3146_hw.build_flags}
; ---- SHIPS AS A CLOUD RADAR ----------------------------------------------
; This env is what the CI release matrix builds and what devices OTA into, so it
; must BE the shipping image. It was not: the cloud feed lived only in a separate
; -prodburn env, so a board flashed for the pilot would take its first OTA and come
; back with the feed compiled out. Nothing reports that -- a non-cloud image simply
; stops contacting the proxy, which reads as a quiet device, not a fault.
; RULE: the env named in .github/workflows/firmware.yml IS the product. Anything the
; product needs belongs here, never in a sibling env CI does not build.
; scripts/check_release_envs.mjs enforces it.
${cloud.prod}
; ===================== Blipscope 1.46 CLOUD (staging bench) ===========
; The S3 1.46" with -DFEATURE_CLOUD_FEED: the radar gains the "Blipscope Cloud"
; data source (default) backed by the proxy/ Worker -- one keep-alive TLS host,
; <=2 KB payloads, /v1/config-driven active/idle/night cadence, tap-to-enrich.
; Kept as a SEPARATE env pointed at STAGING until bench-verified; the release
; env above ships unchanged. Promotion later = move the flag + a production
; CLOUD_FEED_BASE into [env:blipscope-s3-146] and delete this env.
; The device key is NOT baked here (key policy: values never appear in the
; repo): paste it into the config page's "Access key" field after flashing, or
; add a local -DCLOUD_FEED_KEY=\"...\" build flag that never gets committed.
[env:blipscope-s3-146-cloud]
extends = env:blipscope-s3-146
build_flags =
; hw section + staging, NOT the parent's build_flags: the parent ships PRODUCTION
; now, and a macro cannot be taken back out (see the [cloud] note above).
${s3146_hw.build_flags}
${cloud.staging}
; ---- the former *-prodburn envs, COLLAPSED 2026-08-08 ----------------------
; blipscope-s3-128-prodburn / blipscope-s3-146-prodburn were "point a bench board at
; production" envs adding -DFEATURE_CLOUD_FEED + the production CLOUD_FEED_BASE on top
; of their base. Their own note said to delete them once the shipping switch happened.
; It has: those flags now live in the base envs, so these were exact duplicates of
; their parents and are gone.
;
; Worth a paragraph rather than a silent deletion, because their existence is how the
; pilot nearly shipped wrong: the image the boards were to be flashed from was NOT the
; image CI builds or that OTA serves, and the difference was invisible. If you find
; yourself adding a "-prod" or "-real" variant of a shipping env again, that is the
; smell -- put the flag in the env CI builds, or it is not what ships.
; ===================== Missileer (EAM / HFGCS monitor) ================
; A separate compile-time PRODUCT built from the same repo and the same boards: -DFEATURE_EAM
; swaps the radar app for Missileer, the HFGCS EAM monitor (src/eam/). It reuses the shared infra (display,
; Wi-Fi, web config, storage, HTTP/TLS, OTA, ntfy) but NOT the radar/aircraft/ADS-B code, which
; build_src_filter strips. FW_OTA_PREFIX puts these on their own OTA channel (firmware-eam-<slug>.bin)
; so an EAM device never pulls a radar image for the same board. EAM_FEED_BASE is the build-flag
; default for the backend URL; it's overridable at runtime on the config page. It runs on the
; S3 1.46" AMOLED (the intended home for the EAM screens).
; ---- Missileer on the S3 1.46" SPD2010 AMOLED (dual-core + PSRAM, true black) ----
[env:missileer-s3-146]
extends = common
board = esp32-s3-devkitc-1
board_build.arduino.memory_type = qio_opi
board_build.flash_size = 16MB
board_upload.flash_size = 16MB
board_build.partitions = default_16MB.csv
build_flags =
${common.build_flags}
-DBLIPSCOPE_VARIANT_S3_146
-DFEATURE_EAM
-DFW_OTA_PREFIX=\"eam-\"
-DEAM_FEED_BASE=\"https://valar-eam-feed.onrender.com\" ; live backend default; still overridable at runtime in web config
-DBOARD_HAS_PSRAM
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1 ; native USB-CDC: upload + monitor over the USB-C port
build_src_filter = +<*> ${filters.radar_off} ${filters.editions_off} ${filters.hwtest_off} ${filters.gametest_off} ${filters.anim_off} +<eam/>
; ---- Missileer on the Kit S3 1.28" (the default Missileer board going forward) ----
; Same board as [env:blipscope-s3-128] (same partitions + big NVS, so a board moving
; between the radar and Missileer keeps its Wi-Fi and settings), with FEATURE_EAM and
; FEATURE_USB_OPEN: a long press on the screen types the shown message's archive link
; into the connected computer's launcher (src/eam/UsbOpen.h).
;
; USB MODE 0, NOT 1, AND THAT IS THE WHOLE COST OF THE FEATURE. MODE=1 is the S3's
; fixed-function USB-Serial/JTAG peripheral: serial + JTAG, and it cannot be a HID
; keyboard. MODE=0 is TinyUSB on the same USB-C pins (GPIO19/20), enumerating as a
; composite CDC serial + HID keyboard, so flashing and the monitor keep working on
; the same port. What MODE=0 gives up: the JTAG interface; boot/ROM logs printed
; before TinyUSB starts (and a panic's backtrace) no longer reach the USB port; and a
; firmware that dies before USB comes up needs BOOT held + RESET tapped to reflash.
; MONITOR WITH RTS LOW: `pio device monitor -p <port> --rts 0 --dtr 1`. The TinyUSB CDC
; reboots into the ROM bootloader on esptool's DTR/RTS sequence (that is what lets
; `-t upload` work with no buttons), and a monitor that opens with RTS high and then
; raises DTR performs it: the board lands in "waiting for download" (seen 2026-09-22
; with .NET SerialPort). Recover with any esptool command using --after hard_reset.
; No slug yet (skus.yml): bring-up, built and gated, never published over OTA.
[env:missileer-s3-128]
extends = common
board = esp32-s3-devkitc-1
board_build.arduino.memory_type = qio_opi
board_build.flash_size = 16MB
board_upload.flash_size = 16MB
board_build.partitions = partitions-s3-16mb-bignvs.csv
build_flags =
${common.build_flags}
-DBLIPSCOPE_VARIANT_S3_128
-DBLIPSCOPE_EXPECT_BIG_NVS
-DBOARD_HAS_PSRAM
-DFEATURE_EAM
-DFEATURE_USB_OPEN
; THE MISSILEER GAME (src/game/ drill, wired in src/eam/EamManager.cpp). Compiled into
; this env only: the 1.28" is the Missileer board, and this env is bring-up, never
; published over OTA. At runtime the game stays dark until valar-eam-feed serves the
; ppm /config (its #71); the served HOLD flag stands it down without a build.
-DFEATURE_EAM_GAME
-DFW_OTA_PREFIX=\"eam-\"
-DEAM_FEED_BASE=\"https://valar-eam-feed.onrender.com\"
-DARDUINO_USB_MODE=0
-DARDUINO_USB_CDC_ON_BOOT=1 ; TinyUSB CDC + HID composite on the USB-C port
build_src_filter = +<*> ${filters.radar_off} ${filters.editions_off} ${filters.hwtest_off} ${filters.gametest_off} ${filters.anim_off} +<eam/>
; ===================== Orbitscope (live space-data monitor) ===========
; A third compile-time PRODUCT from the same repo and boards: -DFEATURE_SPACE swaps the radar app
; for Orbitscope (src/space/) -- a desk window onto ISS / launches / space weather / deep-space
; probes. Reuses all the shared infra (display, Wi-Fi, web config, storage, HTTP/TLS, OTA, ntfy)
; but NOT the radar/aircraft/ADS-B code or the EAM cluster, which build_src_filter strips.
; FW_OTA_PREFIX puts it on its own OTA channel (firmware-space-<slug>.bin). It pulls directly from
; free public space APIs and bakes in NO backend; the optional valar-space-feed backend (Phase 3)
; is the runtime "space-base-url" config, defaulting to the SPACE_FEED_BASE flag (empty by default).
; Runs on the S3 1.46" AMOLED.
[env:orbitscope-s3-146]
extends = common
board = esp32-s3-devkitc-1
board_build.arduino.memory_type = qio_opi
board_build.flash_size = 16MB
board_upload.flash_size = 16MB
board_build.partitions = default_16MB.csv
; Orbitscope adds the Hopperpop SGP4 library for on-device ISS pass prediction (LDF only
; compiles it into this env, since only src/space includes it).
lib_deps =
${common.lib_deps}
; Pinned to a commit (not the default branch): CI installs libdeps fresh every run, so an
; unpinned git dep would pull whatever HEAD is and could silently change the ISS pass math
; or break the release build. 7c82d31 == tag v1.0.3 == master at time of pin. Bump deliberately.
https://github.com/Hopperpop/Sgp4-Library.git#7c82d31f9d9a286acde5ef55da8308fc93d2ba96
build_flags =
${common.build_flags}
-DBLIPSCOPE_VARIANT_S3_146
-DFEATURE_SPACE
-DFW_OTA_PREFIX=\"space-\"
-DBOARD_HAS_PSRAM
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1 ; native USB-CDC: upload + monitor over the USB-C port
build_src_filter = +<*> ${filters.radar_off} ${filters.editions_off} ${filters.hwtest_off} ${filters.gametest_off} ${filters.anim_off} +<space/> +<astro/>
; ===================== Quakescope (USGS earthquake radar) =============
; A fourth compile-time PRODUCT from the same repo and boards: -DFEATURE_SEISMIC swaps the radar app
; for Quakescope, the Seismic edition (src/seismic/) -- a desk earthquake radar fed by the keyless USGS feed.
; Reuses all the shared infra (display, Wi-Fi, web config, storage, HTTP/TLS, OTA, ntfy) but NOT the
; radar/aircraft/ADS-B code, the EAM cluster, or the Space cluster, which build_src_filter strips.
; FW_OTA_PREFIX puts it on its own OTA channel (firmware-seismic-<slug>.bin). It pulls directly from
; the public USGS FDSN event API and bakes in NO backend; the optional "se-base-url" config is empty
; by default. Runs on the S3 1.46" AMOLED.
[env:quakescope-s3-146]
extends = common
board = esp32-s3-devkitc-1
board_build.arduino.memory_type = qio_opi
board_build.flash_size = 16MB
board_upload.flash_size = 16MB
board_build.partitions = default_16MB.csv
build_flags =
${common.build_flags}
-DBLIPSCOPE_VARIANT_S3_146
-DFEATURE_SEISMIC
-DFW_OTA_PREFIX=\"seismic-\"
-DBOARD_HAS_PSRAM
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1 ; native USB-CDC: upload + monitor over the USB-C port
build_src_filter = +<*> ${filters.radar_off} ${filters.editions_off} ${filters.hwtest_off} ${filters.gametest_off} ${filters.anim_off} +<seismic/>
; ===================== Quillscope (eBird sightings radar) =============
; A compile-time PRODUCT from the same repo and boards: -DFEATURE_BIRDING swaps the radar app for
; Quillscope, the Birding edition (src/birding/) -- notable bird sightings near you, live from the Cornell eBird API.
; Reuses all the shared infra (display, Wi-Fi, web config, storage, HTTP/TLS, OTA, ntfy) but NOT the
; radar/aircraft/ADS-B code, the EAM cluster, the Space cluster, or the Seismic cluster, which
; build_src_filter strips. FW_OTA_PREFIX puts it on its own OTA channel (firmware-birding-<slug>.bin).
; It bakes in NO key: the user supplies their own free eBird token in web config (X-eBirdApiToken).
; Runs on the S3 1.46".
[env:quillscope-s3-146]
extends = common
board = esp32-s3-devkitc-1
board_build.arduino.memory_type = qio_opi
board_build.flash_size = 16MB
board_upload.flash_size = 16MB
board_build.partitions = default_16MB.csv
build_flags =
${common.build_flags}
-DBLIPSCOPE_VARIANT_S3_146
-DFEATURE_BIRDING
-DFW_OTA_PREFIX=\"birding-\"
-DBOARD_HAS_PSRAM
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1 ; native USB-CDC: upload + monitor over the USB-C port
build_src_filter = +<*> ${filters.radar_off} ${filters.editions_off} ${filters.hwtest_off} ${filters.gametest_off} ${filters.anim_off} +<birding/>
; ===================== Reelscope (fishing-conditions console) =========
; A compile-time PRODUCT from the same repo and boards: -DFEATURE_FISHING swaps the radar app for the
; Fishing edition (src/fishing/, product name "Reelscope") -- a fishing-conditions console covering
; BOTH freshwater (USGS Water Services) and saltwater (NOAA CO-OPS tides + NDBC buoys), with on-device
; solunar/sun/moon (its own Solunar.cpp, not the shared src/astro/) and keyless Open-Meteo weather.
; Reuses all the shared infra but NOT the radar/aircraft/ADS-B code or any sibling edition (including
; src/astro/), which build_src_filter strips. FW_OTA_PREFIX -> firmware-fishing-<slug>.bin.
; It bakes in NO backend; the optional "fi-base-url" aggregator config is empty by default. Both water
; families are gated so a fresh-only or salt-only user never hits the other's APIs. Runs on the S3 1.46".
[env:reelscope-s3-146]
extends = common
board = esp32-s3-devkitc-1
board_build.arduino.memory_type = qio_opi
board_build.flash_size = 16MB
board_upload.flash_size = 16MB
board_build.partitions = default_16MB.csv
build_flags =
${common.build_flags}
-DBLIPSCOPE_VARIANT_S3_146
-DFEATURE_FISHING
-DFW_OTA_PREFIX=\"fishing-\"
-DBOARD_HAS_PSRAM
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1 ; native USB-CDC: upload + monitor over the USB-C port
build_src_filter = +<*> ${filters.radar_off} ${filters.editions_off} ${filters.hwtest_off} ${filters.gametest_off} ${filters.anim_off} +<fishing/>
; ===================== Claudescope (Claude usage limits monitor) ======
; A compile-time PRODUCT from the same repo and boards: -DFEATURE_CLAUDESCOPE swaps the radar app for
; the Claudescope edition (src/claudescope/) -- a desk gauge for your live Claude usage limits (session
; + weekly), fed by the on-LAN claudescope-sidecar (tools/claudescope-sidecar/), which holds the OAuth
; token and republishes the usage window as normalized JSON so the device never sees a credential.
; Reuses all the shared infra (display, Wi-Fi, web config, storage, HTTP/TLS, OTA, ntfy) but NOT the
; radar/aircraft/ADS-B code or any sibling edition, which build_src_filter strips. FW_OTA_PREFIX puts
; it on its own OTA channel (firmware-claudescope-<slug>.bin). It bakes in NO backend; the required
; "cl-base-url" (the sidecar's address) is empty by default. Runs on the S3 1.46".
[env:claudescope-s3-146]
extends = common
board = esp32-s3-devkitc-1
board_build.arduino.memory_type = qio_opi
board_build.flash_size = 16MB
board_upload.flash_size = 16MB
board_build.partitions = default_16MB.csv
build_flags =
${common.build_flags}
-DBLIPSCOPE_VARIANT_S3_146
-DFEATURE_CLAUDESCOPE
-DFW_OTA_PREFIX=\"claudescope-\"
-DBOARD_HAS_PSRAM
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1 ; native USB-CDC: upload + monitor over the USB-C port
build_src_filter = +<*> ${filters.radar_off} ${filters.editions_off} ${filters.hwtest_off} ${filters.gametest_off} ${filters.anim_off} +<claudescope/>
; ===================== Speedscope (MiniSpeedCam companion) ============
; A compile-time PRODUCT from the same repo and boards: -DFEATURE_SPEED swaps the radar app for the
; Speedscope edition (src/speed/) -- a desk speed-radar console that ties into a MiniSpeedCam
; (minispeedcam.com) over the LAN via its keyless /api/state (health + live proximity) and /api/events
; (recent passes) endpoints. Reuses all the shared infra (display, Wi-Fi, web config, storage, HTTP,
; OTA, ntfy) but NOT the radar/aircraft/ADS-B code or any sibling edition, which build_src_filter
; strips. FW_OTA_PREFIX puts it on its own OTA channel (firmware-speed-<slug>.bin). It bakes in NO
; backend; the optional "sc-base-url" proxy config is empty by default. Runs on the S3 1.46" AMOLED.
[env:speedscope-s3-146]
extends = common
board = esp32-s3-devkitc-1
board_build.arduino.memory_type = qio_opi
board_build.flash_size = 16MB
board_upload.flash_size = 16MB
board_build.partitions = default_16MB.csv
build_flags =
${common.build_flags}
-DBLIPSCOPE_VARIANT_S3_146
-DFEATURE_SPEED
-DFW_OTA_PREFIX=\"speed-\"
-DBOARD_HAS_PSRAM
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1 ; native USB-CDC: upload + monitor over the USB-C port
build_src_filter = +<*> ${filters.radar_off} ${filters.editions_off} ${filters.hwtest_off} ${filters.gametest_off} ${filters.anim_off} +<speed/>
; ===================== Assembled S3 tiers (TODO) ======================
; Added at hardware bring-up: each needs a variant header in include/variants/
; (pins verified against the board's wiki) and a panel/touch block in LGFX.h.
; The S3 boards have PSRAM, so their variant sets BANDED_RENDER=false and
; BANDED_RENDER=false. Templates -- uncomment + fill in once the board is wired:
;
; [env:blipscope-lite-s3-128] ; S3 1.28" + IMU -> $59
; ---- Blipscope Pro 1.75" AMOLED (BENCH-ONLY SCAFFOLD; pins UNVERIFIED) ----
; Waveshare ESP32-S3-Touch-AMOLED-1.75: ESP32-S3R8 + 1.75" round 466x466 AMOLED (CO5300 QSPI) +
; FT3168 touch + QMI8658 IMU + RTC + MEMS mic. Both display and touch controllers are BUILT INTO
; LovyanGFX (Panel_CO5300 / Touch_FT5x06), so this SKU needs only include/variants/s3_175_amoled.h
; + the LGFX.h blocks + this env -- no custom driver. The variant's pin map is NOT yet verified
; against the board's wiki, so it stays BENCH-ONLY: do not flash a real board until pins are
; confirmed and bring-up is done (a wrong pin is a bug). Cloud sibling (-cloud) mirrors the
; s3-146-cloud pattern once bring-up passes.
;
; CI: it HAS a matrix row, and that row is deliberately SLUG-LESS -- built and run through the
; adsbdb launch gate, never named firmware-*.bin and never attached to a release. It used to have
; no row at all, on the reasoning that a SKU which cannot ship should not be in CI; what that
; bought was an env that quietly stopped compiling. "Not ready to ship" and "not worth building"
; are different claims. See RELEASING.md, "A SKU that isn't ready yet".
[env:blipscope-pro-s3-175-amoled]
extends = common
board = esp32-s3-devkitc-1
board_build.arduino.memory_type = qio_opi
board_build.flash_size = 16MB
board_upload.flash_size = 16MB
board_build.partitions = default_16MB.csv
build_flags =
${common.build_flags}
-DBLIPSCOPE_VARIANT_S3_175_AMOLED
-DBOARD_HAS_PSRAM
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1
; ===================== HARDWARE TEST (S3 1.46B) =======================
; Minimal display/I2C bring-up probe -- NOT a product env, NOT in CI. Built to
; localise a "black screen while the app is provably healthy" fault: the panel
; RESET on this board is on a TCA9554 I2C expander (EXIO2), not a GPIO, so
; tft.init() returns 1 whether or not the panel ever left reset and the app has
; no way to report the difference. This walks I2C scan -> raw backlight ->
; expander pulse -> panel fills, printing each step.
;
; pio run -e hwtest-s3-146 -t upload -t monitor
;
; ===================== Missileer game bench harness ====================
; Answers the three HARDWARE questions gating the game design
; (docs/missileer-game-design.md §13): deputy hold-gesture dropout rate, max
; simultaneous touch points, and NTP sync uncertainty (which sets the deviation
; leaderboard's scoring granularity). Bench-only: no CI row, publishes nothing,
; makes no cloud calls.
;
; Inherits the SKU's board config from env:blipscope-s3-128 rather than restating
; it, so pins, PSRAM, flash and the big-NVS partition table can never drift from
; the product. build_src_filter is the exception -- it drops everything and
; re-includes only the harness TU, because gametest_main.cpp defines its own
; setup()/loop() and would otherwise collide with main.cpp.
;
; Flash with the port PINNED. A second board is usually attached (the soak), and
; auto-detection has already put an image on the wrong one once:
; pio run -e gametest-s3-128 -t upload --upload-port COM119 -t monitor
[env:gametest-s3-128]
extends = env:blipscope-s3-128
build_src_filter = -<*> +<gametest_main.cpp>
lib_deps = lovyan03/LovyanGFX @ 1.2.21
;
; Compiles ONE translation unit (src/hwtest_main.cpp); the whole app, every
; edition, and all lib_deps beyond LovyanGFX are excluded so nothing else can
; touch the bus or the heap during the test.
[env:hwtest-s3-146]
extends = common
board = esp32-s3-devkitc-1
board_build.arduino.memory_type = qio_opi
board_build.flash_size = 16MB
board_upload.flash_size = 16MB
board_build.partitions = default_16MB.csv