Having Hozelock cloud controller kit intergration

Thanks @FloSyno
I don’t do much with Hozelock in my Home Assistant setup, but here is what it looks like:

###############################################################################
# CONFIGURATION
###############################################################################
sensor:
  - platform: rest
    resource: !secret hozelock_controller_0_url
    name: "Hozelock Controller 0"
    timeout: 60
    value_template: '{{ value_json["controller"]["name"] }}'
    json_attributes:
      - controller
  - platform: template
    sensors:
      hozelock_controller_0_pause_starttime:
        friendly_name: "Début pause"
        value_template: >-
          {% if states.sensor.hozelock_controller_0.attributes.controller['pause'] %}
            {{ ( state_attr("sensor.hozelock_controller_0", "controller")["pause"]["startTime"] | int / 1000 ) | int | timestamp_local }}
          {% else %}
            unknown
          {% endif %}
      hozelock_controller_0_pause_endtime:
        friendly_name: "Fin pause"
        value_template: >-
          {% if states.sensor.hozelock_controller_0.attributes.controller['pause'] %}
            {{ ( state_attr("sensor.hozelock_controller_0", "controller")["pause"]["endTime"] | int / 1000 ) | int | timestamp_local }}
          {% else %}
            unknown
          {% endif %}
      hozelock_controller_0_pause_duration:
        friendly_name: "Durée pause"
        value_template: >-
          {% if states.sensor.hozelock_controller_0.attributes.controller['pause'] %}
            {{ state_attr("sensor.hozelock_controller_0", "controller")["pause"]["duration"] | int / 86400000 | int }}
          {% else %}
            unknown
          {% endif %}
        unit_of_measurement: 'j'

      hozelock_controller_0_adjustment_starttime:
        friendly_name: "Début ajustement"
        value_template: >-
          {% if states.sensor.hozelock_controller_0.attributes.controller['adjustment'] %}
            {{ ( state_attr("sensor.hozelock_controller_0", "controller")["adjustment"]["startTime"] | int / 1000 ) | int | timestamp_local }}
          {% else %}
            unknown
          {% endif %}
      hozelock_controller_0_adjustment_endtime:
        friendly_name: "Fin ajustement"
        value_template: >-
          {% if states.sensor.hozelock_controller_0.attributes.controller['adjustment'] %}
            {{ ( state_attr("sensor.hozelock_controller_0", "controller")["adjustment"]["endTime"] | int / 1000 ) | int | timestamp_local }}
          {% else %}
            unknown
          {% endif %}
      hozelock_controller_0_adjustment_wateringadjustment:
        friendly_name: "Quantité ajustement"
        value_template: >-
          {% if states.sensor.hozelock_controller_0.attributes.controller['adjustment'] %}
            {{ state_attr("sensor.hozelock_controller_0", "controller")["adjustment"]["wateringAdjustment"] }}
          {% else %}
            unknown
          {% endif %}
        unit_of_measurement: '%'
        
      hozelock_controller_0_waternowevent_starttime:
        friendly_name: "Arrosage manuel (début)"
        value_template: >-
          {% if states.sensor.hozelock_controller_0.attributes.controller['waterNowEvent'] %}
            {{ ( state_attr("sensor.hozelock_controller_0", "controller")["waterNowEvent"]["startTime"] | int / 1000 ) | int | timestamp_local }}
          {% else %}
            unknown
          {% endif %}
      hozelock_controller_0_waternowevent_endtime:
        friendly_name: "Arrosage manuel (fin)"
        value_template: >-
          {% if states.sensor.hozelock_controller_0.attributes.controller['waterNowEvent'] %}
            {{ ( state_attr("sensor.hozelock_controller_0", "controller")["waterNowEvent"]["endTime"] | int / 1000 ) | int | timestamp_local }}
          {% else %}
            unknown
          {% endif %}
      hozelock_controller_0_waternowevent_duration:
        friendly_name: "Arrosage manuel (durée)"
        value_template: >-
          {% if states.sensor.hozelock_controller_0.attributes.controller['waterNowEvent'] %}
            {{ state_attr("sensor.hozelock_controller_0", "controller")["waterNowEvent"]["duration"] | int / 60000 | int }}
          {% else %}
            unknown
          {% endif %}
        unit_of_measurement: 'min'
        
      hozelock_controller_0_currentwateringevent_starttime:
        friendly_name: "Arrosage en cours (début)"
        value_template: >-
          {% if states.sensor.hozelock_controller_0.attributes.controller['currentWateringEvent'] %}
            {{ ( state_attr("sensor.hozelock_controller_0", "controller")["currentWateringEvent"]["startTime"] | int / 1000 ) | int | timestamp_local }}
          {% else %}
            unknown
          {% endif %}
      hozelock_controller_0_currentwateringevent_endtime:
        friendly_name: "Arrosage en cours (fin)"
        value_template: >-
          {% if states.sensor.hozelock_controller_0.attributes.controller['currentWateringEvent'] %}
            {{ ( state_attr("sensor.hozelock_controller_0", "controller")["currentWateringEvent"]["endTime"] | int / 1000 ) | int | timestamp_local }}
          {% else %}
            unknown
          {% endif %}
      hozelock_controller_0_currentwateringevent_duration:
        friendly_name: "Arrosage en cours (durée)"
        value_template: >-
          {% if states.sensor.hozelock_controller_0.attributes.controller['currentWateringEvent'] %}
            {{ state_attr("sensor.hozelock_controller_0", "controller")["currentWateringEvent"]["duration"] | int / 60000 | int }}
          {% else %}
            unknown
          {% endif %}
        unit_of_measurement: 'min'
        
      hozelock_controller_0_nextwateringevent_starttime:
        friendly_name: "Prochain arrosage (début)"
        value_template: >-
          {% if states.sensor.hozelock_controller_0.attributes.controller['nextWateringEvent'] %}
            {{ ( state_attr("sensor.hozelock_controller_0", "controller")["nextWateringEvent"]["startTime"] | int / 1000 ) | int | timestamp_local }}
          {% else %}
            unknown
          {% endif %}
      hozelock_controller_0_nextwateringevent_endtime:
        friendly_name: "Prochain arrosage (fin)"
        value_template: >-
          {% if states.sensor.hozelock_controller_0.attributes.controller['nextWateringEvent'] %}
            {{ ( state_attr("sensor.hozelock_controller_0", "controller")["nextWateringEvent"]["endTime"] | int / 1000 ) | int | timestamp_local }}
          {% else %}
            unknown
          {% endif %}
      hozelock_controller_0_nextwateringevent_duration:
        friendly_name: "Prochain arrosage (durée)"
        value_template: >-
          {% if states.sensor.hozelock_controller_0.attributes.controller['nextWateringEvent'] %}
            {{ state_attr("sensor.hozelock_controller_0", "controller")["nextWateringEvent"]["duration"] | int / 60000 | int }}
          {% else %}
            unknown
          {% endif %}
        unit_of_measurement: 'min'

      hozelock_controller_0_lastcommunicationwithserver:
        friendly_name: "Dernière communication"
        value_template: '{{ ( state_attr("sensor.hozelock_controller_0", "controller")["lastCommunicationWithServer"] | int / 1000 ) | int | timestamp_local }}'
      hozelock_controller_0_nextcommunicationwithserver:
        friendly_name: "Prochaine communication"
        value_template: '{{ ( state_attr("sensor.hozelock_controller_0", "controller")["nextCommunicationWithServer"] | int / 1000 ) | int | timestamp_local }}'
      hozelock_controller_0_batterystatus:
        friendly_name: "Etat batterie"
        value_template: '{{ state_attr("sensor.hozelock_controller_0", "controller")["batteryStatus"] }}'
      hozelock_controller_0_signalstrength:
        friendly_name: "Puissance signal"
        value_template: '{{ state_attr("sensor.hozelock_controller_0", "controller")["signalStrength"] }}'
      hozelock_controller_0_overridescheduleduration:
        friendly_name: "Remplacement durée prog"
        value_template: >-
          {% if states.sensor.hozelock_controller_0.attributes.controller['overrideScheduleDuration'] %}
            {{ state_attr("sensor.hozelock_controller_0", "controller")["overrideScheduleDuration"] | int / 60000 | int }}
          {% else %}
            unknown
          {% endif %}
        unit_of_measurement: 'min'

      hozelock_controller_0_schedulename:
        friendly_name: "Nom programme"
        value_template: '{{ state_attr("sensor.hozelock_controller_0", "controller")["schedule"]["name"] }}'

