Spectral: light as an instrument โ
Every other layer in this library listens. This one asks.
The central problem in plant electrophysiology is equifinality: different causes produce the same waveform. A thirsty plant and a malnourished one can look identical on a single electrode, and no amount of passive sensing separates them.
So stop waiting for the plant to volunteer an ambiguous signal. Excite one photoreceptor pathway at a time and read what comes back. Blue reaches the guard cells, red reaches Photosystem II, green reaches the tissue neither of them touches โ and the pattern across colours is diagnostic in a way no single channel can be.
await plant.useSpectral( { light : { driver : 'mock' } } ) // or serial / mqtt / callback
const sweep = await plant.interrogate()
console.log( sweep.summary )๐ Amber (590nm) โ baseline: control channel
๐ต Blue (450nm) โ stomatal_response: weak (1.6ร control, SNR 6.2)
Blunted or delayed response: ABA is holding the stomata shut, which means water stress.
๐ด Red (660nm) โ photosynthetic_response: strong (5.1ร control, SNR 21.4)
Electron transport is efficient; the photosynthetic apparatus is intact.
Cross-band diagnosis:
water stress (80%)
ยท blue probe blunted (amplitude 2, 1.6ร control) โ stomata are not opening
ยท red probe normal (10) โ the photosynthetic apparatus is intactWhat each colour does to a plant โ
| Band | Photoreceptor | ๐ As a probe, it reads | ๐ As a treatment, it does | Risk | |
|---|---|---|---|---|---|
| ๐ฃ | UV-B 300nm | UVR8 | never probed โ DNA-damaging | Flavonoid synthesis, thicker cuticle, pathogen resistance | ๐ด critical |
| ๐ช | UV-A 380nm | Cryptochrome, phototropin | Cryptochrome response | Compacts leaf expansion, raises pigment density | ๐ก medium |
| ๐ต | Blue 450nm | Phototropin, cryptochrome | Stomatal competence โ hydration & turgor | Forces stomata open, raising transpiration | ๐ high |
| ๐ข | Green 530nm | Weakly absorbed โ penetrates | Deep mesophyll โ the lower canopy | Lights inner canopy nothing else reaches | ๐ข low |
| ๐ | Amber 590nm | Minimal | The control channel โ what "no stimulus" looks like | Working light for the camera, minimal perturbation | ๐ข low |
| ๐ด | Red 660nm | Chlorophyll a/b, Photosystem II | Electron transport โ photosynthetic capacity | Drives ATP/NADPH synthesis and carbon fixation | ๐ก medium |
| ๐ฅ | Far-red 730nm | Phytochrome (PfrโPr) | Phytochrome state | Stem elongation, end-of-day signal, flowering | ๐ high |
| โฌ | NIR 940nm | none โ thermal only | never probed โ no receptor | Radiant warming without photosynthesis | ๐ high |
The diagnostic that needs two colours โ
This is the whole point:
| Blue probe | Red probe | Conclusion |
|---|---|---|
| ๐ต weak | ๐ด strong | Water stress. Stomata shut while photosynthesis is fine โ that is ABA-mediated closure, not damage. |
| ๐ต strong | ๐ด weak | Nutrient deficiency. Water is adequate but electron transport is impaired: N, Mg, Fe, or photosystem damage. |
| ๐ต weak | ๐ด weak | Severe stress โ or a bad electrode. The system says so rather than guessing. |
| ๐ต strong | ๐ด strong | No cross-band pattern. The pathways agree. |
Add ๐ข green and you also see the lower canopy: a healthy top with a quiet interior means self-shading or senescing lower leaves.
Waveform distortion with amplitude preserved โ a harmonic ratio above 0.5 โ flags ionic imbalance (often salinity) before any visible symptom.
Minutes, not milliseconds โ
The single fact that decides whether any of this works:
Stomatal opening takes 5 to 30 minutes.
So the blue probe runs on a 16-minute period, not a flicker. A probe faster than its pathway measures the noise floor and returns a confident-looking zero โ which is worse than an error, because you would believe it.
The system refuses rather than letting that happen quietly:
await plant.spectral.probe( 'blue', { periodMinutes : 0.008 } ) // 2 Hz
// Error: A 0.008min period gives a 0.0min pulse, but the blue pathway needs
// at least 5min to respond. The probe would read noise, not the plant.Each band carries the period its own physiology allows: ๐ด red 8min ยท ๐ amber 10min ยท ๐ช UV-A 12min ยท ๐ต blue 16min ยท ๐ข green 20min ยท ๐ฅ far-red 30min.
How the measurement works โ
Drive the plant with a periodic light/dark cycle and the surface potential locks to that period, forming a carrier. The plant's internal state then appears as modulation of that carrier โ which is far easier to detect than a transient you have to catch.
- Phase locking โ power concentrated at the stimulus frequency, with an SNR against the surrounding noise floor. This is what separates a real evoked response from drift that happened to coincide.
- Cycle folding โ every cycle averaged onto one. Uncorrelated noise falls as 1/โN while the locked response survives, so twelve cycles recover a signal buried under twice its own amplitude in noise.
- Harmonic content โ distortion rises when a pathway saturates or is stressed, carrying information the fundamental alone does not.
- Everything relative to the ๐ amber control, because absolute millivolts depend on electrode placement and contact impedance and are not comparable across sessions, let alone across plants.
Safety: the interlocks โ
Light is the one actuator here that can damage a plant while looking like care. A pump that overruns floods visibly; a lamp that forces stomata open on a drought-stressed plant kills it quietly while the log says "treatment applied".
So treatment is decided by arithmetic, never by a model's confidence:
await plant.spectral.treat( 'blue', { seconds : 300, context : plant.context() } )
// REFUSED: The plant closed its stomata to conserve water. Forcing them open
// with blue light overrides that defence and accelerates dehydration.| Interlock | Rule |
|---|---|
| ๐ต Blue on dry soil | Hard refusal. Soil under 30%, humidity under 25%, or over 32ยฐC. The plant closed its stomata to survive. |
| Missing data | Blocks. undefined < 30 is false, so an absent soil sensor must never silently permit the treatment that most needs it. |
| ๐ฃ UV-B | Explicit human authorization, 15 min/day, capped intensity. Eye and skin hazard โ the warning says so. |
| ๐ฅ Far-red | Blocked by default. It induces shade-avoidance elongation, which weakens an indoor plant. |
| Dose budgets | Per band, per day, reset at midnight. Authorizations expire with the day too. |
| Dark period | Protected. The circadian rhythm is a health signal the rest of the library reads; irradiating through the night destroys it. |
Probes are held to the dose budget but not to the treatment interlocks: a four-minute blue pulse reads the stomata, it does not force them.
Hardware โ
Any multi-channel LED works. The driver's only job is to emit channel intensities and report honestly what it actually did.
await plant.useSpectral( {
light : { driver : 'serial', path : '/dev/ttyUSB0' }, // an ESP32 driving LED channels
} )| Driver | For |
|---|---|
mock | No hardware โ the whole stack is testable and demonstrable |
serial | ESP32 / Arduino, one JSON line per command |
mqtt | ESPHome, Tasmota, Zigbee2MQTT |
callback | Philips Hue, DMX, WLED, GPIO PWM โ anything with its own SDK |
A fixture declares which bands it has; requesting a channel it lacks is an error, not a silent no-op. Calibrated fixtures can declare irradiance per channel and the dose ledger will use photon flux; without calibration the system tracks time rather than pretending to know ยตmolยทmโปยฒยทsโปยน.
Feeding the rest of the system โ
Spectral findings enter the evidence ledger as an independent source, because they come from controlled excitation rather than from the same passive channel everything else reads. That is what lets a high-risk action clear its corroboration requirement honestly:
await plant.embody()
await plant.interrogate()
plant.justifies( 'water_stress', RISK.HIGH )
// now backed by soil, vision *and* spectral โ three independent sourcesFull example: lib/examples/06-spectral-probe.js, and the @smartplant/spectrum plugin wraps all of it.
