diff --git a/apps/abstraction/main.py b/apps/abstraction/main.py index 9d6439b..8d86b5b 100644 --- a/apps/abstraction/main.py +++ b/apps/abstraction/main.py @@ -180,18 +180,10 @@ async def handle_abstract_set( # Transform abstract payload to vendor-specific format vendor_payload = transform_abstract_to_vendor(device_type, device_technology, abstract_payload) - - # For MAX! thermostats and Shelly relays, vendor_payload is a plain string - # For other devices, it's a dict that needs JSON encoding - if (device_technology == "max" and device_type == "thermostat") or \ - (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}") - logger.debug(f"MQTT message published on {vendor_topic}: {vendor_message}") - await mqtt_client.publish(vendor_topic, vendor_message, qos=1) + + logger.info(f"→ vendor SET {device_id}: {vendor_topic} ← {vendor_payload}") + logger.debug(f"MQTT message published on {vendor_topic}: {vendor_payload}") + await mqtt_client.publish(vendor_topic, vendor_payload, qos=1) async def handle_vendor_state( diff --git a/apps/abstraction/transformation.py b/apps/abstraction/transformation.py index 755ae36..740080e 100644 --- a/apps/abstraction/transformation.py +++ b/apps/abstraction/transformation.py @@ -17,12 +17,12 @@ logger = logging.getLogger(__name__) # 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. 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]: @@ -36,12 +36,12 @@ def _transform_light_simulator_to_abstract(payload: str) -> dict[str, Any]: 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. 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]: @@ -59,7 +59,7 @@ def _transform_thermostat_simulator_to_abstract(payload: str) -> dict[str, Any]: # 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. 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) 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]: @@ -115,7 +115,7 @@ def _transform_light_zigbee2mqtt_to_abstract(payload: str) -> dict[str, Any]: 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. Transformations: @@ -132,7 +132,7 @@ def _transform_thermostat_zigbee2mqtt_to_vendor(payload: dict[str, Any]) -> dict # zigbee2mqtt expects current_heating_setpoint as string 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]: @@ -171,14 +171,14 @@ def _transform_thermostat_zigbee2mqtt_to_abstract(payload: str) -> dict[str, Any # 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. Contact sensors are read-only, so this should not be called for SET commands. Returns payload as-is for compatibility. """ 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]: @@ -225,14 +225,14 @@ def _transform_contact_sensor_zigbee2mqtt_to_abstract(payload: str) -> dict[str, # 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. Contact sensors are read-only, so this should not be called for SET commands. Returns payload as-is for compatibility. """ 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]: @@ -266,13 +266,13 @@ def _transform_contact_sensor_max_to_abstract(payload: str) -> dict[str, Any]: # 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. Temp/humidity sensors are read-only, so this should not be called for SET commands. 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]: @@ -284,36 +284,13 @@ def _transform_temp_humidity_sensor_zigbee2mqtt_to_abstract(payload: str) -> dic 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 # ============================================================================ -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. 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") 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]: @@ -416,7 +393,7 @@ def _transform_relay_hottis_pv_modbus_to_abstract(payload: str) -> dict[str, Any # HANDLER FUNCTIONS: three_phase_powermeter - hottis_pv_modbus technology # ============================================================================ -def _transform_three_phase_powermeter_hottis_pv_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_pv_modbus format. energy: float = Field(..., description="Total energy in kWh") total_power: float = Field(..., description="Total power in W") @@ -447,7 +424,7 @@ def _transform_three_phase_powermeter_hottis_pv_modbus_to_vendor(payload: dict[s "phase3_current": payload.get("phase3_current", 0.0), } - return vendor_payload + return json.dumps(vendor_payload) def _transform_three_phase_powermeter_hottis_pv_modbus_to_abstract(payload: str) -> dict[str, Any]: @@ -573,12 +550,8 @@ TRANSFORM_HANDLERS: dict[tuple[str, str, str], TransformHandler] = { # 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_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_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", "zigbee2mqtt", "to_vendor"): _transform_relay_zigbee2mqtt_to_vendor,