Compare commits

...

30 Commits

Author SHA1 Message Date
cc342245f8 gartenlicht vorne
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-08 10:48:23 +01:00
50253d536d more lights 6
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-08 10:28:30 +01:00
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
2b963a33ef build homekit too
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/build/6 Pipeline was successful
ci/woodpecker/tag/build/1 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/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/deploy/5 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-03 22:32:42 +01:00
1311f7a59b Putzlicht 2
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-01 16:53:17 +01:00
a226fa9268 Putzlicht
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-01 16:26:38 +01:00
3bd8d293a2 fix licht spuele 2
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
2025-12-01 15:46:43 +01:00
be30ad3a3c fix licht spuele 2025-12-01 15:45:12 +01:00
500384b1cd streamline ci 2
All checks were successful
ci/woodpecker/tag/namespace Pipeline was successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/config Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/build/3 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/deploy/1 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-01 14:56:56 +01:00
6b4c247413 forgotten files
All checks were successful
ci/woodpecker/tag/config Pipeline was 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/1 Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/build/2 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/deploy/3 Pipeline was successful
ci/woodpecker/tag/deploy/5 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
2025-12-01 14:53:50 +01:00
04a1807306 streamline ci
Some checks failed
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline failed
ci/woodpecker/tag/build/2 Pipeline failed
ci/woodpecker/tag/build/4 Pipeline failed
ci/woodpecker/tag/build/1 Pipeline failed
2025-12-01 14:52:50 +01:00
db5e4589d0 fix error for devices with missing state 2025-12-01 14:48:32 +01:00
5399f044a1 separation of ui and static
All checks were successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/predeploy Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/build/2 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/1 Pipeline was successful
ci/woodpecker/tag/deploy/2 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
6
2025-12-01 14:24:15 +01:00
16fa5143dd separation of ui and static
All checks were successful
ci/woodpecker/tag/build/1 Pipeline was successful
ci/woodpecker/tag/build/5 Pipeline was successful
ci/woodpecker/tag/build/3 Pipeline was successful
ci/woodpecker/tag/build/2 Pipeline was successful
ci/woodpecker/tag/build/4 Pipeline was successful
ci/woodpecker/tag/predeploy Pipeline was successful
ci/woodpecker/tag/deploy/5 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/4 Pipeline was successful
ci/woodpecker/tag/ingress Pipeline was successful
5
2025-12-01 14:15:54 +01:00
19 changed files with 349 additions and 217 deletions

View File

