Savia FlowOpen instrument open the console →
Setup · wire v0 · firmware 0.2 · console 0.1.0

From a bare board to a plant
playing through your phone

Five steps, each with a test that tells you whether it worked. All the listening happens in the phone: the unit counts pulses and sends numbers, and the phone decides what they mean. Nothing here needs an account, a cloud, or more than one load of internet.

Read this first: no part of this chain has met a plant.

The firmware compiles and runs on a board, and the console is tested end to end in a browser. No radio has ever carried a real frame from a plant, and no electrode has ever been placed. If you get to the end of step 5, you are the first, and everything you find is worth writing down.

00

Thirty seconds, no hardware

Before ordering anything, hear what the far end of this does, with a real signal: your own voice.

00Open the console and press play

Go to savia.flowme.one/console on a phone or a laptop. It starts empty: there is no generator, and nothing plays until a real source is connected. Pick the microphone, allow it, press play and hum.

What you are hearing is the mapping engine, the synth and the light doing with your voice what they will do with a plant on the other end. Move the sensitivity slider and change the scale to continuous to hear how much of the character is the signal and how much is the choice somebody made for it.

Exit testSilence before you pick a source. Sound when you hum, and notes tick along the bottom of the scope.
00bAdd it to the home screen

It is a PWA. Installed, it holds the whole console in the browser cache and runs with the network switched off, which is the condition it was built for.

Exit testAeroplane mode on, open it from the home screen, and it still plays.
01

Which phone, which route

There are two ways in, and the second exists because the first does not work everywhere.

 Android ChromeiOS SafariiOS BluefyDesktop Chrome
BLE to a unityesneveryesyes
Plug into the mic inputyesyesyeswith an interface
Generator, no hardwareyesyesyesyes
MIDI outyesnonoyes
Camera torchyesnonono

iOS Safari has no Web Bluetooth and there is no sign of it getting any. That is why the plug route is a real transport and not a fallback: the 555 goes into the phone's microphone input through a divider and a cap, and the console recovers the same measurement from audio. It is the one path that works on every phone in a room.

02

The board

Two profiles ship in the sketch. Either works.

Waveshare ESP32-S3-LCD-1.85

The default, and the one on our bench. Round, with a screen. It has no bare header, only JST sockets, which is better for this: the electrodes arrive on a connector and nothing gets soldered to the board.

UART socket GPIO44 pulse in.
I2C socket GPIO11 SDA, GPIO10 SCL.

ESP32 DevKitC

Any classic ESP32 with pin headers. Set SAVIA_BOARD to BOARD_DEVKITC in the sketch, or pass -DSAVIA_BOARD=1.

GPIO27 pulse in, GPIO2 LED, GPIO35 battery, I2C on 21/22.

03

Flash it

No libraries beyond the ESP32 core. Built and measured against core 3.3.11.

01Build and upload
arduino-cli core install esp32:esp32
arduino-cli compile --fqbn esp32:esp32:esp32s3 firmware/savia-sense
arduino-cli upload  --fqbn esp32:esp32:esp32s3 -p /dev/cu.usbmodem* firmware/savia-sense

In the Arduino IDE instead: open firmware/savia-sense, pick your board, and leave the partition scheme on its default. The sketch uses 46 % of the app partition on the S3 and 86 % on the classic ESP32, so both fit.

Set Tools › USB CDC On Boot › Enabled on the Waveshare board. The pulse input takes GPIO44, which is the hardware serial console, so the log comes over USB instead.

Exit testThe serial monitor at 115200 prints savia-sense 0.2 · unit 1 · SAVIA-01 · 40 Hz.
04

Check the pin is quiet

There is no self-test and no generator. Every number the console plays comes from a measured edge, so with nothing attached the reading is zero.

02Nothing attached reads nothing

Flash the board with nothing on the pulse pin, open the serial monitor at 115200, and pair the console with the unit.

Exit testSerial prints pulse · 0 edges · period 0 us · 0 Hz once a second. The console pairs with the unit, the check tab says nothing is in the loop, and the phone stays silent.

Confirm nothing else drives GPIO44. Some boards put a USB-to-serial bridge on GPIO43/44. This one uses the ESP32-S3's native USB for programming, so the UART socket should be free, but if the serial line reports edges with nothing attached, move the input to the other pin of the socket: PIN_PULSE 43.