rest_command:
# Hozelock
  hozelock_waternow:
    url: https://hoz3.com/restful/support/hubs/{{ hub_id }}/controllers/actions/waterNow
    method: POST
    payload: '{"controllerIDs":[ {{ controller_id }} ],"duration": {{ duration }} }'
    content_type:  'application/json'
  hozelock_stopwatering:
    url: https://hoz3.com/restful/support/hubs/{{ hub_id }}/controllers/actions/stopWatering
    method: POST
    payload: '{"controllerIDs":[ {{ controller_id }} ]}'
    content_type:  'application/json'
  hozelock_pause:
    url: https://hoz3.com/restful/support/hubs/{{ hub_id }}/controllers/actions/pause
    method: POST
    payload: '{"controllerIDs":[ {{ controller_id }} ],"duration": {{ duration }} }'
    content_type:  'application/json'
  hozelock_unpause:
    url: https://hoz3.com/restful/support/hubs/{{ hub_id }}/controllers/actions/unpause
    method: POST
    payload: '{"controllerIDs":[ {{ controller_id }} ]}'
    content_type:  'application/json'

input_number:
  hozelock_duration:
    name: Durée
    min: 1
    max: 60
    step: 1
    icon: mdi:clock-outline
    unit_of_measurement: "min"
  hozelock_duration_pause:
    name: Durée
    min: 1
    max: 14
    step: 1
    icon: mdi:clock-outline
    unit_of_measurement: "j"

