Writing a component for Luxtronik Heatpumps

Hello all

I own a heatpump supporting network (Luxtronik controller) and I’d like to add a component to HASS which can provide information from the heatpump and even do some settings.

Now I’m totally new to HASS development (and even python) so I’m struggling at quite some places.

Here’s what I want to achieve:

  • Having a set of sensors that show various information (like outside temperature, water temperature, time to next defrosting)
  • Having some input (selects and numbers) to set some values in the heatpump

I want to start with the first one, so only showing sensor values and postpone the setting of values to later.

I already have python code ready to read/set the values by reading using a socket or a websocket. I will encapsulate this in it’s own component when the rest is working.

But now I am struggling already at the very beginning: Do I write a Component or a Platform? I don’t really get the difference between them. I suspect I should write a Platform which has multiple sensors (one for each value). Is this correct?

Also I would like to refresh the value with a certain intervall. Do I use something like async_track_time_interval with a method which calls async_device_update and async_schedule_update_ha_state on all my devices?

I would really appreciate your help and guidance as I have plenty of ideas for more components/plattforms for HASS I’d like to contribute :slight_smile:

Here’s some code I already hacked together too just get some random values in HASS but which is using a component and not a platform.

""" Support for Luxtronik Heatpump Control (like in Alpha Innotec or Novelan heatpumps) """

import asyncio
import logging
from datetime import timedelta

import xml.etree.ElementTree as ET
import websockets

from homeassistant.const import (STATE_OK, STATE_UNKNOWN)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_time_interval

REQUIREMENTS = ['websockets==4.0.1']

DOMAIN = 'luxtronik'
ENTITY_ID = 'lux.lux'

_LOGGER = logging.getLogger(__name__)

@asyncio.coroutine
def async_setup(hass, config):
    _LOGGER.warn("Setting up Luxtronik")
    lux = Luxtronik(hass)

    def hub_refresh(event_time):
        lux.async_device_update()
        lux.async_schedule_update_ha_state()

    interval = timedelta(seconds=300)
    async_track_time_interval(hass, hub_refresh, interval)
    return True

class Luxtronik(Entity):
    
    entity_id = ENTITY_ID

    def __init__(self, hass):        
        self.hass = hass
        self._state = STATE_UNKNOWN
    
    @property
    def name(self):
        return "Luxtronik"

    @property
    def state(self):
        return self._state

    @property
    def state_attributes(self):
        from random import randint
        return {
            'temp1': randint(0, 9),
            'temp2': randint(0, 9)
        }

    @asyncio.coroutine
    def async_update(self):
        self._state = STATE_OK
1 Like

Anyone? Just a few hints?

Really nobody cares to give just a few hints on writing additional home assistant components? :frowning:

I really can’t help you with writing a component/platform as I am about to embark on my own learning experience.
Have you checked out the TCP sensor by chance? It might give you some hints as how to proceed. Also have you have looked out the developers section for additional info?

Good Luck,
Mark

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.

2 Likes

Is there a place where one would find documentation about the protocol?

I got mine here: https://www.loxwiki.eu/plugins/servlet/mobile?contentId=18219334#content/view/18219334

1 Like

Hi,

disclaimer: I’m the author of the loxwiki page @hhermsen has mentioned.

I’m trying to switch from my current home automation solution to HA at the moment and so need to have a component for my Alpha Innotec heat pump.
I looked at your efforts and @hhermsen 's solution works for me (to my amazement, I believed that the disabled the java interface with theupdate) but as my heatpump is software version > V3.81 it also supports the use of websockets.
Using ws is a lot nicer because the response is a nice XML with field names in it!
I have a first version of up and running but I don’t understand at the moment how a entity like luxtronik can have multiple values like for example luxtronik.boiler_temperature etc.
To me it looks not “right” to get that data using templates, but I wasn’t able to find a existing sensor that produces more that one value!?
The only thing I found that maybe is what I’m looking for is the octoprint component. Its split up in an API component that fetches the actual data and a sensor part that is used to set up multiple entities for each value one wants to use in HA.
Can anybody give me a hint if thats the right way of having a component generating more than one sensor?

Anyway, thank you @Roemer and @hhermsen for you efforts so far!

1 Like

Hey Bouni
Great that you’re also working on this. Did you get further now with it? I haven’t had time to continue yet with it as I was busy with other tasks in the home (3d-Printing pieces for the switches, integrating google home, moving the installation to NAS / Docker, …).
Would be great if we’re able to create a fully working component for Alpha innotec heat pumps.

Hey sorry for the long silence, I had more important task as well.
But now I will try to work on this again.

What do you think @Roemer, what approach would be the best for this?
Having just one component that can be configured to use either the websockets or the binary protocol?
Or better an automatic approch that tries to use the websockets protocol an do a fall back in case its not available!?

Let me know what you think about it!

Nice to hear from you again.
Maybe we should first check if one or another interface provides more information than the other. If possible, I think I would prefer an automatic detection.

If you’re planning to put it on Github or similar, feel free to tell me so I could join and help as well.

Same for me!

Unfortunately I can only verfy using my heatpump. Do you own a heatpump that has the websockets protocol?

Can you guys do an nmap scan against you heatpump?

nmap -p T:8888,8889,8214 192.168.1.10

Replace the IP with the IP of your heatpump.
The output should look like this:

Nmap scan report for 192.168.1.10
Host is up (0.00046s latency).

PORT     STATE SERVICE
8214/tcp open  unknown
8888/tcp open  sun-answerbook
8889/tcp open  ddi-tcp-2

Nmap done: 1 IP address (1 host up) scanned in 0.08 seconds

In my case both protocols are available.

Yes, mine works with websockets.
Here’s my output from nmap:

PORT     STATE SERVICE
8214/tcp open  unknown
8888/tcp open  sun-answerbook
8889/tcp open  ddi-tcp-2

I really don’t know if its worth it to implemnent both protocols…
As far as I know, Alpha Innotec gives the updates for free, so everybody could update to a version that supports websockets.
What do you guys think about this?
In my opinion websockets are much easier to use and human readable!

If it provides all information, yes i think we can stick with websockets (at least for the beginning). At a first glance I had the impression that the binary protocol has more values and allows setting more values than the websocket interface. But that was just by very quickly browsing the data :slight_smile:

Also should we maybe oben a gitter/discord chat for discussions instead of the forum here?

1 Like

My heatpump currently doesn’t support websockets. I’m not sure if we can upgrade by ourselves.

@Roemer I think that for 99% of the users the information contained in the websocket protocol is more than sufficant.
Do you think a gitter/dicord chat is better, I really like forums becuase other people can find the conversations later on where chats are long gone…

@hhermsen What model of heatpump do you have?