New electric water heater / boiler (integrated with HA)

I’m looking for the same, found anything?

No, unfort… nothing to can be seamlessly integrated. Kinda put the project on hold until I found something workable.

1 Like

Hello!

Hopefully I bring good news!
you are looking for this:

I have a tesy boiler on the wall and figured all this stuff out while digging around for my daikin heatpump
(GitHub - william-sy/Daikin-BRP069A62: Creating a daikin interface other than the apps)

Now its not a integration, but it should give you a great starting point to figure some stuff out!

3 Likes

Made a simple POC of a tesy boiler:

sensor:
  - platform: rest
    resource: http://192.168.2.254/status?_=1634912123253
    name: TesyBoilerWaterTemp
    method: GET
    value_template: "{{ value_json.gradus }}"
    unit_of_measurement: "°C"

Nice!!! That is really usable

All the basic info you could want is my guess:

sensor:
  - platform: time_date
    display_options:
      - 'time'
      - 'date'
      - 'date_time'
      - 'date_time_utc'
      - 'date_time_iso'
      - 'time_date'
      - 'time_utc'
      - 'beat'
  - platform: worldclock
    name: Toronto
    time_zone: America/Toronto
  - platform: worldclock
    name: Netherlands
    time_zone: Europe/Amsterdam
  - platform: worldclock
    name: Kuala Lumpur
    time_zone: Asia/Kuala_Lumpur
  - platform: rest
    resource: http://192.168.2.254/status?_=1634912123253
    name: tesyboiler
    method: GET
    value_template: "OK"
    json_attributes:
      - gradus
      - ref_gradus
      - heater_state
      - err_flag
      - boost
      - power_sw
      - volume
      - watts
  - platform: rest
    resource: http://192.168.2.254/calcRes?_=1634912123253
    name: tesyboilerpower
    method: GET
    value_template: "OK"
    json_attributes:
      - kwh
      - ltc
      - resetDate
  - platform: template
    sensors:
      tesyboilerwatertemp:
        friendly_name: "TesyBoilerWaterTemp"
        value_template: "{{ state_attr('sensor.tesyboiler', 'gradus') }}"
        unit_of_measurement: "°C"
        icon_template: mdi:temperature-celsius
      tesyboilerwatertargettemp:
        friendly_name: "TesyBoilerWaterTargetTemp"
        value_template: "{{ state_attr('sensor.tesyboiler', 'ref_gradus') }}"
        unit_of_measurement: "°C"
        icon_template: mdi:temperature-celsius
      tesyboilerstate:
        friendly_name: "TesyBoilerState"
        value_template: "{{ state_attr('sensor.tesyboiler', 'heater_state') }}"
        icon_template: mdi:help-circle
      tesyboilerwatts:
        friendly_name: "TesyBoilerWatts"
        value_template: "{{ state_attr('sensor.tesyboiler', 'watts') }}"
        unit_of_measurement: "W"
        icon_template: mdi:power-plug
      tesyboilerwatervolume:
        friendly_name: "TesyBoilerWaterVolume"
        value_template: "{{ state_attr('sensor.tesyboiler', 'volume') }}"
        unit_of_measurement: "L"
        icon_template: mdi:cup-water
      tesyboileronoff:
        friendly_name: "TesyBoilerOnOff"
        value_template: "{{ state_attr('sensor.tesyboiler', 'power_sw') }}"
        icon_template: mdi:help-circle
      tesyboilerboost:
        friendly_name: "TesyBoilerBoost"
        value_template: "{{ state_attr('sensor.tesyboiler', 'boost') }}"
        icon_template: mdi:arrow-up-circle
      tesyboilererror:
        friendly_name: "TesyBoilerError"
        value_template: "{{ state_attr('sensor.tesyboiler', 'err_flag') }}"
        icon_template: mdi:water-boiler-alert
      tesyboilerkwhalltime:
        friendly_name: "TesyBoilerKwhAllTime"
        value_template: "{{ state_attr('sensor.tesyboilerpower', 'ltc') }}"
        unit_of_measurement: "kWh"
        icon_template: mdi:lightning-bolt
      tesyboilerkwhafterreset:
        friendly_name: "TesyBoilerKwhAfterReset"
        value_template: "{{ state_attr('sensor.tesyboilerpower', 'kwh') }}"
        unit_of_measurement: "kWh"
        icon_template: mdi:lightning-bolt
      tesyboilerkwhresetdate:
        friendly_name: "TesyBoilerKwhResetDate"
        value_template: "{{ state_attr('sensor.tesyboilerpower', 'resetDate') }}"
        icon_template: mdi:calendar-blank-outline

