The previous GIF was a tight crop of the ILI9341 panel and rendered huge
and cut off at the top of Screenshots. This one is the landing's
estacion-meteorologica GIF cropped to the circuit (ESP32 + ILI9341 +
BMP280 + DHT22) and displayed at 520px.
- Screenshots and board art come from the velxio_docs portal (fresh
captures of the current UI); the six 2025-era img1..6.png are gone.
- Supported Boards now covers all seven families (Arduino, Pico, ESP32,
ESP32-S3/C3, STM32, Raspberry Pi Linux, branded boards) and says
where each one runs: self-hosted, velxio.dev, or paid plan.
- Trim the deep-dive sections that the docs portal now owns: the ESP32
/ RISC-V / Pi 3 emulation bullets, Serial Monitor, Component System,
Wire System, Library Manager, Portable Persistence, Project Structure,
Technologies, the doc table and Troubleshooting. Documentation is a
single pointer to velxio.dev/docs and github.com/velxio/velxio_docs.
- Hardware Partners gains a line naming the hardware each partner
contributed to the catalog.
Every Pico W WiFi example in the gallery hangs at `while not
wlan.isconnected()`, with `wlan.status()` stuck at 1 (CONNECTING) and
`wlan.active()` reading True.
The CYW43439 sits on GP23/24/25/29, inside the module — there is no canvas net
for those pins. attachPioPeripheral() drives WL_HOST_WAKE (GPIO24) with
setInputValue() straight from the chip's hostWakeLevel(). When RP2040 gained
spice-driven digital inputs, the GPIO listener began seeding a pull level onto
every input pin, GPIO24 included: the CYW43 driver configures that pad as an
input, the seed landed on top of host-wake, the host stopped polling the chip,
and no association event was ever delivered.
The fault is invisible from the CYW43 side — its own harness drives a raw
rp2040js with its own FIFO/PIO plumbing and passes — which is why this looked
like a WiFi bug rather than a GPIO one.
Diagnosed by patching the sketch in the editor to print wlan.status() each
iteration: "WAIT 1 status 1 active True" repeating forever, i.e. the join was
accepted and the link never came up.
cardputer-adv-m5-button, m5stack-core-hello and c6-oled-i2c were the first
slug of their capture run and got the day's announcement overlay in the shot.
The capture script (velxio-prod) now pre-marks the day's post as shown and
strips any announcement overlay before the screenshot.
ESP-IDF compiles with -ggdb, and for debug builds ccache folds the cwd into
every cache key by default — CCACHE_BASEDIR does not exempt it (verified with
CCACHE_DEBUG on 4.11: 'Hashing CWD /var/lib/velxio-build/esp32s3/v_<hash>/
project/build'). Each build variant lives in its own dir, so every variant had
a private key space: eight esp32 variants of the SAME board each recompiled
all ~2100 IDF objects from scratch, the cache filled with dead per-directory
entries, and a cold Cardputer/S3 build (2263 steps) overran the 600s ninja
timeout on a busy box. CCACHE_NOHASHDIR=1 makes variants of the same
board/sdkconfig share objects; a cold variant becomes mostly cache hits. Only
DW_AT_comp_dir in .debug_info of a reused object can differ, which never
reaches the flashed .bin.
The timeout paths were silent server-side; they now log the build dir and how
many ninja steps completed, and the user-facing message says the partial
build is kept and a retry resumes.
Measured against the user corpus, the by-resolved-type long tail was
losing what the old catch-all happened to include: for `Adafruit_SH1106G
display(...); display.setTextSize(` the class itself declares one method
(begin) and everything people call lives four levels up (Adafruit_SH110X ->
Adafruit_GrayOLED -> Adafruit_GFX -> Print). Same story for MCUFRIEND_kbv,
Adafruit_NeoMatrix and every GFX-derived display driver.
Three changes:
- The scanner records `bases` on class symbols (access/virtual keywords
dropped, `ns::Base` -> Base, template args cut). 2,529 classes across the
production cache now carry their bases.
- /type/{name} walks the chain breadth-first through the type index (bases
routinely live in a different library), re-owns inherited members to the
requested type so the editor sees one flat class, tags them with
`inheritedFrom`, and lists the chain in `bases` -- including bases NOT in
the cache (Print, Stream, or an embedded one like Adafruit_GFX) so the
host can supply those itself. Depth capped at 8. Adafruit_SH1106G goes
from 1 member to 64.
- `void a(int), b(), c(char);` declares three methods; the extractor took
only the first. TFT_eSPI declares setTextColor/setTextSize and
setCursor/getCursorX/... this way. Split on top-level commas after a
complete `name(args)` group. TFT_eSPI 118 -> 173 members.
Rescan of the 1,125-entry production cache: +274 member symbols, 15
libraries gain, none lose.
The completion-safe long-tail path. Feeding whole library symbol sets into
the editor's candidate pool was measured to make ranking worse (U8g2 alone
injects ~250 module-level class names; ArduinoJson dozens of ARDUINOJSON_*
macros). What does help is answering `client.` once tree-sitter has resolved
`client` to a declared type the embedded catalog does not know -- and for
that the engine needs "the members of type T", not "the symbols of library
L". This endpoint returns exactly that: symbols whose owner is the requested
class, from whichever cached library declares it, in the same payload shape
as /symbols/ so the engine's RemoteSymbolCache stores it unchanged.
Resolution: a class-name -> cache-entry index built once over the library
cache (each per-entry scan is already disk-cached), persisted under
.symbols/_types.json (3,818 classes / 199 KB over the 1,125-entry
production cache; ~1 s to rebuild from cached scans, ~60 s cold), refreshed
after a few hours or when the cache grows. Many classes are declared by more
than one library -- the real one plus forks and bundles vendoring a copy
(PubSubClient inside "ESP8266 Microgear", NTPClient inside "ESP8266 Weather
Station", AccelStepper inside "Arduino Learning Board") -- so the pick is:
the library named after the class, else the one declaring the most members
of it (vendored copies are trimmed), else alphabetical.
Generic, not pro-specific: any host with a library cache can use it.
The New Project dialog now lists an M5Stack section — M5 Cardputer ADV first,
then M5Stack Core — between ESP32 and STM32. Both kinds are overlay-registered,
so the section only renders when the overlay mounted (an OSS build shows no
empty block). Selecting a card loads the board's on-LCD hello example
(cardputer-adv-hello / m5stack-core-m5-helloworld), which also provides the
card thumbnail.
The sections builder is extracted as buildStarterSections(defs) and covered
by a unit test that pins the display order.
_scan_text applied `public:` / `private:` / `protected:` only inside
handle_statement, which the `{` branch skipped whenever the statement opened
a nested enum, struct or class. So `public:\n typedef enum { ... }` -- the
first thing in AccelStepper's class body, and a common idiom -- took the enum
branch, the label was never seen, the class stayed in its default private
access, and every method after the enum was dropped. AccelStepper shipped 4
of its public methods; DallasTemperature, SoftWire, MFRC522_I2C, RTCZero
shipped none.
Labels are now consumed before the branch. Rescanned all 1,125 entries of
the production library cache: 82 libraries gain members (+1,532 total,
DallasTemperature 0 -> 57), and the 20 that "lose" one or two were leaking
private/protected members past the old check (PMS::loop is under private:,
hd44780::waitReady under protected:) -- precision, not regression.
It printed "Bluetooth Control Ready" and then did nothing, no matter what
you sent — David typed `on` and `off` into the Serial Monitor and neither
reached it.
It reads UART(0), and on this port UART0 IS the REPL's channel: the code
runs through the raw REPL, so bytes typed into the monitor are consumed
there and uart.read() never sees them. `UART(0, 9600)` also retunes the
very port the monitor is talking to at 115200.
Upstream drives this from a phone through an HC-05, where "Bluetooth" is
only the transport and the ESP32 just sees serial. Velxio has no HC-05 on
the canvas, so the Serial Monitor is that link — read sys.stdin through a
select.poll (100 ms timeout, so the loop yields instead of spinning).
Verified in the app: `on` -> "Received: on / LED ON" with the LED lit,
`off` -> "Received: off / LED OFF" with it dark.
analogRead printed a flat 1021-1023 on every loop — the collector pinned at
Vcc. Measured in the running app:
base 0.481 V emitter 0.001 V collector 4.994 V
0.481 V is below the ~0.65 V a 2N2222 needs, so the transistor never
conducted and the "amplifier" was an open switch.
The bias divider was fine on paper (47k/10k -> 0.88 V). What broke it was
the 10k "coupling resistor" from the signal generator, whose own comment
claimed it let the generator "wiggle the biased base without DC-shifting
it". It does the opposite: a generator with 0 V offset is ground at DC, so
that resistor sits in parallel with Rb2 —
10k || 10k = 5k -> Vb = 5 * 5/(47+5) = 0.481 V
which is exactly the measured value. Replace it with a 1 uF coupling
capacitor (159 ohm at the generator's 1 kHz: passes the signal, blocks the
DC that was wrecking the bias), and take Rb2 to 15k so the collector sits
near mid-rail — the operating point the sketch's own comment promises and
the only one with room to swing both ways.
The netlist snapshot could never have caught this: the text was exactly
what the author intended. So the new test asserts the PHYSICS — bias in the
active region, collector off both rails, and a collector that actually
swings — resolving node names through the builder's pin map rather than
hardcoding `n2`, and adding the 5 V rail the board supplies in the app.
`_prepare_persistent_project_dir` keys the persistent build dir on
(idf_target, variant), but the lock was keyed on the FQBN. Several FQBNs
resolve to one variant — arduino-esp32's boards.txt gives both `esp32` and
`esp32cam` build.variant=esp32 — so those two were free to build in the SAME
directory at the same time.
Measured against staging, compiling both concurrently:
esp32 285504 bytes sha f65ca909… contains its own marker
esp32cam 285504 bytes sha f65ca909… contains the OTHER sketch's marker
Byte-identical firmware, and the esp32cam caller got the esp32 sketch's
binary. In the product that is two gallery tabs open at once: the first tab
prints the second example's serial log and does nothing itself. It made
esp32-pwm-led-rgb look broken (dark LED, every SPICE node 0 V) when the
example is fine — it was running someone else's program.
Key the lock on the same identity as the directory. Boards with genuinely
separate build dirs still compile in parallel up to the semaphore cap; an
unresolvable FQBN falls back to the default pair, which is the safe
direction (serialising more only costs throughput).
Also in this commit: battery-voltage-monitor ships the 9 V battery its own
description promises. The divider's top end was wired to nothing, so A0 read
0 V and the sketch printed "Battery: 0.0 V / WARNING: Battery low!" forever.
A pro test lives at ../../pro/frontend/src/pro/..., outside this package,
so Node resolution walks up from THERE and never reaches this project's
node_modules — importing 'esp32js' dies with "Cannot find package". The
config already carries the other wiring overlay tests need (the include
glob, the fs.allow, @pro); this is the missing piece for one that drives
a real engine.
Guarded by existsSync per package: a pure-OSS clone has none of them
installed and no test imports them (the pro glob matches nothing there),
so each missing entry is simply left unaliased instead of pointing at a
path that doesn't exist.
The night-light LDR shipped a 0.001 ohm dark resistance for months and
nothing complained: the netlist built, ngspice converged, the part
rendered, and the example just quietly read 4095 forever. A wrong value
is invisible, so check them all.
Parses every resistor / pot / photoresistor / capacitor / inductor value
in the gallery with the real parser and asserts it lands in a physically
sane range. The failure message names both SPICE suffix traps, because
they are the ones that bite: M is milli (mega is "Meg"), and F is femto
and is deliberately not stripped, so "10uF" would parse as 10 fF rather
than the 10 uF intended.
Verified the check actually bites: putting "1M" back makes it fail with
`dark = "1M" -> 0.001 — LDR dark resistance (ohm) out of range`. A second
case asserts the audit visits a meaningful number of components, so the
test can't silently degrade into a no-op.
Swept the rest of the catalogue while here (all example files, the pro
overlay, and the 49 100-days diagrams): no other value hits either trap.
100d-auto-night-light shipped `dark: "1M"` meaning 1 megaohm. The value
parser follows the SPICE convention where M is MILLI and mega is `Meg`,
so the dark resistance became 0.001 ohm. With the divider's top leg a
short, AO sat at VCC: `ldr.read()` returned 4095 on every sample, the
sketch's `light < 500` branch never ran, and the lamp LED never lit.
Dragging the lux control changed nothing either, because every lux value
still divides a short (measured n1 = 3.299999996 V, which is exactly what
100k / (100k + 0.00011) predicts).
The netlist snapshot had frozen the bug in place:
- R_night-ldr_ldr vcc_rail n1 0.00011111111111111112
+ R_night-ldr_ldr vcc_rail n1 111111.11111111111
Diagram now says "1Meg" (regenerated from test/test_100_days), and the
mapper clamps a sub-1k dark resistance to the documented 1Meg default
with a console warning naming the milli/mega trap — a photoresistor that
reads as a short is always this mistake, and it fails in the most
confusing way possible: the part looks alive, it just never moves.
A sketch that drives several pins between yields lost every pin but the
first. The 4-bit binary counter example (the most-viewed one in the
gallery) blinked bit 0 and left bits 1-3 dark while the firmware counted
0-15 correctly on serial.
Sequence: pin 2's edge arrives first, finds no V-source (the pin was not
in outputPins when the netlist was built), self-heals via handleMcuEdge
and rebuilds. The pin 3/4/5 edges of that same iteration land while the
rebuild solve is in flight, so they queue in pendingMcuEdges. The drain
then looked them up in the loaded context, found no V_uno_3/4/5, and
dropped them. Those pins never got a source, so their LEDs kept the
build-time current (~0) forever — and since the LED part prefers SPICE
current over the digital fallback, they stayed dark for good.
The drop was deliberate: it stops the self-heal -> tick -> self-heal loop
for a pin wired to nothing. Replace the blanket drop with a per-pin
guard: heal a pin at most once per netlist, then drop as before. The
guard re-arms whenever a rebuild changes the V-source list, so a pin that
becomes drivable later still heals.
Verified in the browser on binary-counter-leds; two regression tests
cover both sides (sibling pin heals, unwired pin does not loop).
The RGB colour mixer wired the LED's three legs straight to GPIO25/26/27 —
examples-led-resistor.test.ts fails the build over exactly that, and it is
right to: a bare LED on a pin is a short through the die. Each leg now has
its own 220R.
The 100-days netlist snapshots were recorded when 36 of those examples had
no canvas at all, so every one of them snapshotted as an empty circuit.
Re-recorded now that they have real parts.
The 100-days series is imported from a repo that ships circuit photos, not
machine-readable diagrams, so every example whose canvas nobody had drawn by
hand loaded as a bare board: 36 of 49 had components: [] / wires: []. They
compiled and ran and showed nothing — the LDR night light printed 0 forever
because GPIO34 was floating, and there was no LED on GPIO2 to see either way.
Canvases for 33 of them (LDR, pots, MQ gas sensors, HC-SR04, PIR, 4x4 keypad,
joystick, servos, relays with a lamp on the contact, A4988 + stepper, 7-segment,
10-segment bar graph, RGB, DHT22, indicator LEDs), each wired to the pins its
own sketch uses. LED chains are active HIGH (GPIO -> 220R -> anode, cathode ->
GND); the Smart Home canvas had all four wired the other way round, so led.on()
turned them off.
AQI gains the three analog sensors its code reads (GPIO34/33/35) plus the dust
sensor's LED — without them every reading was 0 and the AQI sat at "0.0 Good".
Board-level repairs in the sources (each commented in place):
- Pin("LED") is a Pico idiom; on ESP32 it is not a pin at all, and on Pico W
it drives the CYW43 LED, which no board element renders. Now real GPIOs
with a real LED on the canvas.
- Battery monitor read ADC(0) (10-bit, single-channel) on a 12-bit per-pin ADC.
- Irrigation used GPIO3/GPIO1 — UART0 on this board, so it killed the console.
- Joystick+servo used another board's ADC0/servo pins.
- Temperature LED indicator shipped the DC-motor sketch by mistake upstream;
replaced with the listing from the project's own README (DHT22 + 3 LEDs).
- GPIO6 (internal flash) and GPIO0 (strap) are not on the DevKit header, so
those wires anchored at the board corner.
Left without a canvas on purpose: the EEPROM and hotspot demos are serial-only,
and the NTP clock needs a MAX7219 element Velxio does not have yet.
A wire endpoint naming a pin the board element does not expose silently
resolved to nothing and rendered anchored at the board corner. A full
audit (all gallery examples + every saved project on velxio.dev) found 15
examples and 28 user projects doing it — the same electrical pin spelled
differently per surface: GND.1 on the classic ESP32 (element exposes
GND/GND2), 3.3V vs 3V3, GND2 vs GND.2, D13 vs 13 vs GP13, A0-A2 vs
GP26-28 on the Pico, 36/39 vs VP/VN, bare 5V vs VBUS/VIN.
calculatePinPosition now tries those equivalences AFTER the exact match
and the two existing fallbacks miss (so no current resolution changes),
healing old saves, .vlx imports and agent-authored wires without a data
migration. The Pico element gains A0/A1/A2 alias pads on the ADC pins
(RX2/16 dual-entry precedent) and a GP25 pad at the onboard LED silk so
pico-blink-style wires have an anchor. The two examples that were
outright wrong (microsd-card-esp32 GND.1, pico-serial-echo TX) now use
the element's real pin names.
Netlist snapshots (166) untouched; verifier + builder suites green.
DEV-guarded (import.meta.env.DEV): the platform-bugs Playwright harness
drives store paths (property updates, group switches) the way the agent
tools do, which raw DOM access cannot reach. Production bundles never
ship it.
deleteFileGroup left editorStore.activeGroupId dangling at the removed
group: Monaco (and agent writes) kept editing a group compile never reads,
so a stale same-named sketch.ino shipped instead of the code on screen
(the 'leftover template Blink built for 35 minutes' failure). The pointer
now re-targets a surviving group, and compile re-points the editor when
the two pointers diverge (with a visible warning log).
compiledProgram is no longer serialised into boards_json: the firmware
blob was 99.99% of a typical ESP32 save (a bare board already ~1.04 MB),
pushing every real project against the request-size ceiling, and a
restored binary is stale the moment the code differs. Loaders still
accept it from old rows.
The custom sketch.ino.cpp path never ran arduino-cli's ctags prototype
pass, so the universal Arduino idiom (helpers defined after loop()) died
with "'foo' was not declared in this scope" on every ESP32 build. A
conservative generator (skips templates, default args, methods,
function-pointer params) injects prototypes before the first definition
with a #line directive so error line numbers stay put. 8 unit tests.
IDF 5.5 renamed ESP_TASK_WDT to the EN/INIT pair; the v4.4 template's =n
became EN=n, compiling the whole implementation out — every sketch using
esp_task_wdt_add/reset (Firebase's client does internally) failed at
link. The v5 fixup now emits EN=y INIT=n: the API links, the watchdog
never auto-starts under emulation. v4.4 semantics untouched.
Three stacked defects made ANY non-knob property change invisible to a
running sim (values restored from saved projects, the agent's
set_component_property, the property dialog):
1. DynamicComponent assigned element properties without firing the events
part simulators bind to — dispatch input/change + dispatchSensorUpdate
on real value changes (applied-value guard breaks the echo loop).
2. Parts received the shared legacy AVR simulator regardless of board —
route attachEvents to the simulator of the board the part is wired to
(getBoardSimulator), so direct analog injection works on non-AVR boards.
3. The SPICE->ADC push stomped un-modeled parts: a net with no SPICE
element gets an auto pull-down, solves to 0 V, and overwrote the
part's own injected voltage every solve (the dead analog-joystick).
Only sourced nets are pushed now.
Verified end-to-end: pot curve exact to the LSB (value 100/300/600 ->
ADC 400/1201/2401 on GPIO35), joystick idle at centre 2047, full-scale
4095 after a store write.
Board supply pins split into the main logic rail plus aux rails by real
voltage (ESP32/XIAO VIN,5V -> aux_rail_5 at 5 V; 5V-Arduino 3.3V pin ->
aux_rail_3v3; Pico VBUS/VSYS -> 5 V). An external 5 V supply wired to VIN
used to read 3.3 V (ideal vcc_rail source clamped the whole net — customer
isolated it with a voltmeter). Aux rails join positiveSourceNets and the
relay nominal-voltage check; the voltmeter and hover overlay read them.
Also: ratings entry for the 4-pin ssd1306-i2c-4pin (supply pin VCC) and a
VCC alias on the 8-pin entry, so its power check works at all.
The unified-toolbar responsiveness work gave .unified-toolbar
container-type: inline-size, which makes it the containing block for
position:fixed descendants. The three modals rendered inside the toolbar
(library manager, install-libraries, circuit verification) shrank to the
toolbar's 89px box: pinned to the top, header and close button clipped
offscreen. createPortal(document.body) makes them immune to ancestor
containment. The library manager also gets a fixed tall height
(min(86vh, 1000px)) instead of a bouncy 80vh cap.
GET /api/intellisense/symbols/{Name@Version} scans the content-addressed
library cache entry's headers (dependency-free C++ scanner) into an editor
symbol table, disk-cached per entry and served with long cache headers.
Plain OSS hosts without a lib cache 404 and the editor degrades gracefully.
The tracked sitemap.xml committed in 3ff984a1 was a stale working-tree
regen from BEFORE df19778a retired the in-app /docs routes; it carried 15
/docs URLs that now 301 to the docs portal (which ships its own
sitemap-index, declared in robots.txt since 720a5a1). Harmless in prod —
build:docker regenerates the file at image build — but noisy in the repo
and wrong as a source of truth. 193 URLs (22 routes + 171 examples).
These binaries are loaded at runtime from /firmware/ (cyw43 installer,
RP2350 simulator, C6 bridge) and were living only in the prod working
tree — a fresh checkout would 404 them. Sitemap regenerated 2026-08-13.
velxio.dev/docs/ ships its own Astro sitemap-index (2142 URLs) but nothing
referenced it — the main sitemap dropped the retired in-app /docs entries
on purpose (df19778a) and robots.txt only listed the app + projects
sitemaps, so crawlers had no discovery path to the new portal.
C3/S3/C6 default to CONFIG_BT_BLE_50_FEATURES_SUPPORTED with 4.2 OFF, so
arduino-esp32's Bluedroid BLE library (esp_ble_gap_config_adv_data_raw,
esp_ble_gap_start_advertising) died at link with undefined references —
esp32c3-ble-advertise never compiled. Enabling 42+50 together matches
arduino-esp32's own per-target sdkconfigs; on classic ESP32 these flags
are already the Kconfig defaults. Found by the nightly gallery smoke.
The dead-branch pruner marked any assumed-live branch as 'taken', so the
#else of an UNKNOWN condition was skipped. GxEPD2 guards its GFX base
class with '#if ENABLE_GxEPD2_GFX (default 0) ... #else #include
<Adafruit_GFX.h>' — the pruned #else silently dropped the Adafruit_GFX
dependency and every GxEPD2 e-paper example failed with 'fatal error:
Adafruit_GFX.h: No such file'. Found by the nightly gallery compile smoke.
'taken' is now set only by PROVABLY-true conditions (all identifiers in
the known-true set), keeping the 2026-08-03 ESPAsyncWebServer behaviour:
provably-dead branches still prune, provably-true branches still kill
their #else.
The ESP32 examples now default to the in-browser JS engines, whose lwIP
serves the sketch's HTTP server from the simulation tab itself. Opening
the IoT gateway in a new tab backgrounds that tab, the browser throttles
its timers, and the in-chip server can't answer before the proxy's
timeout — the exact failure the Pico W iframe path already solved.
- WifiStatus grows an inBrowser flag (set by the overlay's JS bridges);
the serial-monitor link and the WiFi badge now open the in-app iframe
for any board that carries it, not just pi-pico-w.
- /api/gateway polls ~1s before answering 404: the net bridge dials on
'got_ip', which races the sketch printing its URL, so a fast click
used to land before the bridge registered.
Live E2E on the in-browser ESP32 engine surfaced two freezes:
- i2c.scan() stalls on the engine's I2C controller (first probed address
has no device and the transaction never completes). The bmp180 shim now
probes 0x76/0x77 directly via readfrom_mem.
- BlynkLib's blocking socket.connect() froze the demo when the second
outbound connection stalled. The vendored BlynkLib now sets a 10s
timeout before connect, and main.py catches OSError and falls back to
the offline no-op Blynk (probe-then-reconnect removed — one bounded
connection instead of two).
2c4c93a2 gave 13 of the repaired 100-days examples real canvases, so
their netlists are no longer empty. Regenerated with vitest -u; every
change is the expected kind (components and rails appearing, plus the
micropython-watch button rewiring).
- 10 Pico W projects now emit boardType pi-pico-w so the RPI_PICO_W
firmware (network + CYW43) boots; the plain-pico build has no network
module and every Wi-Fi example died on import.
- Entry points: projects whose alphabetically-first file was a library
(BlynkLib.py) or a host script (analyze.py) now ship a real main.py.
- All Blynk projects probe blynk.cloud with a 5s timeout and fall back
to an offline no-op object — a plain connect() blocks forever in the
emulated network and froze the demos.
- DHT11 -> DHT22 across six projects (the emulator speaks DHT22) and
the OLED projects use hardware I2C instead of bit-banged SoftI2C.
- 11 more examples get a wired canvas (DHT22, SSD1306, LCD1602 I2C,
membrane keypad, joystick, heart-beat sensor, LEDs): 13/49 total.
- IoT Atmospheric Monitoring (ESP32+Blynk): ship the missing bmp180.py
driver (BMP180 API over the emulated BMP280), switch SoftI2C -> hardware
I2C(0) so the emulator decodes the bus, survive an unreachable Blynk
Cloud, and add the BMP280 sensor + wiring to the canvas.
- AQI ESP: ship the missing ssd1306.py driver, DHT11 -> DHT22 (the
emulated waveform), local AQI fallback when the Flask server is absent,
and add the OLED + DHT22 canvas with wiring.
- All 49 descriptions now credit Kritish Mohapatra's upstream repo
(github.com/kritishmohapatra/100_Days_100_IoT_Projects).
Generated by test_100_days/_emit_examples_data.py in velxio-prod (which
now supports per-project velxio_diagram.json canvases).
The drag handler computed the split percentage against
getBoundingClientRect().width (border box), but the panels' percentage
widths resolve against the content box. Overlays that reserve space by
padding .app-container (docked side panels) made the handle jump left on
click and track offset from the cursor. Measure against clientWidth minus
padding, with the origin shifted by border + left padding.
The documentation moved to the Starlight portal served statically at
/docs/ (nginx shadows the old SPA routes and 301s the legacy slugs).
The header Documentation link becomes a full-page <a> so it reaches
nginx instead of client-routing into the removed pages.
Board DATA like spiPanelId: the bridge that supports the declared type
instantiates the model at the given address and setBatteryVoltage drives it.
First consumer is the M5Stack Core's IP5306.
Vendor autodetects read the panel's RDDID with the display's own CS and
reject or misdetect the board on the bus's idle 0xFF. The answer is board
DATA (which CS, which id byte), not bridge code, so it moves to the def:
the M5Stack Core is {14, 0xE3}, the Cardputer ADV {37, 0x85}. Bridges that
support the probe consume it generically.