|
|
|
|
@@ -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]:
|
|
|
|
|
@@ -375,10 +352,10 @@ def _transform_relay_shelly_to_abstract(payload: str) -> dict[str, Any]:
|
|
|
|
|
return {"power": payload.strip()}
|
|
|
|
|
|
|
|
|
|
# ============================================================================
|
|
|
|
|
# HANDLER FUNCTIONS: relay - hottis_modbus technology
|
|
|
|
|
# HANDLER FUNCTIONS: relay - hottis_pv_modbus technology
|
|
|
|
|
# ============================================================================
|
|
|
|
|
|
|
|
|
|
def _transform_relay_hottis_modbus_to_vendor(payload: dict[str, Any]) -> str:
|
|
|
|
|
def _transform_relay_hottis_pv_modbus_to_vendor(payload: dict[str, Any]) -> str:
|
|
|
|
|
"""Transform abstract relay payload to Hottis Modbus format.
|
|
|
|
|
|
|
|
|
|
Hottis Modbus expects plain text 'on' or 'off' (not JSON).
|
|
|
|
|
@@ -392,26 +369,32 @@ def _transform_relay_hottis_modbus_to_vendor(payload: dict[str, Any]) -> str:
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
Hottis Modbus sends plain text 'on' or 'off' (not JSON).
|
|
|
|
|
- 'on'/'off' -> power: 'on'/'off'
|
|
|
|
|
Hottis Modbus sends JSON like:
|
|
|
|
|
{"status": "Ok", "timestamp": "...", "state": false, "cnt": 528}
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
- Hottis Modbus: 'on'
|
|
|
|
|
- Abstract: {'power': 'on'}
|
|
|
|
|
We only care about the 'state' field:
|
|
|
|
|
- state: true -> 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()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ============================================================================
|
|
|
|
|
# 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]:
|
|
|
|
|
"""Transform abstract three_phase_powermeter payload to hottis_modbus format.
|
|
|
|
|
|
|
|
|
|
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")
|
|
|
|
|
phase1_power: float = Field(..., description="Power for phase 1 in W")
|
|
|
|
|
@@ -441,17 +424,17 @@ def _transform_three_phase_powermeter_hottis_modbus_to_vendor(payload: dict[str,
|
|
|
|
|
"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]:
|
|
|
|
|
"""Transform hottis_modbus three_phase_powermeter payload to abstract format.
|
|
|
|
|
def _transform_three_phase_powermeter_hottis_pv_modbus_to_abstract(payload: str) -> dict[str, Any]:
|
|
|
|
|
"""Transform hottis_pv_modbus three_phase_powermeter payload to abstract format.
|
|
|
|
|
|
|
|
|
|
Transformations:
|
|
|
|
|
- Direct mapping of all power meter fields
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
- hottis_modbus: {'energy': 123.45, 'total_power': 1500.0, 'phase1_power': 500.0, ...}
|
|
|
|
|
- hottis_pv_modbus: {'energy': 123.45, 'total_power': 1500.0, 'phase1_power': 500.0, ...}
|
|
|
|
|
- Abstract: {'energy': 123.45, 'total_power': 1500.0, 'phase1_power': 500.0, ...}
|
|
|
|
|
"""
|
|
|
|
|
payload = json.loads(payload)
|
|
|
|
|
@@ -567,24 +550,20 @@ 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,
|
|
|
|
|
("relay", "zigbee2mqtt", "to_abstract"): _transform_relay_zigbee2mqtt_to_abstract,
|
|
|
|
|
("relay", "shelly", "to_vendor"): _transform_relay_shelly_to_vendor,
|
|
|
|
|
("relay", "shelly", "to_abstract"): _transform_relay_shelly_to_abstract,
|
|
|
|
|
("relay", "hottis_modbus", "to_vendor"): _transform_relay_hottis_modbus_to_vendor,
|
|
|
|
|
("relay", "hottis_modbus", "to_abstract"): _transform_relay_hottis_modbus_to_abstract,
|
|
|
|
|
("relay", "hottis_pv_modbus", "to_vendor"): _transform_relay_hottis_pv_modbus_to_vendor,
|
|
|
|
|
("relay", "hottis_pv_modbus", "to_abstract"): _transform_relay_hottis_pv_modbus_to_abstract,
|
|
|
|
|
|
|
|
|
|
# Three-Phase Powermeter transformations
|
|
|
|
|
("three_phase_powermeter", "hottis_modbus", "to_vendor"): _transform_three_phase_powermeter_hottis_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_vendor"): _transform_three_phase_powermeter_hottis_pv_modbus_to_vendor,
|
|
|
|
|
("three_phase_powermeter", "hottis_pv_modbus", "to_abstract"): _transform_three_phase_powermeter_hottis_pv_modbus_to_abstract,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|