MAX transformation added

This commit is contained in:
2025-11-10 16:11:28 +01:00
parent a49d56df60
commit b57ddb1589
3 changed files with 346 additions and 15 deletions

223
MAX_INTEGRATION.md Normal file
View File

@@ -0,0 +1,223 @@
# MAX! (eQ-3) Thermostat Integration
## Overview
This document describes the integration of MAX! (eQ-3) thermostats via Homegear into the home automation system.
## Protocol Characteristics
MAX! thermostats use a **simple integer-based protocol** (not JSON):
- **SET messages**: Plain integer temperature value (e.g., `22`)
- **STATE messages**: Plain integer temperature value (e.g., `22`)
- **Topics**: Homegear MQTT format
### MQTT Topics
**SET Command:**
```
homegear/instance1/set/<peerId>/<channel>/SET_TEMPERATURE
Payload: "22" (plain integer as string)
```
**STATE Update:**
```
homegear/instance1/plain/<peerId>/<channel>/SET_TEMPERATURE
Payload: "22" (plain integer as string)
```
## Transformation Layer
The abstraction layer provides automatic transformation between the abstract home protocol and MAX! format.
### Abstract → MAX! (SET)
**Input (Abstract):**
```json
{
"mode": "heat",
"target": 22.5
}
```
**Output (MAX!):**
```
22
```
**Transformation Rules:**
- Extract `target` temperature
- Convert float → integer (round to nearest)
- Return as plain string (no JSON)
- Ignore `mode` field (MAX! always in heating mode)
### MAX! → Abstract (STATE)
**Input (MAX!):**
```
22
```
**Output (Abstract):**
```json
{
"target": 22.0,
"mode": "heat"
}
```
**Transformation Rules:**
- Parse plain string/integer value
- Convert to float
- Add default `mode: "heat"` (MAX! always heating)
- Wrap in abstract payload structure
## Device Configuration
### Example devices.yaml Entry
```yaml
- device_id: "thermostat_wolfgang"
type: "thermostat"
cap_version: "thermostat@1.0.0"
technology: "max"
features:
mode: true
target: true
current: false # SET_TEMPERATURE doesn't report current temp
topics:
set: "homegear/instance1/set/39/1/SET_TEMPERATURE"
state: "homegear/instance1/plain/39/1/SET_TEMPERATURE"
metadata:
friendly_name: "Thermostat Wolfgang"
location: "Arbeitszimmer Wolfgang"
vendor: "eQ-3"
model: "MAX! Thermostat"
peer_id: "39"
channel: "1"
```
### Configuration Notes
1. **technology**: Must be set to `"max"` to activate MAX! transformations
2. **topics.set**: Use Homegear's `/set/` path with `/SET_TEMPERATURE` parameter
3. **topics.state**: Use Homegear's `/plain/` path with `/SET_TEMPERATURE` parameter
4. **features.current**: Set to `false` - SET_TEMPERATURE topic doesn't provide current temperature
5. **metadata**: Include `peer_id` and `channel` for reference
## Temperature Rounding
MAX! only supports **integer temperatures**. The system uses standard rounding:
| Abstract Input | MAX! Output |
|----------------|-------------|
| 20.4°C | 20 |
| 20.5°C | 20 |
| 20.6°C | 21 |
| 21.5°C | 22 |
| 22.5°C | 22 |
Python's `round()` function uses "banker's rounding" (round half to even).
## Limitations
1. **No current temperature**: SET_TEMPERATURE topic only reports target, not actual temperature
2. **No mode control**: MAX! thermostats are always in heating mode
3. **Integer only**: Temperature precision limited to 1°C steps
4. **No battery status**: Not available via SET_TEMPERATURE topic
5. **No window detection**: Not available via SET_TEMPERATURE topic
## Testing
Test the transformation functions:
```bash
poetry run python /tmp/test_max_transform.py
```
Expected output:
```
✅ PASS: Float 22.5 -> Integer string
✅ PASS: Integer string -> Abstract dict
✅ PASS: Integer -> Abstract dict
✅ PASS: Rounding works correctly
🎉 All MAX! transformation tests passed!
```
## Implementation Details
### Files Modified
1. **apps/abstraction/transformation.py**
- Added `_transform_thermostat_max_to_vendor()` - converts abstract → plain integer
- Added `_transform_thermostat_max_to_abstract()` - converts plain integer → abstract
- Registered handlers in `TRANSFORM_HANDLERS` registry
2. **apps/abstraction/main.py**
- Modified `handle_abstract_set()` to send plain string for MAX! devices (not JSON)
- Modified message processing to handle plain text payloads from MAX! STATE topics
### Transformation Functions
```python
def _transform_thermostat_max_to_vendor(payload: dict[str, Any]) -> str:
"""Convert {"target": 22.5} → "22" """
target_temp = payload.get("target", 21.0)
return str(int(round(target_temp)))
def _transform_thermostat_max_to_abstract(payload: str | int | float) -> dict[str, Any]:
"""Convert "22" → {"target": 22.0, "mode": "heat"} """
target_temp = float(payload)
return {"target": target_temp, "mode": "heat"}
```
## Usage Example
### Setting Temperature via API
```bash
curl -X POST http://localhost:8001/devices/thermostat_wolfgang/set \
-H "Content-Type: application/json" \
-d '{
"type": "thermostat",
"payload": {
"mode": "heat",
"target": 22.5
}
}'
```
**Flow:**
1. API receives abstract payload: `{"mode": "heat", "target": 22.5}`
2. Abstraction transforms to MAX!: `"22"`
3. Publishes to: `homegear/instance1/set/39/1/SET_TEMPERATURE` with payload `22`
### Receiving State Updates
**Homegear publishes:**
```
Topic: homegear/instance1/plain/39/1/SET_TEMPERATURE
Payload: 22
```
**Flow:**
1. Abstraction receives plain text: `"22"`
2. Transforms to abstract: `{"target": 22.0, "mode": "heat"}`
3. Publishes to: `home/thermostat/thermostat_wolfgang/state`
4. Publishes to Redis: `ui:updates` channel for real-time UI updates
## Future Enhancements
Potential improvements for better MAX! integration:
1. **Current Temperature**: Subscribe to separate Homegear topic for actual temperature
2. **Battery Status**: Subscribe to LOWBAT or battery level topics
3. **Valve Position**: Monitor actual valve opening percentage
4. **Window Detection**: Subscribe to window open detection status
5. **Mode Control**: Support comfort/eco temperature presets
## Related Documentation
- [Homegear MAX! Documentation](https://doc.homegear.eu/data/homegear-max/)
- [Abstract Protocol Specification](docs/PROTOCOL.md)
- [Transformation Layer Design](apps/abstraction/README.md)