Integration with Gardena Smart Sileno mowers, by using sensors

I had problems with the HACS integration for my Gardena Smart Sileno mower, and noticed that others had too. I found out a simple way to just use sensors that pull data from the API. So far I just pull data, but I will try to add automations for start & stop buttons.

This code will show status and error messages in swedish.
Edit: This will probably NOT work if you have other smart Gardena devices. Unless you make a few changes to extract correct data from the json reply.

First register at Husqvarna Developer Portal
You will then get YOUR_API_KEY and YOUR_API_SECRET.

configuration.yaml:

# Get gardena token 
  - platform: rest
    name: gardena_token
    resource: https://api.authentication.husqvarnagroup.dev/v1/oauth2/token
    method: POST
    scan_interval: 86400  # token expires after 24 hours
    headers:
      Content-Type: application/x-www-form-urlencoded
    payload: grant_type=client_credentials&client_id=YOUR_API_KEY&client_secret=YOUR_API_SECRET
    json_attributes:
      - access_token
      - token_type
      - expires_in
    value_template: 'ok'

# Get your gardena location ID. This value will usually never change.
  - platform: command_line
    name: gardena_location
    scan_interval: 86460  # we do no want this to update often, because of API limit
    command: >-
      curl -X GET -H "Authorization:Bearer {{states.sensor.gardena_token.attributes["access_token"]}}" -H "X-Api-Key:YOUR_API_KEY" https://api.smart.gardena.dev/v1/locations
    value_template: '{{ value_json.data[0].id }}'

# Get mower status and store all values as attributes in a sensor
  - platform: command_line
    name: gardena_status
    scan_interval: 1200 # Make sure not to exceed 700 calls/week (including commands) or your api-key will be permanently disabled by the provider
    command: >-
      curl -H "Authorization:Bearer {{states.sensor.gardena_token.attributes["access_token"]}}" -H "X-Api-Key:YOUR_API_KEY" https://api.smart.gardena.dev/v1/locations/{{states.sensor.gardena_location.state}}
      | jq '{"activity": .included[1].attributes.activity.value, "timestamp": .included[1].attributes.activity.timestamp, "operatingHours": .included[1].attributes.operatingHours.value, "status": .included[1].attributes.state.value, "batteryLevel": .included[2].attributes.batteryLevel.value, "rfLinkLevel": .included[2].attributes.rfLinkLevel.value, "lastErrorCode": (.included[1].attributes.lastErrorCode.value // "none"), "location": .data.id, "serviceId": .included[0].relationships.services.data[0].id}'
    json_attributes:
    - status
    - activity
    - timestamp
    - operatingHours
    - batteryLevel
    - rfLinkLevel
    - lastErrorCode
    - location
    - serviceId
    value_template: '{{ value_json.activity }}'

