Git Product home page Git Product logo

py-smart-gardena's Introduction

py-smart-gardena

License: MIT PyPI version Build Status Updates

Feel free to join the discord server : Support Server

Description

The py-smart-gardena library aims to provide python way to communicate with gardena smart systems and all gardena smart equipment. Configuration of the equipement and inclusion has still to be done using the Gardena application or website.

Support

This project needs your support.
Gardena equipments are expensive, and I need to buy them in order to add support. If you find this library useful and want to help me support more devices (or if you just want to reward me for my spent time), you are very welcome !
Your help is very much appreciated.

Here are the links if you want to show your support :
PayPal donate button

Thx for your support !

Requirements

  • Python 3.8+

Supported devices

For now, only few devices are supported. I may add new ones in the future :

  • Gateway
  • Smart Mower
  • Smart water control
  • Smart sensor
  • Power plugs
  • Smart Irrigation control

Account creation in order to have access to Gardena API

Gardena requires the creation of an account and an application in order to use their API. You can find how to create such an account and application here : Account and application creation

Installation

$ pip install py-smart-gardena

Usage

Data model

The entrypoint of the library is the the SmartSytem class (in gardena.smart_system package). From there, you can get all locations from your account, and for each of these locations, get the declared devices.

All communications are not done directly with the gateway. This library uses a websocket in order to communicate with gardena systems in order to avoid throttling. There is only one connection to authenticate, and two connections to revoke tokens, everything else is done through websockets.

Authentication

You first need to get a client id (also called application key in the API documentation) for your personal installation of gardena. To do so, create an account here : https://developer.husqvarnagroup.cloud/

Then you need to create an application, add APIs (Authentication API and GARDENA smart system API), and copy the application key as explained here: https://developer.husqvarnagroup.cloud/docs/getting-started

The library manages the token for you then. An exception is raised if authentication fails.

from gardena.smart_system import SmartSystem
import pprint

smart_system = SmartSystem(client_id="client_id", client_secret="client_secret")
await smart_system.authenticate()
await smart_system.update_locations()
for location in smart_system.locations.values():
    await smart_system.update_devices(location)
    pprint.pprint(location)
    for device in location.devices.values():
        pprint.pprint(device)

await smart_system.start_ws(smart_system.locations['LOCATION_ID'])

Once authentication is successful, you need to gather locations and devices for the first time and then, you can create start the websocket in order to get updates automatically.

Locations

Locations are automatically retrieved the first time from the API, and then the websocket is used to get updates.

Here is the list of the current available fields and methods :

for location in smart_system.locations.values():
    print("location : " + location.name + "(" + location.id + ")")

Devices

Devices are automatically retrieved the first time from the API, and then the websocket is used to get updates. They are stored in each locations. Depending on the function type, you can have diffrents fields.

Mowers

    for device in smart_system.locations["LOCATION_ID"].find_device_by_type("MOWER"):
          print(f"name : {device.name}")
          print(f"id : {device.id}")
          print(f"type : {device.type}")
          print(f"model_type : {device.model_type}")
          print(f"battery_level : {device.battery_level}")
          print(f"battery_state : {device.battery_state}")
          print(f"rf_link_level : {device.rf_link_level}")
          print(f"rf_link_state : {device.rf_link_state}")
          print(f"serial : {device.serial}")
          print(f"activity : {device.activity}")
          print(f"operating_hours : {device.operating_hours}")
          print(f"state : {device.state}")
          print(f"last_error_code : {device.last_error_code}")

Power Socket

    for device in smart_system.locations["LOCATION_ID"].find_device_by_type("POWER_SOCKET"):
          print(f"name : {device.name}")
          print(f"id : {device.id}")
          print(f"type : {device.type}")
          print(f"model_type : {device.model_type}")
          print(f"battery_level : {device.battery_level}")
          print(f"battery_state : {device.battery_state}")
          print(f"rf_link_level : {device.rf_link_level}")
          print(f"rf_link_state : {device.rf_link_state}")
          print(f"serial : {device.serial}")
          print(f"activity : {device.activity}")
          print(f"state : {device.state}")

Sensor

    for device in smart_system.locations["LOCATION_ID"].find_device_by_type("SENSOR"):
          print(f"name : {device.name}")
          print(f"id : {device.id}")
          print(f"type : {device.type}")
          print(f"model_type : {device.model_type}")
          print(f"battery_level : {device.battery_level}")
          print(f"battery_state : {device.battery_state}")
          print(f"rf_link_level : {device.rf_link_level}")
          print(f"rf_link_state : {device.rf_link_state}")
          print(f"serial : {device.serial}")
          print(f"ambient_temperature : {device.ambient_temperature}")
          print(f"light_intensity : {device.light_intensity}")
          print(f"soil_humidity : {device.soil_humidity}")
          print(f"soil_temperature : {device.soil_temperature}")

