Lay-Z-Spa Hot Tub wi-fi pump automation

I’ve recently got a Lay-Z-Spa (Bestway) Hot Tub with the wi-fi pump (egg).
https://www.lay-z-spa.co.uk/lay-z-spa-wifi-app

I’ve captured the traffic from the app to allow me to automate in home assistant.
Interestingly at the moment, their app is not working properly, so this home assistant setup works better than the official app !

If anyone wants to do the same, then this might be useful.

Please get in touch if you have any questions.

This is an example call to check if the pump is online.

  - platform: rest
    name: hottub_online
    resource: "https://mobileapi.lay-z-spa.co.uk/v1/gizwits/is_online"
    headers:
      Content-Type: application/x-www-form-urlencoded
    method: POST
    payload: !secret hottub_api_did
    value_template: "{{ value_json.data }}"

The secrets you will need are…

hottub_api_did: did=AAaAaAAAAAAaaaaAAA9A9b&api_token=a1a1a1a1a2a2a2a2a12a1a1a1a2a2a2a2
hottub_email_password: email=email%40domain.com&password=XXxxYYyy

The API DID and API Token are obtained by using:

  - platform: rest
    name: hottub_login
    scan_interval: 172000
    resource: "https://mobileapi.lay-z-spa.co.uk/v1/auth/login"
    headers:
      Content-Type: application/x-www-form-urlencoded
    method: POST
    payload: !secret hottub_email_password
    value_template: "{{ value_json.data.api_token }}"    
    json_attribute_path: "$.devices[0]"
    json_attributes:
      - did

You can get all status information using…

  - platform: rest
    name: hottub_status
    resource: "https://mobileapi.lay-z-spa.co.uk/v1/gizwits/status"
    headers:
      Content-Type: application/x-www-form-urlencoded
    method: POST
    payload: !secret hottub_api_did
    value_template: "{{ value_json.data.attr.power }}"    
    json_attributes_path: "$.data.attr"
    json_attributes:
      - system_err2
      - wave_appm_min
      - heat_timer_min
      - heat_power
      - earth
      - wave_timer_min
      - system_err6
      - system_err7
      - system_err4
      - system_err5
      - heat_temp_reach
      - system_err3
      - system_err1
      - system_err8
      - system_err9
      - filter_timer_min
      - heat_appm_min
      - power
      - temp_set_unit
      - filter_appm_min
      - temp_now
      - wave_power
      - locked
      - filter_power
      - temp_set

I’ve created template sensors…

  - platform: template
    sensors:
      hottub_pump_temp:
        availability_template: "{{ states('sensor.hottub_online') }}"
        friendly_name: "HotTub Pump Temperature"
        unit_of_measurement: '°C'
        value_template: "{{ state_attr('sensor.hottub_status', 'temp_now') | int }}"  
      hottub_water_temp:
        availability_template: "{{ states('sensor.hottub_online') }}"
        friendly_name: "HotTub Water Temperature"
        unit_of_measurement: '°C'        
        value_template: "{{ states('input_number.hottub_water_temp') | int }}"
      hottub_pump_target:
        availability_template: "{{ states('sensor.hottub_online') }}"
        friendly_name: "HotTub Pump Target"
        unit_of_measurement: '°C'
        value_template: "{{ state_attr('sensor.hottub_status', 'temp_set') | int }}"      
      hottub_summary:
        availability_template: "{{ states('sensor.hottub_online') }}"
        friendly_name: "HotTub Status"
        value_template: "{% if states('sensor.hottub_online') == 'false' %}offline{% elif states('input_boolean.hottub_scheduled') == 'on' and states('switch.hottub_filter') == 'off' %}scheduled{% elif states('switch.hottub_power') == 'off' %}off{% elif states('input_boolean.hottub_scheduled')=='on' %}scheduled{% elif states('switch.hottub_filter')=='off' %}on{% elif states('switch.hottub_heat')=='off'%}filter{% elif state_attr('sensor.huttub_status','heat_temp_reach')=='1' %}reached{% elif states('switch.hottub_bubbles')=='off' %}heat{% else %}bubbles{% endif %}"

