Skip to content

Commit 8dc4603

Browse files
authored
Merge pull request #138 from engmung/dev
Dev
2 parents 265d81d + 9ca5b04 commit 8dc4603

6 files changed

Lines changed: 471 additions & 20 deletions

File tree

firmware/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ firmware/patternflow/
5353
├── core_color.h # PFColor:: hsvToRgb, ColorStop, sampleRamp
5454
├── core_noise.h # PFNoise:: perlin2D, fractal2D
5555
├── core_wifi.h # Shared Wi-Fi bring-up (single WiFi.begin for all features)
56+
├── core_improv.h # Improv-Serial Wi-Fi provisioning from the browser flasher
5657
├── core_osc.h # OSC sidechannel (UDP send when PF_OSC_ENABLED)
5758
├── core_audio_ws.h # Browser audio-react HTTP/WebSocket server
5859
├── audio_index.h # Built-in patternflow.local audio UI bundle
@@ -123,6 +124,16 @@ PatternflowOta::handle(); // first line of loop() — UDP poll, ~free when idle
123124

124125
Shares Wi-Fi credentials and connection with OSC (if both are enabled, the connection is reused). When `PF_OTA_ENABLED` is 0 everything compiles to a no-op. See the [OTA Updates](#ota-updates-for-developers) section below for the user-facing workflow.
125126

127+
### `core_improv.h` — PatternflowImprov
128+
Self-contained [Improv-Serial](https://www.improv-wifi.com/serial/) implementation so the **browser flasher** (ESP Web Tools, behind the website's "Flash" button) can set Wi-Fi over USB right after flashing — no rebuild, no `patternflow_secrets.h`. The main sketch only needs:
129+
130+
```cpp
131+
PatternflowImprov::begin(); // in setup() — announces it speaks Improv
132+
PatternflowImprov::handle(); // in loop() — drains Serial, drives provisioning
133+
```
134+
135+
The flasher sends the SSID/password over serial; `core_wifi.h` stores them in NVS (separate `pf_wifi` namespace) and uses them in preference to the built-in `PF_WIFI_SSID/PASS` placeholders on every boot. Shares the debug USB Serial — the host parser scans for the `IMPROV` header and ignores `println()` noise. Compiled in only when Wi-Fi is actually used (one of OTA/OSC/audio enabled) and `PF_IMPROV_ENABLED` is 1; otherwise a no-op. Set `#define PF_IMPROV_ENABLED 0` in `patternflow_secrets.h` to drop it.
136+
126137
## Patterns
127138

128139
Current registered patterns:

firmware/patternflow/net_config.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,22 @@
3838
#define PF_WIFI_RETRY_INTERVAL_MS 5000
3939
#endif
4040

41+
// ── Improv-Serial Wi-Fi provisioning ─────────────────────────
42+
// Lets the browser flasher (ESP Web Tools, behind the website's "Flash"
43+
// button) set Wi-Fi over USB serial right after flashing, instead of baking
44+
// credentials into the binary. The SSID/password are stored in NVS and used
45+
// in preference to the placeholders above on the next boot. See
46+
// src/core_improv.h. On by default; only compiled in when Wi-Fi is actually
47+
// used (i.e. at least one of OTA/OSC/audio is enabled).
48+
#ifndef PF_IMPROV_ENABLED
49+
#define PF_IMPROV_ENABLED 1
50+
#endif
51+
// Firmware version string reported to the flasher (Improv device-info RPC).
52+
// Keep in sync with web/public/flash/manifest.json.
53+
#ifndef PF_IMPROV_FW_VERSION
54+
#define PF_IMPROV_FW_VERSION "2.0.0"
55+
#endif
56+
4157
// ── OTA (wireless flashing from Arduino IDE / espota.py) ─────
4258
// On by default. Loop cost is one UDP poll per frame when idle.
4359
#ifndef PF_OTA_ENABLED

firmware/patternflow/patternflow.ino

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "src/core_display.h"
55
#include "src/core_encoders.h"
66
#include "src/core_wifi.h"
7+
#include "src/core_improv.h"
78
#include "src/core_osc.h"
89
#include "src/core_ota.h"
910
#include "src/core_audio_ws.h"
@@ -81,6 +82,10 @@ void setup() {
8182
// or not Wi-Fi is up yet.
8283
PatternflowWifi::begin();
8384

85+
// Improv-Serial: lets the browser flasher set Wi-Fi over USB after a web
86+
// flash. Just listens on Serial; no Wi-Fi required to be up yet.
87+
PatternflowImprov::begin();
88+
8489
buildPatternList(); // presets first (pattern 1 = Origin), custom appended last
8590
for (int i = 0; i < NUM_PATTERNS; i++) {
8691
patterns[i].setup();
@@ -104,6 +109,23 @@ void drawCenteredText(const char* text, int y, uint16_t color, int textSize = 1)
104109
dma_display->print(text);
105110
}
106111

112+
// Centered text on a small dark scrim band, so it stays legible drawn on top
113+
// of the live pattern the SELECT screen now previews behind the overlay.
114+
// One fillRect + one text draw per label — cheap enough not to slow the frame
115+
// (a per-glyph outline tripled the per-frame pixel writes and tore the
116+
// double-buffered panel).
117+
void drawCenteredTextScrim(const char* text, int y, uint16_t color, int textSize = 1) {
118+
int16_t x1, y1;
119+
uint16_t w, h;
120+
dma_display->setTextSize(textSize);
121+
dma_display->getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
122+
int x = (dma_display->width() - w) / 2;
123+
dma_display->fillRect(x - 2, y - 2, w + 4, h + 4, 0);
124+
dma_display->setTextColor(color);
125+
dma_display->setCursor(x, y);
126+
dma_display->print(text);
127+
}
128+
107129
void drawContentNotice() {
108130
dma_display->fillRect(0, 18, dma_display->width(), 28, 0);
109131
drawCenteredText(currentContentName(), 30, dma_display->color565(255, 255, 255), 1);
@@ -156,32 +178,27 @@ void drawNetworkInfo() {
156178
drawCenteredText("HOLD K2 = EXIT", 57, dim, 1);
157179
}
158180

181+
// Draws the SELECT overlay ON TOP of the live pattern preview the loop has
182+
// already rendered into the buffer (so no fillScreen here). Each label sits on
183+
// a small dark scrim so it stays readable over whatever pattern is behind it.
159184
void drawSelectingMode() {
160-
dma_display->fillScreen(0);
161-
162-
uint16_t screenW = dma_display->width();
163185
uint16_t screenH = dma_display->height();
164186

165187
char pageStr[16];
166188
snprintf(pageStr, sizeof(pageStr), "%d / %d", currentPatternIdx + 1, NUM_PATTERNS);
189+
drawCenteredTextScrim(pageStr, 10, dma_display->color565(190, 190, 190), 1);
167190

191+
const char* name = patterns[currentPatternIdx].name;
192+
int nameSize = strlen(name) > 8 ? 1 : 2;
168193
int16_t x1, y1;
169194
uint16_t w, h;
170-
dma_display->setTextSize(1);
171-
dma_display->setTextColor(dma_display->color565(100, 100, 100));
172-
dma_display->getTextBounds(pageStr, 0, 0, &x1, &y1, &w, &h);
173-
dma_display->setCursor((screenW - w) / 2, 10);
174-
dma_display->print(pageStr);
175-
176-
const char* name = patterns[currentPatternIdx].name;
177-
dma_display->setTextSize(strlen(name) > 8 ? 1 : 2);
178-
dma_display->setTextColor(dma_display->color565(255, 255, 255));
195+
dma_display->setTextSize(nameSize);
179196
dma_display->getTextBounds(name, 0, 0, &x1, &y1, &w, &h);
180-
dma_display->setCursor((screenW - w) / 2, (screenH / 2) - (h / 2));
181-
dma_display->print(name);
197+
drawCenteredTextScrim(name, (screenH / 2) - (h / 2),
198+
dma_display->color565(255, 255, 255), nameSize);
182199

183-
const char* hint = "HOLD TO SELECT";
184-
drawCenteredText(hint, screenH - 22, dma_display->color565(150, 150, 150), 1);
200+
drawCenteredTextScrim("HOLD TO SELECT", screenH - 22,
201+
dma_display->color565(200, 200, 200), 1);
185202
}
186203

187204
void readInputFrame(InputFrame& input) {
@@ -292,6 +309,11 @@ void loop() {
292309
Serial.println("[NET] services started");
293310
}
294311

312+
// Improv-Serial provisioning: drains any browser-flasher Wi-Fi setup
313+
// traffic on Serial and reports connect success/failure back. Cheap when
314+
// idle (one Serial.available() check).
315+
PatternflowImprov::handle();
316+
295317
// OTA must run early in the loop so a long pattern render doesn't
296318
// starve the upload handler. Cheap when no upload is in flight.
297319
PatternflowOta::handle();
@@ -391,7 +413,6 @@ void loop() {
391413
if (currentMode == MODE_RUNNING) {
392414
currentMode = MODE_SELECTING;
393415
contentNoticeTimer = 0.0f;
394-
dma_display->setRotation(1);
395416
Serial.printf(">>> SELECT MODE ENTERED: %s\n", patterns[currentPatternIdx].name);
396417
} else {
397418
currentMode = MODE_RUNNING;
@@ -451,7 +472,23 @@ void loop() {
451472
Serial.printf("SELECTING: %s\n", patterns[currentPatternIdx].name);
452473
}
453474

475+
// Live preview behind the overlay so you can see what you're choosing.
476+
// Render the pattern in the native landscape orientation (rotation 0, same
477+
// as running mode) so present() fills the whole panel — then switch to the
478+
// device's portrait orientation (rotation 1) for the SELECT text so it
479+
// reads upright, exactly as before. Feed a neutral input frame (no knob
480+
// deltas / buttons) so browsing with K4 doesn't drive the pattern's own
481+
// parameters — it just animates over time.
482+
dma_display->setRotation(0);
483+
InputFrame preview = {};
484+
preview.now = input.now;
485+
for (int i = 0; i < 4; i++) preview.knobs[i] = input.knobs[i];
486+
patterns[currentPatternIdx].update(dt, preview);
487+
patterns[currentPatternIdx].draw();
488+
489+
dma_display->setRotation(1);
454490
drawSelectingMode();
491+
dma_display->setRotation(0); // back to landscape for the next frame
455492
}
456493

457494
dma_display->flipDMABuffer();

0 commit comments

Comments
 (0)