SPRSUN Heatpump R290 Integration Home Assistant knowledge share

Hi,
I’m looking for experience exchange to get an integration to SPRSUN based heatpumps.

End of 2024 we imported some SPRSUN R290 heat pumps direcly from China (9KW, 12kW, 15kW and 18kW models) and I was looking for some integration into Home Assistant. I hardly can’t find anything.

As I was less experienced in HA and got a brief, but quite uncomplete official modbus description I started with a python modul. The heat pumps are coming with a poorly working EW11 RS485 Modbus adapter for an App control plus an Hardware controller OEM from Carel obviously also using an RS485 connection. With all 3 you can in principal operate the heat pumps, but none of them are really useable.
I tried all 3: the app on Android and iphone, a carel control and the modbus registers, but all 3 way are far away from being really useable, complete and consistent. However, with some work I was able to extend them.

I tried first the App as it seems an official connector, but I soon realized that it is a) very poor b) very instable and worst c) totally lacks any kind of security.

However, SPRSUN delivers an EW11 WLAN RS485 Adapter for it, but it seems to have a special custom version of firmware, which allows the EW11 partially be initialized by the App - if you are able to connect. From 5 heat pumps we bought only one EW11 got a connection without any issue, the rest either does not work at all (not able to connect, even not with the normal EW11 methods or with the SPRSUN app), the rest worked with a lot of tricks.

So we finally acquired some stock EW11 sticks and EE11 (it’s Ethernet based sibling) to get some connection done.
The trick is to use the E*11 is to correct serial settings and also to switch the sticks to Modbus mode, so that you can use Modbus TCP commands to get to the registers with TCP.

Due to lack of on hand development experience with Home Assistant, especially with the Modbus integration I started with a Python script which connects to the Heat pump throught Modbus TCP, reads some blocks of registers at adresses (you can read max 100 registers in a chunk) I was interested and pushed the data in a single MQTT packets and wrote an MQTT package to populate the entities for my SPRSUN dashboard.
To be able to also change parameters such as Fan Mode, Heating Setp etc I had also to start with Modbus register writing from HA, so I’m now able to monitor and operate the Heatpump.

Learning more abput the modbus integration while working with the system I also attempt a restart by reading chunks of 100 registers at a time via modbus and learned that besides the 80-90 official registers there a over 500 registers in place. Comparing sequences of registers in Carel Controler Menu with requences odf register content I have a much wider understanding of the registers not.

However the most of the problems comes from the lack of documentation of the parameters (such as those of the Carel control menus) and their meaning for the heat pump.

In addition there are some tricky parameters such as the Control Mask 1 and Control Mask 2 registers, which semms to be RW register, but writing to them semms not to work. The bits in the register do have special meanings, but even if you write into register without error you won’t get the register values again if you read from them. All of this is quite time consuming and quite messy work, which might be easier if more people coul contribute and shar their knowledge.

I would be very interested in exchanging this knowledge and leaning how to make better use of the devices.

Hi,

I also recently got a (rebranded) sprsun heat pump. Found that the app is not very user friendly and tried to make my own homeassistant app for it, although I’ve since went back to the original app due to not having remote access without it
(because for some reason HA and the SPRSUN app can’t run at the same time, communication mode for the app has to be “None” for it to work, while HA requires “Modbus”, and I don’t want to expose homeassistant to the internet/port forward)

Found some information on this polish forum using Google translate Pompa ciepła SprSun Select CGK40V3L - Klimatyzacja i ogrzewanie - ArturHome

EDIT: Found the same thing in English

The original poster provided a word document with modbus registers for everything, and I used chatgpt to help write me a configuration.yaml with that .doc file. And it was very successful, although I often get N/A results in the queries, could be a limitation of the Carel controller or the EW-11, I just filter out N/A with the last “good” value. Since homeassistant can directly use modbus, you just enter the IP address and port of the EW-11 (and set the protocol in the settings from “none” to “modbus”).

If you want I can send you my configuration.yaml but it’s incomplete as I paused the project, I’m trying to figure out how to have both the SPRSUN app and HA running together, but that’s not very clean. Another issue i personally have is that the technicians use the app to check my heat pump stats remotely in case anything goes wrong, mine had some issues with the compressor (AL038 and AL039 errors), and they replaced the whole unit.

I was also thinking about making my own app from scratch for it, but lacking motivation for it as the SPRSUN app is terrible, but “works”

