How to add a Tuya based pool heat pump to OpenHAB

I’ve just got a pool heater / heat pump. The brand is Madimack in Australia, but it’s just a rebadged pool heat pump from China. It uses “Invergo” app, but it is basically a Tuya device and I could add it to Tuya Smart app instead of Invergo. If your pool heat pump works with Invergo or Tuya Smart app, chances are it will work exactly the same way.

I managed to integrate it into OpenHAB using tuya-mqtt. Please refer to their documentation on how to get it working. It communicates directly to the device over the LAN, not via the cloud.

The general steps are:

  • Add the pool heat pump to the Tuya Smart App
    For Madimack:
    • Unlock the control panel on the heat pump
    • Press the power button for 3 seconds then release
    • The wifi icon on the control panel will blink fast
    • Go to Tuya Smart app on your phone, add a Heat Pump (Wifi)

Next you need to find your device’s localkey. The local key is a key used to link between the device and the app. Each time the device is linked to the app, a new localkey will be generated. There are several ways to obtain this local key - see the documentation from tuya-mqtt or tuyapi tuyapi/ at master · codetheweb/tuyapi · GitHub. One way that’s relatively easy is outlined below:

  • Sign up for an account on select “United States” as your country!
  • Create a cloud project
  • Click on the project, link device by app and scan the qr code with the Tuya smart app. This will bring the device into your account
  • Click Cloud → Api Explorer → Get Device Details. Enter the Device ID for your heat pump
  • You’ll see your local key in the json response

