← Back to Blog

Monitoring HHO with Lateralus

November 10, 2025 hhomonitoringlateralus

An HHO system is only as good as its monitoring. Without real-time visibility into gas flow, temperature, current, and efficiency, you're flying blind. Here's how we use Lateralus pipelines to monitor HHO systems end-to-end.

◉ The Monitoring Challenge

HHO systems have multiple interdependent variables:

Traditional approaches: individual sensors with separate displays, manual logging, no correlation between metrics. We wanted something better.

◉ Hardware Setup

Our monitoring stack:

Sensor Model Interface Cost
Temperature (x4)DS18B201-Wire$2
CurrentACS712 30AAnalog$3
VoltageVoltage dividerAnalog$0.50
Flow (gas)YF-S201Pulse$4
PressureMPX5010DPAnalog$8
Water levelFloat switchDigital$3

Controller: RP2040 (Raspberry Pi Pico) running Lateralus firmware. Total sensor cost: ~$25.

◉ The Sensor Pipeline

Core monitoring logic in Lateralus:

@freestanding
module hho.monitor

import hw.adc
import hw.onewire
import hw.gpio
import data.ring_buffer

// Sensor reading structure
struct Reading {
    timestamp: u64
    voltage: float
    current: float
    temps: [float; 4]
    flow_rate: float
    pressure: float
    water_ok: bool
}

// Ring buffer for 1 hour of data at 1Hz
let history = ring_buffer.new[Reading](3600)

fn read_all_sensors() -> Reading {
    Reading {
        timestamp: time.now_ms(),
        voltage: adc.read(0) * 3.3 * 11.0,  // Voltage divider ratio
        current: (adc.read(1) - 2.5) * 20.0, // ACS712 scaling
        temps: onewire.read_all_temps(),
        flow_rate: flow.read_lpm(),
        pressure: (adc.read(2) / 3.3) * 10.0, // kPa
        water_ok: gpio.read(WATER_PIN)
    }
}

fn calibrate(r: Reading) -> Reading {
    // Apply sensor-specific corrections
    Reading {
        ...r,
        voltage: r.voltage * 1.02,  // Calibrated against DMM
        current: r.current + 0.15,  // Zero offset
        temps: r.temps |> map(fn(t) { t - 0.5 }), // DS18B20 typical offset
    }
}

fn calculate_efficiency(r: Reading) -> Reading with { efficiency: float } {
    // Theoretical: 1 L HHO requires ~2.4 Wh
    let power_in = r.voltage * r.current  // Watts
    let power_theoretical = r.flow_rate * (60.0 / 1000.0) * 2400.0  // Wh/min to W

    {
        ...r,
        efficiency: match power_in > 0.0 {
            true => (power_theoretical / power_in) * 100.0,
            false => 0.0
        }
    }
}

◉ The Full Pipeline

Putting it together:

fn monitoring_loop() {
    loop {
        let reading = read_all_sensors()
            |> calibrate()
            |> calculate_efficiency()

        history.push(reading)

        // Log to SD card every minute
        if reading.timestamp % 60000 == 0 {
            log_to_sd(reading)
        }

        // Check for anomalies
        reading |> check_anomalies() |> alert_if_needed()

        // Update display
        display.update(reading)

        sleep_ms(1000)
    }
}

fn check_anomalies(r: Reading) -> Option[Alert] {
    // Temperature check
    for i, temp in enumerate(r.temps) {
        if temp > 80.0 {
            return Some(Alert::OverTemp { sensor: i, value: temp })
        }
    }

    // Pressure check
    if r.pressure > 4.5 {
        return Some(Alert::OverPressure { value: r.pressure })
    }

    // Water check
    if !r.water_ok {
        return Some(Alert::LowWater)
    }

    // Efficiency anomaly (sudden drop might indicate leak)
    let avg_eff = history.last(60) |> map(_.efficiency) |> mean()
    if r.efficiency < avg_eff * 0.8 && avg_eff > 50.0 {
        return Some(Alert::EfficiencyDrop { current: r.efficiency, average: avg_eff })
    }

    None
}

◉ Remote Monitoring

For off-grid installations, we add WiFi connectivity:

fn upload_readings() {
    let batch = history.last(60)  // Last minute of readings

    let payload = batch
        |> map(fn(r) {
            { ts: r.timestamp, v: r.voltage, i: r.current,
              t: r.temps[0], f: r.flow_rate, p: r.pressure }
        })
        |> json.encode()

    // Upload to local server or cloud
    match wifi.post("http://192.168.1.100/hho/data", payload) {
        Ok(_) => led.blink(GREEN, 100),
        Err(e) => log.warn("Upload failed: " + e.message)
    }
}

◉ Dashboard

The receiver side runs a simple web dashboard (also in Lateralus):

// dashboard.ltl - runs on local server
import web

fn main() {
    let app = web.new()

    app.get("/", fn(req) {
        web.render_template("dashboard.html", {
            current: db.latest_reading(),
            hourly: db.hourly_averages(24),
            alerts: db.recent_alerts(10)
        })
    })

    app.get("/api/readings", fn(req) {
        let since = req.param("since", 0)
        db.readings_since(since) |> json.response()
    })

    app.listen(8080)
}

◉ What We Learned

◉ Results

With this monitoring in place, we've:

Full source code at hho-monitor. See also the HHO page for system specifications.