Viessmann Optolink Splitter

Hello everyone. I was wondering if there are any other users here of philippoo66’s great Optolink Splitter project?
For those who don’t know it yet: With the help of the Optolink Splitter, the Optolink signals of a Viessmann heating system can be read out relatively easily and sent to MQTT. At the same time, an existing Vitoconnect and thus the Viessmann app can also still be used via a UART USB adapter.

More information in the repo: philippoo66/optolink-splitter: Splitter for Viessmann Optolink connection (github.com)

I was wondering, for all users of the Optolink splitter, what your configurations look like?

In particular, I have not yet found a good way to create an MQTT Climate entity from the Viessmann information. My Viessmann heating works with a normal and reduced operation mode. Depending on the operating mode, there are different MQTT topics with different target temperatures. However, the Home Assistant Climate entity only ever expects one global topic for the temperature_state_topic. As a result, only the target temperature of the normal operating mode is ever displayed in my climate entity, regardless of which operating mode was actually selected.

I would also be interested to know how you set the time for the operating modes via Home Assistant. I could use automations for this, but I would like to make the whole thing as easy as possible for my for my father-in-law to control visually. Then we could finally get away from the Viessmann app altogether. :wink: Or do most of you still use the app to set the times for the operating modes?

To make a start. Here is my Optolink splitter configuration:

settings_ini.py:

'''
   Copyright 2024 philippoo66
   
   Licensed under the GNU GENERAL PUBLIC LICENSE, Version 3 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       https://www.gnu.org/licenses/gpl-3.0.html

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
'''

# serial ports +++++++++++++++++++
port_vitoconnect = '/dev/ttyAMA0'  # '/dev/ttyS0'  older Pi:'/dev/ttyAMA0'  {optional} set None if no Vitoconnect
port_optolink = '/dev/ttyUSB0'   # '/dev/ttyUSB0'  {mandatory}

vs2timeout = 600                 # seconds to detect VS2 protocol on vitoconnect connection

# MQTT +++++++++++++++++++
mqtt = "192.168.176.3:1883"      # e.g. "192.168.0.123:1883"; set None to disable MQTT
mqtt_user = "user:password"                 # "<user>:<pwd>"; set None for anonymous connect
mqtt_topic = "vitocrossal"          # "optolink"
mqtt_fstr = "{dpname}"           # "{dpaddr:04X}_{dpname}"
mqtt_listen = "vitocrossal/cmnd"    # "optolink/cmnd"; set None to disable listening
mqtt_respond = "vitocrossal/resp"   # "optolink/resp"


# TCP/IP +++++++++++++++++++
tcpip_port = None         # e.g. 65234 is used by Viessdataby default; set None to disable TCP/IP


# full raw timing
fullraw_eot_time = 0.05    # seconds. time no receive to decide end of telegram 
fullraw_timeout = 2        # seconds. timeout, return in any case 

# logging, info +++++++++++++++++++
log_vitoconnect = False    # logs communication with Vitoconnect (rx+tx telegrams)
show_opto_rx = False        # display on screen (no output when ran as service)

# format +++++++++++++++++++
max_decimals = 4
data_hex_format = '02x'    # set to '02X' for capitals
resp_addr_format = 'd'     # format of DP address in MQTT/TCPIP request response; e.g. 'd': decimal, '04X': hex 4 digits

# Viessdata utils +++++++++++++++++++
write_viessdata_csv = False
viessdata_csv_path = ""
buffer_to_write = 60
dec_separator = ","

# 1-wire sensors +++++++++++++++++++
w1sensors = {}
#     # addr : ('<w1_folder/sn>', '<slave_type>'),
#     0xFFF4 : ('28-3ce1d4438fd4', 'ds18b20'),   # highest known Optolink addr is 0xff17
#     0xFFFd : ('28-3ce1d443a4ed', 'ds18b20'),
# }


