Writing a component for Luxtronik Heatpumps

Hi Roemer,

Good to see that someone else is also interested in getting the Luxtronik / Alpha Innotec heatpump data into Home Assistant over the network. Since I’m generally more interested in the general performance of the heatpump, I’ve whipped up a component that only reads the status, indoor, outdoor and boiler temperature values. In December I’ve written my first component prototype and a week ago I’ve made some small changes to the status reading, which deals with the Luxtronic not responding to network requests (which it occasionally does when it is too busy doing other stuff), which I wanted to test before I published the component. Over the last days this version has been running smoothly and it really gives a good insight in the operation of the heatpump and the triggering values.

Here is the code that I’m using, currently in a custom component:

import logging

from collections import namedtuple
from datetime import timedelta
import voluptuous as vol
import socket
import struct
import datetime

from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
    CONF_NAME, CONF_HOST, CONF_PORT,
    STATE_UNKNOWN, STATE_ON )
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv

_LOGGER = logging.getLogger(__name__)

ATTR_TEMPERATURE_OUTDOOR = 'temperature_outdoor'
ATTR_TEMPERATURE_INDOOR = 'temperature_indoor'
ATTR_TEMPERATURE_BOILER = 'temperature_boiler'
ATTR_STATUS = 'status'

DEFAULT_NAME = 'Luxtronik'

SCAN_INTERVAL = timedelta(minutes=1)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Required(CONF_HOST): cv.string,
    vol.Required(CONF_PORT): cv.port,
    vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})


def setup_platform(hass, config, add_devices, discovery_info=None):
    """Set up the RESTful sensor."""
    name = config.get(CONF_NAME)
    host = config.get(CONF_HOST)
    port = config.get(CONF_PORT)

    add_devices([Luxtronik(hass, name, host, port)])

class Luxtronik(Entity):
    """Representation of a Sensor."""

    def __init__(self, hass, name, host, port):
        """Initialize the sensor."""
        self._hass = hass
        self._name = name
        self._host = host
        self._port = port

        self._state = STATE_UNKNOWN
        self.output = None
        self.status = namedtuple(
            'status', [ ATTR_TEMPERATURE_OUTDOOR, ATTR_TEMPERATURE_INDOOR,
                        ATTR_TEMPERATURE_BOILER, ATTR_STATUS ])

        self.update()

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def state(self):
        """Return the state of the sensor."""
        return self._state

    @property
    def state_attributes(self):
        """Return the state attributes of the monitored installation."""
        if self.output is not None:
            return {
                ATTR_TEMPERATURE_OUTDOOR: self.output.temperature_outdoor,
                ATTR_TEMPERATURE_INDOOR: self.output.temperature_indoor,
                ATTR_TEMPERATURE_BOILER: self.output.temperature_boiler,
                ATTR_STATUS: self.output.status,
            }

    def update(self):
        """Fetch new state data for the sensor.

        This is the only method that should fetch new data for Home Assistant.
        """

        try:
            s = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
            
            s.settimeout( 1 )
            s.connect( (self._host, self._port) )
            s.send( struct.pack( '!i', 3004 ) )
            s.send( struct.pack( '!i', 0 ) )

            if struct.unpack( '!i', s.recv(4) )[0] != 3004:

                # self.output = None
                _LOGGER.error(
                    "Unable to fetch data from Luxtronik, no 3004" )
                # self._state = STATE_UNKNOWN

            else:

                stat = struct.unpack( '!i', s.recv(4) )[0]
                len = struct.unpack( '!i', s.recv(4) )[0]
                array_calculated = []
                for i in range(len):array_calculated.append(struct.unpack( '!i', s.recv(4) )[0])

                s.close()

                if array_calculated[80] == 0:
                    status = 'heating'
                elif array_calculated[80] == 1:
                    status = 'hot water'
                elif array_calculated[80] == 2:
                    status = 'swiming pool / photovoltaic'
                elif array_calculated[80] == 3:
                    status = 'power supply'
                elif array_calculated[80] == 4:
                    status = 'defrost'
                elif array_calculated[80] == 5:
                    status = 'no request'
                elif array_calculated[80] == 6:
                    status = 'heating ext. power source'
                elif array_calculated[80] == 7:
                    status = 'cooling'
                else:
                    status = 'not known'

                self.output = self.status._make([
                    float(array_calculated[15])/10,
                    float(array_calculated[227])/10,
                    float(array_calculated[17])/10,
                    status,
                ])
                self._state = STATE_ON

        except:
            # self.output = None
            _LOGGER.error(
                "Unable to fetch data from Luxtronik" )
            # self._state = STATE_UNKNOWN

As you can see, it should be fairly easy to get other sensor values into HASS if you want to. For me the current set of values are more than enough.

The configuration is quite self explanatory:

sensor:
  - platform: luxtronik
    name: Luxtronik
    host: 192.168.1.X
    port: 8889
  - platform: template
    sensors:
      luxtronik_temperature_indoor:
        friendly_name: 'Indoor'
        value_template: '{{ states.sensor.luxtronik.attributes.temperature_indoor }}'
        unit_of_measurement: '°C'
        icon_template: 'mdi:home'
      luxtronik_temperature_outdoor:
        friendly_name: 'Outdoor'
        value_template: '{{ states.sensor.luxtronik.attributes.temperature_outdoor }}'
        unit_of_measurement: '°C'
        icon_template: 'mdi:tree'
      luxtronik_temperature_boiler:
        friendly_name: 'Boiler'
        value_template: '{{ states.sensor.luxtronik.attributes.temperature_boiler }}'
        unit_of_measurement: '°C'
        icon_template: 'mdi:water-pump'
      luxtronik_status:
        friendly_name: 'Status'
        value_template: '{{ states.sensor.luxtronik.attributes.status }}'
        icon_template: 'mdi:gauge'

I also use a group to bundle all the information neatly in the UI:

luxtronik:
  name: Luxtronik
  icon: 'mdi:desktop-tower'
  entities:
    - sensor.luxtronik_status
    - sensor.luxtronik_temperature_indoor
    - sensor.luxtronik_temperature_outdoor
    - sensor.luxtronik_temperature_boiler

When I have a bit of time I’m very much willing to turn this into a component which can be included into Home Assistant by default, but before that I’m happy to receive feedback into the values that people would want to read out.

Hope this helps to get you started.