binary_sensor:
  - platform: template
    sensors:
      hozelock_controller_0_haswaternowevent:
        friendly_name: "Water now event"
        value_template: '{{ state_attr("sensor.hozelock_controller_0", "controller")["hasWaterNowEvent"] }}'
      hozelock_controller_0_ischildlockenabled:
        friendly_name: "Verrou enfant"
        value_template: '{{ state_attr("sensor.hozelock_controller_0", "controller")["isChildlockEnabled"] }}'
      hozelock_controller_0_iswatering:
        friendly_name: "Arrosage en cours"
        value_template: '{{ state_attr("sensor.hozelock_controller_0", "controller")["isWatering"] }}'
      hozelock_controller_0_ispanelremoved:
        friendly_name: "Panneau retiré"
        value_template: '{{ state_attr("sensor.hozelock_controller_0", "controller")["isPanelRemoved"] }}'
      hozelock_controller_0_isadjusted:
        friendly_name: "Ajustement"
        value_template: '{{ state_attr("sensor.hozelock_controller_0", "controller")["isAdjusted"] }}'
      hozelock_controller_0_isscheduleuptodate:
        friendly_name: "Calendrier à jour"
        value_template: '{{ state_attr("sensor.hozelock_controller_0", "controller")["isScheduleUpToDate"] }}'
      hozelock_controller_0_ispaused:
        friendly_name: "En pause"
        value_template: '{{ state_attr("sensor.hozelock_controller_0", "controller")["isPaused"] }}'
      hozelock_controller_0_nextwateringevent_enabled:
        friendly_name: "Prochain arrosage activé"
        value_template: '{{ state_attr("sensor.hozelock_controller_0", "controller")["nextWateringEvent"]["enabled"] }}'
      hozelock_controller_0_currentwateringevent_enabled:
        friendly_name: "Arrosage en cours activé"
        value_template: '{{ state_attr("sensor.hozelock_controller_0", "controller")["currentWateringEvent"]["enabled"] }}'

###############################################################################
# AUTOMATIONS
###############################################################################
automation:
  - id: notification_debut_arrosage
    alias: Notification début d'arrosage
    trigger:
      - platform: state
        entity_id: binary_sensor.hozelock_controller_0_iswatering
        to: 'on'
    action:
      - delay: '00:00:10'
      - service: persistent_notification.create
        data_template:
          title: Hozelock
          message: "{{ states('sensor.hozelock_controller_0_currentwateringevent_starttime') }}: début d'arrosage pour une durée de {{ states('sensor.hozelock_controller_0_currentwateringevent_duration') }} min"

###############################################################################
# SCRIPTS
###############################################################################
script:
  # Hozelock
  hozelock_waternow:
    alias: Démarrer arrosage manuel
    sequence:
      - service: rest_command.hozelock_waternow
        data_template:
          hub_id: !secret hozelock_hub_id
          controller_id: 0
          duration: "{{ states('input_number.hozelock_duration') | int * 60000 }}"
  hozelock_stopwatering:
    alias: Arrêter arrosage manuel
    sequence:
      - service: rest_command.hozelock_stopwatering
        data_template:
          hub_id: !secret hozelock_hub_id
          controller_id: 0
  hozelock_pause:
    alias: Suspendre arrosage
    sequence:
      - service: rest_command.hozelock_pause
        data_template:
          hub_id: !secret hozelock_hub_id
          controller_id: 0
          duration: "{{ states('input_number.hozelock_duration_pause') }}"
  hozelock_unpause:
    alias: Reprise arrosage
    sequence:
      - service: rest_command.hozelock_unpause
        data_template:
          hub_id: !secret hozelock_hub_id
          controller_id: 0


And the UI:

I’m sure someone else could do better :smile:

1 Like

