MAX transformation added
This commit is contained in:
223
MAX_INTEGRATION.md
Normal file
223
MAX_INTEGRATION.md
Normal 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)
|
||||
Reference in New Issue
Block a user