Vaillant eBUS hardware adapter (ebusd software) Thread

Through some more poking and prodding with assistance from Gemini I found the flow rate. So my main.py code is now:

import requests
import time
import json
import sys

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

# --- SENSORS MAP ---
SENSORS = {
    # --- USER REQUESTED REGISTERS ---
    "flow_temp":    ("hmu", "RunDataFlowTemp"),
    "return_temp":  ("hmu", "RunDataReturnTemp"),
    "inside_temp":  ("basv", "Z1RoomTemp"),
    
    # --- OTHER ESSENTIALS ---
    "elec_power":   ("hmu", "RunDataElectricPowerConsumption"),
    "flow_rate":    ("hmu", "BuildingCircuitFlow"),       # Confirmed working
    "env_yield":    ("hmu", "CurrentYieldPower"),         # Used for Heat Output calc
    
    # --- AUXILIARY ---
    "outside_temp": ("basv", "DisplayedOutsideTemp"),
    "target_temp":  ("basv", "Z1ActualRoomTempDesired"),
    "dhw_temp":     ("basv", "HwcStorageTemp"),
}

def fetch_value(circuit, name):
    """Fetches a value from ebusd."""
    try:
        # Force a fresh read (maxage=0) for dynamic data (Flow/Yield)
        # Use cached (maxage=60) for Temps to save bus traffic
        is_dynamic = (name in ["BuildingCircuitFlow", "CurrentYieldPower"])
        maxage = "0" if is_dynamic 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 Production) ---")

    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 (except internal calc variables)
                if key != "env_yield":
                    payload[key] = val

        # 2. Calculate True Heat Output
        # Total Heat = 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. 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()

This is run in a docker container as set out here. For how I went from this point to using the data on emonCMS .org and then on to heatpumpmonitor .org, see this post

1 Like