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/SETUP.md at master · codetheweb/tuyapi · GitHub. One way that’s relatively easy is outlined below:

  • Sign up for an account on iot.tuya.com 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 iot.tuya.com 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) {
    Channels:
        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!

1 Like

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:

PUMP_POWER = Quantity.new('300 W') # approximate value - VFD pump running on low speed
HEATER_POWER = Quantity.new('2.5 kW') # approximate value running at full power

rule 'Pool: Heat Pump Control' do
  every 5.minutes
  between '7am'..'10pm'
  run do
    avg_grid_draw = Solar_Grid_Power.average_since(15.minutes, :influxdb)
    power_needed = HEATER_POWER * (PoolHeater_Power.on? ? PoolHeater_Power_Level / 100 : 0.8)
    power_needed += PUMP_POWER if Pool_Chlorinator.off?
    if PoolHeater_Power.off?
      PoolHeater_Power.on if avg_grid_draw < (power_needed - '500W').negate
    elsif PoolHeater_Power_Level.positive? && avg_grid_draw > power_needed && PoolHeater_Boost.off?
      PoolHeater_Power.off
    elsif avg_grid_draw.positive? && PoolHeater_Boost.on?
      PoolHeater_Boost.off
    elsif avg_grid_draw < '-1 kW' && PoolHeater_Boost.off? && PoolHeater_Power_Level > 70
      PoolHeater_Boost.on
    end
  end
end

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) }
end
1 Like