Smart irrigation control

    for device in smart_system.locations["LOCATION_ID"].find_device_by_type("SMART_IRRIGATION_CONTROL"):
          print(f"name : {device.name}")
          print(f"id : {device.id}")
          print(f"type : {device.type}")
          print(f"model_type : {device.model_type}")
          print(f"battery_level : {device.battery_level}")
          print(f"battery_state : {device.battery_state}")
          print(f"rf_link_level : {device.rf_link_level}")
          print(f"rf_link_state : {device.rf_link_state}")
          print(f"serial : {device.serial}")
          print(f"valve_set_id : {device.valve_set_id}")
          print(f"valve_set_state : {device.valve_set_state}")
          print(f"valve_set_last_error_code : {device.valve_set_last_error_code}")
          for valve in device.valves.values():
            print(f"name : {valve['name']}")
            print(f"{valve['name']} - id : {valve['id']}")
            print(f"{valve['name']} - activity : {valve['activity']}")
            print(f"{valve['name']} - state : {valve['state']}")
            print(f"{valve['name']} - last_error_code : {valve['last_error_code']}")

Smart water control

    for device in smart_system.locations["LOCATION_ID"].find_device_by_type("WATER_CONTROL"):
          print(f"name : {device.name}")
          print(f"id : {device.id}")
          print(f"type : {device.type}")
          print(f"model_type : {device.model_type}")
          print(f"battery_level : {device.battery_level}")
          print(f"battery_state : {device.battery_state}")
          print(f"rf_link_level : {device.rf_link_level}")
          print(f"rf_link_state : {device.rf_link_state}")
          print(f"serial : {device.serial}")
          print(f"valve_set_id : {device.valve_set_id}")
          print(f"valve_name : {device.valve_name}")
          print(f"valve_id : {device.valve_id}")
          print(f"valve_activity : {device.valve_activity}")
          print(f"valve_state : {device.valve_state}")

Using websocket

Once the websocket has been started, everything is managed and the devices are automatically updated once their state change. In order for your to be alerted of such a change, you need to add a callback to the device. This callback will be called each time the device state changed :

def my_callback(device):
    print(f"The device {device.name} has been updated !")

device.add_callback(my_callback)

Development environment

To install the dev environment, you just have to do, in the source code directory :

$ pip install -e .[dev]

py-smart-gardena's People

Contributors

crazyfx1 avatar dependabot[bot] avatar domochip avatar fabaff avatar grm avatar nanosonde avatar osks avatar pyup-bot avatar silvermind avatar stromnet avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

py-smart-gardena's Issues

Support for pressure pump

I own a gardena Pressure Pump. It is recognized as a Water Control, which should be fine, but the wrong id is used for running commands.
In order to work, the commands need to be run on the valve_id (with :0), instead of the base id. I do not own a Water Control, therefore I do not know, how the change would affect the Water Control.
It would be great, if someone could check, how this affects the Water Control.
This would also fix py-smart-gardena/hass-gardena-smart-system#28.

Here the relevant part from the gardena API:

[
    {
      "id": "01234567-0123-0123-0123-012345678901",
      "type": "DEVICE",
      "relationships": {
        "location": {
          "data": {
            "id": "00000000-0000-0000-0000-000000000000",
            "type": "LOCATION"
          }
        },
        "services": {
          "data": [
            {
              "id": "01234567-0123-0123-0123-012345678901:epp",
              "type": "VALVE_SET"
            },
            {
              "id": "01234567-0123-0123-0123-012345678901:0",
              "type": "VALVE"
            },
            {
              "id": "01234567-0123-0123-0123-012345678901",
              "type": "COMMON"
            }
          ]
        }
      }
    },
    {
      "id": "01234567-0123-0123-0123-012345678901:epp",
      "type": "VALVE_SET",
      "relationships": {
        "device": {
          "data": {
            "id": "01234567-0123-0123-0123-012345678901",
            "type": "DEVICE"
          }
        }
      },
      "attributes": {}
    },
  {
    "id": "01234567-0123-0123-0123-012345678901:0",
    "type": "VALVE",
    "relationships": {
      "device": {
        "data": {
          "id": "01234567-0123-0123-0123-012345678901",
          "type": "DEVICE"
        }
      }
    },
    "attributes": {
      "name": {
        "value": "Pump"
      },
      "activity": {
        "value": "CLOSED",
        "timestamp": "2020-06-23T17:46:24.269+0000"
      }
    }
  },
  {
    "id": "01234567-0123-0123-0123-012345678901",
    "type": "COMMON",
    "relationships": {
      "device": {
        "data": {
          "id": "01234567-0123-0123-0123-012345678901",
          "type": "DEVICE"
        }
      }
    },
    "attributes": {
      "name": {
        "value": "Pump"
      },
      "batteryState": {
        "value": "NO_BATTERY"
      },
      "rfLinkLevel": {
        "value": 70,
        "timestamp": "2020-06-26T15:40:02.997+0000"
      },
      "serial": {
        "value": "00000000"
      },
      "modelType": {
        "value": "GARDENA smart Pump"
      },
      "rfLinkState": {
        "value": "ONLINE"
      }
    }
  }
]