# Make a set of sensors with  values from the attributes of the gardena_status sensor      
template:
- sensor:
    - name: gardena_activity
      state: >-
        {% set mapper =  {
            'PAUSED' : 'Pausad med luckan stängd',
            'OK_CUTTING' : 'Klipper pĂĄ schema',
            'OK_CUTTING_TIMER_OVERRIDDEN' : 'Klipper utanför schema',
            'OK_SEARCHING' : 'Letar efter laddstationen',
            'OK_LEAVING' : 'Lämnar laddstationen',
            'OK_CHARGING' : 'Fortsätter klippa när batteriet är laddat',
            'PARKED_TIMER' : 'Väntar på start enligt schema',
            'PARKED_PARK_SELECTED' : 'Tillsagd att inte klippa',
            'PARKED_AUTOTIMER' : 'Gräset är redan välklippt',
            'NONE' : 'Ett fel har inträffat' } %}
        {% set state =  states.sensor.gardena_status.attributes["activity"] %}
        {{ mapper[state] if state in mapper else 'Unknown' }}
    - name: gardena_battery
      device_class: battery
      unit_of_measurement: '%'
      state: '{{states.sensor.gardena_status.attributes["batteryLevel"]}}'
    - name: gardena_state
      state: '{{states.sensor.gardena_status.attributes["status"]}}'
    - name: gardena_hours
      device_class: duration
      unit_of_measurement: 'timmar'
      state: '{{states.sensor.gardena_status.attributes["operatingHours"]}}'
    - name: gardena_error
      state: >-
        {% set mapper =  {
              'none' : 'Inget',
              'OUTSIDE_WORKING_AREA' : 'Utanför arbetsområdet',
              'NO_LOOP_SIGNAL' : 'Ingen signal frĂĄn slingan',
              'WRONG_LOOP_SIGNAL' : 'Fel slingsignal',
              'WRONG_PIN_CODE' : 'Fel PIN-kod',
              'TRAPPED' : 'Instängd',
              'UPSIDE_DOWN' : 'Vält omkull',
              'EMPTY_BATTERY' : 'Tomt batteri',
              'TEMPORARILY_LIFTED' : 'Tillfälligt lyft från marken',
              'LIFTED' : 'Blivit lyft frĂĄn marken',
              'STUCK_IN_CHARGING_STATION' : 'Fast i laddstation',
              'CHARGING_STATION_BLOCKED' : 'Laddstation blockerad',
              'CUTTING_MOTOR_DRIVE_DEFECT' : 'Klippsystemet blockerat',
              'CUTTING_SYSTEM_BLOCKED' : 'Klippsystemet blockerat',
              'CHARGING_SYSTEM_PROBLEM' : 'Problem med laddsystemet',
              'MOWER_TILTED' : 'Gräsklipparen lutad',
              'ALARM_MOWER_SWITCHED_OFF' : 'Larm: Avstängd',
              'ALARM_MOWER_STOPPED' : 'Larm: Stannat',
              'ALARM_MOWER_LIFTED' : 'Larm: Lyfts',
              'ALARM_MOWER_TILTED' : 'Larm: Lutats',
              'ALARM_MOWER_IN_MOTION' : 'Larm: Flyttas',
              'ALARM_OUTSIDE_GEOFENCE' : 'Larm: Utanför geofence',
              'SLIPPED' : 'Gräsklipparen har halkat.',
              'OFF_DISABLED' : 'Avstängd med strömbrytaren',
              'OFF_HATCH_OPEN' : 'Väntar med luckan öppen',
              'OFF_HATCH_CLOSED' : 'Väntar med luckan stängd',
              'PARKED_DAILY_LIMIT_REACHED' : 'Max tillåten klipptid för idag' } %}
        {% set state =  states.sensor.gardena_status.attributes["lastErrorCode"] %}
        {{ mapper[state] if state in mapper else 'Okänt fel' }}
    - name: gardena_last_change
      device_class: timestamp
      state: '{{states.sensor.gardena_status.attributes["timestamp"]}}'
    - name: gardena_signal
      device_class: signal_strength
      unit_of_measurement: '%'
      state: '{{states.sensor.gardena_status.attributes["rfLinkLevel"]}}'

The code has a problem: On reboot of Home Assistant, all three sensors try to update at once. That will not work, as they are dependent of each other. So on reboot we need to update sensor gardena_location with a delay, and then gardena_status with an even longer delay. These two automations does the trick:

alias: Uppdatera Gardena location vid reboot
description: ""
trigger:
  - platform: homeassistant
    event: start
condition: []
action:
  - delay: "00:00:30"
  - service: homeassistant.update_entity
    target:
      entity_id: sensor.gardena_location
    data: {}
mode: single

alias: Uppdatera Gardena status vid reboot
description: ""
trigger:
  - platform: homeassistant
    event: start
condition: []
action:
  - delay: "00:00:59"
  - service: homeassistant.update_entity
    target:
      entity_id: sensor.gardena_status
    data: {}
mode: single

Finaly, make a lovelace card to show the data. I’m using some swedish here:

type: entities
entities:
  - entity: sensor.gardena_activity
    name: Aktivitet
    icon: mdi:robot-mower
  - entity: sensor.gardena_last_change
    name: Sedan
  - entity: sensor.gardena_battery
    name: Laddning
  - entity: sensor.gardena_signal
    name: Signalstyrka
  - entity: sensor.gardena_hours
    name: Drifttid
  - entity: sensor.gardena_error
    name: Felmeddelande
    icon: mdi:alert-circle

Result:

gardena_card

And here are the addons for controlling the mower.

configuration.yaml:


shell_command:
    # Start_seconds_to_override must be a multiple of 3600
    gardena_on_1h: "curl -X PUT -d '{\"data\": {\"type\": \"MOWER_CONTROL\", \"id\": \"request-HA\", \"attributes\": {\"command\": \"START_SECONDS_TO_OVERRIDE\", \"seconds\": 3600}}}' -H 'Authorization:Bearer {{states.sensor.gardena_token.attributes[\"access_token\"]}}' -H 'X-Api-Key:YOUR_API_KEY' -H 'Content-Type: application/vnd.api+json' https://api.smart.gardena.dev/v1/command/{{states.sensor.gardena_status.attributes[\"serviceId\"]}}"
    gardena_on_3h: "curl -X PUT -d '{\"data\": {\"type\": \"MOWER_CONTROL\", \"id\": \"request-HA\", \"attributes\": {\"command\": \"START_SECONDS_TO_OVERRIDE\", \"seconds\": 10800}}}' -H 'Authorization:Bearer {{states.sensor.gardena_token.attributes[\"access_token\"]}}' -H 'X-Api-Key:YOUR_API_KEY' -H 'Content-Type: application/vnd.api+json' https://api.smart.gardena.dev/v1/command/{{states.sensor.gardena_status.attributes[\"serviceId\"]}}"
    gardena_park: "curl -X PUT -d '{\"data\": {\"type\": \"MOWER_CONTROL\", \"id\": \"request-HA\", \"attributes\": {\"command\": \"PARK_UNTIL_NEXT_TASK\", \"seconds\": 0}}}' -H 'Authorization:Bearer {{states.sensor.gardena_token.attributes[\"access_token\"]}}' -H 'X-Api-Key:YOUR_API_KEY' -H 'Content-Type: application/vnd.api+json' https://api.smart.gardena.dev/v1/command/{{states.sensor.gardena_status.attributes[\"serviceId\"]}}"