@@ -1,5 +1,8 @@
when:
event: [tag]
ref:
exclude:
- refs/tags/*-configchange
matrix:
APP:
@@ -8,6 +11,7 @@ matrix:
- abstraction
- rules
- static
- homekit
steps:
build-${APP}:
@@ -22,8 +26,3 @@ steps:
repo: ${FORGE_NAME}/${CI_REPO}/${APP}
auto_tag: true
dockerfile: apps/${APP}/Dockerfile
when:
event: [tag]
ref:
exclude:
- refs/tags/*-configchange

View File

@@ -1,23 +1,10 @@
when:
event: [tag]
depends_on:
- namespace
steps:
create_namespace:
image: quay.io/wollud1969/k8s-admin-helper:0.3.4
environment:
KUBE_CONFIG_CONTENT:
from_secret: kube_config
NAMESPACE: "homea2"
commands:
- printf "$KUBE_CONFIG_CONTENT" > /tmp/kubeconfig
- export KUBECONFIG=/tmp/kubeconfig
- kubectl create namespace $NAMESPACE || echo "Namespace $NAMESPACE already exists"
when:
event: [tag]
ref:
exclude:
- refs/tags/*-configchange
apply_configuration:
image: quay.io/wollud1969/k8s-admin-helper:0.3.4
environment:
@@ -36,6 +23,4 @@ steps:
--namespace=$NAMESPACE
--dry-run=client -o yaml | kubectl apply -f -
- kubectl apply -f deployment/configmap.yaml -n $NAMESPACE
when:
event: [tag]

View File

@@ -1,9 +1,13 @@
when:
event: [tag]
ref:
exclude:
- refs/tags/*-configchange
depends_on:
- build
- predeploy
- namespace
- config
matrix:
APP:
@@ -26,9 +30,5 @@ steps:
- export KUBECONFIG=/tmp/kubeconfig
- echo "Deploying application ${APP} ($IMAGE) to namespace $NAMESPACE"
- cat deployment/${APP}-deployment.yaml | sed "s,%IMAGE%,$IMAGE,g" | kubectl apply -n $NAMESPACE -f -
when:
event: [tag]
ref:
exclude:
- refs/tags/*-configchange

View File

@@ -1,5 +1,8 @@
when:
event: [tag]
ref:
exclude:
- refs/tags/*-configchange
depends_on:
- deploy
@@ -15,9 +18,4 @@ steps:
- printf "$KUBE_CONFIG_CONTENT" > /tmp/kubeconfig
- export KUBECONFIG=/tmp/kubeconfig
- kubectl apply -f deployment/ingress.yaml -n $NAMESPACE
when:
event: [tag]
ref:
exclude:
- refs/tags/*-configchange

15
.woodpecker/namespace.yml Normal file
View File

@@ -0,0 +1,15 @@
when:
event: [tag]
steps:
create_namespace:
image: quay.io/wollud1969/k8s-admin-helper:0.3.4
environment:
KUBE_CONFIG_CONTENT:
from_secret: kube_config
NAMESPACE: "homea2"
commands:
- printf "$KUBE_CONFIG_CONTENT" > /tmp/kubeconfig
- export KUBECONFIG=/tmp/kubeconfig
- kubectl create namespace $NAMESPACE || echo "Namespace $NAMESPACE already exists"

View File

@@ -181,16 +181,9 @@ 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}")
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(

View File

@@ -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,40 @@ def _transform_relay_shelly_to_abstract(payload: str) -> dict[str, Any]:
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.
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
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,32 +454,53 @@ 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, ...}
- Abstract: {'energy': 123.45, 'total_power': 1500.0, 'phase1_power': 500.0, ...}
- Map vendor field names to abstract field names
- totalImportEnergy -> energy
- powerL1/powerL2/powerL3 -> phase1_power/phase2_power/phase3_power
- voltageL1/voltageL2/voltageL3 -> phase1_voltage/phase2_voltage/phase3_voltage
- 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 = {
"energy": payload.get("energy", 0.0),
"total_power": payload.get("total_power", 0.0),
"phase1_power": payload.get("phase1_power", 0.0),
"phase2_power": payload.get("phase2_power", 0.0),
"phase3_power": payload.get("phase3_power", 0.0),
"phase1_voltage": payload.get("phase1_voltage", 0.0),
"phase2_voltage": payload.get("phase2_voltage", 0.0),
"phase3_voltage": payload.get("phase3_voltage", 0.0),
"phase1_current": payload.get("phase1_current", 0.0),
"phase2_current": payload.get("phase2_current", 0.0),
"phase3_current": payload.get("phase3_current", 0.0),
"energy": energy,
"total_power": phase1_power + phase2_power + phase3_power,
"phase1_power": phase1_power,
"phase2_power": phase2_power,
"phase3_power": phase3_power,
"phase1_voltage": phase1_voltage,
"phase2_voltage": phase2_voltage,
"phase3_voltage": phase3_voltage,
"phase1_current": phase1_current,
"phase2_current": phase2_current,
"phase3_current": phase3_current,
}
return abstract_payload
@@ -567,24 +601,22 @@ 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,
("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", "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,
}

View File

@@ -1,8 +1,6 @@
services:
homekit-bridge:
build:
context: ../..
dockerfile: apps/homekit/Dockerfile
image: gitea.hottis.de/wn/home-automation/homekit:0.5.0
container_name: homekit-bridge
# 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)
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:
logger.warning(f"No accessory mapping for device: {device.name} ({device.type})")
except Exception as e:

View File

@@ -1,43 +0,0 @@
{
"name": "Home Automation",
"short_name": "Home",
"description": "Smart Home Automation System",
"start_url": "/",
"display": "standalone",
"background_color": "#667eea",
"theme_color": "#667eea",
"orientation": "portrait",
"icons": [
{
"src": "/static/apple-touch-icon-180x180.png",
"sizes": "180x180",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/static/apple-touch-icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "/static/apple-touch-icon-120x120.png",
"sizes": "120x120",
"type": "image/png"
},
{
"src": "/static/apple-touch-icon-76x76.png",
"sizes": "76x76",
"type": "image/png"
},
{
"src": "/static/apple-touch-icon-32x32.png",
"sizes": "32x32",
"type": "image/png"
},
{
"src": "/static/apple-touch-icon-16x16.png",
"sizes": "16x16",
"type": "image/png"
}
]
}

View File

@@ -16,7 +16,6 @@
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="Dashboard">
<meta name="theme-color" content="#667eea">
<link rel="manifest" href="{{ STATIC_BASE }}/manifest.json">
<style>
* {
margin: 0;

View File

@@ -15,7 +15,6 @@
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="Gerät">
<meta name="theme-color" content="#667eea">
<link rel="manifest" href="{{ STATIC_BASE }}/manifest.json">
<style>
* {
margin: 0;
@@ -359,6 +358,7 @@
let deviceData = null;
let deviceState = {};
let roomName = '';
let deviceStateUnknown = false;
// Device type icons
const deviceIcons = {
@@ -381,8 +381,19 @@
// NEW: Use new endpoints for device info and layout
deviceData = await window.apiClient.getDevice(deviceId);
console.log("Loaded device data:", deviceData);
try {
deviceState = await window.apiClient.getDeviceState(deviceId);
console.log("Loaded device state:", deviceState);
if (!deviceState || Object.keys(deviceState).length === 0) {
deviceStateUnknown = true;
deviceState = {};
}
} catch (stateError) {
console.warn('No state for device, using unknown state:', stateError);
deviceStateUnknown = true;
deviceState = {};
}
const layoutInfo = await window.apiClient.getDeviceLayout(deviceId);
console.log("Loaded layout info:", layoutInfo);
roomName = layoutInfo.room;
@@ -518,6 +529,14 @@
}, 0);
}
if (deviceStateUnknown) {
const hint = document.createElement('div');
hint.className = 'device-meta';
hint.style.marginTop = '12px';
hint.textContent = 'Status unbekannt';
card.appendChild(hint);
}
container.appendChild(card);
}
@@ -553,6 +572,14 @@
`;
card.appendChild(sliderGroup);
if (deviceStateUnknown) {
const hint = document.createElement('div');
hint.className = 'device-meta';
hint.style.marginTop = '12px';
hint.textContent = 'Status unbekannt';
card.appendChild(hint);
}
container.appendChild(card);
setTimeout(() => {
@@ -581,6 +608,14 @@
powerGroup.appendChild(powerButton);
card.appendChild(powerGroup);
if (deviceStateUnknown) {
const hint = document.createElement('div');
hint.className = 'device-meta';
hint.style.marginTop = '12px';
hint.textContent = 'Status unbekannt';
card.appendChild(hint);
}
container.appendChild(card);
}
@@ -599,6 +634,14 @@
`;
card.appendChild(statusDiv);
if (deviceStateUnknown) {
const hint = document.createElement('div');
hint.className = 'device-meta';
hint.style.marginTop = '12px';
hint.textContent = 'Status unbekannt';
card.appendChild(hint);
}
container.appendChild(card);
}

View File

@@ -16,7 +16,6 @@
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="Garage">
<meta name="theme-color" content="#667eea">
<link rel="manifest" href="{{ STATIC_BASE }}/manifest.json">
<style>
* {
margin: 0;

View File

@@ -16,7 +16,6 @@
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="Home Automation">
<meta name="theme-color" content="#667eea">
<link rel="manifest" href="{{ STATIC_BASE }}/manifest.json">
<style>
* {
margin: 0;

View File

@@ -15,7 +15,6 @@
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="{{ room_name }}">
<meta name="theme-color" content="#667eea">
<link rel="manifest" href="{{ STATIC_BASE }}/manifest.json">
<style>
* {
margin: 0;

View File

@@ -16,7 +16,6 @@
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="Räume">
<meta name="theme-color" content="#667eea">
<link rel="manifest" href="{{ STATIC_BASE }}/manifest.json">
<style>
* {
margin: 0;

View File

@@ -241,22 +241,6 @@ devices:
ieee_address: "0x0017880108a03e45"
model: "929002241201"
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
name: Deckenlampe oben
type: light
@@ -730,8 +714,19 @@ devices:
features:
power: true
topics:
set: "shellies/LightKitchenSink/relay/0/command"
state: "shellies/LightKitchenSink/relay/0"
set: "shellies/shellyplug-s-DED4E4/relay/0/command"
state: "shellies/shellyplug-s-DED4E4/relay/0"
- device_id: putzlicht_kueche
name: Putzlicht
type: light
cap_version: "light@1.2.0"
technology: zigbee2mqtt
features:
power: true
brightness: true
topics:
state: "zigbee2mqtt/0xa4c138563834406c"
set: "zigbee2mqtt/0xa4c138563834406c/set"
- device_id: licht_schrank_esszimmer
name: Schrank
type: relay
@@ -772,24 +767,118 @@ devices:
topics:
set: "shellies/lichtterasse/relay/0/command"
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: gartenlicht_vorne
name: Gartenlicht vorne
type: relay
cap_version: "relay@1.0.0"
technology: tasmota
features:
power: true
topics:
set: "cmnd/tasmota/06/POWER"
state: "stat/tasmota/06/POWER"
- device_id: power_relay_caroutlet
name: Car Outlet
type: relay
cap_version: "relay@1.0.0"
technology: hottis_modbus
technology: hottis_pv_modbus
features:
power: true
topics:
set: "caroutlet/cmd"
state: "caroutlet/state"
set: "IoT/Car/Control"
state: "IoT/Car/Control/State"
- device_id: powermeter_caroutlet
name: Car Outlet
type: three_phase_powermeter
cap_version: "three_phase_powermeter@1.0.0"
technology: hottis_modbus
technology: hottis_pv_modbus
topics:
state: "caroutlet/powermeter"
state: "IoT/Car/Values"
- device_id: schranklicht_flur_vor_kueche
name: Schranklicht Flur vor Küche
type: light
cap_version: "relay@1.0.0"
technology: zigbee2mqtt
features:
power: true
topics:
state: "zigbee2mqtt/0xf0d1b80000155a1f"
set: "zigbee2mqtt/0xf0d1b80000155a1f/set"

View File

@@ -17,6 +17,10 @@ rooms:
title: Medusa-Lampe Schlafzimmer
icon: 💡
rank: 40
- device_id: licht_kommode_schlafzimmer
title: Kommode Schlafzimmer
icon: 💡
rank: 42
- device_id: thermostat_schlafzimmer
title: Thermostat Schlafzimmer
icon: 🌡️
@@ -39,10 +43,10 @@ rooms:
title: Leselampe Esszimmer
icon: 💡
rank: 60
# - device_id: standlampe_esszimmer
# title: Standlampe Esszimmer
# icon: 💡
# rank: 70
- device_id: licht_fensterbank_esszimmer
title: Fensterbank Esszimmer
icon: 💡
rank: 70
- device_id: kleine_lampe_links_esszimmer
title: kleine Lampe links Esszimmer
icon: 💡
@@ -123,6 +127,14 @@ rooms:
title: Küche Spüle
icon: 💡
rank: 142
- device_id: putzlicht_kueche
title: Küche Putzlicht
icon: 💡
rank: 143
- device_id: kueche_fensterbank_licht
title: Küche Fensterbank
icon: 💡
rank: 144
- device_id: thermostat_kueche
title: Kueche
icon: 🌡️
@@ -161,6 +173,14 @@ rooms:
title: Schranklicht vorne Patty
icon: 💡
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
title: Thermostat Patty
icon: 🌡️
@@ -205,18 +225,22 @@ rooms:
title: Deckenlampe Flur oben
icon: 💡
rank: 210
- device_id: haustuer
title: Haustür
icon: 💡
rank: 220
- device_id: licht_flur_schrank
title: Schranklicht Flur
- device_id: kugeln_regal_flur
title: Kugeln Regal
icon: 💡
rank: 222
- device_id: licht_flur_oben_am_spiegel
title: Licht Flur oben am Spiegel
title: Licht oben am Spiegel
icon: 💡
rank: 230
- device_id: schrank_flur_haustür
title: Schranklicht an der Haustür
icon: 💡
rank: 231
- device_id: schranklicht_flur_vor_kueche
title: Schranklicht vor Küche
icon: 💡
rank: 232
- device_id: sensor_flur
title: Temperatur & Luftfeuchte
icon: 🌡️
@@ -279,6 +303,10 @@ rooms:
title: Licht Terasse
icon: 💡
rank: 290
- device_id: gartenlicht_vorne
title: Gartenlicht vorne
icon: 💡
rank: 291
- name: Garage
devices:
- device_id: power_relay_caroutlet

View File

@@ -14,7 +14,7 @@ data:
# UI specific environment variables
UI_UI_PORT: "8002"
UI_API_BASE: "https://homea2-api.hottis.de"
UI_STATIC_BASE: "http://homea2-static.hottis.de"
UI_STATIC_BASE: "https://homea2-static.hottis.de"
UI_BASE_PATH: "/"