This page can train a model right here in the browser, using the identical Conv1D+Dense+Dense+Output architecture both boards run, then push the trained weights to either board. The Nano33 never trains - it only ever receives a finished model (from the ESP32, from this page, or baked in at compile time) and runs inference locally, reporting results back over BLE. Quick and full inference both work everywhere.
What's new in v16 - browser-only fix, no firmware changes needed (nano33-v14.ino / esp32-v14.ino still current):
myNormalizeWindow() - and all of that was already correct; zero-filling unavailable
channels is the right approach and was never the problem. The actual bug: the hand-rolled SGD in
myTrainStep() applied every weight update completely unclipped. Phone datasets tend to be smaller
and lower-diversity than board ones (fewer samples, and 10 of the 17 input channels carry zero information for
every single sample), which concentrates all the learning signal onto a handful of live weights - making it much
easier for one epoch to take an oversized step, overshoot a weight into NaN/Infinity, and then have every future
step touching that weight compute to NaN forever after (there was no path back once that happened). Every weight/
bias update is now routed through a new myClipStep() that bounds each individual update to a small
fixed range - this makes that first NaN structurally impossible, for phone OR board data, with no effect on runs
that were already numerically stable. Added an epoch-snapshot-and-rollback safety net in
myTrainInBrowser() as well, in case anything else ever manages to produce a non-finite loss - it
rolls back to the last good epoch and halves the learning rate instead of continuing to train on a corrupted
model.What's new in v14.2:
e Serial command that dumps its trained model as
plain hex text over USB Serial. Copy it out of the Serial Monitor and paste it into the new box under the ESP32's
panel - this goes over USB + clipboard, not BLE, so it's immune to any BLE-side limitation entirely. Same
underlying idea as the existing "Save myWeights.h" download, applied to a live model load instead of a
compile-time bake-in.What's new in v14.1:
predIdx,conf,p0,p1,...,pN-1 - predicted index, then a deliberate duplicate of that class's own
confidence, then the full per-class array. The page was only skipping the first field, so the duplicate "conf"
value got mistaken for class 0's probability and every real value shifted one slot late - the classic symptom
was two classes both showing near 100%, even though each board's own Serial Monitor (which never goes through
this parsing) was correct the whole time. Now skips both fields correctly.myServerSendBinary() was changed to retry a stuck chunk in a
bounded loop (up to 3 seconds) instead of giving up almost immediately. Turned out to help but not fully solve
it - see v14.2.What's new in v14 - requires nano33-v14.ino AND esp32-v14.ino:
On-device result: 1normal (0unknown=3%, 1normal=90%, 2issue=7%,)myServerSendBinary() now checks each indicate()'s own
return value instead of just hoping (superseded by the more patient retry logic in v14.1 above).What was new in v13:
What was new in v12: model pushes to the Nano33 switched from write-without-response to write-with-response (the browser now waits for each chunk's acknowledgement before sending the next, instead of flooding faster than the board could keep up), plus other commands to a board are held off while a push to it is in flight.
e and press enter. Select everything between (not
including) the ===MODEL_EXPORT_BEGIN=== / ===MODEL_EXPORT_END=== lines, copy it, and
paste it below - goes over USB instead of BLE, so it isn't affected by the stall above.Uses the browser's built-in accelerometer + gyroscope to add a third data source to the dataset below. No magnetometer, mic, or static sensors on a phone, so those channels are sent as 0. Best for quick gesture-shape demos - see the code comments for details/caveats.
Choose a source and a class below, then hit "Capture Labeled Sample" - it now triggers a FRESH capture on that board (or the phone) itself and adds whatever comes back, so there's no need to separately press Capture/Quick on the panels above first. (Those panels' own buttons are still there if you just want to preview a reading, or to grab a quiet/neutral one for Step 1's calibration baseline below, without adding it to the dataset.) Once you've got a few samples per class, set a calibration baseline, train right here in the page, and push the result to either board.
Protocol notes (must match the firmware exactly):
Service UUID: 7e400001-b2c3-5d4e-af60-9b3c7d8eaf20
Control char (write): 7e400002-... - browser sends "CAPTURE", "QUICK", "TRAIN", "INFER", "QUICKINFER",
"PUSHTONANO", or "PUSHMODEL" as plain ASCII text (Nano33 only understands CAPTURE/QUICK/INFER/QUICKINFER - it never trains)
Heartbeat char (notify, ~2Hz): 7e400003-... - CSV text "ax,ay,az,mic,temp,hum,prox"
Binary char (notify, chunked) [CHANGED v14.2 - was indicate on the ESP32 side; the Nano33's binary char is still
indicate, since its transfers are small (up to ~1.6KB) and haven't shown this issue]: 7e400004-... - 4-byte
little-endian uint32 length header, then raw float32LE
payload in ~180-byte chunks. 407 floats = full 1-second window, 17 floats = QUICK reading, 5872 floats = a full
MODEL PACKAGE (only sent by the ESP32 in response to "PUSHMODEL").
Model char (write, chunked) [NEW v02]: 7e400005-... - same 4-byte-header + chunk pattern, browser -> board,
carries a model package (numClasses + weights + calibration) into either board's live weights.
Result char (notify) [NEW v02]: 7e400006-... - CSV text "predIdx,conf,p0,p1,...,pN-1" sent after any INFER/QUICKINFER,
by whichever board just ran the forward pass.
myFusionWeights.h [NEW v09]: not BLE - a plain-text C++ header download (Save myWeights.h button) with the same
numbers as the .bin, for baking a model into either sketch at compile time (USE_BAKED_WEIGHTS).
Phone motion training [NEW v09]: not BLE either - uses the browser's own DeviceMotionEvent API on the phone/tablet
the page is open on, feeding straight into the same labeled dataset as the two boards.
On-device result text format [CHANGED v14]: both boards' Serial Monitor AND this page's "On-device result" line
now use the exact same format, produced by one shared function on each board (myPrintResultLine in both .ino
files): "On-device result: 1normal (0unknown=3%, 1normal=90%, 2issue=7%,)" - the trailing comma before the
closing paren is intentional (falls out of the shared per-class loop), kept so the two are byte-for-byte identical.
Serial Monitor commands (type these into each board's own USB Serial Monitor at 115200 baud - these are
NOT sent over BLE, they only work with a USB cable plugged into that specific board):
Nano33 (nano33-v14.ino) - no training on this board by design:
i = Infer once/continuous (real 1s window, 'x' to stop)
q = Quick continuous infer (fast replicated reading, 'x' to stop)
s = status (BLE connection + model-loaded state)
d = toggle debug output [NEW v14] (verbose per-timestep sampling lines)
z = reset (restart the board)
? = show this menu
ESP32S3 (esp32-v14.ino):
1-3 = Collect a sample for that class (then c to capture, x to exit)
t = Train (on-device, from SD-collected samples)
i = Infer once/continuous (real 1s window, 'x' to stop)
q = Quick continuous infer (fast replicated reading, 'x' to stop)
r = (re)connect to the Nano33
k = recalibrate
s = status
p = push the current model to the Nano33 for on-device inference
e = export the current model as pasteable hex text [NEW v14.2] - BLE-free fallback, see "Paste Model From Serial" on this page
d = toggle debug output (verbose BLE/transfer diagnostics)
b = cycle BLE mode (Dual / Peripheral only / Central only / Off)
z = restart now, to apply a newly-selected BLE mode
? = show this menu
The Control char commands above ("CAPTURE", "QUICK", etc) are the separate BLE-over-the-air command set this
webpage actually sends - use the "Custom command" box on either board's panel to send any of these, or the single
letters 'r'/'k'/'s' (the ESP32 also accepts those same letters over BLE, not just USB Serial).