Lovelace card:

type: entities
title: Mower controls
entities:
  - type: button
    name: Run for 1 hour
    icon: mdi:power-cycle
    tap_action:
      action: call-service
      confirmation:
        text: Are you sure you want to start mower?
      service: shell_command.gardena_on_1h
  - type: button
    name: Run for 3 hours
    icon: mdi:power-cycle
    tap_action:
      action: call-service
      confirmation:
        text: Are you sure you want to start mower?
      service: shell_command.gardena_on_3h
  - type: button
    name: Park until next task
    icon: mdi:power-cycle
    tap_action:
      action: call-service
      confirmation:
        text: Are you sure you want to park mower?
      service: shell_command.gardena_park

Result:
mower_ctrl_card

An improvement of this would be making a script for the shell commands, and make the buttons call that script.
Then make the script send shell command, delay, then update gardena_status.
This will make the status to update after a button press.

Thanks Gustaf, this is working great.

Here´s another improvement.

The API is limited to 700 requests per week. Do not exceed the limit, or your API-key will be disabled, and you have to register for a new one. If you want more frequent updates, you can update only during the day. This is how:

Set the scan_interval of the sensor gardena_status to a very high number of seconds (> 100000).

Add an automation to update the sensor during daytime. This one will make 600 API-calls per week, with 100 left for sending commands:

alias: Update sensor.gardena_status daytime
description: ""
trigger:
  - platform: time_pattern
    minutes: /7
condition:
  - condition: time
    after: "10:00:00"
    before: "20:00:00"
    weekday:
      - mon
      - tue
      - wed
      - thu
      - fri
      - sat
      - sun
action:
  - service: homeassistant.update_entity
    target:
      entity_id: sensor.gardena_status
    data: {}
mode: single
1 Like

Hi! First of all thank you for work. But I’m stuck. I have managed to get the sensors to show up, but they aren’t refreshing. And when I try to use the buttons for the start and stop commands nothing works. I’m not so found in programming so I tried to copy-paste your code and change the api key and secret to my keys everywhere I could find them. One each in the token segment one key in the location another in the values section. I also copied my key to the 1h 3h and dock parts. Did I miss something or what am I doing wrong?
Thank you for your help in advance.

For troubleshooting, add a card with entities: gardena_token, gardena_location and gardena_status.
gardena_token should be set to “OK” (with the token data stored in an attribute)
gardena_location should be set with a 36-character value

Do you have values in any of them?

Yes, I have an ok for the token, a 36-character value for the location and for status it’s none

That’s good news. You have a connection to the API and your device is found.

You can try adding this custom card for trobleshooting.
You will be able to update token, location, and status manually by pressing “Run”.
The token has to be refreshed every 24h, so try pushing that if HA is not restarted today.
Then try to update the status.

Do you have other connected Gardena devices? I don’t know if this integration can handle that.

type: entities
entities:
  - type: button
    name: Update token
    icon: mdi:power-cycle
    tap_action:
      action: call-service
      service: homeassistant.update_entity
      target:
        entity_id: sensor.gardena_token
      data: {}
  - type: button
    name: Update location
    icon: mdi:power-cycle
    tap_action:
      action: call-service
      service: homeassistant.update_entity
      target:
        entity_id: sensor.gardena_location
      data: {}
  - type: button
    name: Update status
    icon: mdi:power-cycle
    tap_action:
      action: call-service
      service: homeassistant.update_entity
      target:
        entity_id: sensor.gardena_status
      data: {}

Yes, I have an irrigation control too. I have made the card and run it. Should the token change? Because if I understand right, than I should receive a new token at least after 24h. And I’m also using trying to use the hacs gardena integration for the mower. But for the irrigation control the home kit integration is more solid for me.