Switch to API https://smart.gardena.com/v1/devices?locationId=xxxx-xxxx-xxxx

Hello,
I was just scrolling a bit through the Dev documentation and was disappointed for how less information it is providing compared to what can be seen on the gardena smart website and app.

While watching the network tab I discovered the endpoint
https://smart.gardena.com/v1/devices?locationId=xxxx-xxxx-xxxx
It requires the same headers as the Husqvarna API
Authorization: Bearer xyz
Authorization-Provider: husqvarna
and not even the api key.

Also it excepts the same locationId as the gardena.dev one.

But it is returning a loooot more information.

I know that it is not documentated, but with this api we could finally access the Longitude and Latitude.

Unfortunetly, I'm lacking Python skills to help you. But maybe you can have a look into it.

Thank you

"Limit Exceeded": missing exception

I used the API a bit too much and I've reached my quota, now in the first call after authentication (in my case smart_system.update_locations(), but I guess it could be any other), I see on the stderr the following error message:

2021-05-10 22:35:51 ERROR    429 : Limit Exceeded
<gardena.smart_system.SmartSystem object at 0x7fb35699fe20>

However the call doesn't give an exception!
Instead of the error message I should get an exception.

Resilience after HTTP connection problems

I would send a patch but Im in a hurry:

If something goes wrong establishing a connection in smart_system.py start_ws it only checks for a handful of exceptions but not for example httpcore.ConnectError. This exception occurs when DNS fails (maybe due to no internet connection). This crashes the integration altogether - no attempt is made to recover from it.

My two points:

  1. Add at least httpcore.ConnectError or better httpcore.* to the list of caught exceptions.
  2. Do not propagate exceptions to HA but restart integration if an unrecoverable condition occurs.

Thx

Tim

get timestamp

Hello all
i have build a garage around my mower with a automatic door with a raspberry py. I open this door automatic by retrieving the next start date and time. 2 min before start time i open the door This was all functional for 2 years until the beginning of this mow season. I couldn't connect anymore with the smart system so i had to rebuild my program.

Unfortunately i cant get the code right to receive the timestamp_next_start. Does anyone here has a way to get this?
this is how my code looks like and works fine. As you can see in the buttom line i put the device.activity in a variable so i can use this variable to control my garage door. The only thing im missing is the timestamp_next_start variable.

for location in smart_system.locations.values():
print("location : " + location.name + "(" + location.id + ")")
for device in smart_system.locations["LOCATION_ID"].find_device_by_type("MOWER"):
print(f"name : {device.name}")
print(f"id : {device.id}")
print(f"type : {device.type}")
print(f"model_type : {device.model_type}")
Status_Mower=device.activity

ERROR 404 : {r['errors'][0]['title']} - {r['errors'][0]['detail']}

Hello,

today I would like to try your code for the first time. I would like to start with your example code from readme.md. But unfortunately I get this error:

2021-08-15 16:15:38 ERROR 404 : {r['errors'][0]['title']} - {r['errors'][0]['detail']}

My credentials and client id are correct. Both APIs are connected to my application as described in readme. Do you have any hint for me?

Michael

Error from callback

Hi.
sometimes i got this error:
error from callback <bound method Client.on_close of <gardena.smart_system.Client object at 0x5ecdbdb0>>: (missing_token) Missing access token parameter.

I started the websocket and a while loop scan every 15seconds the devices for changes in my py-script but the script is still alive and not interrupted.

confusing error: simplejson.errors.JSONDecodeError

I've got an exception after update_locations(). My code is simple like that:

smart_system = SmartSystem(email=g_email, password=g_pass, client_id=g_client)
smart_system.authenticate()
smart_system.update_locations()

I got the exception in the last call (update_locations):

Traceback (most recent call last):
  File "./gardenatest.py", line 22, in <module>
    smart_system.update_locations()
  File "/home/user/.local/lib/python3.8/site-packages/gardena/smart_system.py", line 217, in update_locations
    response_data = self.__call_smart_system_get(f"{self.SMART_HOST}/v1/locations")
  File "/home/user/.local/lib/python3.8/site-packages/gardena/smart_system.py", line 188, in __call_smart_system_get
    if self.__response_has_errors(response):
  File "/home/user/.local/lib/python3.8/site-packages/gardena/smart_system.py", line 162, in __response_has_errors
    r = response.json()
  File "/usr/lib/python3/dist-packages/requests/models.py", line 897, in json
    return complexjson.loads(self.text, **kwargs)
  File "/usr/lib/python3/dist-packages/simplejson/__init__.py", line 518, in loads
    return _default_decoder.decode(s)
  File "/usr/lib/python3/dist-packages/simplejson/decoder.py", line 370, in decode
    obj, end = self.raw_decode(s)
  File "/usr/lib/python3/dist-packages/simplejson/decoder.py", line 400, in raw_decode
    return self.scan_once(s, idx=_w(s, idx).end())
