Compare commits

...

17 Commits

Author SHA1 Message Date
e0aa50c9d2 more lights 5
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-08 09:22:38 +01:00
dc20d9f4b2 more lights 4
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-08 09:15:32 +01:00
ffb35928b4 more lights 3
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-07 22:12:23 +01:00
ac84ff7103 more lights 2
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-07 21:49:34 +01:00
c185494da3 more lights
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-07 21:44:57 +01:00
ec4a37a268 tasmoto and kommode
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-07 21:32:36 +01:00
d4b1d27b81 accessory name in logging
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-07 21:19:41 +01:00
ad07bc79e2 kugellampe patty
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-07 19:14:32 +01:00
ab41e79cb2 car outlet adjusted 8
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-05 15:53:06 +01:00
fe92d336b1 car outlet adjusted 7
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-05 15:47:54 +01:00
0ca59896ad car outlet adjusted 6
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-05 15:46:25 +01:00
7858996d0f car outlet adjusted 5
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-05 15:42:41 +01:00
a0f7cc7bd9 car outlet adjusted 4
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-05 15:25:50 +01:00
a98802437c car outlet adjusted 3
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-05 15:11:18 +01:00
708e287016 car outlet adjusted 2
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-05 13:59:42 +01:00
d11eab8474 car outlet adjusted
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/3 Pipeline was successful
ci/woodpecker/tag/deploy/4 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-05 13:49:12 +01:00
eccffbbd55 use image from registry 2025-12-03 22:38:26 +01:00
6 changed files with 239 additions and 128 deletions

View File