# polling datapoints +++++++++++++++++++
poll_interval = 10      # seconds. 0 for continuous, set -1 to disable Polling
poll_items = [
    # VScotHO1_72
    # (Name, DpAddr, Len, Scale/Type, Signed)
    ("aussentemperatur", 0x5525, 2, 1/10, True),
    ("kesseltemperatur", 0x0810, 2, 1/10, False),
    ("kesselsolltemperatur", 0x555A, 1, 1, False),
    ("brenner", 0x55D3, 1, 1, False),
    ("modulationsgrad", 0xA305, 1, 1, False),
    ("interne_pumpe", 0x7660, 1, 1, False),
    ("interne_pumpe_drehzahl", 0x7660, 2, 1, False),
    ("brennerstarts", 0x088A, 4, 1, False),
    ("brenner_betriebszeit", 0x08A7, 2, 1, False),
    ("hydraulische_weiche_temperatur", 0x080C, 2, 1/10, False),
    ("abgastemperatur", 0x0816, 2, 1/10, False),
    ("solar_kollektortemperatur", 0x6564, 2, 1/10, True),
    ("solar_speichertemperatur", 0x6566, 2, 1/10, False),
    ("solar_betriebszeit", 0x6568, 2, 1, False),
    ("solar_waermemenge", 0x6560, 4, 1/1000, False),
    ("solar_solarpumpe", 0x6552, 1, 1, False),
    ("solar_nachladeunterdrueckung", 0x6551, 1, 1, False),
    ("solar_ertrag_aktueller_tag", 0xCF30, 4, 1/1000, False),
    ("heizkreispumpe_m2", 0x3906, 1, 1, False),
    ("heizkreispumpe_m3", 0x4906, 1, 1, False),
    ("heizkreispumpe_drehzahl_m2", 0x7665, 2, 1, False),
    ("heizkreispumpe_drehzahl_m3", 0x7667, 2, 1, False),
    ("aktuelle_betriebsart_m2", 0x2500, 1, 1, False),
    ("aktuelle_betriebsart_m3", 0x3500, 1, 1, False),
    ("betriebsart_m2", 0x3323, 1, 1, False),
    ("betriebsart_m3", 0x4323, 1, 1, False),
    ("vorlauftemperatur_m2", 0x3900, 2, 1/10, False),
    ("vorlauftemperatur_m3", 0x4900, 2, 1/10, False),
    ("raumtemperatur_soll_normalbetrieb_m2", 0x3306, 1, 1, False),
    ("raumtemperatur_soll_normalbetrieb_m3", 0x4306, 1, 1, False),
    ("raumtemperatur_soll_eco_m2", 0x3307, 1, 1, False),
    ("raumtemperatur_soll_eco_m3", 0x4307, 1, 1, False),
    ("raumtemperatur_soll_party_m2", 0x3308, 1, 1, False),
    ("raumtemperatur_soll_party_m3", 0x4308, 1, 1, False),
    ("raumtemperatur_m2", 0x0898, 2, 1/10, False),
    ("raumtemperatur_m3", 0x089A, 2, 1/10, False),
    ("warmwasserbereitung", 0x650A, 1, 1, False),
    ("warmwasser_temperatur_speicher", 0x0812, 2, 1/10, False),
    ("warmwasser_auslauftemperatur", 0x0814, 2, 1/10, False),
    ("warmwasser_solltemperatur", 0x6300, 1, 1, False),
    ("warmwasser_speicherladepumpe", 0x6513, 1, 1, False),
    ("warmwasser_zirkulationspumpe", 0x6515, 1, 1, False),
]

vitocrossal.json (For the automatic discovery of MQTT entities by Homeassitant)