If you want more you can look at my repo for the URL / JSON responses.
Also if you happen to update it do share back :slight_smile:

Pushing on/off, a new temp and boost should be possible but at some point its better to have a full integration made that also can reset kWh and change schedules

Edit reason: Updated the sensor with more info and icons
Also a template for a dashboard:

type: custom:mini-graph-card
hours_to_show: 1
points_per_hour: 15
update_interval: 30
group_by: value
name: TesyBoiler
line_width: 2
entities:
  - entity: sensor.tesyboilerwatertemp
    name: Water Temp
  - entity: sensor.tesyboilerwatertargettemp
    name: Target Temp
  - entity: sensor.tesyboilerboost
    show_state: true
    show_graph: false
    name: Boost
  - entity: sensor.tesyboilerstate
    show_state: true
    show_graph: false
    name: Boost
show:
  graph: line
  name: true
  legend: true
  icon: false
  labels: true
  labels_secondary: true
state_map:
  - value: READY
    label: Standby
  - value: HEATING
    label: HeatingUp
  - value: '0'
    label: Not boosting
  - value: '0'
    label: Boosting

Screenshot 2022-09-22 at 16.04.59

1 Like

This looks really promising! The sensors are awesome!
You captured the URL’s, but was this not HTTPS (as Tesy describes the communication highly secure between the cloud and boiler)…

What type of Tesy are you testing on? The Modeco cloud, or Bellisimo cloud?

For my scenario, switching the unit on/off, or maybe switch low/mid/high consumption/temperature, is a requirement (solar energy availability) from within HA.

Https is only needed if you are connecting to the cloud. You technically dont need to do this the local webpage does all that you want its just less pretty.

Its a Model : modeco 2 with a tesy on it, See the screen shots below :slight_smile:

Switching the boiler on / off is not something I am immediately after at the moment as I am moving next year, So I am just experimenting with everything in this house to de-cloud myself. But happy to help script away if someone sets up a propper integration.

You can also re-flash the boiler with ESP home, as there is just a ESP in there anyway.

Screenshot 2022-09-24 at 11.22.38

Also If the URLS dont work, just open up a inspector page:

1 Like

Hi I tried the following for on / off / boost:

switch:
  - platform: command_line
    switches:
      tesyboostonoff:
        command_on: "/usr/bin/curl -X GET http://192.168.2.254/boostSW?mode=1&_=1634911445424"
        command_off: "/usr/bin/curl -X GET http://192.168.2.254/boostSW?mode=0&_=1634911446795"
        #command_state: "/usr/bin/curl -sX GET http://192.168.2.254/status?_=1634912123253"
        value_template: >
          "{{ sensor.tesyboilerboost }}"
        friendly_name: Boost Tesy!
        icon_template: >
          {% if sensor.tesyboilerboost %} mdi:rocket-launch
          {% else %} mdi:rocket-launch-outline
          {% endif %}
      tesyonoff:
        command_on: "/usr/bin/curl -X GET http://192.168.2.254/power?val=on&_=1664015398260"
        command_off: "/usr/bin/curl -X GET http://192.168.2.254/power?val=off&_=1664015398260"
        #command_state: "/usr/bin/curl -sX GET http://192.168.2.254/status?_=1634912123253"
        value_template: >
         "{{ sensor.tesyboilerstate }}"
        friendly_name: Tesy power switch!
        icon_template: >
          {% if sensor.tesyboilerstate %} mdi:toggle-switch
          {% else %} mdi:toggle-switch-off
          {% endif %}