@@ -181,16 +181,9 @@ async def handle_abstract_set(
# Transform abstract payload to vendor-specific format # Transform abstract payload to vendor-specific format
vendor_payload = transform_abstract_to_vendor(device_type, device_technology, abstract_payload) vendor_payload = transform_abstract_to_vendor(device_type, device_technology, abstract_payload)
# For MAX! thermostats and Shelly relays, vendor_payload is a plain string logger.info(f"→ vendor SET {device_id}: {vendor_topic}{vendor_payload}")
# For other devices, it's a dict that needs JSON encoding logger.debug(f"MQTT message published on {vendor_topic}: {vendor_payload}")
if (device_technology == "max" and device_type == "thermostat") or \ await mqtt_client.publish(vendor_topic, vendor_payload, qos=1)
(device_technology == "shelly" and device_type == "relay"):
vendor_message = vendor_payload # Already a string
else:
vendor_message = json.dumps(vendor_payload)
logger.info(f"→ vendor SET {device_id}: {vendor_topic}{vendor_message}")
await mqtt_client.publish(vendor_topic, vendor_message, qos=1)
async def handle_vendor_state( async def handle_vendor_state(

View File

@@ -17,12 +17,12 @@ logger = logging.getLogger(__name__)
# HANDLER FUNCTIONS: simulator technology # HANDLER FUNCTIONS: simulator technology
# ============================================================================ # ============================================================================
def _transform_light_simulator_to_vendor(payload: dict[str, Any]) -> dict[str, Any]: def _transform_light_simulator_to_vendor(payload: dict[str, Any]) -> str:
"""Transform abstract light payload to simulator format. """Transform abstract light payload to simulator format.
Simulator uses same format as abstract protocol (no transformation needed). Simulator uses same format as abstract protocol (no transformation needed).
""" """
return payload return json.dumps(payload)
def _transform_light_simulator_to_abstract(payload: str) -> dict[str, Any]: def _transform_light_simulator_to_abstract(payload: str) -> dict[str, Any]:
@@ -36,12 +36,12 @@ def _transform_light_simulator_to_abstract(payload: str) -> dict[str, Any]:
return payload return payload
def _transform_thermostat_simulator_to_vendor(payload: dict[str, Any]) -> dict[str, Any]: def _transform_thermostat_simulator_to_vendor(payload: dict[str, Any]) -> str:
"""Transform abstract thermostat payload to simulator format. """Transform abstract thermostat payload to simulator format.
Simulator uses same format as abstract protocol (no transformation needed). Simulator uses same format as abstract protocol (no transformation needed).
""" """
return payload return json.dumps(payload)
def _transform_thermostat_simulator_to_abstract(payload: str) -> dict[str, Any]: def _transform_thermostat_simulator_to_abstract(payload: str) -> dict[str, Any]:
@@ -59,7 +59,7 @@ def _transform_thermostat_simulator_to_abstract(payload: str) -> dict[str, Any]:
# HANDLER FUNCTIONS: zigbee2mqtt technology # HANDLER FUNCTIONS: zigbee2mqtt technology
# ============================================================================ # ============================================================================
def _transform_light_zigbee2mqtt_to_vendor(payload: dict[str, Any]) -> dict[str, Any]: def _transform_light_zigbee2mqtt_to_vendor(payload: dict[str, Any]) -> str:
"""Transform abstract light payload to zigbee2mqtt format. """Transform abstract light payload to zigbee2mqtt format.
Transformations: Transformations:
@@ -84,7 +84,7 @@ def _transform_light_zigbee2mqtt_to_vendor(payload: dict[str, Any]) -> dict[str,
# Convert percentage (0-100) to zigbee2mqtt range (0-254) # Convert percentage (0-100) to zigbee2mqtt range (0-254)
vendor_payload["brightness"] = round(abstract_brightness * 254 / 100) vendor_payload["brightness"] = round(abstract_brightness * 254 / 100)
return vendor_payload return json.dumps(vendor_payload)
def _transform_light_zigbee2mqtt_to_abstract(payload: str) -> dict[str, Any]: def _transform_light_zigbee2mqtt_to_abstract(payload: str) -> dict[str, Any]:
@@ -115,7 +115,7 @@ def _transform_light_zigbee2mqtt_to_abstract(payload: str) -> dict[str, Any]:
return abstract_payload return abstract_payload
def _transform_thermostat_zigbee2mqtt_to_vendor(payload: dict[str, Any]) -> dict[str, Any]: def _transform_thermostat_zigbee2mqtt_to_vendor(payload: dict[str, Any]) -> str:
"""Transform abstract thermostat payload to zigbee2mqtt format. """Transform abstract thermostat payload to zigbee2mqtt format.
Transformations: Transformations:
@@ -132,7 +132,7 @@ def _transform_thermostat_zigbee2mqtt_to_vendor(payload: dict[str, Any]) -> dict
# zigbee2mqtt expects current_heating_setpoint as string # zigbee2mqtt expects current_heating_setpoint as string
vendor_payload["current_heating_setpoint"] = str(payload["target"]) vendor_payload["current_heating_setpoint"] = str(payload["target"])
return vendor_payload return json.dumps(vendor_payload)
def _transform_thermostat_zigbee2mqtt_to_abstract(payload: str) -> dict[str, Any]: def _transform_thermostat_zigbee2mqtt_to_abstract(payload: str) -> dict[str, Any]:
@@ -171,14 +171,14 @@ def _transform_thermostat_zigbee2mqtt_to_abstract(payload: str) -> dict[str, Any
# HANDLER FUNCTIONS: contact_sensor - zigbee2mqtt technology # HANDLER FUNCTIONS: contact_sensor - zigbee2mqtt technology
# ============================================================================ # ============================================================================
def _transform_contact_sensor_zigbee2mqtt_to_vendor(payload: dict[str, Any]) -> dict[str, Any]: def _transform_contact_sensor_zigbee2mqtt_to_vendor(payload: dict[str, Any]) -> str:
"""Transform abstract contact sensor payload to zigbee2mqtt format. """Transform abstract contact sensor payload to zigbee2mqtt format.
Contact sensors are read-only, so this should not be called for SET commands. Contact sensors are read-only, so this should not be called for SET commands.
Returns payload as-is for compatibility. Returns payload as-is for compatibility.
""" """
logger.warning("Contact sensors are read-only - SET commands should not be used") logger.warning("Contact sensors are read-only - SET commands should not be used")
return payload return json.dumps(payload)
def _transform_contact_sensor_zigbee2mqtt_to_abstract(payload: str) -> dict[str, Any]: def _transform_contact_sensor_zigbee2mqtt_to_abstract(payload: str) -> dict[str, Any]:
@@ -225,14 +225,14 @@ def _transform_contact_sensor_zigbee2mqtt_to_abstract(payload: str) -> dict[str,
# HANDLER FUNCTIONS: contact_sensor - max technology (Homegear MAX!) # HANDLER FUNCTIONS: contact_sensor - max technology (Homegear MAX!)
# ============================================================================ # ============================================================================
def _transform_contact_sensor_max_to_vendor(payload: dict[str, Any]) -> dict[str, Any]: def _transform_contact_sensor_max_to_vendor(payload: dict[str, Any]) -> str:
"""Transform abstract contact sensor payload to MAX! format. """Transform abstract contact sensor payload to MAX! format.
Contact sensors are read-only, so this should not be called for SET commands. Contact sensors are read-only, so this should not be called for SET commands.
Returns payload as-is for compatibility. Returns payload as-is for compatibility.
""" """
logger.warning("Contact sensors are read-only - SET commands should not be used") logger.warning("Contact sensors are read-only - SET commands should not be used")
return payload return json.dumps(payload)
def _transform_contact_sensor_max_to_abstract(payload: str) -> dict[str, Any]: def _transform_contact_sensor_max_to_abstract(payload: str) -> dict[str, Any]:
@@ -266,13 +266,13 @@ def _transform_contact_sensor_max_to_abstract(payload: str) -> dict[str, Any]:
# HANDLER FUNCTIONS: temp_humidity_sensor - zigbee2mqtt technology # HANDLER FUNCTIONS: temp_humidity_sensor - zigbee2mqtt technology
# ============================================================================ # ============================================================================
def _transform_temp_humidity_sensor_zigbee2mqtt_to_vendor(payload: dict[str, Any]) -> dict[str, Any]: def _transform_temp_humidity_sensor_zigbee2mqtt_to_vendor(payload: dict[str, Any]) -> str:
"""Transform abstract temp/humidity sensor payload to zigbee2mqtt format. """Transform abstract temp/humidity sensor payload to zigbee2mqtt format.
Temp/humidity sensors are read-only, so this should not be called for SET commands. Temp/humidity sensors are read-only, so this should not be called for SET commands.
Returns payload as-is for compatibility. Returns payload as-is for compatibility.
""" """
return payload return json.dumps(payload)
def _transform_temp_humidity_sensor_zigbee2mqtt_to_abstract(payload: str) -> dict[str, Any]: def _transform_temp_humidity_sensor_zigbee2mqtt_to_abstract(payload: str) -> dict[str, Any]:
@@ -284,36 +284,13 @@ def _transform_temp_humidity_sensor_zigbee2mqtt_to_abstract(payload: str) -> dic
return payload return payload
# ============================================================================
# HANDLER FUNCTIONS: temp_humidity_sensor - MAX! technology
# ============================================================================
def _transform_temp_humidity_sensor_max_to_vendor(payload: str) -> dict[str, Any]:
"""Transform abstract temp/humidity sensor payload to MAX! format.
Temp/humidity sensors are read-only, so this should not be called for SET commands.
Returns payload as-is for compatibility.
"""
payload = json.loads(payload)
return payload
def _transform_temp_humidity_sensor_max_to_abstract(payload: str) -> dict[str, Any]:
"""Transform MAX! temp/humidity sensor payload to abstract format.
Passthrough - MAX! provides temperature, humidity, battery directly.
"""
payload = json.loads(payload)
return payload
# ============================================================================ # ============================================================================
# HANDLER FUNCTIONS: relay - zigbee2mqtt technology # HANDLER FUNCTIONS: relay - zigbee2mqtt technology
# ============================================================================ # ============================================================================
def _transform_relay_zigbee2mqtt_to_vendor(payload: dict[str, Any]) -> dict[str, Any]: def _transform_relay_zigbee2mqtt_to_vendor(payload: dict[str, Any]) -> str:
"""Transform abstract relay payload to zigbee2mqtt format. """Transform abstract relay payload to zigbee2mqtt format.
Relay only has power on/off, same transformation as light. Relay only has power on/off, same transformation as light.
@@ -325,7 +302,7 @@ def _transform_relay_zigbee2mqtt_to_vendor(payload: dict[str, Any]) -> dict[str,
power_value = vendor_payload.pop("power") power_value = vendor_payload.pop("power")
vendor_payload["state"] = power_value.upper() if isinstance(power_value, str) else power_value vendor_payload["state"] = power_value.upper() if isinstance(power_value, str) else power_value
return vendor_payload return json.dumps(vendor_payload)
def _transform_relay_zigbee2mqtt_to_abstract(payload: str) -> dict[str, Any]: def _transform_relay_zigbee2mqtt_to_abstract(payload: str) -> dict[str, Any]:
@@ -375,10 +352,40 @@ def _transform_relay_shelly_to_abstract(payload: str) -> dict[str, Any]:
return {"power": payload.strip()} return {"power": payload.strip()}
# ============================================================================ # ============================================================================
# HANDLER FUNCTIONS: relay - hottis_modbus technology # HANDLER FUNCTIONS: relay - tasmota technology
# ============================================================================ # ============================================================================
def _transform_relay_hottis_modbus_to_vendor(payload: dict[str, Any]) -> str: def _transform_relay_tasmota_to_vendor(payload: dict[str, Any]) -> str:
"""Transform abstract relay payload to Tasmota format.
Tasmota expects plain text 'on' or 'off' (not JSON).
- power: 'on'/'off' -> 'on'/'off' (plain string)
Example:
- Abstract: {'power': 'on'}
- Tasmota: 'on'
"""
power = payload.get("power", "off")
return power
def _transform_relay_tasmota_to_abstract(payload: str) -> dict[str, Any]:
"""Transform Tasmota relay payload to abstract format.
Tasmota sends plain text 'on' or 'off' (not JSON).
- 'on'/'off' -> power: 'on'/'off'
Example:
- Tasmota: 'ON'
- Abstract: {'power': 'on'}
"""
return {"power": payload.strip().lower()}
# ============================================================================
# HANDLER FUNCTIONS: relay - hottis_pv_modbus technology
# ============================================================================
def _transform_relay_hottis_pv_modbus_to_vendor(payload: dict[str, Any]) -> str:
"""Transform abstract relay payload to Hottis Modbus format. """Transform abstract relay payload to Hottis Modbus format.
Hottis Modbus expects plain text 'on' or 'off' (not JSON). Hottis Modbus expects plain text 'on' or 'off' (not JSON).
@@ -392,26 +399,32 @@ def _transform_relay_hottis_modbus_to_vendor(payload: dict[str, Any]) -> str:
return power return power
def _transform_relay_hottis_modbus_to_abstract(payload: str) -> dict[str, Any]: def _transform_relay_hottis_pv_modbus_to_abstract(payload: str) -> dict[str, Any]:
def _transform_relay_hottis_pv_modbus_to_abstract(payload: str) -> dict[str, Any]:
"""Transform Hottis Modbus relay payload to abstract format. """Transform Hottis Modbus relay payload to abstract format.
Hottis Modbus sends plain text 'on' or 'off' (not JSON). Hottis Modbus sends JSON like:
- 'on'/'off' -> power: 'on'/'off' {"status": "Ok", "timestamp": "...", "state": false, "cnt": 528}
Example: We only care about the 'state' field:
- Hottis Modbus: 'on' - state: true -> power: 'on'
- Abstract: {'power': 'on'} - state: false -> power: 'off'
""" """
data = json.loads(payload)
state = data.get("state", False)
power = "on" if bool(state) else "off"
return {"power": payload.strip()} return {"power": payload.strip()}
# ============================================================================ # ============================================================================
# HANDLER FUNCTIONS: three_phase_powermeter - hottis_modbus technology # HANDLER FUNCTIONS: three_phase_powermeter - hottis_pv_modbus technology
# ============================================================================ # ============================================================================
def _transform_three_phase_powermeter_hottis_modbus_to_vendor(payload: dict[str, Any]) -> dict[str, Any]: def _transform_three_phase_powermeter_hottis_pv_modbus_to_vendor(payload: dict[str, Any]) -> str:
"""Transform abstract three_phase_powermeter payload to hottis_modbus format. """Transform abstract three_phase_powermeter payload to hottis_pv_modbus format.
energy: float = Field(..., description="Total energy in kWh") energy: float = Field(..., description="Total energy in kWh")
total_power: float = Field(..., description="Total power in W") total_power: float = Field(..., description="Total power in W")
phase1_power: float = Field(..., description="Power for phase 1 in W") phase1_power: float = Field(..., description="Power for phase 1 in W")
@@ -441,32 +454,53 @@ def _transform_three_phase_powermeter_hottis_modbus_to_vendor(payload: dict[str,
"phase3_current": payload.get("phase3_current", 0.0), "phase3_current": payload.get("phase3_current", 0.0),
} }
return vendor_payload return json.dumps(vendor_payload)
def _transform_three_phase_powermeter_hottis_modbus_to_abstract(payload: str) -> dict[str, Any]: def _transform_three_phase_powermeter_hottis_pv_modbus_to_abstract(payload: str) -> dict[str, Any]:
"""Transform hottis_modbus three_phase_powermeter payload to abstract format. """Transform hottis_pv_modbus three_phase_powermeter payload to abstract format.
Transformations: Transformations:
- Direct mapping of all power meter fields - Map vendor field names to abstract field names
- totalImportEnergy -> energy
Example: - powerL1/powerL2/powerL3 -> phase1_power/phase2_power/phase3_power
- hottis_modbus: {'energy': 123.45, 'total_power': 1500.0, 'phase1_power': 500.0, ...} - voltageL1/voltageL2/voltageL3 -> phase1_voltage/phase2_voltage/phase3_voltage
- Abstract: {'energy': 123.45, 'total_power': 1500.0, 'phase1_power': 500.0, ...} - currentL1/currentL2/currentL3 -> phase1_current/phase2_current/phase3_current
- Sum of powerL1..3 -> total_power
""" """
payload = json.loads(payload) data = json.loads(payload)
# Helper to read numeric values uniformly as float
def _get_float(key: str, default: float = 0.0) -> float:
return float(data.get(key, default))
# Read all numeric values via helper for consistent error handling
phase1_power = _get_float("powerL1")
phase2_power = _get_float("powerL2")
phase3_power = _get_float("powerL3")
phase1_voltage = _get_float("voltageL1")
phase2_voltage = _get_float("voltageL2")
phase3_voltage = _get_float("voltageL3")
phase1_current = _get_float("currentL1")
phase2_current = _get_float("currentL2")
phase3_current = _get_float("currentL3")
energy = _get_float("totalImportEnergy")
abstract_payload = { abstract_payload = {
"energy": payload.get("energy", 0.0), "energy": energy,
"total_power": payload.get("total_power", 0.0), "total_power": phase1_power + phase2_power + phase3_power,
"phase1_power": payload.get("phase1_power", 0.0), "phase1_power": phase1_power,
"phase2_power": payload.get("phase2_power", 0.0), "phase2_power": phase2_power,
"phase3_power": payload.get("phase3_power", 0.0), "phase3_power": phase3_power,
"phase1_voltage": payload.get("phase1_voltage", 0.0), "phase1_voltage": phase1_voltage,
"phase2_voltage": payload.get("phase2_voltage", 0.0), "phase2_voltage": phase2_voltage,
"phase3_voltage": payload.get("phase3_voltage", 0.0), "phase3_voltage": phase3_voltage,
"phase1_current": payload.get("phase1_current", 0.0), "phase1_current": phase1_current,
"phase2_current": payload.get("phase2_current", 0.0), "phase2_current": phase2_current,
"phase3_current": payload.get("phase3_current", 0.0), "phase3_current": phase3_current,
} }
return abstract_payload return abstract_payload
@@ -567,24 +601,22 @@ TRANSFORM_HANDLERS: dict[tuple[str, str, str], TransformHandler] = {
# Temperature & humidity sensor transformations (support both type aliases) # Temperature & humidity sensor transformations (support both type aliases)
("temp_humidity_sensor", "zigbee2mqtt", "to_vendor"): _transform_temp_humidity_sensor_zigbee2mqtt_to_vendor, ("temp_humidity_sensor", "zigbee2mqtt", "to_vendor"): _transform_temp_humidity_sensor_zigbee2mqtt_to_vendor,
("temp_humidity_sensor", "zigbee2mqtt", "to_abstract"): _transform_temp_humidity_sensor_zigbee2mqtt_to_abstract, ("temp_humidity_sensor", "zigbee2mqtt", "to_abstract"): _transform_temp_humidity_sensor_zigbee2mqtt_to_abstract,
("temp_humidity_sensor", "max", "to_vendor"): _transform_temp_humidity_sensor_max_to_vendor,
("temp_humidity_sensor", "max", "to_abstract"): _transform_temp_humidity_sensor_max_to_abstract,
("temp_humidity", "zigbee2mqtt", "to_vendor"): _transform_temp_humidity_sensor_zigbee2mqtt_to_vendor, ("temp_humidity", "zigbee2mqtt", "to_vendor"): _transform_temp_humidity_sensor_zigbee2mqtt_to_vendor,
("temp_humidity", "zigbee2mqtt", "to_abstract"): _transform_temp_humidity_sensor_zigbee2mqtt_to_abstract, ("temp_humidity", "zigbee2mqtt", "to_abstract"): _transform_temp_humidity_sensor_zigbee2mqtt_to_abstract,
("temp_humidity", "max", "to_vendor"): _transform_temp_humidity_sensor_max_to_vendor,
("temp_humidity", "max", "to_abstract"): _transform_temp_humidity_sensor_max_to_abstract,
# Relay transformations # Relay transformations
("relay", "zigbee2mqtt", "to_vendor"): _transform_relay_zigbee2mqtt_to_vendor, ("relay", "zigbee2mqtt", "to_vendor"): _transform_relay_zigbee2mqtt_to_vendor,
("relay", "zigbee2mqtt", "to_abstract"): _transform_relay_zigbee2mqtt_to_abstract, ("relay", "zigbee2mqtt", "to_abstract"): _transform_relay_zigbee2mqtt_to_abstract,
("relay", "shelly", "to_vendor"): _transform_relay_shelly_to_vendor, ("relay", "shelly", "to_vendor"): _transform_relay_shelly_to_vendor,
("relay", "shelly", "to_abstract"): _transform_relay_shelly_to_abstract, ("relay", "shelly", "to_abstract"): _transform_relay_shelly_to_abstract,
("relay", "hottis_modbus", "to_vendor"): _transform_relay_hottis_modbus_to_vendor, ("relay", "hottis_pv_modbus", "to_vendor"): _transform_relay_hottis_pv_modbus_to_vendor,
("relay", "hottis_modbus", "to_abstract"): _transform_relay_hottis_modbus_to_abstract, ("relay", "hottis_pv_modbus", "to_abstract"): _transform_relay_hottis_pv_modbus_to_abstract,
("relay", "tasmota", "to_vendor"): _transform_relay_tasmota_to_vendor,
("relay", "tasmota", "to_abstract"): _transform_relay_tasmota_to_abstract,
# Three-Phase Powermeter transformations # Three-Phase Powermeter transformations
("three_phase_powermeter", "hottis_modbus", "to_vendor"): _transform_three_phase_powermeter_hottis_modbus_to_vendor, ("three_phase_powermeter", "hottis_pv_modbus", "to_vendor"): _transform_three_phase_powermeter_hottis_pv_modbus_to_vendor,
("three_phase_powermeter", "hottis_modbus", "to_abstract"): _transform_three_phase_powermeter_hottis_modbus_to_abstract, ("three_phase_powermeter", "hottis_pv_modbus", "to_abstract"): _transform_three_phase_powermeter_hottis_pv_modbus_to_abstract,
} }

View File

@@ -1,8 +1,6 @@
services: services:
homekit-bridge: homekit-bridge:
build: image: gitea.hottis.de/wn/home-automation/homekit:0.5.0
context: ../..
dockerfile: apps/homekit/Dockerfile
container_name: homekit-bridge container_name: homekit-bridge
# Required for mDNS/Bonjour to work properly # Required for mDNS/Bonjour to work properly

View File

@@ -78,7 +78,7 @@ def build_bridge(driver: AccessoryDriver, api_client: ApiClient) -> Bridge:
bridge.add_accessory(accessory) bridge.add_accessory(accessory)
accessory_map[device.device_id] = accessory accessory_map[device.device_id] = accessory
logger.info(f"Added accessory: {device.friendly_name} ({device.type}) in room: {device.room or 'Unknown'}") logger.info(f"Added accessory: {device.friendly_name} ({device.type}, {accessory.__class__.__name__}) in room: {device.room or 'Unknown'}")
else: else:
logger.warning(f"No accessory mapping for device: {device.name} ({device.type})") logger.warning(f"No accessory mapping for device: {device.name} ({device.type})")
except Exception as e: except Exception as e:

View File

@@ -241,22 +241,6 @@ devices:
ieee_address: "0x0017880108a03e45" ieee_address: "0x0017880108a03e45"
model: "929002241201" model: "929002241201"
vendor: "Philips" vendor: "Philips"
- device_id: haustuer
name: Haustür-Lampe
type: light
cap_version: "light@1.2.0"
technology: zigbee2mqtt
features:
power: true
brightness: true
topics:
state: "zigbee2mqtt/0xec1bbdfffea6a3da"
set: "zigbee2mqtt/0xec1bbdfffea6a3da/set"
metadata:
friendly_name: "Haustür"
ieee_address: "0xec1bbdfffea6a3da"
model: "LED1842G3"
vendor: "IKEA"
- device_id: deckenlampe_flur_oben - device_id: deckenlampe_flur_oben
name: Deckenlampe oben name: Deckenlampe oben
type: light type: light
@@ -783,24 +767,108 @@ devices:
topics: topics:
set: "shellies/lichtterasse/relay/0/command" set: "shellies/lichtterasse/relay/0/command"
state: "shellies/lichtterasse/relay/0" state: "shellies/lichtterasse/relay/0"
- device_id: kugellampe_patty
name: Kugellampe Patty
type: light
cap_version: "light@1.2.0"
technology: zigbee2mqtt
features:
power: true
brightness: true
topics:
state: "zigbee2mqtt/0xbc33acfffe21f547"
set: "zigbee2mqtt/0xbc33acfffe21f547/set"
- device_id: kueche_fensterbank_licht
name: Fensterbank Küche
type: light
cap_version: "light@1.2.0"
technology: zigbee2mqtt
features:
power: true
brightness: true
topics:
state: "zigbee2mqtt/0xf0d1b8000017515d"
set: "zigbee2mqtt/0xf0d1b8000017515d/set"
- device_id: licht_kommode_schlafzimmer
name: Kommode Schlafzimmer
type: relay
cap_version: "relay@1.0.0"
technology: tasmota
features:
power: true
topics:
set: "cmnd/tasmota/04/POWER"
state: "stat/tasmota/04/POWER"
- device_id: licht_fensterbank_esszimmer
name: Fensterbank Esszimmer
type: relay
cap_version: "relay@1.0.0"
technology: tasmota
features:
power: true
topics:
set: "cmnd/tasmota/02/POWER"
state: "stat/tasmota/02/POWER"
- device_id: licht_schreibtisch_patty
name: Schreibtisch Patty
type: relay
cap_version: "relay@1.0.0"
technology: tasmota
features:
power: true
topics:
set: "cmnd/tasmota/03/POWER"
state: "stat/tasmota/03/POWER"
- device_id: kugeln_regal_flur
name: Kugeln Regal Flur
type: relay
cap_version: "relay@1.0.0"
technology: tasmota
features:
power: true
topics:
set: "cmnd/tasmota/01/POWER"
state: "stat/tasmota/01/POWER"
- device_id: schrank_flur_haustür
name: Schrank Flur Haustür
type: relay
cap_version: "relay@1.0.0"
technology: tasmota
features:
power: true
topics:
set: "cmnd/tasmota/05/POWER"
state: "stat/tasmota/05/POWER"
- device_id: power_relay_caroutlet - device_id: power_relay_caroutlet
name: Car Outlet name: Car Outlet
type: relay type: relay
cap_version: "relay@1.0.0" cap_version: "relay@1.0.0"
technology: hottis_modbus technology: hottis_pv_modbus
features: features:
power: true power: true
topics: topics:
set: "caroutlet/cmd" set: "IoT/Car/Control"
state: "caroutlet/state" state: "IoT/Car/Control/State"
- device_id: powermeter_caroutlet - device_id: powermeter_caroutlet
name: Car Outlet name: Car Outlet
type: three_phase_powermeter type: three_phase_powermeter
cap_version: "three_phase_powermeter@1.0.0" cap_version: "three_phase_powermeter@1.0.0"
technology: hottis_modbus technology: hottis_pv_modbus
topics: topics:
state: "caroutlet/powermeter" state: "IoT/Car/Values"
- device_id: schranklicht_flur_vor_kueche
name: Schranklicht Flur vor Küche
type: light
cap_version: "light@1.2.0"
technology: zigbee2mqtt
features:
power: true
brightness: true
topics:
state: "zigbee2mqtt/0xf0d1b80000155a1f"
set: "zigbee2mqtt/0xf0d1b80000155a1f/set"

View File

@@ -17,6 +17,10 @@ rooms:
title: Medusa-Lampe Schlafzimmer title: Medusa-Lampe Schlafzimmer
icon: 💡 icon: 💡
rank: 40 rank: 40
- device_id: licht_kommode_schlafzimmer
title: Kommode Schlafzimmer
icon: 💡
rank: 42
- device_id: thermostat_schlafzimmer - device_id: thermostat_schlafzimmer
title: Thermostat Schlafzimmer title: Thermostat Schlafzimmer
icon: 🌡️ icon: 🌡️
@@ -39,10 +43,10 @@ rooms:
title: Leselampe Esszimmer title: Leselampe Esszimmer
icon: 💡 icon: 💡
rank: 60 rank: 60
# - device_id: standlampe_esszimmer - device_id: licht_fensterbank_esszimmer
# title: Standlampe Esszimmer title: Fensterbank Esszimmer
# icon: 💡 icon: 💡
# rank: 70 rank: 70
- device_id: kleine_lampe_links_esszimmer - device_id: kleine_lampe_links_esszimmer
title: kleine Lampe links Esszimmer title: kleine Lampe links Esszimmer
icon: 💡 icon: 💡
@@ -127,6 +131,10 @@ rooms:
title: Küche Putzlicht title: Küche Putzlicht
icon: 💡 icon: 💡
rank: 143 rank: 143
- device_id: kueche_fensterbank_licht
title: Küche Fensterbank
icon: 💡
rank: 144
- device_id: thermostat_kueche - device_id: thermostat_kueche
title: Kueche title: Kueche
icon: 🌡️ icon: 🌡️
@@ -165,6 +173,14 @@ rooms:
title: Schranklicht vorne Patty title: Schranklicht vorne Patty
icon: 💡 icon: 💡
rank: 180 rank: 180
- device_id: kugellampe_patty
title: Kugellampe Patty
icon: 💡
rank: 181
- device_id: licht_schreibtisch_patty
title: Licht Schreibtisch Patty
icon: 💡
rank: 182
- device_id: thermostat_patty - device_id: thermostat_patty
title: Thermostat Patty title: Thermostat Patty
icon: 🌡️ icon: 🌡️
@@ -209,18 +225,22 @@ rooms:
title: Deckenlampe Flur oben title: Deckenlampe Flur oben
icon: 💡 icon: 💡
rank: 210 rank: 210
- device_id: haustuer - device_id: kugeln_regal_flur
title: Haustür title: Kugeln Regal Flur
icon: 💡
rank: 220
- device_id: licht_flur_schrank
title: Schranklicht Flur
icon: 💡 icon: 💡
rank: 222 rank: 222
- device_id: licht_flur_oben_am_spiegel - device_id: licht_flur_oben_am_spiegel
title: Licht Flur oben am Spiegel title: Licht Flur oben am Spiegel
icon: 💡 icon: 💡
rank: 230 rank: 230
- device_id: schrank_flur_haustür
title: Schrank Flur Haustür
icon: 💡
rank: 231
- device_id: schranklicht_flur_vor_kueche
title: Schranklicht Flur vor Küche
icon: 💡
rank: 232
- device_id: sensor_flur - device_id: sensor_flur
title: Temperatur & Luftfeuchte title: Temperatur & Luftfeuchte
icon: 🌡️ icon: 🌡️