{
    "base_topic": "vitocrossal/",
    "dp_prefix": "vitocrossal_",
    "device": {
        "identifiers": [
            "VScottHO1_72"
        ],
        "name": "Vitocrossal",
        "model": "Vitocrossal 300C",
        "manufacturer": "Viessmann"
    },
    "device_short": {
        "identifiers": [
            "VScottHO1_72"
        ]
    },
    "datapoints": [
        {
            "domain": "climate",
            "name": "M2 Thermostat",
            "optimistic": true,
            "mode_command_topic": "cmnd",
            "mode_state_topic": "betriebsart_m2",
            "modes": [
                "auto",
                "off",
                "heat"
            ],
            "mode_state_template": "{% if value==\"0\" %} off {% elif value==\"1\" %} off {% elif value==\"2\" %} auto {% elif value==\"3\" %} heat {% elif value==\"4\" %} heat {% endif %}",
            "mode_command_template": "{% if value==\"off\" %} w;0x3323;1;0 {% elif value==\"auto\" %} w;0x3323;1;2 {% elif value==\"heat\" %} w;0x3323;1;4 {% endif %}",
            "preset_mode_command_topic": "cmnd",
            "preset_mode_state_topic": "betriebsart_m2",
            "preset_modes": [
                "Nur Warmwasser",
                "eco",
                "comfort"
            ],
            "preset_mode_value_template": "{% if value==\"1\" %} Nur Warmwasser {% elif value==\"3\" %} eco {% elif value==\"4\" %} comfort {% else %} None {% endif %}",
            "preset_mode_command_template": "{% if value==\"Nur Warmwasser\" %} w;0x3323;1;1 {% elif value==\"eco\" %} w;0x3323;1;3 {% elif value==\"comfort\" %} w;0x3323;1;4 {% else %} w;0x3323;1;2 {% endif %}",
            "current_temperature_topic": "vorlauftemperatur_m2",
            "temperature_state_topic": "raumtemperatur_soll_normalbetrieb_m2",
            "temperature_command_topic": "raumtemperatur_soll_normalbetrieb_m2",
            "precision": 0.1,
            "temperature_unit": "C",
            "min_temp": 3,
            "max_temp": 37,
            "temp_step": 1
        },
        {
            "domain": "climate",
            "name": "M3 Thermostat",
            "optimistic": true,
            "mode_command_topic": "cmnd",
            "mode_state_topic": "betriebsart_m3",
            "modes": [
                "auto",
                "off",
                "heat"
            ],
            "mode_state_template": "{% if value==\"0\" %} off {% elif value==\"1\" %} off {% elif value==\"2\" %} auto {% elif value==\"3\" %} heat {% elif value==\"4\" %} heat {% endif %}",
            "mode_command_template": "{% if value==\"off\" %} w;0x4323;1;0 {% elif value==\"auto\" %} w;0x4323;1;2 {% elif value==\"heat\" %} w;0x4323;1;4 {% endif %}",
            "preset_mode_command_topic": "cmnd",
            "preset_mode_state_topic": "betriebsart_m3",
            "preset_modes": [
                "Nur Warmwasser",
                "eco",
                "comfort"
            ],
            "preset_mode_value_template": "{% if value==\"1\" %} Nur Warmwasser {% elif value==\"3\" %} eco {% elif value==\"4\" %} comfort {% else %} None {% endif %}",
            "preset_mode_command_template": "{% if value==\"Nur Warmwasser\" %} w;0x4323;1;1 {% elif value==\"eco\" %} w;0x4323;1;3 {% elif value==\"comfort\" %} w;0x4323;1;4 {% else %} w;0x4323;1;2 {% endif %}",
            "current_temperature_topic": "vorlauftemperatur_m3",
            "temperature_state_topic": "raumtemperatur_soll_normalbetrieb_m3",
            "temperature_command_topic": "raumtemperatur_soll_normalbetrieb_m3",
            "precision": 0.1,
            "temperature_unit": "C",
            "min_temp": 3,
            "max_temp": 37,
            "temp_step": 1
        },
        {
            "domain": "water_heater",
            "name": "Warmwasser",
            "mode_state_topic": "warmwasserbereitung",
            "optimistic": true,
            "modes": [
                "on"
            ],
            "mode_state_template": "{% if value==\"0\" %} on {% else %} off {% endif %}",
            "current_temperature_topic": "warmwasser_temperatur_speicher",
            "temperature_state_topic": "warmwasser_solltemperatur",
            "temperature_command_topic": "warmwasser_solltemperatur",
            "precision": 0.1,
            "temperature_unit": "C",
            "min_temp": 10,
            "max_temp": 60,
            "temp_step": 1
        },
        {
            "domain": "sensor",
            "name": "aussentemperatur",
            "unit_of_measurement": "°C",
            "icon": "mdi:thermometer"
        },
        {
            "domain": "sensor",
            "name": "kesseltemperatur",
            "unit_of_measurement": "°C",
            "icon": "mdi:thermometer"
        },
        {
            "domain": "binary_sensor",
            "name": "brenner",
            "icon": "mdi:gas-burner",
            "entity_category": "diagnostic",
            "payload_on": "1",
            "payload_off": "0"
        },
        {
            "domain": "sensor",
            "name": "modulationsgrad",
            "unit_of_measurement": "%",
            "icon": "mdi:auto-mode"
        },
        {
            "domain": "binary_sensor",
            "name": "interne_pumpe",
            "icon": "mdi:pump",
            "entity_category": "diagnostic",
            "payload_on": "1",
            "payload_off": "0"
        },
        {
            "domain": "sensor",
            "name": "brennerstarts",
            "icon": "mdi:timer-play-outline"
        },
        {
            "domain": "sensor",
            "name": "brenner_betriebszeit",
            "unit_of_measurement": "h",
            "icon": "mdi:clock-start"
        },
        {
            "domain": "sensor",
            "name": "hydraulische_weiche_temperatur",
            "unit_of_measurement": "°C",
            "icon": "mdi:thermometer"
        },
        {
            "domain": "sensor",
            "name": "abgastemperatur",
            "unit_of_measurement": "°C",
            "icon": "mdi:thermometer"
        },
        {
            "domain": "sensor",
            "name": "solar_kollektortemperatur",
            "unit_of_measurement": "°C",
            "icon": "mdi:thermometer"
        },
        {
            "domain": "sensor",
            "name": "solar_speichertemperatur",
            "unit_of_measurement": "°C",
            "icon": "mdi:thermometer"
        },
        {
            "domain": "sensor",
            "name": "solar_betriebszeit",
            "unit_of_measurement": "h",
            "icon": "mdi:clock-start"
        },
        {
            "domain": "sensor",
            "name": "solar_waermemenge",
            "unit_of_measurement": "MWh",
            "icon": "mdi:heat-wave"
        },
        {
            "domain": "binary_sensor",
            "name": "solar_solarpumpe",
            "icon": "mdi:pump",
            "entity_category": "diagnostic",
            "payload_on": "1",
            "payload_off": "0"
        },
        {
            "domain": "binary_sensor",
            "name": "solar_nachladeunterdrueckung",
            "icon": "mdi:upload-off",
            "entity_category": "diagnostic",
            "payload_on": "1",
            "payload_off": "0"
        },
        {
            "domain": "sensor",
            "name": "solar_ertrag_aktueller_tag",
            "unit_of_measurement": "kWh",
            "icon": "mdi:solar-power"
        },
        {
            "domain": "sensor",
            "name": "vorlauftemperatur_m2",
            "unit_of_measurement": "°C",
            "icon": "mdi:thermometer"
        },
        {
            "domain": "sensor",
            "name": "vorlauftemperatur_m3",
            "unit_of_measurement": "°C",
            "icon": "mdi:thermometer"
        },
        {
            "domain": "number",
            "name": "raumtemperatur_soll_normalbetrieb_m2",
            "unit_of_measurement": "°C",
            "icon": "mdi:thermometer",
            "mode": "box",
            "min": 3,
            "max": 37,
            "step": 1,
            "state_topic": "raumtemperatur_soll_normalbetrieb_m2",
            "command_topic": "raumtemperatur_soll_normalbetrieb_m2"
        },
        {
            "domain": "number",
            "name": "raumtemperatur_soll_normalbetrieb_m3",
            "unit_of_measurement": "°C",
            "icon": "mdi:thermometer",
            "mode": "box",
            "min": 3,
            "max": 37,
            "step": 1,
            "state_topic": "raumtemperatur_soll_normalbetrieb_m3",
            "command_topic": "raumtemperatur_soll_normalbetrieb_m3"
        },
        {
            "domain": "number",
            "name": "raumtemperatur_soll_eco_m2",
            "unit_of_measurement": "°C",
            "icon": "mdi:thermometer",
            "mode": "box",
            "min": 3,
            "max": 37,
            "step": 1,
            "state_topic": "raumtemperatur_soll_eco_m2",
            "command_topic": "raumtemperatur_soll_eco_m2"
        },
        {
            "domain": "number",
            "name": "raumtemperatur_soll_eco_m3",
            "unit_of_measurement": "°C",
            "icon": "mdi:thermometer",
            "mode": "box",
            "min": 3,
            "max": 37,
            "step": 1,
            "state_topic": "raumtemperatur_soll_eco_m3",
            "command_topic": "raumtemperatur_soll_eco_m3"
        },
        {
            "domain": "binary_sensor",
            "name": "warmwasser_speicherladepumpe",
            "icon": "mdi:pump",
            "entity_category": "diagnostic",
            "payload_on": "1",
            "payload_off": "0"
        },
        {
            "domain": "binary_sensor",
            "name": "warmwasser_zirkulationspumpe",
            "icon": "mdi:pump",
            "entity_category": "diagnostic",
            "payload_on": "1",
            "payload_off": "0"
        },
        {
            "domain": "binary_sensor",
            "name": "heizkreispumpe_m2",
            "icon": "mdi:pump",
            "entity_category": "diagnostic",
            "payload_on": "1",
            "payload_off": "0"
        },
        {
            "domain": "binary_sensor",
            "name": "heizkreispumpe_m3",
            "icon": "mdi:pump",
            "entity_category": "diagnostic",
            "payload_on": "1",
            "payload_off": "0"
        }
    ]
}