Hi guys. I’m new to this GitHub thing. I bought this hozelock cloud controller for one reason and one reason only. That is to turn water on and turn water off. It looks like I can’t do this instantly. I’m not using it to water my plants. I just need to switch water on and off remotely. Is there a way to do this?

Thanks for sharing the API documentation in GitHub. I found that the duration units for pause and adjust actions to be incorrect.

Both should be days rather than milliseconds. I coud not raise an issue in GitHub so posting here instead.

Thanks for the input. I have changed this in the Repo. Not sure why it wouldn’t let you raise an issue or a PR.

1 Like

Building upon the previous posts I am sharing my YAML configuration. I’ve made a few corrections and improvements to the above examples, please feel welcome to reuse. Remember to replace “123ABC” with your unique hub ID.

/config/configuration.yaml

sensor: !include sensors.yaml
rest_command: !include rest_command.yaml
template: !include templates.yaml

/config/sensors.yaml

##########################################
### Hozelock Sensors #####################
##########################################
# Hozelock hub status
- platform: rest
  name: Hozelock Hub Status
  resource: https://hoz3.com/restful/support/hubs/123ABC/
  verify_ssl: false
  method: GET
  value_template: '{{ value_json["hub"]["name"] }}'
  json_attributes:
    - hub
  scan_interval: 60

# Hozelock controller status
- platform: rest
  name: Hozelock Controller Status
  resource: https://hoz3.com/restful/support/hubs/123ABC/controllers/0
  verify_ssl: false
  method: GET
  value_template: '{{ value_json["controller"]["name"] }}'
  json_attributes:
    - controller
  scan_interval: 60

/config/rest_command.yaml

##########################################
### Hozelock Commands ####################
##########################################
# Hozelock hub water irrigation control
hozelock_water_now:
  url: "https://hoz3.com/restful/support/hubs/123ABC/controllers/actions/waterNow"
  method: POST
  payload: '{"controllerIDs":[0],"duration":600000}' # 10 mins
  content_type: 'application/json'
  verify_ssl: false

hozelock_stop_watering:
  url: "https://hoz3.com/restful/support/hubs/123ABC/controllers/actions/stopWatering"
  method: POST
  payload: '{"controllerIDs":[0]}'
  content_type: 'application/json'
  verify_ssl: false

hozelock_pause:
  url: "https://hoz3.com/restful/support/hubs/123ABC/controllers/actions/pause"
  method: POST
  payload: '{"controllerIDs":[0],"duration":{{ states("input_number.hozelock_pause_duration") }} }'
  content_type: 'application/json'
  verify_ssl: false

hozelock_unpause:
  url: "https://hoz3.com/restful/support/hubs/123ABC/controllers/actions/unpause"
  method: POST
  payload: '{"controllerIDs":[0]}'
  content_type: 'application/json'
  verify_ssl: false

hozelock_adjust:
  url: "https://hoz3.com/restful/support/hubs/123ABC/controllers/actions/adjust"
  method: POST
  payload: '{"controllerIDs":[0],"duration":{{ states("input_number.hozelock_adjustment_duration") }},"wateringAdjustment":{{ states("input_number.hozelock_adjustment_change") }} }'
  content_type: 'application/json'
  verify_ssl: false

hozelock_unadjust:
  url: "https://hoz3.com/restful/support/hubs/123ABC/controllers/actions/unadjust"
  method: POST
  payload: '{"controllerIDs":[0]}'
  content_type: 'application/json'
  verify_ssl: false
  

/config/templates.yaml

- sensor:

##########################################
### Hozelock Status ######################
##########################################
# Watering now
  - name: "Hozelock Watering Now"
    unique_id: hozelock_watering_now
    state: "{{ state_attr('sensor.hozelock_controller_status', 'controller')['isWatering'] }}"
# Watering paused
  - name: "Hozelock Watering Paused"
    unique_id: hozelock_watering_paused
    state: "{{ state_attr('sensor.hozelock_controller_status', 'controller')['isPaused'] }}"
# Watering adjusted
  - name: "Hozelock Watering Adjusted"
    unique_id: hozelock_watering_adjusted
    state: "{{ state_attr('sensor.hozelock_controller_status', 'controller')['isAdjusted'] }}"
# Battery status
  - name: "Hozelock Battery Status"
    unique_id: hozelock_battery_status
    state: "{{ state_attr('sensor.hozelock_controller_status', 'controller')['batteryStatus'] }}"
# Signal strength
  - name: "Hozelock Signal Strength"
    unique_id: hozelock_signal_strength
    state: "{{ state_attr('sensor.hozelock_controller_status', 'controller')['signalStrength'] }}"