The token should change if you press Run at “Update token” on the testing card. But you should not really need to do that, as the sensor is set to update every 24h anyway.
The value of gardena_token just says “OK” so you need to look in the attribute of it to see the actual token value.

If your gardena_status does not get any values, there might be a problem with that sensor if you have multiple gardena devices. I will have a look at the API documentation and see if I can sort this out, but i’s hard as I can’t test it myself.

I read the documentation, and yes there will be a problem when you have more connected Gardena devices. This is not easy to fix in a simple integration like this, but I think you can fix it by just making small changes to the template sensor in configuration.yaml

This is the line we need to edit:

| jq '{"activity": .included[1].attributes.activity.value, "timestamp": .included[1].attributes.activity.timestamp, "operatingHours": .included[1].attributes.operatingHours.value, "status": .included[1].attributes.state.value, "batteryLevel": .included[2].attributes.batteryLevel.value, "rfLinkLevel": .included[2].attributes.rfLinkLevel.value, "lastErrorCode": (.included[1].attributes.lastErrorCode.value // "none"), "location": .data.id, "serviceId": .included[0].relationships.services.data[0].id}'

.included[1] and .included[2] should probably have other values for you.
You can try by replacing 1 and 2 with other values 3,4,5…
You can find these values if you get hold of the json response (not sure about how to help you with that)

Thank you for the answer. I’m a little bit short on time but i’m triing to solve the problem with your suggestion. It looks like i can use the sensors for the irrigation too. At least now I have a sensor wich is for the irrigation control. For the json response I couldn’t fint it yet.

Thanks very much for kicking this off.
I’ve used a bash script to figure out a two-devide setup (humidity sensor and mower).
Maybe useful for everyone else?

#!/bin/bash

CLIENT_ID="YOUR_API_KEY"
CLIENT_SECRET="YOUR_SECRET"
AUTHAPI="https://api.authentication.husqvarnagroup.dev/v1"

# Use the first command to get the access token
ACCESS_TOKEN=$(curl -s -X POST "$AUTHAPI/oauth2/token" \
    -H "Accept: application/json" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    --data-urlencode "grant_type=client_credentials" \
    --data-urlencode "client_id=$CLIENT_ID" \
    --data-urlencode "client_secret=$CLIENT_SECRET" | jq -r .access_token)

echo token:
echo $ACCESS_TOKEN
echo

# Use the second command with the access token obtained from the first command
LOCATIONS=$(curl -s -X GET \
  https://api.smart.gardena.dev/v1/locations \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "X-Api-Key: $CLIENT_ID")

ID=$(echo $LOCATIONS | jq -r .data[0].id)
echo location id: 
echo $ID
echo

echo STATUS PRETTY and UNFILTERED
# Use the second command with the access token obtained from the first command
RESPONSE=$(curl -s -X GET \
  https://api.smart.gardena.dev/v1/locations/$ID \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "X-Api-Key: $CLIENT_ID")

echo $RESPONSE | jq 
echo

echo STATUS FILTERED
echo $RESPONSE | jq '{"activity": .included[4].attributes.activity.value, "timestamp": .included[4].attributes.activity.timestamp, "operatingHours": .included[4].attributes.operatingHours.value, "status": .included[4].attributes.state.value, "batteryLevel": .included[5].attributes.batteryLevel.value, "rfLinkLevel": .included[5].attributes.rfLinkLevel.value, "lastErrorCode": (.included[4].attributes.lastErrorCode.value // "none"), "location": .data.id, "serviceId": .included[1].relationships.services.data[0].id}'

Status card works fine now. Does starting the mower work for people? I get an error…

Bash script:

MOW=$(curl -s -X PUT \
  -d '{"data": {"type": "MOWER_CONTROL", "id": "request-HA", "attributes": {"command": "START_SECONDS_TO_OVERRIDE", "seconds": 3600}}}' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "X-Api-Key: $CLIENT_ID" \
  -H "Content-Type: application/vnd.api+json" \
  "https://api.smart.gardena.dev/v1/command/$SERVICE_ID")

Output:

{"errors":[{"id":"10fa0686-c398-4a40-bfa9-5befc964431b","status":"Bad Gateway","title":"Send Command"}]}

As Gardena is using the Husqvarna API, did anyone try this integration to check if it also handled the Gardena mower? GitHub - Thomas55555/husqvarna_automower: Custom component for Home Assistant to monitor and control your Husqvarna Automower

According to the comment from @Thomas55555 in this issue, the custom component is not going to support Gardena Smart Sileno mowers:

Sorry, but this integration is only for Husqvarna mowers.

1 Like

I installed Ă­t without success (see attachment)

Can anyone pls help me? Thanks

Does any know why Ă­t failed?
I am using RP4 with the newest HA version.

May be Ă­t is a common BT Problem???
Thanks