But I am not happy with the state fetching seems to not really work

As you can see it assumes the state:

Ideally it just gets the state form the curl or the other sensor but no luck.

It took a bit of puzzling, but here we are its working :slight_smile:

switch:
  - platform: command_line
    switches:
      tesyboostonoff:
        command_on: "/usr/bin/curl -X GET http://192.168.2.254/boostSW?mode=1&_=1634911445424"
        command_off: "/usr/bin/curl -X GET http://192.168.2.254/boostSW?mode=0&_=1634911446795"
        command_state: "/usr/bin/curl -sX GET http://192.168.2.254/status?_=1634912123253"
        value_template: "{{ value_json['boost'] == '1' }}"
        friendly_name: Boost Tesy!
        icon_template: >
          {% if value_json['boost'] == '0' %} mdi:rocket
          {% elif value_json['boost'] == '1' %} mdi:rocket-launch
          {% else %} mdi:help-circle
          {% endif %}
      tesyonoff:
        command_on: "/usr/bin/curl -X GET http://192.168.2.254/power?val=on&_=1664015398260"
        command_off: "/usr/bin/curl -X GET http://192.168.2.254/power?val=off&_=1664015398260"
        command_state: "/usr/bin/curl -sX GET http://192.168.2.254/status?_=1634912123253"
        value_template: "{{ value_json['power_sw'] == 'on' }}"
        friendly_name: Tesy power switch!
        icon_template: >
          {% if value_json['power_sw'] == "on" %} mdi:toggle-switch
          {% else %} mdi:toggle-switch-off
          {% endif %}
3 Likes

What beautiful work!
Can you show me an example how to set the temperature with a command line

You could if you have not programmed a schedule (we have that so its hard without a integration)

A schedule looks like this:

&Mon00=32&Mon01=32&Mon02=32&Mon03=32&Mon04=32&Mon05=32&Mon06=50&Mon07=50&Mon08=50&Mon09=50&Mon10=50&Mon11=50&Mon12=50&Mon13=50&Mon14=50&Mon15=50&Mon16=60&Mon17=60&Mon18=40&Mon19=40&Mon20=40&Mon21=40&Mon22=30&Mon23=30

Repeat for all 7 days.
You would need to send it as plain text like this:

# P1 is week program 1
curl -X POST -H "Content-Type: text/plain" --data "this is raw data" http://192.168.2.254/setP1

But if you are in manual mode (or force it to be in manual mode):

# Set manual mode
http://192.168.2.254/modeSW?mode=1&_=1664177920325

Then you can send it with:

# Send temperature 25
http://192.168.2.254/setTemp?val=25&_=1664177655995

You can get the current value from the status one: http://192.168.2.254/status?_=1664177656195 and look at ref_gradus ( this is tesyboilerwatertargettemp in the above ymls)

Here are all the modes:

# manual mode
http://192.168.2.254/modeSW?mode=1&_=1634912123253
# set program 1 (P1)
http://192.168.2.254/modeSW?mode=2&_=1634912027732
# set 2 Schedule 2 (P2)
http://192.168.2.254/modeSW?mode=3&_=1634912041121
# set 3 Schedule 3 (P3)
http://192.168.2.254/modeSW?mode=4&_=1634912071235
# eco 1 ( this is a Mode you cant change settings in - see the manual )
http://192.168.2.254/modeSW?mode=5&_=1634912151669
# eco 2 ( this is a Mode you cant change settings in - see the manual )
http://192.168.2.254/modeSW?mode=6&_=1634912164061
# eco 3 ( this is a Mode you cant change settings in - see the manual )
http://192.168.2.254/modeSW?mode=7&_=1634912180802

So first thing would be to get the current mode you are in:

  - platform: rest
    resource: http://192.168.2.254/status?_=1634912123253
    name: tesyboiler
    method: GET
    value_template: "OK"
    json_attributes:
      - mode
  - platform: template
    sensors:
      tesyboilermode:
        friendly_name: "TesyBoilerMode"
        value_template: "{{ state_attr('sensor.tesyboiler', 'mode') }}"