some commands…

rest_command:
  hottub_command: 
    method: POST
    content_type: "application/x-www-form-urlencoded"
    url: "https://mobileapi.lay-z-spa.co.uk/v1/gizwits/{{hottub_command}}"
    payload: !secret hottub_api_did

  hottub_temp_set: 
    method: POST
    content_type: "application/x-www-form-urlencoded"
    url: "https://mobileapi.lay-z-spa.co.uk/v1/gizwits/temp_set"
    payload: "&{{hottub_api_did}}&temperature={{ states('input_number.hottub_water_target') | int }}"

  hottub_temp_set_unit: 
    method: POST
    content_type: "application/x-www-form-urlencoded"
    url: "https://mobileapi.lay-z-spa.co.uk/v1/gizwits/temp_set_unit"
    payload: "{{hottub_api_did}}&unit={{hottub_tempcode}}"
  # on for 3 hours, off for 4 at 36
  # 0, 0, 0 for cancel schedule
  hottub_set_schedule: 
    method: POST
    content_type: "application/x-www-form-urlencoded"
    url: "https://mobileapi.lay-z-spa.co.uk/v1/gizwits/set_schedule"
    payload: "{{ hottub_api_did }}&heat_in={{ hottub_heat_in | int | string }}&duration={{ hottub_duration | int | string }}&temperature={{ hottub_temp | int | string }}"

and some switches…

switch:
  - platform: template
    switches:
      hottub_power:
        availability_template: "{{ states('sensor.hottub_online') }}"
        friendly_name: Power
        unique_id: hottub_power
        value_template: "{% if state_attr('sensor.hottub_status', 'power') == 1 %}on{% else %}off{% endif %}"
        turn_on:
          - service: rest_command.hottub_command
            data: 
              hottub_command: turn_on
          - delay: 00:00:05
          - service: homeassistant.update_entity
            entity_id: sensor.hottub_status
        turn_off:
          - service: rest_command.hottub_command
            data: 
              hottub_command: turn_off
          - delay: 00:00:05
          - service: homeassistant.update_entity
            entity_id: sensor.hottub_status
      hottub_filter:
        availability_template: "{{ states('sensor.hottub_online') }}"
        unique_id: hottub_filter
        friendly_name: Filter  
        value_template: "{% if state_attr('sensor.hottub_status', 'filter_power') == 1 %}on{% else %}off{% endif %}"
        turn_on:
          - service: rest_command.hottub_command
            data: 
              hottub_command: turn_filter_on
          - delay: 00:00:05
          - service: homeassistant.update_entity
            entity_id: sensor.hottub_status
        turn_off:
          - service: rest_command.hottub_command
            data: 
              hottub_command: turn_filter_off
          - delay: 00:00:05
          - service: homeassistant.update_entity
            entity_id: sensor.hottub_status
      hottub_heat:
        availability_template: "{{ states('sensor.hottub_online') }}"
        unique_id: Heater
        friendly_name: Heater      
        value_template: "{% if state_attr('sensor.hottub_status', 'heat_power') == 1 %}on{% else %}off{% endif %}"
        turn_on:
          - service: rest_command.hottub_command          
            data: 
              hottub_command: turn_heat_on
          - delay: 00:00:05
          - service: homeassistant.update_entity
            entity_id: sensor.hottub_status
        turn_off:
          - service: rest_command.hottub_command
            data: 
              hottub_command: turn_heat_off
          - delay: 00:00:05
          - service: homeassistant.update_entity
            entity_id: sensor.hottub_status
      hottub_bubbles:
        availability_template: "{{ states('sensor.hottub_online') }}"
        unique_id: hottub_bubbles
        friendly_name: Bubbles 
        value_template: "{% if state_attr('sensor.hottub_status', 'wave_power') == 1 %}on{% else %}off{% endif %}"
        turn_on:
          - service: rest_command.hottub_command
            data: 
              hottub_command: turn_wave_on
          - delay: 00:00:05
          - service: homeassistant.update_entity
            entity_id: sensor.hottub_status
        turn_off:
          - service: rest_command.hottub_command   
            data: 
              hottub_command: turn_wave_off
          - delay: 00:00:05
          - service: homeassistant.update_entity
            entity_id: sensor.hottub_status