# Uresponsive
  - name: "Hozelock Uresponsive"
    unique_id: hozelock_Uresponsive
    state: "{{ state_attr('sensor.hozelock_hub_status', 'hub')['isUresponsive'] }}"
# Last server contact
  - name: "Hozelock Server Last Poll"
    unique_id: hozelock_server_last_poll
    state: "{{ as_timestamp((state_attr('sensor.hozelock_hub_status', 'hub')['lastServerContactDate'] | int / 1000) | as_datetime) | timestamp_custom('%a %d/%b %-H:%M') }}"

##########################################
### Hozelock Schedule ####################
##########################################
# Water start
  - name: "Hozelock Water Start"
    unique_id: hozelock_water_start
    state: >-
      {% if (state_attr('sensor.hozelock_controller_status', 'controller')['nextWateringEvent'] is none ) %} NA
      {% else %} {{ as_timestamp((state_attr('sensor.hozelock_controller_status', 'controller')['nextWateringEvent']['startTime'] | int / 1000) | as_datetime) | timestamp_custom('%a %d/%b %-H:%M') }}
      {% endif %}    
# Water end
  - name: "Hozelock Water End"
    unique_id: hozelock_water_end
    state: >-
      {% if (state_attr('sensor.hozelock_controller_status', 'controller')['nextWateringEvent'] is none ) %} NA
      {% else %} {{ as_timestamp((state_attr('sensor.hozelock_controller_status', 'controller')['nextWateringEvent']['endTime'] | int / 1000) | as_datetime) | timestamp_custom('%a %d/%b %-H:%M') }}
      {% endif %}    
# Water duration
  - name: "Hozelock Water Duration"
    unique_id: hozelock_water_duration
    state: >-
      {% if (state_attr('sensor.hozelock_controller_status', 'controller')['nextWateringEvent'] is none ) %} NA
      {% else %} {{ (state_attr('sensor.hozelock_controller_status', 'controller')['nextWateringEvent']['duration'] | int / 60000) | int }} mins
      {% endif %}    

##########################################
### Hozelock Pause #######################
##########################################
# Pause start
  - name: "Hozelock Pause Start"
    unique_id: hozelock_pause_start
    state: >-
      {% if (state_attr('sensor.hozelock_controller_status', 'controller')['pause'] is none ) %} NA
      {% else %} {{ as_timestamp((state_attr('sensor.hozelock_controller_status', 'controller')['pause']['startTime'] | int / 1000) | as_datetime) | timestamp_custom('%a %d/%b %-H:%M') }}
      {% endif %}
# Pause end
  - name: "Hozelock Pause End"
    unique_id: hozelock_pause_end
    state: >-
      {% if (state_attr('sensor.hozelock_controller_status', 'controller')['pause'] is none ) %} NA
      {% else %} {{ as_timestamp((state_attr('sensor.hozelock_controller_status', 'controller')['pause']['endTime'] | int / 1000) | as_datetime) | timestamp_custom('%a %d/%b %-H:%M') }}
      {% endif %}
# Pause duration
  - name: "Hozelock Pause Duration"
    unique_id: hozelock_pause_duration
    state: >-
      {% if (state_attr('sensor.hozelock_controller_status', 'controller')['pause'] is none ) %} NA
      {% else %} {{ (state_attr('sensor.hozelock_controller_status', 'controller')['pause']['duration'] | int / 86400000) | int }} days
      {% endif %}

##########################################
### Hozelock Adjust ######################
##########################################
# Adjust start
  - name: "Hozelock Adjustment Start"
    unique_id: hozelock_adjustment_start
    state: >-
      {% if (state_attr('sensor.hozelock_controller_status', 'controller')['adjustment'] is none ) %} NA
      {% else %} {{ as_timestamp((state_attr('sensor.hozelock_controller_status', 'controller')['adjustment']['startTime'] | int / 1000) | as_datetime) | timestamp_custom('%a %d/%b %-H:%M') }}
      {% endif %}
# Adjust end
  - name: "Hozelock Adjustment End"
    unique_id: hozelock_adjustment_end
    state: >-
      {% if (state_attr('sensor.hozelock_controller_status', 'controller')['adjustment'] is none ) %} NA
      {% else %} {{ as_timestamp((state_attr('sensor.hozelock_controller_status', 'controller')['adjustment']['endTime'] | int / 1000) | as_datetime) | timestamp_custom('%a %d/%b %-H:%M') }}
      {% endif %}
# Adjust duration
  - name: "Hozelock Adjustment Change"
    unique_id: hozelock_adjustment_change
    state: >-
      {% if (state_attr('sensor.hozelock_controller_status', 'controller')['adjustment'] is none ) %} NA
      {% else %} {{ state_attr('sensor.hozelock_controller_status', 'controller')['adjustment']['wateringAdjustment'] }}%
      {% endif %}