Set manual? :

switch:
  - platform: command_line
    switches:
      tesysetmanualmode:
        command_on: "/usr/bin/curl -X GET http://192.168.2.254/modeSW?mode=1&_=1664177920325"
        command_state: "/usr/bin/curl -sX GET http://192.168.2.254/status?_=1634912123253"
        value_template: "{{ value_json['mode'] == '1' }}"
        friendly_name: Set Tesy manual mode
      tesysetmanualtemperature:
        # Some how get the temperature in here
        command_on: "/usr/bin/curl -X GET http://192.168.2.254/setTemp?val=25&_=1664177655995"
        command_state: "/usr/bin/curl -sX GET http://192.168.2.254/status?_=1634912123253"
        # Some how this needs to evaluate to true
        value_template: "{{ value_json['ref_gradus'] ==  }}"
        friendly_name: Set Tesy manual temp

Note this is not tested, as I use the schedules

Here is a Full config yml:


rest_command:
  tsmt:
    url: "http://192.168.2.254/setTemp?val={{ temperature }}&_=1664177655995"
    method: GET

automation:
  - alias: "Tesy Temperature input automatically changed"
    trigger:
      platform: state
      entity_id: sensor.tesyboilertargettemp
    action:
      service: input_number.set_value
      target:
        entity_id: input_number.tesyboilermanualtempinput
      data:
        value: "{{ trigger.to_state.state }}"
  - alias: "Tesy Temperature input manually changed"
    trigger:
      platform: state
      entity_id: input_number.tesyboilermanualtempinput
    action:
      service: rest_command.tsmt
      data:
        temperature: "{{ trigger.to_state.state }}"

input_number:
  tesyboilermanualtempinput:
    name: Set Tesy Manual Temp
    min: 14
    max: 75
    step: 1
    unit_of_measurement: "°C"
    icon: mdi:temperature-celsius

switch:
  - platform: command_line
    switches:
      tesyboostonoff:
        command_on: "/usr/bin/curl -X GET http://192.168.2.254/boostSW?mode=1&_=1634911445424"
        command_off: "/usr/bin/curl -X GET http://192.168.2.254/boostSW?mode=0&_=1634911446795"
        command_state: "/usr/bin/curl -sX GET http://192.168.2.254/status?_=1634912123253"
        value_template: "{{ value_json['boost'] == '1' }}"
        friendly_name: Boost Tesy!
        icon_template: >
          {% if value_json['boost'] == '0' %} mdi:rocket
          {% elif value_json['boost'] == '1' %} mdi:rocket-launch
          {% else %} mdi:help-circle
          {% endif %}
      tesyonoff:
        command_on: "/usr/bin/curl -X GET http://192.168.2.254/power?val=on&_=1664015398260"
        command_off: "/usr/bin/curl -X GET http://192.168.2.254/power?val=off&_=1664015398260"
        command_state: "/usr/bin/curl -sX GET http://192.168.2.254/status?_=1634912123253"
        value_template: "{{ value_json['power_sw'] == 'on' }}"
        friendly_name: Tesy power switch!
        icon_template: >
          {% if value_json['power_sw'] == "on" %} mdi:toggle-switch
          {% else %} mdi:toggle-switch-off
          {% endif %}
      tesyresetpower:
        command_on: "/usr/bin/curl -X GET http://192.168.2.254/resetPow?_=1634912213060"
        friendly_name: Tesy power Reset!
      tesysetsetmanualmode:
        command_on: "/usr/bin/curl -X GET http://192.168.2.254/modeSW?mode=1&_=1664177920325"
        # Set a default schedule here if you dont want manual mode any longer
        command_off: "/usr/bin/curl -X GET http://192.168.2.254/modeSW?mode=2&_=1664177920325"
        command_state: "/usr/bin/curl -sX GET http://192.168.2.254/status?_=1634912123253"
        value_template: "{{ value_json['mode'] == '1' }}"
        friendly_name: Set Tesy manual mode
        icon_template: >
          {% if value_json['mode'] == '1' %} mdi:water-boiler
          {% elif  value_json['mode'] == '2' %} mdi:calendar
          {% elif  value_json['mode'] == '3' %} mdi:calendar
          {% elif  value_json['mode'] == '4' %} mdi:calendar
          {% elif  value_json['mode'] == '5' %} mdi:sprout
          {% elif  value_json['mode'] == '6' %} mdi:sprout
          {% elif  value_json['mode'] == '7' %} mdi:sprout
          {% else %} mdi:help-circle
          {% endif %}