Another EDIT: with the official SPRSUN app, I noticed that different devices, iPhones and different brands of androids get different versions of the SPRSUN app. I found that the iPhone app is the most responsive, and my newer Android phone had more features (refresh button on the “Query data” screen), while an older Android had a different app, even though both were downloaded from the Play store.
I also had issues binding the EW-11 to the app. I had to connect to it directly (it creates a WiFi access point “Heat pump”, password is heatpump123.
Also I noticed that “removing” an EW-11 from your account in the app doesn’t remove it from sprsun servers, it continues to send data to them, since it remains connected to the local WiFi network, and you can add it on another phone using just the MAC address of the EW-11

1 Like

Great answer, with a bunch of information. I will try to reply in chunks, otherwise I guess it is too much.
What is the brand and in which region is it sold? Is there a commercial support service involved?

Sorry, I’m not familiar with this system, not sure if my post is useful.

We reached out to SPRSUN in China and bought them directly from there and got the shipped via train within 2 month, without bad surprises.

Unfortunately we needed some time for the different projects and so the first installations were ready only when the heating period had ended (end of March, begin of April).

Actually we used the original EW11 and an own EW11 resp EE11 in parallel, both attached to the cable for the original EW11. The original EW11 was kept parametrized as the App setup and the other used in its serial setup “Protocol Setup” set to Modbus instead of none.
With that setting you can run the HA and the App in parallel, even not exactly in same time.
The EW11 acts as Modbus master when used, but keep quite if they are not working.
To have the monitoring read done in the shortest time and don’t block the line for a longer period I decided to start with an external Python program which connects, reads the data via some Modbus TCP calls in blocks (reading multiple register blocks at the same time) and ship the results via MQTT to HA, where I wrote an integration package to create the corresponding entities.
That allows in parallel the use of the app, as the line is not always blocked.
But I realized that there seems to be a “always phone home” and a security of “MAC address only”.
So the heatpump if connected with the App seems to ship that data constantly to a cloud service in China, which does not sounds critical at first glance. But obviously the heat pump / EW11 setup seems to actively open the connection and also seems to be able to receive requests from China as well.
That this can - and most probaly will - be critical I realized in spring 2024. Suddenly my Sungrow battery, part of my PV home system, stopped working and was not startable anymore. Reason for that was an failed firmware update attempt from Sungrow, which they said was needed because of a major design change of their monitoring. But that new firmware was not well tested and a large numbers of Sungrow SBR Battery systems in Germany were affected.
At that point in time they had only a minor support team, totally overwhelmed with a flood a new support requests. The BMS part had to be exchanged, which needs a severe number of parallel exchange requests and it took month to get all the systems working again.
That however was an unwanted incident, not a hostile attack, but it shows that - even on a “friendly purpose” cloud connection Chinese verndors are capable to perform changes over the same connection and can generally also deactivate all these devices from remote.
The only protection is to isolate the devives and don’t let them call home.
Next I realized, that the transfer of heatpumps from one account to the next is based on an QR code, which the app generates for this purpose. That QR Code is just the MAC address of the EW11 stick. As these are vendor specific you can easily guess other EW11 Mac addresses and that would enable you with you account to work on other people machines.

So I decided to run the App just for tests and reverse engineering and do the rest with Modbus and Carel controller as they are purely running local.

I also found the forum at arturhome.pl with /t/pompa-ciepla-sprsun-select-cgk40v3l/12596/6 and that referenced a repo github.com repo PiterPiotr/Sprsun-PC from Piter / Pjotr (I guess he’s also active here). Meanwhile that us not existing anymore, but there are clones like GitHub - paddyponchero/Sprsun-PC: Integration of the Sprsun CGKxxxV3L-B heat pump with HA and I relized that he had more register in use then the official Modbus register description.

So I leared a way how to use the monitoring with Modbus in HA directly, but I didn’t really like the way using single requests per Entity, as we have over 500 registers to watch and that will lead to a constant reading on the wire, blocking the connection and also probably lead to trouble.

So I tried to use parallel reads and with 6 chunks with 100 register each I was able to read them with HA methods via modbus in bulk as well.
Here is my package I started a an early prototype, which reads in 6 calls the register from 0 to 599 und some of them are set as own entities.
Unfortunately the modbus implementation was recently changed and I have to work out new working version as that
But the the code I have.
I’m running out of time, rest will follow later today.
Regards from a winterly Cologne
Detlef

# SPRSUN Wärmepumpe - Modbus Integration (2025.11-kompatibel)
# ACHTUNG: Alte Entities (wp_h_0 bis wp_h_5) existieren nicht mehr!
# Neue Entity-IDs: sensor.wp_register_block_0 bis sensor.wp_register_block_5

modbus:
  - name: "WP"
    type: tcp
    host: !secret wp_modbus_host_ip
    port: !secret wp_modbus_port
    delay: 5
    timeout: 10
    sensors:
      - name: "WP Register Block 0"
        slave: !secret wp_modbus_slave
        address: 0
        count: 100
        data_type: custom
        structure: "100H"
        scan_interval: 29
        unique_id: "wp_modbus_block_0"

      - name: "WP Register Block 1"
        slave: !secret wp_modbus_slave
        address: 100
        count: 100
        data_type: custom
        structure: "100H"
        scan_interval: 31
        unique_id: "wp_modbus_block_1"

      - name: "WP Register Block 2"
        slave: !secret wp_modbus_slave
        address: 200
        count: 100
        data_type: custom
        structure: "100H"
        scan_interval: 31
        unique_id: "wp_modbus_block_2"

      - name: "WP Register Block 3"
        slave: !secret wp_modbus_slave
        address: 300
        count: 100
        data_type: custom
        structure: "100H"
        scan_interval: 41
        unique_id: "wp_modbus_block_3"

      - name: "WP Register Block 4"
        slave: !secret wp_modbus_slave
        address: 400
        count: 100
        data_type: custom
        structure: "100H"
        scan_interval: 29
        unique_id: "wp_modbus_block_4"

      - name: "WP Register Block 5"
        slave: !secret wp_modbus_slave
        address: 500
        count: 100
        data_type: custom
        structure: "100H"
        scan_interval: 37
        unique_id: "wp_modbus_block_5"

template:
  # Binary Sensoren aus Register 4 in Block 0 (Bit-Operationen)
  - binary_sensor:
      - name: "WP OS1 Compressor"
        unique_id: wp_os1_compressor
        state: >
          {% set values = state_attr('sensor.wp_register_block_0', 'value') | default([]) %}
          {{ (values[4] | default(0) | int) | bitwise_and(1) > 0 }}
        device_class: running

      - name: "WP OS1 Fan"
        unique_id: wp_os1_fan
        state: >
          {% set values = state_attr('sensor.wp_register_block_0', 'value') | default([]) %}
          {{ (values[4] | default(0) | int) | bitwise_and(32) > 0 }}
        device_class: running

      - name: "WP OS1 4-way valve"
        unique_id: wp_os1_4_way_valve
        state: >
          {% set values = state_attr('sensor.wp_register_block_0', 'value') | default([]) %}
          {{ (values[4] | default(0) | int) | bitwise_and(64) > 0 }}
        device_class: opening

      - name: "WP OS1 High or low fan speed"
        unique_id: wp_os1_high_or_low_fan_speed
        state: >
          {% set values = state_attr('sensor.wp_register_block_0', 'value') | default([]) %}
          {{ (values[4] | default(0) | int) | bitwise_and(128) > 0 }}
        device_class: running

  # Sensoren aus verschiedenen Registern
  - sensor:
      # Register 1 in Block 0
      - name: "WP COP"
        unique_id: wp_cop
        state: >
          {% set values = state_attr('sensor.wp_register_block_0', 'value') | default([]) %}
          {{ (values[1] | default(0) | float) * 0.1 }}
        unit_of_measurement: ""
        state_class: measurement

      # Register 23 in Block 0
      - name: "WP AC Voltage Phase A"
        unique_id: wp_ac_voltage_phase_a
        state: >
          {% set values = state_attr('sensor.wp_register_block_0', 'value') | default([]) %}
          {{ values[23] | default(0) | float }}
        unit_of_measurement: "V"
        device_class: voltage
        state_class: measurement

      # Register 48 in Block 5 (Adresse 548)
      - name: "WP AC Voltage Phase B"
        unique_id: wp_ac_voltage_phase_b
        state: >
          {% set values = state_attr('sensor.wp_register_block_5', 'value') | default([]) %}
          {{ values[48] | default(0) | float }}
        unit_of_measurement: "V"
        device_class: voltage
        state_class: measurement

      # Register 50 in Block 5 (Adresse 550)
      - name: "WP AC Voltage Phase C"
        unique_id: wp_ac_voltage_phase_c
        state: >
          {% set values = state_attr('sensor.wp_register_block_5', 'value') | default([]) %}
          {{ values[50] | default(0) | float }}
        unit_of_measurement: "V"
        device_class: voltage
        state_class: measurement

      # Register 15 in Block 0
      - name: "WP Outlet Temperature"
        unique_id: wp_outlet_temperature
        state: >
          {% set values = state_attr('sensor.wp_register_block_0', 'value') | default([]) %}
          {{ (values[15] | default(0) | float) * 0.1 }}
        device_class: temperature
        unit_of_measurement: "°C"
        state_class: measurement

      # Register 14 in Block 0
      - name: "WP Inlet Temperature"
        unique_id: wp_inlet_temperature
        state: >
          {% set values = state_attr('sensor.wp_register_block_0', 'value') | default([]) %}
          {{ (values[14] | default(0) | float) * 0.1 }}
        device_class: temperature
        unit_of_measurement: "°C"
        state_class: measurement

      # Register 17 in Block 0
      - name: "WP Ambient Temperature"
        unique_id: wp_ambient_temperature
        state: >
          {% set values = state_attr('sensor.wp_register_block_0', 'value') | default([]) %}
          {{ (values[17] | default(0) | float) * 0.5 }}
        device_class: temperature
        unit_of_measurement: "°C"
        state_class: measurement

      #### DC PUMP (Fnn Values) - Block 3, Register 90-96
      - name: "F13 DC Pumpmodus 0 Manuell/1 Auto"
        unique_id: wp_f13_dc_pumpmodus
        state: >
          {% set values = state_attr('sensor.wp_register_block_3', 'value') | default([]) %}
          {{ values[90] | default(0) | int }}
        unit_of_measurement: ""
        state_class: measurement

      - name: "F14 DC Pumpenzyklus"
        unique_id: wp_f14_dc_pumpenzyklus
        state: >
          {% set values = state_attr('sensor.wp_register_block_3', 'value') | default([]) %}
          {{ values[91] | default(0) | int }}
        unit_of_measurement: "sec"
        state_class: measurement

      - name: "F15 DC PUMP FREQ SET"
        unique_id: wp_f15_dc_pump_freq_set
        state: >
          {% set values = state_attr('sensor.wp_register_block_3', 'value') | default([]) %}
          {{ values[92] | default(0) | int }}
        unit_of_measurement: "%"
        state_class: measurement

      - name: "F16 DC PUMP MAX FREQ"
        unique_id: wp_f16_dc_pump_max_freq
        state: >
          {% set values = state_attr('sensor.wp_register_block_3', 'value') | default([]) %}
          {{ values[93] | default(0) | int }}
        unit_of_measurement: "%"
        state_class: measurement

      - name: "F17 DC PUMP MIN FREQ"
        unique_id: wp_f17_dc_pump_min_freq
        state: >
          {% set values = state_attr('sensor.wp_register_block_3', 'value') | default([]) %}
          {{ values[94] | default(0) | int }}
        unit_of_measurement: "%"
        state_class: measurement

      - name: "F18 DC Pumpskala Faktor"
        unique_id: wp_f18_dc_pumpskala_faktor
        state: >
          {% set values = state_attr('sensor.wp_register_block_3', 'value') | default([]) %}
          {{ values[95] | default(0) | int }}
        unit_of_measurement: ""
        state_class: measurement

      - name: "F19 DC PUMP Diff."
        unique_id: wp_f19_dc_pump_diff
        state: >
          {% set values = state_attr('sensor.wp_register_block_3', 'value') | default([]) %}
          {{ values[96] | default(0) | int }}
        unit_of_measurement: ""
        state_class: measurement

      #### Letzte Aktualisierungszeiten (vereinfachte Version)
      - name: "WP Block 0 Last Update"
        unique_id: wp_block_0_last_update
        state: >
          {% set e = 'sensor.wp_register_block_0' %}
          {% if is_state_attr(e, 'timestamp', defined) %}
            {{ as_datetime(state_attr(e, 'timestamp')) | as_local | strftime('%d.%m.%Y %H:%M:%S') }}
          {% elif states(e) and states[e].last_updated %}
            {{ as_datetime(states[e].last_updated) | as_local | strftime('%d.%m.%Y %H:%M:%S') }}
          {% else %}
            unknown
          {% endif %}
        attributes:
          age_seconds: >
            {% set e = 'sensor.wp_register_block_0' %}
            {% if is_state_attr(e, 'timestamp', defined) %}
              {{ (now().timestamp() - as_timestamp(state_attr(e, 'timestamp'))) | int }}
            {% elif states(e) and states[e].last_updated %}
              {{ (now().timestamp() - as_timestamp(states[e].last_updated)) | int }}
            {% else %}
              -1
            {% endif %}

      - name: "WP Block 1 Last Update"
        unique_id: wp_block_1_last_update
        state: >
          {% set e = 'sensor.wp_register_block_1' %}
          {% if is_state_attr(e, 'timestamp', defined) %}
            {{ as_datetime(state_attr(e, 'timestamp')) | as_local | strftime('%d.%m.%Y %H:%M:%S') }}
          {% elif states(e) and states[e].last_updated %}
            {{ as_datetime(states[e].last_updated) | as_local | strftime('%d.%m.%Y %H:%M:%S') }}
          {% else %}
            unknown
          {% endif %}
        attributes:
          age_seconds: >
            {% set e = 'sensor.wp_register_block_1' %}
            {% if is_state_attr(e, 'timestamp', defined) %}
              {{ (now().timestamp() - as_timestamp(state_attr(e, 'timestamp'))) | int }}
            {% elif states(e) and states[e].last_updated %}
              {{ (now().timestamp() - as_timestamp(states[e].last_updated)) | int }}
            {% else %}
              -1
            {% endif %}

      - name: "WP Block 2 Last Update"
        unique_id: wp_block_2_last_update
        state: >
          {% set e = 'sensor.wp_register_block_2' %}
          {% if is_state_attr(e, 'timestamp', defined) %}
            {{ as_datetime(state_attr(e, 'timestamp')) | as_local | strftime('%d.%m.%Y %H:%M:%S') }}
          {% elif states(e) and states[e].last_updated %}
            {{ as_datetime(states[e].last_updated) | as_local | strftime('%d.%m.%Y %H:%M:%S') }}
          {% else %}
            unknown
          {% endif %}
        attributes:
          age_seconds: >
            {% set e = 'sensor.wp_register_block_2' %}
            {% if is_state_attr(e, 'timestamp', defined) %}
              {{ (now().timestamp() - as_timestamp(state_attr(e, 'timestamp'))) | int }}
            {% elif states(e) and states[e].last_updated %}
              {{ (now().timestamp() - as_timestamp(states[e].last_updated)) | int }}
            {% else %}
              -1
            {% endif %}

      - name: "WP Block 3 Last Update"
        unique_id: wp_block_3_last_update
        state: >
          {% set e = 'sensor.wp_register_block_3' %}
          {% if is_state_attr(e, 'timestamp', defined) %}
            {{ as_datetime(state_attr(e, 'timestamp')) | as_local | strftime('%d.%m.%Y %H:%M:%S') }}
          {% elif states(e) and states[e].last_updated %}
            {{ as_datetime(states[e].last_updated) | as_local | strftime('%d.%m.%Y %H:%M:%S') }}
          {% else %}
            unknown
          {% endif %}
        attributes:
          age_seconds: >
            {% set e = 'sensor.wp_register_block_3' %}
            {% if is_state_attr(e, 'timestamp', defined) %}
              {{ (now().timestamp() - as_timestamp(state_attr(e, 'timestamp'))) | int }}
            {% elif states(e) and states[e].last_updated %}
              {{ (now().timestamp() - as_timestamp(states[e].last_updated)) | int }}
            {% else %}
              -1
            {% endif %}

      - name: "WP Block 4 Last Update"
        unique_id: wp_block_4_last_update
        state: >
          {% set e = 'sensor.wp_register_block_4' %}
          {% if is_state_attr(e, 'timestamp', defined) %}
            {{ as_datetime(state_attr(e, 'timestamp')) | as_local | strftime('%d.%m.%Y %H:%M:%S') }}
          {% elif states(e) and states[e].last_updated %}
            {{ as_datetime(states[e].last_updated) | as_local | strftime('%d.%m.%Y %H:%M:%S') }}
          {% else %}
            unknown
          {% endif %}
        attributes:
          age_seconds: >
            {% set e = 'sensor.wp_register_block_4' %}
            {% if is_state_attr(e, 'timestamp', defined) %}
              {{ (now().timestamp() - as_timestamp(state_attr(e, 'timestamp'))) | int }}
            {% elif states(e) and states[e].last_updated %}
              {{ (now().timestamp() - as_timestamp(states[e].last_updated)) | int }}
            {% else %}
              -1
            {% endif %}

      - name: "WP Block 5 Last Update"
        unique_id: wp_block_5_last_update
        state: >
          {% set e = 'sensor.wp_register_block_5' %}
          {% if is_state_attr(e, 'timestamp', defined) %}
            {{ as_datetime(state_attr(e, 'timestamp')) | as_local | strftime('%d.%m.%Y %H:%M:%S') }}
          {% elif states(e) and states[e].last_updated %}
            {{ as_datetime(states[e].last_updated) | as_local | strftime('%d.%m.%Y %H:%M:%S') }}
          {% else %}
            unknown
          {% endif %}
        attributes:
          age_seconds: >
            {% set e = 'sensor.wp_register_block_5' %}
            {% if is_state_attr(e, 'timestamp', defined) %}
              {{ (now().timestamp() - as_timestamp(state_attr(e, 'timestamp'))) | int }}
            {% elif states(e) and states[e].last_updated %}
              {{ (now().timestamp() - as_timestamp(states[e].last_updated)) | int }}
            {% else %}
              -1
            {% endif %}

This is the brand: https://www.thermoflux.ba/en/heat-pumps/

From what I’ve seen they have a pretty good support system (at least in Bosnia). I had a couple of issues at first and everything was resolved within 24-48h. Their technicians have a database for each customer (separate from SPRSUN), with information on the type of unit and equipment used, service intervals and replaced parts. They give a 3 year warranty, with mandatory inspections every year. I don’t know if they provide support outside of Bosnia, most likely they are just a supplier.

I also realised it is possible to add random heat pumps through just the MAC address… Seems like a big security vulnerability.

I started working on this to monitor power usage, as I noticed the controller provides inverter voltage and current, and total power draw (register 333 I think), but the app itself doesn’t show power usage over time, only power draw in that moment. I can say that the power measurement is pretty accurate as it matches the measurement on my power meter.
I personally don’t see the need to read all of the registers. My homeassistant setup has only set temperatures, actual temperatures (outside, return, flow etc.), power usage, inverter and fan data, current working mode (heating/hot water/idle), and alarm and protection status. So in total I think I’m only reading maybe 40 registers.

I wanted a simpler version of the official app for ease of use for my family. I paused the project before adding the functionality to send data via homeassistant, although I confirmed it working (managed to change the heat and water temperature)

I thought about using two EW-11s as well, but it didn’t seem very clean to me, and I don’t understand modbus very well yet and didn’t know if having two will have them interfere with eachother (having two masters on one line?, also couldn’t find any locally, would have to order EW-11 from china). It seems to have worked for you though, I’ll keep that in mind.

I will start up homeassistant again later tonight, send you my code snippets and some screenshots and maybe try out your code.

1 Like

My files are in the link GitHub - PiterPiotr/SPRSUN-CGK-V3L-B-Elfin-EW11: This topic concerns the integration of a Sprsun heat pump with a color display. Communication is via the Elfin EW11 module - settings: EW11_1,2.. They concern a pump with a color touch panel for R32.

1 Like

You might get issues if two masters are active on a RS485 bus at the same time. Thus I try to keep the traffic clean by limiting the time of my monitoring fetches short and if one call of the sequences fail I just retry, until I get all data.
That allowed to run the app (when needed) in parallel and I was able to adapt essential control parameters by the app sich as P01 Heating Setp.

Meanwhile I added to my HA Dashboard some input parameters for this and other simple setups such as P07 Fan Mode, G02 Pump work etc.
If changed, an automation will write from HA directly through the Modbus enabled E*11 device and perform the change, so I have no need anymore to use the pump.

Currently my monitoring parts works fine together. At least good enought to get first experience with the heatpump. However, On Dec 29th one of our heat pumps suddenly stopped working and we need to take the date from monitoring to work on the issue.

Let me me describe our experience so far.

The heat pump shows 4 different values for the Fan Mode:

  1. Normal
  2. Night
  3. Eco
  4. Test or HiCOP

Actually we had a lot of issues at the beginning to find the right Fan mode.
One heat pump where I hardly had access to it for monitoring put the warm water into a 500 l heat water reservoir, where a floor heating system took its energy from. The pump was using Night mode and run smoothlessly, without much cycling (switch on an off). The house is not well isolated and thus has a high demand, but the 12kW 3 phase HP worked fine, running almost constantly and adapt compressor frequency. However, because of limited access we hardly had no monitoring for that.

On a second system - with similar setup, just a 9kW heatpump and probbaly a much better isolated house - we had much worse experience.

Regardless which parameter we tried the heat pump was cycling, i.e. switches on and off the whole day. Even if P07 Fan Mode was set to Test mode (aka HiCOP according to the modbus register description) we observed such behaviour. Worst behaviour we had was Normal mode, the heatpump seems to always starts runs full power (90Hz compressor frequency) and switches off as soon as the target temperature was ireached, almost without trying to use lower compressor frequencies instead to produce the right energy to keep the temperature. P07 Fan Mode set to *Night" behaved slightly better, it at least used also uses lower frequencies, but also switches off. HiCOP behaviour best, but we still saw a lot of cycling.

The temperature time series of the vessel temperature and the inlet / outlet temperature had a sawtooth shaped curvature, and when temperature went up then the heat pump stopped (switches off), even if it could have reduce the compressor frequency.

Because of that I insisted the owner to temporarily remove all addition heat regulation controls from the floor heating system (no heat ragulators), which seems to have a kind of bang bang control, opening and closing the single heat circles all at the same time and opened it again. When we start feeding the heated water from the vessel (reservoir) directly and unregulated into the hydrostatic balanced floor cycles in floor heating all problems are gone. From then on the heat pump worked smoothly, it constantly runs with moderating the temperatures by compresser frequency and valve opening only.

However, after a while we also observerd, that if the HiCOP (Test) setting was used, the energy production of heat pump was too low (thus the COP very high), but on the other hand the given P01 Heating Setp temperature was not reached! When ambient temperature we tried to increase P01 Heating Setp but as it was not obeyed the temperature also the room temperatures were too low. So we switched to Night mode, which obeyed the given temperatures.
Next I will program an heat curve and use Eco mode. In eco mode you define a “curve” with 4 given P01 Heating Setp values for 4 diffent ambient temperature points, which will be used with the actual ambient temperature, probably segmentwise linearly interpolated. That will allow to have an active moving P01 Heating Setp value.

However, since Dec 29th we have an issue. The heat pump stopped working with an “E69 Too low pressure protect” error. I checked the Disch. pressure and Sect. pressure value from the influxdb where store the HA values. That shows that during start the Sect. pressure indeed runs instead of the usual 2-3 bar down to some mBars, so that the heat pump really have to stop.

Unfortunately we have no information about the inner coolant cycle elements and we assume that eithe we have a stucked valve or a blocked R290 cooling media flow. It could also be a block / defective step motor. We even see that the evaporation temperature reaches -42C which would be the boiling temperature of R290 (Progan) at the given ambient temperatures.

So we reached out to SPRSUN and wait for their support (which is - I confess - a bit off topic :slight_smile: )

Because of this issue I tried to extend my HA Dashboard, as the are the 3 RW registers

  • Parameter marker definition (PMD) @ 0x32 = 50
  • Control mark 1 (CM1) @ 0x33 = 51
  • Control mark 1 (CM2) @ 0x34 =52

These register - according to the description - contains bits for different coil like information. While the PMD is most often used, as it bit 1 controls to switch the heat pump on and off via modbus, we had no real need for using CM1 and CM2, especially as we had no idea what they control.
However using such registers have to be done with care, as - other to coils - you cannot write to a single bit. Instead you have to use the whole register, compute the register value from the single bits and write the register value.

Here the description of the RW (read / write) registers with bits according to the documentation:

PMD
bit 0: switch on/off:0-off,1-on(bit address:0x0032*16+0)
bit 1: Main valve mode selection:0-Auto,1-Manual(bit address:0x0032*16+1)
bit 2 : Manual frequency selection:(bit address:0x0032*16+2)
bit 3: Reserved
bit 4: Reserved
bit 5: Auxiliary valve initial opening selection:0-Auto,1-Manual(bit address:0x0032*16+5)
bit 6: Expansion valve initial opening selection:0-fixed,1-adjustable(bit address:0x0032*16+6)
bit 7: Reserved

CM1
bit 0: Thermostatic adjustment:0-no,1-yes(bit address:0x0033*16+0)
bit 1: Pressure sensor valid or not:1-yes,0-no(bit address:0x0033*16+1)
bit 2 : Cooling Auxiliary Circuit Enable:0-allowed to open,1-not allowed to open(bit address:0x0033*16+2)
bit 3 : Auxiliary expansion valve mode:0-Enhanced enthalpy superheat 1-Exhaust superheat(bit address:0x0033*16+3)
bit 4 : DC Fan 1 Selection:0-no,1-yes(bit address:0x0033*16+4)
bit 5 : DC Fan 2 Selection:0-no,1-yes(bit address:0x0033*16+5)
bit 6 : Parameter reset selection:0-normal,1-requires reset(bit address:0x0033*16+6)
bit 7 : lockout fault reset selection:0-normal,1-requires reset(bit address:0x0033*16+7)
CM2
bit 0 : with or without antilegionella(bit address:0x0034*16+0)
bit 1 : with or without hot water(bit address:0x0034*16+1)
bit 2 : whether the clock is at night 20:00~08:00(bit address:0x0034*16+2)
bit 3 : Load fast check mode(0-normal,1-load)(bit address:0x0034*16+3)
bit 4 : Forced defrost(0-no effect,1-forced entry)(bit address:0x0034*16+4)
bit 5 : Reserved
bit 6 : with or without smart grid(0-no,1-yes)(bit address:0x0034*16+6)
bit 7 : Smart Grid with or without electric heater(0-no,1-yes)(bit address:0x0034*16+7)

As we had the pressure issue I described in the reply before I thought it might be worth to use it in this context.

The “manual” approach to try to restart heat pump was to use the carel controler, go to the list of errors send cleanse it.However once cleansed, the error reappear immediately with new timestamp!
So we had to switch-off the whole electric power and started heatpump some seconds later (when the Carel control got dark as well).

After reestablish power the heat pump starts some minutes later the fan and also starts producing heat energy. While observing the “Suct. and Desch. pressure” values we saw that Disch.pressure went down to 0. After it reached 0 bar the heat pump switched off again.

I checked register CM1 and got a value 31, which means bit 0 to 4 are all on, rest 0.
I tried to write a 29 (which means bit 1 set to 0) and I didn’t got any error on writing. But when I checked the register value by reading it it again it was 31, so I thought the write must have failed.

So I wondered if this register cannot be written. But thinking over it, I now assume that the CM registers are to give some option to reset such faults. Immediately after setting / clearing bit 1 by my modbus write the heat pump sets the bit again as the error persist. When I was reading it a second later I got again an 31!

When using PMD register it work more as expected, if I clear bit 1 the heat pump is switched off and register read gets that state.

Thanks Pjotr,
I was just confused as the yaml files are as attached files and linked to the .md file, but not as part of the project files. So I overlooked it. But I have seen them before

Yes, thanks again for sharing, it gave me a good starting point for my own first steps in modbus HA programming. And I must confess; without that I won’t have started these attempt to write a versionj of my own.

However using own sensor definitions to independetly get every single register on its own schedule will probably produce a constant stream of requests, espcially if you like to read all available register which are 500+. In the moment we don’t know exactly which a the hundreds of registers are real important for monitoring. Many of them are just parameter important to know, but don’t contain read life data which changes every second. Many of those fixed (slow changing) parameters are setup and can found in the Carel controler under project or factory settings. Good to know for documentation and error handling, but not really needed for monitoring.

When comparing the registers you were referencing to the official register description I realized that you probably also have done a kind of reverse engeneering to get them (or you have good connetion to SPRSUN internals)

I did this reverse engeneering and when I was comparing a whole series of parameters I read with bulk reader and compared them with Carel controler menues factory settings and project sittings (there are vectors of frequency settings used as tables) and and compared the values in subsequent unknown registers which I had read their values without knowing their content I realized that the are related.

Unfortunately I myself have only temporary on-hand access to the SPRSUN heat pumps, so I was not yet able to verify them. A simple way for that would be to set a certain value into a parameter in Carel and read again the address candidates as guessed. If the content matches, I have found and verified the address of the register.

Same has to be done with some of the parameters in the App, which seems also to have additional parameters compared to the official modbus documentation and the Carel control settings. All in all I assume that Modbus register will allow to access to the complete set of parameters.

Sample for that are the Phase B and C AC voltage and power values, which ar enot in the values the Carel control shows, but the App does and the official Modbus documentation doesn’t show neither.

So e.g. the official register show only for phase A the voltage and current in this register,

Addr Decimal Addr. Doc(Hex) Instructions Sample Data Data range Note
23 R 0x0017 AC Voltage 237 W AC Voltage
35 R 0x0023 Comp.current 0 A Press current

REM: Unit W for Voltage is one of the errors in the documentations.

But from my investigation I also found the values for Phase B and C in higher values of the registers. Unfortunately my Excel which I used to document the results was broken, so I need to redo the work or finding a working version from by backups. Here the values I had noted in my workbook

Addr Decimal Addr. Doc(Hex) Instructions Sample Data Data range Note
548 R 0x0224 AC Voltage Phase B 237 V AC Voltage Phase B
549 R 0x0225 AC Current Phase B 0 A AC Current Phase B
550 R 0x0226 AC Voltage Phase B 236 A AC Voltage Phase B
551 R 0x0227 AC Current Phase B 0 A AC Current Phase B
Regards
Detlef

Thanks for sharing your code, quite interesting. I will check it tomorrow. Currently i started to get my former bulk modbus read approach to support/work with the HA2025.12+ changes. Will come to this later.

From the graphs in you posting it looks as if you also suffer cycling of you heat pump, as both power consumption and compresser frequency get 0 for some periods, repeating multiple times, instead of reducing compressor frequencies down to 30Hz. That operating behavior is bad for COP and also stresses the parts such as compressor, even if modern heat pumps are less fragile. But as the Carnot cycle have to be build up after each switch on and that means pressure level has been rebuild up, which costs energy without deliver corresponding heat energy, so it should be avoided.

I would try the Fan Modes Night or Service (seems HiCOP orTest) mode if the behavior is better.
How is the heat used?
Are you using radiators or a floor heating system?

From the Heating Setp I assume you are using radiators, as 47°C seems to be too high for a normal floor heating system.

Are you using a vessel (buffer) from which the radiators are fed?
Unfortunately there is a general difference between floor heating and radiator based systems. Floor heating systems have a much higher inertial which is good and bad, depending from your demands and requirements.
Floor heating systems change room temperatures much slower so reducing temperatures over night does not make sense. But you can on the other hand also set higher Heating setp values when electrical energy is cheep and so you can buffer heat in the floor to.
Radiators react much quicker, so unused rooms could be switched off. But usually radiators are used with heat contol valves, which opens and closes independently and it is quite difficult for heatpumps to deliver the right heat power. The SPRSUN heatpump build in control (firmware) are not how I would program it, making much more use of frequency / main valve step control to adapt the het energy demand to the house.
But honestly a similar issue exist in case of modern gas or oil based modern heating system, the show a similar cycling behaviour with many switch on / off during a day.

However the heat pump will probably work without cycling if all radiator valves are completely open, which is not an issue if you have a hydrostatic adjustment. Some valve in unused rooms could be partially or completely closed (reduced flow)
In that case you will have a constant not variating constant, non oszillating heat flow through the house. As a result you’ll probably see that the heat pump will perform much better, run continously without switching off, just moderated by mainly compressor frequency.

Thanks for the suggestions, I’m still new to heat pumps and similar, only got it 2 months ago, switched from a gas boiler. We have mostly radiators in each room + floor heating in bathroom and kitchen. We have a 200/60 l accumulation/buffer https://www.thermoflux.ba/en/stainless-steel-combined-tanks/
The radiators only have a manual valve (not a TRV) and the lockshield valve which from what I read is used for the hydrostatic adjustment you mentioned? I haven’t yet tried adjusting them.

I will try the different fan modes you mentioned. The heat pump itself has some kind of pump as well it seems, I can set it to Interval/Demand/Always but I’m not sure if there’s a built in circulation pump or if it talks to the primary one (a grundfos circulation pump)

UPDATE picture above: set the fan mode to HI-COP, heat temp diff to 1 and stop temp diff to 1, now it hovers around 47, no cycling at all (although it’s been running for only an hour but seems much cleaner than before (changed settings at ~2:30). The drop in the middle was a defrost cycle. It inverter % hovers around 35 (it never hit that low) and compressor is at 30-40 as well, when it was 60 before. Thanks a lot for the suggestions.
It seems that the stop temp diff setting in the app was key, as before it would instantly shut off when it hit the set return temp, now it hovers around it.

UPDATE 2: I returned the fan mode to daily instead of HI-COP because in some documentation it says Test/service/pressure so I’m not too comfortable using it. Setting both temp diffs to ±1 has been amazing. It’s been running the whole day without cycling once, only defrost. Holding perfectly steady at the set temp, and only using 1.5-1.7 kW. Down to 43kWh from 51 kWh yesterday.

Great to hear you made some improvements.
Actually I’m also still not sure about the HI-COP / Testing setting, as we have setup were in that mode the temperature of the house remains too low as the P01 Heatung Setp temperatures are not reached.
However we made some progress with our system which was faulty. I had tracked all parameters and we saw that the Disch Pressure was too low (down to mbars and 0) so that the heat pump run on fault. We documented that and reached out to support, which asked to get access to the system via the App connection (we had to share mac address / qr code). They checked the system and found that in the parameter tables (you can reach them through the factory / project settings in Carel control) the initial values for the EEV (Main valve) was too high, that means the valve seems to bee too closed and the gas flow was too small and pressure dropped.
The set the heatpump back to factory initials and it started to work (even better). Unfortunately you need a special authorized account for these actions, support is capable, we are not which is a pity. But lesson learned was, that we need to have the App integration working at least if support is envolved. So it cannot be totally dropped, just need to be blocked in normal case and needs to be revoked in case support needs it.

That these parameter were wrote set was a bit surprising for us, as we never had changed any of these pararmeter tables. Neither manually in Carel control nor though Modbus. In Carel the menus are Pin protected, so nobody we able to change them. And I myself went only one time through them an documented it by VCR recording to have the values recorded and a never changed any of them.
So they must have been delivered with that.

Meanwhile I have started with a new approach of a modbus based integration to replace my external data collect program. But there had been recent changed in the modbus HA implementation which broke my well working earlier attemp with bulk read. Really bad, as it worked quite ok.

After some attemp to get the new versions working I had to give up and started a new attempt with pyscript. With help of Google Gemini (version 3 ist really so much better then ChatGPT) I have now a hybrid version running, which takes all 600 modbus registers with status data and the configuration tables and ship them to HA. Those registers I knew from documentation and other sources and I have added, others are transferred in a generic way (just their register numbers) and I will then verify their meaning by changing them in Carel controller, check where the change appear and document the whole stuff.
So it should be possible to get all parameters maintainable by modbus. However I also leaned today that the Modbus access to SPRSUN though the EW11 is quite fragile, as if you heavily use it the EW11 (or the SPRSUN RS485 communication behind) might get stuck.
I saw this already earlier with my Python program, that I might have to restart the EW11 from time to time if it get irresposive. So if I get connection problems then I fire a REST call to the EW11 to restart which works find and in 99.9% it works again. But from time time it does not work, then the Power of the EW11 had to be switched off for a hardware reset.

I will try soon if an ESP32 with ESPHOME in connection with an RS485 modem board (all from aliexpress for ca. 5 €) provides a better and more reliable connection.
Regards
Detlef