# Adjust duration
  - name: "Hozelock Adjustment Duration"
    unique_id: hozelock_adjustment_duration
    state: >-
      {% if (state_attr('sensor.hozelock_controller_status', 'controller')['adjustment'] is none ) %} NA
      {% else %} {{ (state_attr('sensor.hozelock_controller_status', 'controller')['adjustment']['duration'] | int / 86400000) | int }} days
      {% endif %}

Configure helpers through the UI or add them manually using the below text.
…\config\.storage\input_number

{
  "version": 1,
  "minor_version": 1,
  "key": "input_number",
  "data": {
    "items": [
      {
        "id": "hozelock_pause_duration",
        "min": 1.0,
        "max": 355.0,
        "name": "Hozelock Pause Duration",
        "mode": "box",
        "step": 1.0,
        "unit_of_measurement": "Days",
        "icon": "mdi:clock-time-eight-outline"
      },
      {
        "id": "hozelock_adjustment_duration",
        "min": 1.0,
        "max": 14.0,
        "name": "Hozelock Adjustment Duration",
        "mode": "box",
        "step": 1.0,
        "unit_of_measurement": "Days",
        "icon": "mdi:clock-time-eight-outline"
      },
      {
        "id": "hozelock_adjustment_change",
        "min": -100.0,
        "max": 100.0,
        "name": "Hozelock Adjustment Change",
        "step": 25.0,
        "mode": "box",
        "unit_of_measurement": "%",
        "icon": "mdi:water-percent"
      }
    ]
  }
}

and here is my simple Lovelace design for the above YAML configuration.

Simply copy and paste below text into the “raw configuration editor” into a new dashboard.

views:
  - title: Hozelock
    badges: []
    cards:
      - type: entities
        entities:
          - entity: sensor.hozelock_water_start
            icon: mdi:calendar
          - entity: sensor.hozelock_water_end
            icon: mdi:calendar
          - entity: sensor.hozelock_water_duration
            icon: mdi:calendar
          - entity: sensor.hozelock_pause_start
            icon: mdi:calendar
          - entity: sensor.hozelock_pause_end
            icon: mdi:calendar
          - entity: sensor.hozelock_pause_duration
            icon: mdi:calendar
          - entity: sensor.hozelock_adjustment_start
            icon: mdi:calendar
          - entity: sensor.hozelock_adjustment_end
            icon: mdi:calendar
          - entity: sensor.hozelock_adjustment_duration
            icon: mdi:calendar
          - entity: sensor.hozelock_adjustment_change
            icon: mdi:calendar
        title: Watering Schedule
      - type: entities
        entities:
          - entity: sensor.hozelock_watering_now
            icon: mdi:watering-can
          - entity: sensor.hozelock_watering_paused
            icon: mdi:watering-can
          - entity: sensor.hozelock_watering_adjusted
            icon: mdi:watering-can
          - entity: sensor.hozelock_battery_status
            icon: mdi:battery-charging
          - entity: sensor.hozelock_signal_strength
            icon: mdi:wifi-strength-2
          - entity: sensor.hozelock_uresponsive
            icon: mdi:wan
          - entity: sensor.hozelock_server_last_poll
            icon: mdi:wan
        title: Watering Status
      - title: Watering Control
        square: false
        type: grid
        cards:
          - show_name: true
            show_icon: false
            type: button
            tap_action:
              action: call-service
              service: rest_command.hozelock_water_now
              target: {}
            entity: sensor.hozelock_hub_status
            name: Water Now
          - show_name: true
            show_icon: false
            type: button
            tap_action:
              action: call-service
              service: rest_command.hozelock_stop_watering
              target: {}
            entity: sensor.hozelock_hub_status
            name: Stop Watering
          - show_name: true
            show_icon: false
            type: button
            tap_action:
              action: call-service
              service: rest_command.hozelock_pause
              target: {}
            entity: sensor.hozelock_hub_status
            name: Pause Schedule
          - show_name: true
            show_icon: false
            type: button
            tap_action:
              action: call-service
              service: rest_command.hozelock_unpause
              target: {}
            entity: sensor.hozelock_hub_status
            name: Remove Pause
          - show_name: true
            show_icon: false
            type: button
            tap_action:
              action: call-service
              service: rest_command.hozelock_adjust
              target: {}
            entity: sensor.hozelock_hub_status
            name: Adjust Duration
          - show_name: true
            show_icon: false
            type: button
            tap_action:
              action: call-service
              service: rest_command.hozelock_unadjust
              target: {}
            entity: sensor.hozelock_hub_status
            name: Remove Adjustment
        columns: 2
      - type: entities
        entities:
          - entity: input_number.hozelock_pause_duration
          - entity: input_number.hozelock_adjustment_duration
          - entity: input_number.hozelock_adjustment_change
        title: Watering Override