sensor:
  - platform: rest
    resource: http://192.168.2.254/status?_=1634912123253
    name: tesyboiler
    method: GET
    value_template: "OK"
    json_attributes:
      - gradus
      - ref_gradus
      - heater_state
      - err_flag
      - boost
      - power_sw
      - volume
      - watts
      - mode
  - platform: rest
    resource: http://192.168.2.254/calcRes?_=1634912123253
    name: tesyboilerpower
    method: GET
    value_template: "OK"
    json_attributes:
      - kwh
      - ltc
      - resetDate
  - platform: template
    sensors:
      tesyboilerwatertemp:
        friendly_name: "TesyBoilerWaterTemp"
        value_template: "{{ state_attr('sensor.tesyboiler', 'gradus') }}"
        unit_of_measurement: "°C"
        icon_template: mdi:temperature-celsius
      tesyboilerwatertargettemp:
        friendly_name: "TesyBoilerWaterTargetTemp"
        value_template: "{{ state_attr('sensor.tesyboiler', 'ref_gradus') }}"
        unit_of_measurement: "°C"
        icon_template: mdi:temperature-celsius
      tesyboilertargettemp:
        friendly_name: "TesyBoilerTargetTemp"
        value_template: "{{ state_attr('sensor.tesyboiler', 'ref_gradus') }}"
      tesyboilerstate:
        friendly_name: "TesyBoilerState"
        value_template: "{{ state_attr('sensor.tesyboiler', 'heater_state') }}"
        icon_template: mdi:help-circle
      tesyboilermode:
        friendly_name: "TesyBoilerMode"
        value_template: "{{ state_attr('sensor.tesyboiler', 'mode') }}"
        icon_template: mdi:calendar
      tesyboilerwatts:
        friendly_name: "TesyBoilerWatts"
        value_template: "{{ state_attr('sensor.tesyboiler', 'watts') }}"
        unit_of_measurement: "W"
        icon_template: mdi:power-plug
      tesyboilerwatervolume:
        friendly_name: "TesyBoilerWaterVolume"
        value_template: "{{ state_attr('sensor.tesyboiler', 'volume') }}"
        unit_of_measurement: "L"
        icon_template: mdi:cup-water
      tesyboileronoff:
        friendly_name: "TesyBoilerOnOff"
        value_template: "{{ state_attr('sensor.tesyboiler', 'power_sw') }}"
        icon_template: mdi:help-circle
      tesyboilerboost:
        friendly_name: "TesyBoilerBoost"
        value_template: "{{ state_attr('sensor.tesyboiler', 'boost') }}"
        icon_template: mdi:arrow-up-circle
      tesyboilererror:
        friendly_name: "TesyBoilerError"
        value_template: "{{ state_attr('sensor.tesyboiler', 'err_flag') }}"
        icon_template: mdi:water-boiler-alert
      tesyboilerkwhalltime:
        friendly_name: "TesyBoilerKwhAllTime"
        value_template: "{{ state_attr('sensor.tesyboilerpower', 'ltc') }}"
        unit_of_measurement: "kWh"
        icon_template: mdi:lightning-bolt
      tesyboilerkwhafterreset:
        friendly_name: "TesyBoilerKwhAfterReset"
        value_template: "{{ state_attr('sensor.tesyboilerpower', 'kwh') }}"
        unit_of_measurement: "kWh"
        icon_template: mdi:lightning-bolt
      tesyboilerkwhresetdate:
        friendly_name: "TesyBoilerKwhResetDate"
        value_template: "{{ state_attr('sensor.tesyboilerpower', 'resetDate') }}"
        icon_template: mdi:calendar-blank-outline