Finally some automations to turn on the pump and log the water temperature periodically and to allow target temp to be captured and updated…

automation:
  - alias: Set Hot Tub Temp
    mode: restart
    trigger:
      - platform: state
        entity_id: sensor.hottub_pump_temp
      - platform: time_pattern
        minutes: '/15'
    condition:
      - condition: state
        entity_id: 'switch.hottub_filter'
        state: 'on'
      - condition: numeric_state
        entity_id: 'sensor.hottub_pump_temp'
        above: 20
    action:
      - delay: 00:01:00
      - service: input_number.set_value
        data:
          entity_id: input_number.hottub_water_temp
          value: "{{ states('sensor.hottub_pump_temp') | int }}"
        
  - alias: Pump on five every 30
    trigger:
      - platform: time_pattern
        minutes: '/30'
    condition:
      condition: state
      entity_id: 'switch.hottub_filter'
      state: 'off'
    action:
      - service: rest_command.hottub_command
        data: 
          hottub_command: turn_filter_on
      - delay: 00:05:00
      - service: rest_command.hottub_command
        data: 
          hottub_command: turn_off

  - alias: Hot Tub Off At Peak
    trigger:
      - platform: state
        entity_id: switch.hottub_heat
        to: 'on'
      - platform: time
        at: "16:00:00"
    condition:
      - condition: state
        entity_id: switch.hottub_heat
        state: 'on'
      - condition: time
        after: '15:59:00'
        before: '19:00:00'
    action:
      service: rest_command.hottub_command
      data: 
        hottub_command: turn_off

  - alias: Set Hot Tub Target
    trigger:
      platform: state
      entity_id: sensor.hottub_pump_target
    condition: 
      condition: numeric_state
      entity_id: 'sensor.hottub_pump_target'
      above: 20
    action:
      service: input_number.set_value
      data_template:
        entity_id: input_number.hottub_water_target
        value: "{{ states('sensor.hottub_pump_target') | int }}"
        
  - alias: Change Hot Tub Target
    trigger:
      platform: state
      entity_id: input_number.hottub_water_target
    action:
      - service: rest_command.hottub_temp_set
        data: 
          hottub_api_did: !secret hottub_api_did

Next is to use an automation to ensure that the tub is at target temperature by a particular time whilst not heating during peak electric pricing (4 till 7pm - Octopus Agile).

Thanks for reading.

9 Likes

I was waiting for someone to do this! Brilliant.

What do you use to capture the traffic, wireshark?

1 Like

Thats great work - now all I need is to wait for my existing pump to break again and I can get the wifi one :slight_smile:

1 Like

I used fiddler to listen in on the app.
Weirdly the app never uses the status call to get the current status.
It seems to rely on some kind of local network broadcast.
So when you are away from home you can’t see current status in the app.
Luckily I guessed the URL as “status” abs for all the info.

1 Like

Hey @BruceH5200! Thanks for the tips, I am a noob to HA, I tried to add the code to configuration.yaml and the secrets.yaml file (editing the usn and psw) but it doesn’t seem to be working. Any chance you could give a hint in the right direction? :slight_smile:

Thanks

M

@mayeul - did you get it working ?

@BruceH5200 Yes I did manage to find the DID and API Token :slight_smile:
Now I’m trying to have the system be recognised as a Thermostat and be controllable from HomeKit :slight_smile:

Thanks a lot for everything!

interesting.
I’m guessing you are using…

I’m having a go myself too.
Let me know how you get on.

Thanks

Did you find them through the login sensor? or another way?