title: Hozelock

I made a simple Hozelock card using Button-Card , Stack-in-Card and Card-Mod for those interested

image

and here is the yaml

type: custom:stack-in-card
title: Watering
mode: vertical
box_shadow: false
cards:
  - type: custom:stack-in-card
    mode: horizontal
    box_shadow: false
    card_mod:
      style: |
        ha-card { border: none; box-shadow: none; }
    cards:
      - type: custom:button-card
        entity: sensor.hozelock_watering
        show_name: false
        show_state: false
        show_label: false
        name: Watering
        size: 100%
        color_type: auto
        icon: mdi:watering-can
        card_mod:
          style: |
            ha-card { border: none; box-shadow: none; }
        state:
          - value: 'True'
            styles:
              icon:
                - color: var(--paper-item-icon-active-color)
          - value: 'False'
            styles:
              icon:
                - color: var(--paper-item-icon-color)
        styles:
          icon:
            - color: steelblue
          card:
            - height: 185px
            - padding: 5px
      - type: custom:stack-in-card
        mode: vertical
        box_shadow: false
        card_mod:
          style: |
            ha-card { border: none; box-shadow: none; }        
        cards:
          - type: custom:button-card
            entity: sensor.hozelock_water_start
            name: Start
            show_icon: false
            show_state: true
            layout: name_state
            tooltip: Schedule start time
            card_mod:
              style: |
                ha-card { border: none; box-shadow: none; }
            styles:
              card:
                - height: 35px
                - width: 180px
                - padding: 5px 0px 5px 0px
                - margin-top: 40px
              name:
                - white-space: nowrap
                - overflow: hidden
                - text-overflow: clip
                - justify-self: start
          - type: custom:button-card
            entity: sensor.hozelock_water_end
            name: End
            show_icon: false
            show_state: true
            layout: name_state
            tooltip: Schedule end time
            card_mod:
              style: |
                ha-card { border: none; box-shadow: none; }
            styles:
              card:
                - height: 35px
                - width: 180px
                - padding: 5px 0px 5px 0px
              name:
                - white-space: nowrap
                - overflow: hidden
                - text-overflow: clip
                - justify-self: start
          - type: custom:button-card
            entity: sensor.hozelock_water_duration
            name: Duration
            show_icon: false
            show_state: true
            layout: name_state
            tooltip: Schedule duration
            card_mod:
              style: |
                ha-card { border: none; box-shadow: none; }
            styles:
              card:
                - height: 35px
                - width: 180px
                - padding: 5px 0px 5px 0px
              name:
                - white-space: nowrap
                - overflow: hidden
                - text-overflow: clip
                - justify-self: start
  - type: custom:stack-in-card
    mode: horizontal
    box_shadow: false
    card_mod:
      style: |
        ha-card { border: none; box-shadow: none; }
    cards:
      - type: conditional
        conditions:
          - entity: sensor.hozelock_watering_now
            state: 'False'
        card:
          type: custom:button-card
          color: auto
          icon: mdi:water-plus
          name: Water Now
          size: 100%
          tooltip: Water Now
          styles:
            icon:
              - color: steelblue
            card:
              - height: 110px
              - padding: 15px 0px 15px 0px
            name:
              - white-space: nowrap
              - overflow: hidden
              - text-overflow: clip
          tap_action:
            action: call-service
            service: rest_command.hozelock_water_now
            target: {}
      - type: conditional
        conditions:
          - entity: sensor.hozelock_watering_now
            state: 'True'
        card:
          type: custom:button-card
          color: auto
          icon: mdi:water-minus
          name: Stop Watering
          size: 100%
          tooltip: Stop Watering
          styles:
            icon:
              - color: steelblue
            card:
              - height: 110px
              - padding: 15px 0px 15px 0px
            name:
              - white-space: nowrap
              - overflow: hidden
              - text-overflow: clip
          tap_action:
            action: call-service
            service: rest_command.hozelock_stop_watering
            target: {}
      - type: conditional
        conditions:
          - entity: sensor.hozelock_watering_adjusted
            state: 'False'
        card:
          type: custom:button-card
          color: auto
          icon: mdi:weather-sunny
          name: Adjust
          size: 100%
          tooltip: Adjust Watering
          styles:
            icon:
              - color: steelblue
            card:
              - height: 110px
              - padding: 15px 0px 15px 0px
            name:
              - white-space: nowrap
              - overflow: hidden
              - text-overflow: clip
          tap_action:
            action: call-service
            service: rest_command.hozelock_adjust
            target: {}
      - type: conditional
        conditions:
          - entity: sensor.hozelock_watering_adjusted
            state: 'True'
        card:
          type: custom:button-card
          color: auto
          icon: mdi:weather-rainy
          name: Remove Adjust
          size: 100%
          tooltip: Remove Adjustment
          styles:
            icon:
              - color: steelblue
            card:
              - height: 110px
              - padding: 15px 0px 15px 0px
            name:
              - white-space: nowrap
              - overflow: hidden
              - text-overflow: clip
          tap_action:
            action: call-service
            service: rest_command.hozelock_unadjust
            target: {}
      - type: conditional
        conditions:
          - entity: sensor.hozelock_watering_paused
            state: 'False'
        card:
          type: custom:button-card
          color: auto
          icon: mdi:calendar-month-outline
          name: Pause
          size: 100%
          tooltip: Pause Watering
          styles:
            icon:
              - color: steelblue
            card:
              - height: 110px
              - padding: 15px 0px 15px 0px
            name:
              - white-space: nowrap
              - overflow: hidden
              - text-overflow: clip
          tap_action:
            action: call-service
            service: rest_command.hozelock_pause
            target: {}
      - type: conditional
        conditions:
          - entity: sensor.hozelock_watering_paused
            state: 'True'
        card:
          type: custom:button-card
          color: auto
          icon: mdi:calendar-remove-outline
          name: Remove Pause
          size: 100%
          tooltip: Remove Pause
          styles:
            icon:
              - color: steelblue
            card:
              - height: 110px
              - padding: 15px 0px 15px 0px
            name:
              - white-space: nowrap
              - overflow: hidden
              - text-overflow: clip
          tap_action:
            action: call-service
            service: rest_command.hozelock_unpause
            target: {}