Next, add that device to tuya-mqtt devices.conf. This is mine:

    name: 'Pool Heater',
    id: 'the-device-id',
    key: 'the-local-key',
    template: {
        power: {
            key: 1,
            type: 'bool'
        current_temperature: {
            key: 102,
            type: 'float'
        power_level: {
            key: 104,
            type: 'int'
        operating_mode: {
            key: 105,
            type: 'str'
        set_temperature: {
            key: 106,
            type: 'float',
            topicMin: 18,
            topicMax: 45
        boost: {
            key: 117,
            type: 'bool'

Then create openhab things file:

// tuya/pool_heater/dps/1/state false
// tuya/pool_heater/dps/102/state 14
// tuya/pool_heater/dps/103/state true
// tuya/pool_heater/dps/104/state 0
// tuya/pool_heater/dps/105/state warm
// tuya/pool_heater/dps/106/state 22
// tuya/pool_heater/dps/107/state 18
// tuya/pool_heater/dps/108/state 40
// tuya/pool_heater/dps/115/state 0
// tuya/pool_heater/dps/116/state 0
// tuya/pool_heater/dps/117/state true

//  1: power
// 102: current temp, integer, readonly
// 103: temperature unit, false: Fahrenheit, true: Celcius
// 104: power level, integer
// 105: operating mode, string, readonly
// 106: set temp, integer, 18-45
// 117: power mode, boolean, false: silent, true: boost

Thing mqtt:topic:mosquitto:pool_heater "Pool Heater" (mqtt:broker:mosquitto) {
        Type switch: power "Power" [ stateTopic="tuya/pool_heater/dps/1/state", commandTopic="tuya/pool_heater/dps/1/command", on="true", off="false" ]
        Type number: current_temperature "Current Temperature" [ stateTopic="tuya/pool_heater/dps/102/state", unit="°C" ]
        Type number: power_level "Power Level" [ stateTopic="tuya/pool_heater/dps/104/state" ]
        Type string: mode "Operating Mode" [ stateTopic="tuya/pool_heater/dps/105/state" ]
        Type number: set_temperature "Set Temperature" [ stateTopic="tuya/pool_heater/dps/106/state", commandTopic="tuya/pool_heater/dps/106/command", min=18, max=45, unit="°C" ]
        Type switch: boost "Boost" [ stateTopic="tuya/pool_heater/dps/117/state", commandTopic="tuya/pool_heater/dps/117/command", on="true", off="false" ]
        Type string: command "Command" [ commandTopic="tuya/pool_heater/command" ]

In my case I just use Celsius. If someone knows how to dynamically change mqtt min/max based on another channel (the celsius/fahrenheit channel that I didn’t create), let me know.

Yes I could’ve used the friendly topic as defined in the template, but I prefer using the DPS numbers in the things topics.

My Items file:

Switch    PoolHeater_Power           "Pool Heater Power"                            {channel="mqtt:topic:mosquitto:pool_heater:power"}
Number    PoolHeater_Current_Temp    "Pool Heater Current Temperature [%.1f °C]"    {channel="mqtt:topic:mosquitto:pool_heater:current_temperature"}
Number    PoolHeater_Set_Temp        "Pool Heater Set Temperature [%d °C]"          {channel="mqtt:topic:mosquitto:pool_heater:set_temperature"}
Number    PoolHeater_Power_Level     "Pool Heater Power Level [%d %%]"              {channel="mqtt:topic:mosquitto:pool_heater:power_level"}
String    PoolHeater_Mode            "Pool Heater Mode"                             {channel="mqtt:topic:mosquitto:pool_heater:mode"}
Switch    PoolHeater_Boost           "Pool Heater Boost"                            {channel="mqtt:topic:mosquitto:pool_heater:boost"}
String    PoolHeater_Command         "Pool Heater Command"                          {channel="mqtt:topic:mosquitto:pool_heater:command"}

I use the PoolHeater_Command to refresh the data by commanding it with a string get-states as per GitHub - TheAgentK/tuya-mqtt: Nodejs-Script to combine tuyaapi and openhab via mqtt - I do this in a cron trigger every 5 minutes.

Now I can control the heat pump’s on/off based on available excess PV power, monitor the pool’s temperature, etc. via OpenHAB!


EDIT: code refactor to make it easier to read / follow

I’ve been testing out my JRuby rule to automate my pool heater so it only uses power from my PV / solar generated output, and not draw from the grid. I have a Fronius inverter + smart meter and using the Fronius binding, I can get the grid import (positive number) / export (negative) data.

I also control my Chlorinator timing / power in another rule, based on the sun elevation which changes according to the season. The pool pump is connected to a “logic or” circuit, so it will turn on when at least one of the chlorinator or the heat pump is on.

Here’s my current rule to control the pool heat pump:

require 'openhab'


# Run the pool heater on a strict time based schedule
# Set the Target Temperature to normal
# Turn on the heater earlier and keep it on if there's plenty of solar power outside the schedule
def run_scheduled_heating
  PoolHeater_Set_Temp.ensure << TARGET_TEMPERATURE
  if @avg_grid_export > '3kW'
    when '9am'..'3pm' then PoolHeater_Power.ensure.on
    when ('4pm'..) then

def update_power_stats
  @avg_solar_power = Solar_AC_Power.average_since(15.minutes, :influxdb) ||"0 W")
  @avg_grid_draw = Solar_Grid_Power.average_since(1.minutes, :influxdb)
  @avg_grid_draw60 = Solar_Grid_Power.average_since(60.minutes, :influxdb)
  @avg_grid_export = @avg_grid_draw.negate

# Decide whether the heater needs to be turned on
# @return [Boolean] true if the heater is on, false otherwise
def power_on
  return true if PoolHeater_Power.on?
  return false unless'7am'..'3pm')
  return false unless @avg_solar_power.positive?
  return false unless @avg_grid_export > '3 kW' || Pool_Chlorinator.on?


# Send an OFF command when necessary, but always returns true
# @return [Boolean] Always returns true
def turn_off_heater

# Decide whether the heater needs to be turned off
# @return [Boolean] true if the heater is turned off, false/nil otherwise
def power_off
  return turn_off_heater if >= '6pm'
  return turn_off_heater if > '4pm' && @avg_grid_draw > '1 kW'
  return turn_off_heater unless @avg_solar_power.positive?
  return turn_off_heater if @avg_grid_draw60 && @avg_grid_draw60 > '2 kW'

# Adaptively return the target temperature.
# Lower the target temp on a day with low solar production
# @return [QuantityType] The adjusted target temperature
def target_temperature
  return TARGET_TEMPERATURE - '2 °C' if @avg_grid_draw60.nil? || @avg_grid_draw60 > '2 kW' || @avg_solar_power < '1 kW'


# Returns true if the target temp has been reached. Also adjust the set temp
# @return [Boolean] true if the pool's current temp is the same or more than the target temp
def target_temp_reached?
  result = PoolHeater_Current_Temp >= target_temperature
  PoolHeater_Set_Temp.ensure << (result ? target_temperature : SET_TEMPERATURE)

# Adjust the heater's Boost mode on/off depending on excess solar power
def power_saving_mode
  return unless @avg_grid_draw
  return if target_temp_reached?

  if PoolHeater_Boost.on? if @avg_grid_draw > '2 kW'
  elsif @avg_grid_export > '1 kW'

rule 'Pool: Heater Control' do
  every 5.minutes
  between '9am'..'7pm'
  run do
    if PoolHeater_Power_Saving.on?
      power_saving_mode if power_on && !power_off

To plot the pool temperature, I only persist the data when the pool heater is on. When it’s off overnight, the water isn’t circulating and it will only sense the temperature of the water inside the unit. So I use this rule:

rule 'Pool: Persist pool temp' do
  changed PoolHeater_Current_Temp
  only_if PoolHeater_Power
  run { PoolHeater_Current_Temp.persist(:influxdb) }
1 Like

Having trouble with this - I get the following response:

  "code": 1106,
  "msg": "permission deny",
  "success": false,
  "t": 1624121848147

Any ideas?

@mvnixon I haven’t got much experience dealing with tuya, so you might have better luck here: tuyapi/ at master · codetheweb/tuyapi · GitHub

Have you imported the devices into your developer account / project, and can you see them in the device list (under the project)?

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.