Vaillant eBUS hardware adapter (ebusd software) Thread

OK maybe I was a bit too quick to judge Gemini; we seem to be sending data to emoncms.org in the form of:

https://emoncms.org/input/post?node=vaillant&apikey=[apireadandwritekeyremoved]&fulljson={"flow_temp":36.86,"return_temp":35.54,"elec_power":511.73,"outside_temp":5.67,"inside_temp":18.94,"target_temp":19.0,"dhw_temp":53.0,"heat_output":1311.73}

I just have no idea what to do next (and my brain is fried)

For the record, this is my docker compose.yaml

services:

  ebusd:
    image: john30/ebusd:latest
    container_name: ebusd
    restart: always
    network_mode: host
    command: >
      --scanconfig
      -d ens:10.0.1.23:9999
      --accesslevel=*
      --httpport=8080

  hpm_bridge:
    image: python:3.9-slim
    container_name: hpm_bridge
    restart: always
    network_mode: host
    depends_on:
      - ebusd
    volumes:
      - ./heatpump-monitor/main.py:/app/main.py
    command: sh -c "pip install requests && python -u /app/main.py"

And this is the python program main.py Gemini wrote:

import requests
import time
import json
import sys

# --- CONFIGURATION ---
EBUSD_URL = "http://localhost:8080/data"
API_KEY = "[read&writeAPIkey removed]"
EMONCMS_URL = "https://emoncms.org/input/post"
NODE_NAME = "vaillant"
INTERVAL = 30

# --- SENSORS MAP ---
# Format: "EmonCMS_Name": ("Circuit", "Register_Name")
SENSORS = {
    # 1. Physics (Temps & Power)
    "flow_temp":    ("hmu", "RunDataFlowTemp"),
    "return_temp":  ("hmu", "RunDataReturnTemp"),
    "elec_power":   ("hmu", "RunDataElectricPowerConsumption"),
    
    # 2. Calculation Source (Internal variable, not sent to dashboard)
    "env_yield":    ("hmu", "CurrentYieldPower"), 
    
    # 3. Environmental
    "outside_temp": ("basv", "DisplayedOutsideTemp"),
    "inside_temp":  ("basv", "Z1RoomTemp"),
    "target_temp":  ("basv", "Z1ActualRoomTempDesired"),
    "dhw_temp":     ("basv", "HwcStorageTemp"),
}

def fetch_value(circuit, name):
    """Fetches a value from ebusd, handling caching rules."""
    try:
        # Force a fresh read (maxage=0) for Yield to ensure accurate Physics
        # Use cache (maxage=60) for Temps to reduce bus load
        is_yield = (name == "CurrentYieldPower")
        maxage = "0" if is_yield else "60"
        
        url = f"{EBUSD_URL}/{circuit}/{name}?def&maxage={maxage}"
        r = requests.get(url, timeout=5)
        if r.status_code != 200: return None
        data = r.json()
        
        # Recursive finder for "value"
        def find_val_recursive(d):
            if isinstance(d, dict):
                if "value" in d:
                    v = d["value"]
                    if isinstance(v, (int, float)) and not isinstance(v, bool):
                        return float(v)
                for item in d.values():
                    res = find_val_recursive(item)
                    if res is not None: return res
            return None

        return find_val_recursive(data)
    except:
        return None

def main():
    print("--- Vaillant Bridge (Final Clean) ---")

    while True:
        payload = {}
        raw_data = {}
        
        # 1. Fetch All Sensors
        for key, (circuit, reg_name) in SENSORS.items():
            val = fetch_value(circuit, reg_name)
            if val is not None:
                raw_data[key] = val
                # Add to payload automatically, unless it's a calc variable
                if key != "env_yield":
                    payload[key] = val

        # 2. Calculate True Heat Output (Physics)
        # Total Heat (W) = Environmental Yield (kW * 1000) + Electrical Input (W)
        elec = raw_data.get("elec_power", 0.0)
        env_yield_kw = raw_data.get("env_yield", 0.0)
        
        if env_yield_kw > 0:
            payload["heat_output"] = (env_yield_kw * 1000.0) + elec
        else:
            payload["heat_output"] = 0.0

        # 3. Safety Defaults
        if "elec_power" not in payload: payload["elec_power"] = 0.0

        # 4. Upload
        if len(payload) > 1:
            try:
                params = {
                    "node": NODE_NAME,
                    "fulljson": json.dumps(payload),
                    "apikey": API_KEY
                }
                requests.post(EMONCMS_URL, params=params, timeout=10)
                print(f"Uploaded: {payload}")
            except Exception as e:
                print(f"Upload failed: {e}")
        else:
            print("Waiting for valid data...")

        sys.stdout.flush()
        time.sleep(INTERVAL)

if __name__ == "__main__":
    main()

Edit: see the updated main.py in my reply below, and follow the replies for the next steps