Hello there, I have just got the hozelock cloud controller and I am going to use your code.
Any tips?

How long of a time dely do you get.

Hi @martynjsimpson

Thanks for your reverse engineering article write up! :wink:

I am wondering whether you can share the schedule PATCH example that you make reference too in the article?

PATCH https://hoz3.com/restful/support/hubs/{hubId}/schedules/{scheduleId}

I am specifically looking for an example to modify the watering duration for existing schedules. i.e. from 10 minutes to say 5, etc.

So I found if you simply change from PATCH to PUT it works by sending the JSON schedule in the payload and the schedule updates work :grinning:

PUT https://hoz3.com/restful/support/hubs/{hubId}/schedules/{scheduleId}

But on a separate note, when I logged into the Hozelock mobile app I was kindly notified the Hozelock cloud service will end in April 2027. Really annoying but we’re often reminded to stay clear of cloud services! :rofl:

Since the Hozelock Cloud Controller seems like it’s heading for e-waste after April 2027, does anyone have any thoughts on a way to send commands / requests directly to the controller itself on a local network (unless that’s already possible) instead of through the app and to the cloud?

If it’s not possible to send commands to the controller, is there potentially a way to create a server (or something similar) that can simulate the commands Hozelock sends to the cloud controller and create a basic Web UI for it? I did read through these posts and it looks like nobody has tried local communication to the controller hub.

I’m new to Home Assistant and APIs in general, and I know the Hozelock Cloud Controller is more or less a legacy (and soon to be unsupported) device, but I’m willing to try and save this hardware from going to e-waste if it’s possible. Is anyone here able to provide some help / knowledge / expertise in reverse engineering this?

Hi

I’ve just seen you post how can I modify the app or is it not possible, how best is it to overcome the April 27 deadline many thanks

I don’t have any solutions for local control of Hozelock, however I’ve decided to bail and switch early to LinkTap ahead of the Hozelock Cloud going dark in 2027.

I purchased the two valve D1 version and so far I’m really impressed. It has local MQTT and HTTP API support, alongside their cloud offering using the mobile app, web interface or cloud API. There is also a HA custom local integration which does the job well. It also has MQTT HA discovery if you want to go down that route.

The LinkTap is a like-for-like swap out. It uses a local hub/controller which communicates over proprietary Zigbee to the wireless tap valve, just like the Hozelock solution. This keeps battery drain to a minimum which they say is up to two years.

LinkTap is much more responsive to control too, maximum is 15 seconds from sending the command to water. Whilst the water valve itself is using some type of solenoid valve, which is instant to open or close, unlike the Hozelock solution which is using some type of motorised gear.

Overall happy with it.