Things that could be implemented more:

  • Rest comands:
    – Set a different mode (1 to 7)
    – Reset kWh usage (good for adding it to the energy dashboard?)
  • Full display with edit:
    – Change a actual schedule (better to have a proper implementation)

Hope it helps, From here everything should be easy enough for the next person. Ill stop posting more config :wink:

1 Like

Thank you for your extensive example.
It works great! now I can heat my water through solar panels.
If the temperature is too low, the gas boiler takes over.

1 Like

Thanks! That is greath! Can you share your cards config?

Really great stuff. I will have a look at how an integration should look like, and what is needed for it.

As I have no boiler/heater yet, it will be difficult to test. I will have a first look at simulating a boiler. Maybe I can find the Tesy firmware, and put it in an ESP device have a stub test device.

Hi Jos,what type Tesy boiler/heater do you own? Is it the same one (Modeco cloud)?

Hi William,

Great, I did not find out how to send commands towards the boiler.
Thanks a lot!

Is there a specific reason that you chose to use an automation for rest?
I found out that the boiler didn’t like a lot of frequent rest commands (I used to have each attribute in a seperate entity). Had to turn it off every 2 to 3 weeks because it became unresponsive.

I use this currently:

  - platform: rest
    name: boiler
    resource: http://boiler/status
    json_attributes:
      - gradus
      - ref_gradus
      - heater_state
      - boost
      - power_sw
    value_template: "{{ value_json.boiler }}"
    scan_interval: 60
  - platform: template
    sensors:
      gradus:
        friendly_name: "Huidige temperatuur"
        value_template: "{{ state_attr('sensor.boiler', 'gradus') }}"
        device_class: temperature
        unit_of_measurement: "°C"
      ref_gradus:
        friendly_name: "Ingestelde temperatuur"
        value_template: "{{ state_attr('sensor.boiler', 'ref_gradus') }}"
        device_class: temperature
        unit_of_measurement: "°C"
      heater_state:
        friendly_name: "Verwarm status"
        value_template: "{{ state_attr('sensor.boiler', 'heater_state') }}"
      boost:
        friendly_name: "Boost status"
        value_template: "{{ state_attr('sensor.boiler', 'boost') }}"
      power_sw:
        friendly_name: "Apparaat status"
        value_template: "{{ state_attr('sensor.boiler', 'power_sw') }}"

  - platform: rest
    name: boiler_power
    resource: http://boiler/calcRes
    value_template: '{{ value_json.sum | title }}'
    scan_interval: 60

  - platform: template
    sensors:
      boiler_power_usage:
        friendly_name: "Totaal stroomverbruik boiler"
        unit_of_measurement: 'kWh'
        device_class: power
        icon_template: mdi:power-plug
        value_template: "{{ ((states('sensor.boiler_power') | float / 3600000) * 2400) | round(2) }}"

And to get it on the energy dashboard, I use an utility meter

utility_meter: 
  daily_energy:
    name: boiler_energy-daily
    source: sensor.boiler_power_usage
    cycle: daily
    net_consumption: false

Regarding the power calculation, I think you have a newer (firmware) version.
My boiler has a weird calculation of power usage, hence the calculation in the template
It is formatted like this (output from /calcRes)

{"sum":"1695972","resetDate":"2022-01-01 02:43:41","volume":"100","watt":"2400"}

My latest yaml can be found here:

I use rest because it is what the app and the webinterface are using if you go to the local IP, And it spams the boiler if you look at the traffic!
So unless you send the wrong command I have not been able to make the boiler upset.
(been there :wink: , but also its has not been 3 weeks yet :stuck_out_tongue: )

Perhaps you should post a screen shot or 2 for the power usage on the boilers website! (power/version)
A JSON output of calcRes would also be of interest

Did anyone find a download page for the boiler firmwares? I am unable sofar and send a request for this via their website, but no answer yet.