Files
home-automation/MAX_INTEGRATION.md

6.0 KiB

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):

{
  "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):

{
  "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

- 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:

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

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

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