Do not move the pulse input to the I2C socket. Those pins carry the display expander, the IMU, the clock and the audio codec, and a 555 on them takes the screen down with it.

Then wire the analog side. The first edges that arrive are the circuit's, and the console's plug source reads the same 555 through the phone's microphone input with no BLE at all, which is how to tell a circuit fault from a pairing fault.

05

Wire the 555

The plant becomes the R2 leg of an astable oscillator, so its resistance is the pulse rate, and the pulse rate is the measurement.

03The circuit
      3V3 ────┬──────────────┬───────────┐
              │              │           │
            [R1 10k]        ┌┴┐ 555     ─┴─ 0.1µF
              │             │ │          │
 ELECTRODE A ─┴─── pin 7 ───┤ ├── pin 3 ──── OUT ──[10k]── GPIO44
                            │ │
 ELECTRODE B ───── pin 6/2 ─┤ │
                            └┬┘
      GND ─────────────────── ┴──────────────────────────── GND
                             │
                          [C 4.7nF]
                             │
                            GND

Three wires from the 555 into the UART socket: 3V3, GND, and the output into GPIO44. The firmware holds that pin at INPUT_PULLUP, so an unplugged socket reads a clean nothing instead of counting phantom edges off the mains hum in the room.

Exit testWith a 100 kΩ resistor across the electrode leads instead of a plant, the console's check tab reads roughly 100 kΩ.
04Which capacitor, and an argument about it

The spec says two things that do not both fit. It puts plant tissue between 50 kΩ and 2 MΩ, and it describes the output as a 1 to 10 kHz pulse train. With the 4.7 nF cap in the bill of materials, those disagree:

Cap50 kΩ reads as2 MΩ reads as
4.7 nF (in the BOM)2.8 kHz76 Hz
1 nF13.1 kHz359 Hz

The 4.7 nF circuit works: 76 Hz is far above the wire's 15.3 Hz floor and the console reads it fine. But it lands an order of magnitude below the band the spec claims, and 1 nF is the part that would actually put a plant where the spec says it goes. Buy both. Whichever you use, set it in the console's check tab so the resistance estimate stays honest.

Exit testChange the capacitor in the check tab and the resistance reading moves. If it does not, you are not looking at the same circuit.
06

Place the electrodes

The check tab exists for the ten minutes after a pad goes on. It reads the resistance between the two electrodes, which is the number you are actually trying to get right.

05Two pads, far enough apart

Ag/AgCl ECG or TENS gel pads, from any pharmacy. Leaf and stem is the usual pair. Far enough apart that they cannot touch and the gel cannot bridge between them.

Then open check in the console. It names the fault instead of just failing:

It saysIt means
no pulsesnothing is arriving: leads, or power
too lowthe electrodes are touching, or gel has bridged them
too highdry pad, or past what the circuit can measure
not movingsteady pulses, plant not in the loop
nothing arrivingconnected, but the unit has gone quiet
outside the bandmeasuring, but outside 50 kΩ to 2 MΩ
settlingreading, baseline still walking. Wait a minute.
goodinside the band and moving. Play it.

A bad contact does not go quiet. It makes music, and that is the failure worth guarding against: nobody notices an instrument that sounds fine and is measuring a wire.

Exit testgood, holding for a minute, and the sound changes when you touch a leaf or water the pot.
07

What to write down

Everything below is unknown to us. Any of it is worth more than a working demo.

The console's log tab writes a CSV, a session JSON and the audio, all on the phone and nowhere else. Fill in species and location before you start, and hit TAG the moment anything happens to the plant: a touch, a cut, a light change. A row that says what was done at 412.6 s is worth more than an hour of unattended recording.

  • Did BLE pair at all, and on what phone and browser?
  • What resistance did your plant actually read, and what species was it?
  • Does lost ever appear in the status bar? That is dropped BLE frames.
  • Is GPIO1 really the battery divider on the Waveshare board? We could not confirm it, and it is the one pin in the sketch taken on faith.
  • Does the mic-input route work on an iPhone, and through which adapter?
Exit testA CSV and a JSON in the repo, or in an issue, with the species written in.
08

If you want the reasoning

Each of these argues one decision rather than describing it.

PROTOCOL.mdthe wire, and why the mapping engine moved off the device into the phone
ELECTRODES.mdthree routes into the board, with the pins verified
SPEC.mdthe four-stage chain and every option at each stage
console/engine.jsthe mapping engine itself, with the two places it departs from the spec and why