#!/bin/bash
# lm-sensors extend for snmpd: native sensors + TEMPer USB block [#341/#439]
#
# Deployed to /usr/local/bin/lmsensors-extend on sensor hosts by deploy.sh.
# Output = lm-sensors format, consumed by Home Assistant's SNMP integration
# (nsExtendOutputFull."lmsensors") and, if enabled later, LibreNMS's
# lm-sensors app.
#
# Layers (each optional, at least one required):
#   1. native /usr/bin/sensors output (CPU/board temps, fans) — lm-sensors pkg
#   2. TEMPer USB probe (temperusb in venv /opt/temper-venv) — probe-optional
#      (venv-on-host is founder-approved for sensor hosts, incl. Proxmox)
set -euo pipefail

VENV_PY="${VENV_PY:-/opt/temper-venv/bin/python}"
NATIVE_SENSORS_BIN="${NATIVE_SENSORS_BIN:-/usr/bin/sensors}"
SYSFS_THERMAL_ROOT="${SYSFS_THERMAL_ROOT:-/sys/class/thermal}"
CHIP_NAME="${CHIP_NAME:-temper-usb-1}"

# Render lm-sensors format block from internal/external Celsius values.
# Empty/absent external renders internal only. Non-numeric input is rejected.
format_block() {
    local internal="${1:-}" external="${2:-}"
    local num_re='^-?[0-9]+(\.[0-9]+)?$'
    [[ "$internal" =~ $num_re ]] || {
        echo "error: internal temp not numeric: '$internal'" >&2
        return 1
    }
    local signed
    printf '%s\nAdapter: USB adapter\n' "$CHIP_NAME"
    signed="${internal#+}"; [[ "$signed" == -* ]] || signed="+$signed"
    printf 'Internal:  %s°C\n' "$signed"
    if [[ -n "$external" && "$external" =~ $num_re ]]; then
        signed="${external#+}"; [[ "$signed" == -* ]] || signed="+$signed"
        printf 'External:  %s°C\n' "$signed"
    fi
}

# True when a TEMPer USB dongle (0c45:7401) is attached and the venv exists.
has_temper_probe() {
    command -v lsusb >/dev/null 2>&1 || return 1
    [[ -x "$VENV_PY" ]] || return 1
    lsusb | grep -qi '0c45:7401'
}

# Read the TEMPer probe via temperusb; prints "<internal> <external>" in C.
# Sensor 0 = internal, sensor 1 = external probe (if attached).
read_temper() {
    "$VENV_PY" - <<'PY'
from temperusb import TemperHandler

internal = external = ""
for device in TemperHandler().get_devices():
    try:
        temps = device.get_temperatures(sensors=[0, 1])
    except (ValueError, KeyError):
        temps = device.get_temperatures()
    for index, slot in ((0, "internal"), (1, "external")):
        reading = temps.get(index)
        if reading and reading.get("temperature_c") is not None:
            value = f"{reading['temperature_c']:.1f}"
            if slot == "internal":
                internal = value
            else:
                external = value
print(f"{internal} {external}".strip())
PY
}

run_native_sensors() {
    [[ -x "$NATIVE_SENSORS_BIN" ]] || return 1
    "$NATIVE_SENSORS_BIN" 2>/dev/null
}

# SBC fallback (Raspberry Pi et al.): lm-sensors often reports nothing; SoC
# temperatures live in /sys/class/thermal/thermal_zone*/{type,temp} (mC).
read_sysfs_thermal() {
    [[ -d "$SYSFS_THERMAL_ROOT" ]] || return 1
    local z type mc found=0
    for z in "$SYSFS_THERMAL_ROOT"/thermal_zone[0-9]*; do
        [[ -r "$z/type" && -r "$z/temp" ]] || continue
        type="$(<"$z/type")" mc="$(<"$z/temp")"
        [[ "$mc" =~ ^-?[0-9]+$ ]] || continue
        printf '%s %s\n' "$type" "$mc"
        found=1
    done
    ((found)) || return 1
}

# Render lm-sensors format block from read_sysfs_thermal lines "type millicelsius".
format_sysfs_block() {
    local input="${1:-}" type mc signed n=0
    [[ -n "$input" ]] || return 1
    printf 'soc-thermal-virtual-0\nAdapter: Virtual device\n'
    while read -r type mc; do
        ((++n))
        signed="$(awk -v mc="$mc" 'BEGIN { printf "%.1f", mc / 1000 }')"
        signed="${signed#+}"
        [[ "$signed" == -* ]] || signed="+$signed"
        printf 'temp%d:        %s°C\n' "$n" "$signed"
    done <<<"$input"
}

main() {
    local out="" block internal external native="" sysinput
    if native="$(run_native_sensors)" && [[ -n "$native" ]]; then
        out+="$native"$'\n'
    else
        native=""
    fi
    if has_temper_probe; then
        if block="$(read_temper 2>/dev/null)" \
           && read -r internal external <<<"$block" \
           && block="$(format_block "${internal:-}" "${external:-}")"; then
            out+="$block"$'\n'
        else
            echo "warn: TEMPer probe present but read failed" >&2
        fi
    fi
    if [[ -z "$native" ]] \
       && sysinput="$(read_sysfs_thermal 2>/dev/null)" \
       && block="$(format_sysfs_block "$sysinput")"; then
        out+="$block"$'\n'
    fi
    [[ -n "$out" ]] || {
        echo "error: no sensor sources available (lm-sensors pkg? TEMPer venv? sysfs thermal?)" >&2
        return 1
    }
    printf '%s' "$out"
}

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
    main
fi