@BruceH5200 I used Fiddler to capture the traffic from the app in my iPhone :slight_smile:
I couldn’t manage to have something nice and convenient to work with the thermostat (unable pass on the target T°, choose between statuses: OFF, ON, FILTER, HEAT, AIRJETS. So in the end, I just kept the 4 Switches and passed the PumpTemp sensor info to HomeKit, so I have the main informations. I just leave the Target T° to a “permanent” value, which I can only change from the Laz-Y-Spa App or from their website :slight_smile:

Thanks again!

You should be able to use the login sensor, it should show you those values to save you the trouble of using fiddler.
Using the generic thermostat, I’ve got it working, but don’t think I’ll use it as it will always be battling with the settings in my integration.
So yes, the swithces and a temp feedback are probably the best for homekit.

I don’t use the pump temp, I use the water temp.
I have it scheduled so that the pump goes on for 5 minutes every 30mins and it captures the pump temp when it is running. I guess if you have your pump on all the time, then the pump temp is fine !

Water T° is at 0°C, maybe we don’t have the same model (Milan here), so the Pump T° is the only info I have about T°. The filtering is almost running 24/7 as the spa is being used usually around 2/3 times a day. :slight_smile:

In my case, Home Assistant is basically used to get in HomeKit everything we have connected in the house (mainly: Legrand/BTicino SCS for lighting & covers, Gree for the AC/Heating, Miele for the appliances)

@mayeul How do you integrate the Miele appliances?
Would like to do the same but can’t find any info on this.

@BruceH5200 Great work! This is exactly what I want to do with my lay-z-spa (the app is very buggy in my opinion).
However, I am very much a noob at HA. Is there any way this could be packaged in HACS as an integration or custom component?

When I paste the code for the hottub login sensor into the sensor definitions in my configuration.yaml file, I get an error " Invalid config for [sensor.rest]: [json_attribute_path] is an invalid option for [sensor.rest]. Check: sensor.rest->json_attribute_path. (See ?, line ?). " Any ideas what I’m doing wrong please?

@BruceH5200 I appear to have taught myself enough to get this working!
When I ask for the “temp unit set” data the string returned is in Chinese. Is there a parameter to add to the request to get the value returned in English?

That’s what it returns. I just ignore it as I will always have it set to C (not F) so it doesn’t really matter.
Well done for getting it working.

1 Like

Hi @BruceH5200,
thats an awesome find - thanks for shareing :slight_smile:

i’m not using HA (but OH) - however i would love to get this going with i.e. MQTT or similar.
From what I can see, the snippets for REST API is for some HA specific scripts?
Would it be possible to give an example with say Postman? :slight_smile:
Did some quick tries, and only get 404’s :slight_smile:

Regards,
Chris

Hi, You’ve got a lot of stuff there that shouldn’t be there.

LOGIN

POST -> https://mobileapi.lay-z-spa.co.uk/v1/auth/login

Headers:
Content-Type: application/x-www-form-urlencoded
Body: email=[email address - replace “@” with “%40”]&password=[password]

eg. email=bob%40gmail.com&password=abc123

STATUS

POST -> https://mobileapi.lay-z-spa.co.uk/v1/gizwits/status

Headers:
Content-Type: application/x-www-form-urlencoded

Body:
did=xxxxxxxxxx&api_token=xxxxxxxxxxxx
(get them from the login call)

Most of the other calls are the same:

URLS

https://mobileapi.lay-z-spa.co.uk/v1/gizwits/is_online
https://mobileapi.lay-z-spa.co.uk/v1/gizwits/turn_off
https://mobileapi.lay-z-spa.co.uk/v1/gizwits/turn_on
https://mobileapi.lay-z-spa.co.uk/v1/gizwits/turn_filter_off
https://mobileapi.lay-z-spa.co.uk/v1/gizwits/turn_filter_on
https://mobileapi.lay-z-spa.co.uk/v1/gizwits/turn_heat_off
https://mobileapi.lay-z-spa.co.uk/v1/gizwits/turn_heat_on
https://mobileapi.lay-z-spa.co.uk/v1/gizwits/turn_wave_off
https://mobileapi.lay-z-spa.co.uk/v1/gizwits/turn_wave_on
https://mobileapi.lay-z-spa.co.uk/v1/gizwits/temp_set (need to add temperature=xx to the body)

Great, thank you - got it working now.
the trick is to use “raw” as body, then it works :smiley:

/Chris

1 Like

Love this project @BruceH5200… copied with pride. :slightly_smiling_face:
thanks for sharing!

1 Like