"""
Lab 1 - thermal · GAUGE firmware  (board 9 of 9)
school.worhl.net · MicroPython on ESP32-S3 (N16R8)

This board watches the bath and says what it sees. It has no load, no
MOSFET, no control authority, and no way to influence the thing it
measures. It is a window, not a hand.

    The regulating scalar never becomes a packet.

The gauge is deliberately NOT one of the 8 nodes and is deliberately
wired, not wireless: telemetry radio would add a small constant heat
term to the very bath being measured. Mount its sensor in the centre of
the box, in free air, not touching a heater resistor or a board.

It reports uptime, not wall-clock time. This board has no clock and will
not pretend to have one; jedas stamps arrival time when it logs.

Copy to the gauge board as main.py:
    mpremote connect <port> fs cp gauge.py :main.py

Wiring: DS18B20 data -> GPIO 4, 4.7k pull-up to 3V3, VDD/GND -> 3V3/GND.
"""

import time
from machine import Pin

import onewire
import ds18x20

# ---------------------------------------------------------------- config
PIN_SENSOR = 4
PERIOD_MS = 2000       # sample interval
CONVERT_MS = 750       # DS18B20 12-bit conversion time

T_MIN = -20.0
T_MAX = 80.0
T_POR_SENTINEL = 85.0  # DS18B20 power-on-reset value, never a real reading

# ---------------------------------------------------------------- sensor
_ow = onewire.OneWire(Pin(PIN_SENSOR))
_ds = ds18x20.DS18X20(_ow)
_rom = None


def find_sensor():
    try:
        roms = _ds.scan()
    except Exception as e:
        print("# gauge scan failed:", e)
        return None
    return roms[0] if roms else None


def read_temp():
    if _rom is None:
        return None
    try:
        t = _ds.read_temp(_rom)
    except Exception as e:
        print("# gauge read failed:", e)
        return None
    if t is None or t == T_POR_SENTINEL or t < T_MIN or t > T_MAX:
        return None
    return t


# ---------------------------------------------------------------- main
def main():
    global _rom

    print("# lab 1 gauge · read-only · uptime_ms,celsius")

    _rom = find_sensor()
    while _rom is None:
        print("# no DS18B20 found on GPIO %d" % PIN_SENSOR)
        time.sleep_ms(PERIOD_MS)
        _rom = find_sensor()

    print("# gauge sensor:", "".join("%02x" % b for b in _rom))

    while True:
        try:
            _ds.convert_temp()
        except Exception as e:
            print("# gauge convert failed:", e)
        time.sleep_ms(CONVERT_MS)

        t = read_temp()
        if t is None:
            # Say nothing rather than say something false. The logger
            # will see the gap; a gap is honest, a fabricated value is not.
            print("# no reading")
        else:
            print("%d,%.3f" % (time.ticks_ms(), t))

        time.sleep_ms(PERIOD_MS - CONVERT_MS)


try:
    main()
except KeyboardInterrupt:
    print("# gauge stopped by user")