Final result:

I’m using the Optolink-Splitter solution since a few months. It’s running very stable. I only have to sometimes restart the service on the RPI after a power down situation. Presumably because the Vitoconnect is recovering earlier than the service on the RPI.
The definition of my installation can be found in my tutorial about integrating with HA in the Wiki of Otpolink Splitter: 210 Home Assitant Integration of Optolink‐Splitter · philippoo66/optolink-splitter Wiki · GitHub.

Rendering the Viessmann Climate entity is not very straightforward. The Viessmann way of temporarily altering the target temperature is by setting Party or Reduced mode. The set temperature of each mode is defined seperately. Also The different modes (auto, off, cooling, party, reduced are mapped in the bits of one byte, which makes it difficult to transition from one mode to another. I did choose to not implement the target temperature in my interface from HA to the Optolink, but define separate controls for the set temperature for Party and Reduced mode. A change in target temperature from Optolink to HA is implemented though. So the target temperature is shown in HA but cannot be changed directly in HA, only by selecting Party or Reduced mode: Comfort and Eco mode in HA terminology.

1 Like

I’m still using the app to set the times for the modes. I’ve been looking in implementing it, but the way the times are encoded is somewaht peculiar. It’s encoded in a single byte with the first 5 bits defining the hour and the remaining three bits defining the 10 minute interval in the hour. A leftover from when memory was scarce I presume.
As reprogramming the times is hardly ever necessary I did not bother to implement it in the interface. It can be done thanks to the basic flexibility of the Optolink-Switch, but in terms of value for effort…

So your thermostats in Home Assistant always display the right temperature for the selected operating mode? That’s great!
I’ll have to see if I can find something like hk1_requested_temperature for my Vitocrossal. Unfortunately, I haven’t been able to find anything like that yet, but as I said, only the setpoints for the individual operating modes.

Hallo Simon,
I’ll try to upload the full list of codes for the Vitocrossal to the Optolink-Switch wiki.
I found these parameters that could be the actual required temperature:

HK_RaumsolltemperaturaktuellM2~0x3500 0x3500 Div10 R 2 ecnUnit.Grad C
VT_SolltemperaturM2~0x3544 0x3544 Div10 R 2 ecnUnit.Grad C
1 Like

Awesome! Thank you very much!
Sadly the 0x3544 only returns 0.0 as value. :-/

And 0x3500?

The address only returns a single-digit number without decimal places. According to philippoo66’s address collection, 0x3500 is the address for the current operating mode at heating circuit M3. But in my case, the value never changes and constantly returns 5 for my heating circuit M3 and constantly returns 2 for heating circuit M2 (address 0x2500).

Maybe I can also build an mqtt sensor that returns the corresponding setpoint depending on the currently selected operating mode :thinking:

According to the openv wiki address 0x2500/0x3500 for VScotHO1 has a length of 22 bytes, so presumably a field with multiple values. It may be helpfull to read 22 bytes raw at address 0x2500/0x3500 and see if you recognise some values. the table from the database gives 3 datapoints for the same address.