simplejson.errors.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

All what I could figure out (by checking/editing the mentioned smart_system.py) is I got 401 in the response. I don't really understand why, my user/pass/client_id are correct and I didn't get any error in the previous line in the authenticate() call.

Anyway this 401 response is not handled very well here.

suppport timestamps

As I can see on developer.husqvarnagroup.cloud the API gives back a timestamp for every values. eg:

      "type": "SENSOR",
      "attributes": {
        "soilHumidity": {
          "value": 42,
          "timestamp": "2018-06-23T04:58:08Z"
        },
        "soilTemperature": {
          "value": 21,
          "timestamp": "2018-06-23T04:58:08Z"
        },

but when I use the py-smart-gardena, the timestamps are not available. That would be quite essential as the value could be hours old and I would like to know that. (If I am correct and these timestamps are the time of the last updates, what you also can see on the Gardena web app)

ERROR websocket error from callback <bound method Client.on_close of <gardena.smart_system.Client object at 0x600239b0>>: (invalid_grant) Failed to find token

Hi again,
seams i got the similar error again (from 7.June):
ERROR websocket error from callback <bound method Client.on_close of <gardena.smart_system.Client object at 0x600239b0>>: (invalid_grant) Failed to find token
i got this error at 6:38 before i open the websocket again.
I open the socket every day at 7:00 and close it at 22:00 because
I usually want to keep the connection alive every time but somedays the status seams to be frozen.
I have to reconnect and everything works fine until some days.

To disconnect the websocket is there a special command to exit the websocket.
At the moment i disconnect via "break" i the while loop

Here my complete pyhton code..

#!/usr/bin/env python3
# gardena_garten_system.py


# SmartSystem need : 
# https://github.com/py-smart-gardena/py-smart-gardena
# https://developer.husqvarnagroup.cloud/docs/getting-started#/docs/getting-started/#3connect-api-to-application

from gardena.smart_system import SmartSystem
from datetime import datetime
from requests import HTTPError
import pprint
import pytest
import unittest
import requests
import requests_mock
import time
import sys

    

LOCATION_ID = "xxx"  #nicht ändern!

sh.Gardena.OnOff(1) #Setze beim Auslösen den Schalter auf Eins



smart_system = SmartSystem(email="xxx", password="xxx", client_id="xxx") # ID aus API nehmen und anpassen. Letzte Änderung der ID am 18.7.2020
smart_system.authenticate()
smart_system.update_locations()



for location in smart_system.locations.values():
    smart_system.update_devices(location)
    pprint.pprint(location)
    for device in location.devices.values():
        pprint.pprint(device)

smart_system.start_ws(smart_system.locations[LOCATION_ID])        

sh.Gardena.mower.status('Warte auf Start...')
sh.Gardena.mower.battery_state("Warte auf Start...") 


while sh.Gardena.OnOff:
    tageszeit = sh.now()
    if (tageszeit.hour >= 20) and (tageszeit.hour < 23):
        sh.Gardena.OnOff(0)
        break
    if (tageszeit.hour >= 12) and (tageszeit.hour < 13):
        sh.Gardena.OnOff(0)
        break
    if (tageszeit.hour >= 7) and (tageszeit.hour < 20):
        for device in smart_system.locations[LOCATION_ID].find_device_by_type("MOWER"):
            sh.Gardena.mower.battery(device.battery_level)
            sh.Gardena.mower.radio_quality(device.rf_link_level)    

            if (device.battery_level < 5):
                sh.telegram.photo_broadcast('https://icon-icons.com/icons2/2248/PNG/96/robot_mower_icon_136263.png', 'Batterie kritisch: %d '% device.battery_level, 832868784)
                #Schalte Tunnel öffnen
                requests.get('http://192.168.178.56/control?cmd=gpio,14,0')     
                
            if device.activity == 'PARKED_PARK_SELECTED':
                sh.Gardena.mower.status('Geparkt')
            elif device.activity == 'OK_CUTTING_TIMER_OVERRIDDEN':
                sh.Gardena.mower.status('Manuelles Mähen')
                #Schalte Tunnel schliessen
                requests.get('http://192.168.178.56/control?cmd=gpio,14,1')
            elif device.activity == 'OK_SEARCHING':
                sh.Gardena.mower.status('Suche Ladestation')
                #Schalte Tunnel öffnen
                requests.get('http://192.168.178.56/control?cmd=gpio,14,0')
            elif device.activity == 'OK_CUTTING':
                sh.Gardena.mower.status('Am Mähen')
                #Schalte Tunnel schliessen
                requests.get('http://192.168.178.56/control?cmd=gpio,14,1')
            elif device.activity == 'OK_LEAVING':
                sh.Gardena.mower.status('Fahre zur Startposition')
                sh.Gardena.mower.in_charge(0)
                #Schalte Tunnel öffnen
                requests.get('http://192.168.178.56/control?cmd=gpio,14,0')
            elif device.activity == 'OK_CHARGING':
                sh.Gardena.mower.status('Am Laden')
                sh.Gardena.mower.in_charge(1)
                #Schalte Tunnel öffnen
                requests.get('http://192.168.178.56/control?cmd=gpio,14,0')
            elif device.activity == 'PARKED_TIMER':
                sh.Gardena.mower.status('Geparkt bis Timer')
                #Schalte Tunnel öffnen
                requests.get('http://192.168.178.56/control?cmd=gpio,14,0')
            elif device.activity == 'PARKED_AUTOTIMER':
                sh.Gardena.mower.status('Geparkt bis Timer - Grashöhe unzureichend')
                #Schalte Tunnel öffnen
                requests.get('http://192.168.178.56/control?cmd=gpio,14,0')
            elif device.activity == 'PAUSED':
               sh.Gardena.mower.status('Angehalten')
            elif device.activity == 'NONE':                
                if device.last_error_code == 'OFF_HATCH_OPEN':                
                    sh.Gardena.mower.status('Klappe geöffnet')               
            else:
                sh.Gardena.mower.status(device.activity)

            if (sh.Gardena.mower.last_status() != sh.Gardena.mower.status()):
                logger.warning('Mäher: Neuer Status : %s' % sh.Gardena.mower.status())
                sh.telegram.photo_broadcast('https://icon-icons.com/icons2/2248/PNG/96/robot_mower_icon_136263.png', 'Neuer Status : %s' % sh.Gardena.mower.status(), 832868784)
                sh.Gardena.mower.last_status(sh.Gardena.mower.status())        
            
           
                
            
            if device.battery_state == 'OK':
                sh.Gardena.mower.battery_state("Alles OK")
            elif device.battery_state == 'LOW':
                sh.Gardena.mower.battery_state("Niedrig")
            elif device.battery_state == 'REPLACE_NOW':    
                sh.Gardena.mower.battery_state("Batterie bitte ersetzen")
            elif device.battery_state == 'OUT_OF_OPERATION':    
                sh.Gardena.mower.battery_state("Batterie muss ersetzt werden")
            elif device.battery_state == 'CHARGING':    
                sh.Gardena.mower.battery_state("Am Laden")
            elif device.battery_state == 'NO_BATTERY':
                sh.Gardena.mower.battery_state("Keine Batterie")
            else:
                sh.Gardena.mower.battery_state("unbekannt")   


sh.Gardena.Update_Date(tageszeit)            
    time.sleep(5)
    
      

        
sh.Gardena.mower.status('Offline')
sh.Gardena.mower.battery(0)
sh.Gardena.mower.battery_state("Offline") 
sh.Gardena.mower.radio_quality(0)   
sh.Gardena.mower.in_charge(0)
sh.Gardena.mower.last_status("Offline")
`

Source dists on PyPI cannot be installed

Binary dist (wheel) can still be installed.

The problem is that setup.py tries to get the version by running git commands, which doesn't work without the git repository, like when having downloaded the source dist.

Reproduce with: $ pip install --no-binary ":all:" py-smart-gardena==0.6.16

Error message (without traceback):

Collecting py-smart-gardena==0.6.16
  Using cached py-smart-gardena-0.6.16.tar.gz (12 kB)
    ERROR: Command errored out with exit status 1:
[...]
    Complete output (12 lines):
    fatal: not a git repository (or any of the parent directories): .git
    Traceback (most recent call last):
[...]
    subprocess.CalledProcessError: Command '['git', 'describe', '--tags', '--long', '--dirty']' returned non-zero exit status 128.
    ----------------------------------------
ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.

Unauthorized client: `grant_type` is invalid

I would like to use gardena2mqtt in my environment.
gardena2mqtt is transmitting the data from py-smart-gardena by MQTT and can be run inside a docker container.

gardena2mqtt imports the SmartSystem from py-smart-gardena (https://github.com/Domochip/gardena2mqtt/blob/master/gardena2mqtt.py) and is using the original functions.

After building the docker container I can find the following failure in container logs:

Traceback (most recent call last):
  File "/app/gardena2mqtt.py", line 210, in <module>
    smart_system.authenticate()
  File "/usr/local/lib/python3.9/site-packages/gardena/smart_system.py", line 115, in authenticate
    self.token = self.oauth_session.fetch_token(
  File "/usr/local/lib/python3.9/site-packages/requests_oauthlib/oauth2_session.py", line 360, in fetch_token
    self._client.parse_request_body_response(r.text, scope=self.scope)
  File "/usr/local/lib/python3.9/site-packages/oauthlib/oauth2/rfc6749/clients/base.py", line 429, in parse_request_body_response
    self.token = parse_token_response(body, scope=scope)
  File "/usr/local/lib/python3.9/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 425, in parse_token_response
    validate_token_parameters(params)
  File "/usr/local/lib/python3.9/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 432, in validate_token_parameters
    raise_from_error(params.get('error'), params)
  File "/usr/local/lib/python3.9/site-packages/oauthlib/oauth2/rfc6749/errors.py", line 402, in raise_from_error
    raise cls(**kwargs)
oauthlib.oauth2.rfc6749.errors.UnauthorizedClientError: (unauthorized_client) Unauthorized client: `grant_type` is invalid

I already checked the Gardena credentials and changed the password but the problem still the same.

Do you have an idea where to search for the problem?

Convert to JSON

Hi, I'm looking for a way to convert device to JSON.
I found out that a good way to do this is to implement a toJSON method.
What's your mind about this?
Do you want me to do a PR about this?

Package install fails with python 3.12.1

Trying to use this package with python=3.12.1. Installation with pip fails with the exception below.

Looks like the used python-versioneer (/versioneer.py) script is outdated.

A potential quick hack before open pandora's box of build pipeline: lines 342-344. SafeConfigParser is deprecated since Python=3.2.

Replace:

    parser = configparser.SafeConfigParser()
    with open(setup_cfg, "r") as f:
        parser.readfp(f)

With:

    parser = configparser.ConfigParser()
    parser.read(setup_cfg)

Thx

Exception:

2024-01-17 14:48:37.721 ERROR (SyncWorker_3) [homeassistant.util.package] Unable to install package py-smart-gardena==1.3.7: error: subprocess-exited-with-error
  
  × python setup.py egg_info did not run successfully.
  │ exit code: 1
  ╰─> [18 lines of output]
      /tmp/pip-install-s2wr1lq0/py-smart-gardena_a7e7fb1d2ff34e95874d70b48c6c2294/versioneer.py:421: SyntaxWarning: invalid escape sequence '\s'
        LONG_VERSION_PY['git'] = '''
      Traceback (most recent call last):
        File "<string>", line 2, in <module>
        File "<pip-setuptools-caller>", line 34, in <module>
        File "/tmp/pip-install-s2wr1lq0/py-smart-gardena_a7e7fb1d2ff34e95874d70b48c6c2294/setup.py", line 16, in <module>
          version=versioneer.get_version(),
                  ^^^^^^^^^^^^^^^^^^^^^^^^
        File "/tmp/pip-install-s2wr1lq0/py-smart-gardena_a7e7fb1d2ff34e95874d70b48c6c2294/versioneer.py", line 1480, in get_version
          return get_versions()["version"]
                 ^^^^^^^^^^^^^^
        File "/tmp/pip-install-s2wr1lq0/py-smart-gardena_a7e7fb1d2ff34e95874d70b48c6c2294/versioneer.py", line 1412, in get_versions
          cfg = get_config_from_root(root)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/tmp/pip-install-s2wr1lq0/py-smart-gardena_a7e7fb1d2ff34e95874d70b48c6c2294/versioneer.py", line 342, in get_config_from_root
          parser = configparser.SafeConfigParser()
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      AttributeError: module 'configparser' has no attribute 'SafeConfigParser'. Did you mean: 'RawConfigParser'?
      [end of output]
  
  note: This error originates from a subprocess, and is likely not a problem with pip.
error: metadata-generation-failed

× Encountered error while generating package metadata.


Environment: Ubuntu 23.10, Python Virtual Environment

Power sockets throw Error

An error is thrown, when a power socket is turned on or off.
Switches can therefor currently not be controlled.

Here is the error thrown by Home Assistant (the error seems to come from this package and not the Home Assistant package).

File "/usr/src/homeassistant/homeassistant/components/websocket_api/commands.py", line 193, in handle_call_service
await hass.services.async_call(
File "/usr/src/homeassistant/homeassistant/core.py", line 1713, in async_call
task.result()
File "/usr/src/homeassistant/homeassistant/core.py", line 1750, in _execute_service
await cast(Callable[[ServiceCall], Awaitable[None]], handler.job.target)(
File "/usr/src/homeassistant/homeassistant/helpers/entity_component.py", line 204, in handle_service
await service.entity_service_call(
File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 680, in entity_service_call
future.result() # pop exception if have
File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 930, in async_request_call
await coro
File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 717, in _handle_entity_call
await result
File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 984, in async_turn_on
await self.hass.async_add_executor_job(ft.partial(self.turn_on, **kwargs))
File "/usr/local/lib/python3.10/concurrent/futures/thread.py", line 58, in run
result = self.fn(*self.args, **self.kwargs)
File "/config/custom_components/gardena_smart_system/switch.py", line 249, in turn_on
).result()
File "/usr/local/lib/python3.10/concurrent/futures/_base.py", line 446, in result
return self.__get_result()
File "/usr/local/lib/python3.10/concurrent/futures/_base.py", line 391, in __get_result
raise self._exception
File "/usr/local/lib/python3.10/site-packages/gardena/devices/power_socket.py", line 36, in start_override
await self.location.smart_system.call_smart_system_service(self.id, data)
File "/usr/local/lib/python3.10/site-packages/gardena/smart_system.py", line 104, in call_smart_system_service
raise Exception(f"{r.status_code} : {response['errors'][0]['title']}")
KeyError: 'errors'

Possible to implement Automower Connect API?

The Automower Connect API is completely separate from the GARDENA smart system API and works quite differently.

I don't know if it's suitable to implement in py-smart-gardena, or we should simply create a separate Python library for it?

Does my Gardena lawn mower think it's a vacuum cleaner?

My Gardena Smart Sileno 750 tells Home Assistant that is "cleaning" (= "städar" in Swedish) when its cutting the grass...

image

Is it possible to change these status messages?

image

"Städar" = Cleaning isnt the same as OK_CUTTING

Token Validity through websocket

Hello,

First, thank you for your work and this library.
I would like to know how many time websocket connection can be handled using the same token?

I'm using it into a Domoticz plugin which never stops.

husqvarnagroup provides an application secret which is for token renewal I guess? Can we imagine token renewal management by the library?

Thank you.

Get limit exceeded

Hi

I am new to python so bare with me.

I want to get status of my Gardena Mover sent to mqtt to be handled by node-red.

If I read the documentation correctly I should be able to read the values of my Gardena Mover as often as I want. The values will be updated via websocket only when the status change.
However if I read the values too often like every 10 sec, I get an error after a day or so "ERROR 429 : Limit Exceeded".
I guess the reason is that hit the 10k/monthA API limit. Is the documentation wrong or do I understand it incorrectly, or something else?

Any hints how I can make it only read the values when any value change would be appreciated, I have tried to fiddle with callback but could never get that working.

Thanks
PeterA

from gardena.smart_system import SmartSystem
import pprint
import time
import json
import paho.mqtt.publish as mqttpublish

smart_system = SmartSystem(email="<email>", password="<password>", client_id="<client-id>")
smart_system.authenticate()


while 1:
  print ("")
  localtime = time.localtime()
  result = time.strftime("%I:%M:%S %p", localtime)
  print(result)
  smart_system.update_locations()
  for location in smart_system.locations.values():
      smart_system.update_devices(location)
      pprint.pprint(location)
      for device in location.devices.values():
          pprint.pprint(device)
  for location in smart_system.locations.values():
    print("location : " + location.name + "(" + location.id + ")")
  for device in smart_system.locations[location.id].find_device_by_type("MOWER"):
    print(f"name : {device.name}")
    print(f"id : {device.id}")
    print(f"type : {device.type}")
    print(f"model_type : {device.model_type}")
    print(f"battery_level : {device.battery_level}")
    print(f"battery_state : {device.battery_state}")
    print(f"rf_link_level : {device.rf_link_level}")
    print(f"rf_link_state : {device.rf_link_state}")
    print(f"serial : {device.serial}")
    print(f"activity : {device.activity}")
    print(f"operating_hours : {device.operating_hours}")
    print(f"state : {device.state}")
    print(f"last_error_code : {device.last_error_code}")
    msg=json.dumps({'name': device.name, 'battery_level': device.battery_level, 'battery_state': device.battery_state, 'rf_link_level': device.rf_link_level, 'rf_link_state': device.rf_link_state, 'activity': device.activity, 'operating_hours': device.operating_hours, 'state': device.state, 'last_error_code': device.last_error_code})
    print("Sending:" + msg)
    mqttpublish.single("gardena/mover", msg, hostname="localhost")

  time.sleep (10)

429 Limit Exceeded

This is not really a bug in the library but more of a thread to discuss improved handling perhaps..

Recently I've had some issues with older version of this library going into a dead-ish mode after 429 Limit Exceeded responses.
Updated to latest today, and rewrote my (quite small) app to use asyncio.
Quite soon I had more of these errors (lots of restarts). I've so far seen them come in two variations:

2022-07-19 11:15:31,718 ERROR [gardena.smart_system] 429 : Limit Exceeded
Traceback (most recent call last):
  File "test.py", line 296, in <module>
    asyncio.run(main())
  File "/usr/local/lib/python3.7/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/usr/local/lib/python3.7/asyncio/base_events.py", line 587, in run_until_complete
    return future.result()
  File "test.py", line 224, in main
    await smart_system.update_locations()
  File "/usr/home/johan/dev/gardena-async/py-smart-gardena/src/gardena/smart_system.py", line 134, in update_locations
    f"{self.SMART_HOST}/v1/locations"
  File "/usr/home/johan/dev/gardena-async/py-smart-gardena/src/gardena/smart_system.py", line 128, in __call_smart_system_get
    if self.__response_has_errors(response):
  File "/usr/home/johan/dev/gardena-async/py-smart-gardena/src/gardena/smart_system.py", line 119, in __response_has_errors
    raise Exception(msg)
Exception: Limit Exceeded

An anonymous exception, or in some cases httpx.HTTPStatusError with response.status_code 429 (don't have a stack trace for that one).
This could perhaps be improved to at least just return one type of these exceptions.

Anyway, added catches for those, to restart the app (basically re-authenticate, get devices and re-start websocket).

After running for a few hours, I noticed that the server had hung up the WS connection, and library attempted reconnect. Which failed with 429. And then even after 1hr, Gardena API refused with 429..
That is of course nothing the library can change. But perhaps worth a discussion, can something be done in library (or generally lib usage) be done to prevent it?

In some case I even had >1hr between 429's.. The app never made any direct calls besides update_locations and then update_devices (with 1 location, 1 device), followed by a start_ws and just listen for WS events.

2022-07-19 19:17:37,201 DEBUG [websockets.client] > CLOSE 1001 (going away) Going away [12 bytes]                                                                                                                                                                                                                              
2022-07-19 19:17:37,202 DEBUG [websockets.client] = connection is CLOSED                                                                                                                                                                                                                                                       
2022-07-19 19:17:37,202 DEBUG [websockets.client] x closing TCP connection                                                                                                                                                                                                                                                     
2022-07-19 19:17:37,202 DEBUG [gardena.smart_system] Cloosing socket ..                                                                                                                                                                                                                                                        
2022-07-19 19:17:47,210 DEBUG [gardena.smart_system] Sleeping 10 seconds ..                                                                                                                                                                                                                                                    
2022-07-19 19:17:47,210 DEBUG [gardena.smart_system] Trying to connect to gardena API....                                                                                                                                                                                                                                      
2022-07-19 19:17:47,210 DEBUG [gardena.smart_system] Trying to get Websocket url                                                                                                                                                                                                                                               
2022-07-19 19:17:47,551 DEBUG [httpx._client] HTTP Request: POST https://api.smart.gardena.dev/v1/websocket "HTTP/1.1 429 "                                                                                                                                                                                                    
2022-07-19 19:17:47,551 DEBUG [gardena.smart_system] Websocket url retrieved !                                                                                                                                                                                                                                                 
2022-07-19 19:17:47,552 WARNING [root] Gardena API limit exceeded..  retrying in 3600...                                                                 
2022-07-19 20:17:47,553 DEBUG [asyncio] Using selector: KqueueSelector                                                                                                                                                                                                                                                         
2022-07-19 20:17:48,139 DEBUG [httpx._client] HTTP Request: POST https://api.authentication.husqvarnagroup.dev/v1/oauth2/token "HTTP/1.1 200 OK"                                                                                                                                                                               
2022-07-19 20:17:48,140 DEBUG [gardena.smart_system] We got a token : xxxyyyyx                                                                                                                                                     
2022-07-19 20:17:48,688 DEBUG [httpx._client] HTTP Request: GET https://api.smart.gardena.dev/v1/locations "HTTP/1.1 429 "                                                                                                                                                                                                     
2022-07-19 20:17:48,689 ERROR [gardena.smart_system] 429 : Limit Exceeded                                                                                                                                                                                                                                                      
2022-07-19 20:17:48,689 WARNING [root] Gardena API limit exceeded..  retrying in 5400...                                                                 
2022-07-19 21:47:48,694 DEBUG [asyncio] Using selector: KqueueSelector                                                                                                                                                                                                                                                         
2022-07-19 21:47:49,081 DEBUG [httpx._client] HTTP Request: POST https://api.authentication.husqvarnagroup.dev/v1/oauth2/token "HTTP/1.1 200 OK"                                                                                                                                                                               
2022-07-19 21:47:49,081 DEBUG [gardena.smart_system] We got a token : xxxx
2022-07-19 21:47:49,297 DEBUG [httpx._client] HTTP Request: GET https://api.smart.gardena.dev/v1/locations "HTTP/1.1 429 "                                                                                                                                                                                                     
2022-07-19 21:47:49,297 ERROR [gardena.smart_system] 429 : Limit Exceeded                                                                                                                                                                                                                                                      
2022-07-19 21:47:49,298 WARNING [root] Gardena API limit exceeded..  retrying in 8100...                                                                                                                                                                                                                                 

Imports from authlib but does not require authlib

In the file smart_system.py there is a line:

from authlib.integrations.httpx_client import AsyncOAuth2Client

But authlib is not mentioned in setup.py as a required lib.
So it has to be manually installed for the lib to work.
(Note this is not oauthlib).

Initial Update

The bot created this issue to inform you that pyup.io has been set up on this repo.
Once you have closed it, the bot will open pull requests for updates as soon as they are available.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.