zxdavb / evohome-async

An asyncio Python client to access the Evohome web service
http://evohome-client.readthedocs.org/en/latest/
Apache License 2.0
11 stars 11 forks source link

schedule backup: object of type 'NoneType' has no len() #12

Closed mptheone closed 10 months ago

mptheone commented 10 months ago

Hello,

i just wanted your nice python script to set schedules..therofore i wanted an example what is it look like currently:

During handling of the above exception, another exception occurred:

after running the command:

client.zone_schedules_backup('filename.json')

i get:

  File "<stdin>", line 1, in <module>
  File "C:\Users\xx\AppData\Local\Programs\Python\Python310\lib\site-packages\evohomeasync2\__init__.py", line 301, in zone_schedules_backup
    return self._get_single_heating_system().zone_schedules_backup(filename)
  File "C:\Users\xx\AppData\Local\Programs\Python\Python310\lib\site-packages\evohomeasync2\__init__.py", line 196, in _get_single_heating_system
    if len(self.locations) != 1:
TypeError: object of type 'NoneType' has no len()
zxdavb commented 10 months ago

Please download the code with tag 0.4.11 or later.

Then try:

python client.py -u username@gmail.com -p password get-schedules

... and/or:

python client.py -u username@gmail.com -p password set-schedules -f schedules.json

You can also use: --loc_idx, but I haven't fully tested this as yet, so YMMV.

mptheone commented 10 months ago

Hello,

thanks for helping.., not sure whats still wrong.., becuase my instance has an sensor which really is nothing, can we overjump "invalid" entries?

the issue is, there seems an "broken" temperature value in the instance.. grafik

2023-11-26 23:32:59 INFO evohomeasync2.client GET https://tccna.honeywell.com/WebAPI/emea/api/v1/temperatureZone/xxx/schedule (200) = {'dailySchedules': [{'dayOfWeek': 'Monday', 'switchpoints': [{'heatSetpoint': 10.0, 'timeOfDay': '07:00:00'}, {'heatSetpoint': 10.0, 'timeOfDay': '22:30:00'}]}, {'dayOfWeek': 'Tuesday', 'switchpoints': [{'heatSetpoint': 11.0, 'timeOfDay': '07:00:00'}, {'heatSetpoint': 11.0, 'timeOfDay': '22:30:00'}]}, {'dayOfWeek': 'Wednesday', 'switchpoints': [{'heatSetpoint': 10.0, 'timeOfDay': '07:00:00'}, {'heatSetpoint': 10.0, 'timeOfDay': '22:30:00'}]}, {'dayOfWeek': 'Thursday', 'switchpoints': [{'heatSetpoint': 23.0, 'timeOfDay': '07:00:00'}, {'heatSetpoint': 22.0, 'timeOfDay': '11:00:00'}, {'heatSetpoint': 10.0, 'timeOfDay': '12:00:00'}, {'heatSetpoint': 10.0, 'timeOfDay': '15:00:00'}, {'heatSetpoint': 10.0, 'timeOfDay': '22:30:00'}]}, {'dayOfWeek': 'Friday', 'switchpoints': [{'heatSetpoint': 10.0, 'timeOfDay': '07:00:00'}, {'heatSetpoint': 10.0, 'timeOfDay': '16:30:00'}, {'heatSetpoint': 10.0, 'timeOfDay': '19:00:00'}, {'heatSetpoint': 10.0, 'timeOfDay': '22:30:00'}]}, {'dayOfWeek': 'Saturday', 'switchpoints': [{'heatSetpoint': 10.0, 'timeOfDay': '07:00:00'}, {'heatSetpoint': 10.0, 'timeOfDay': '12:00:00'}, {'heatSetpoint': 10.0, 'timeOfDay': '17:00:00'}, {'heatSetpoint': 10.0, 'timeOfDay': '22:30:00'}]}, {'dayOfWeek': 'Sunday', 'switchpoints': [{'heatSetpoint': 20.5, 'timeOfDay': '06:00:00'}, {'heatSetpoint': 10.0, 'timeOfDay': '08:00:00'}, {'heatSetpoint': 10.0, 'timeOfDay': '13:30:00'}, {'heatSetpoint': 10.0, 'timeOfDay': '15:00:00'}, {'heatSetpoint': 10.0, 'timeOfDay': '22:30:00'}]}]}

now its where it seems broken:

2023-11-26 23:32:59 DEBUG evohomeasync2.client Getting schedule of yyyyyy (temperatureZone)...

2023-11-26 23:33:00 INFO evohomeasync2.client GET https://tccna.honeywell.com/WebAPI/emea/api/v1/temperatureZone/yyyyyyyy/schedule (400) = [{'code': 'ScheduleNotFound', 'message': 'Schedule not found.'}]
Traceback (most recent call last):
  File "E:\evohome\evohome-async-master/src\evohomeasync2\broker.py", line 233, in get
    response.raise_for_status()
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\site-packages\aiohttp\client_reqrep.py", line 1059, in raise_for_status
    raise ClientResponseError(
aiohttp.client_exceptions.ClientResponseError: 400, message='Bad Request', url=URL('https://tccna.honeywell.com/WebAPI/emea/api/v1/temperatureZone/7784771/schedule')

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "E:\evohome\evohome-async-master\client.py", line 19, in <module>
    main()
  File "E:\evohome\evohome-async-master/src\evohomeasync2\__init__.py", line 192, in main
    cli(obj={})  # default for ctx.obj is None
    ^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\site-packages\click\core.py", line 1157, in __call__
    return self.main(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\site-packages\click\core.py", line 1078, in main
    rv = self.invoke(ctx)
         ^^^^^^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\site-packages\click\core.py", line 1688, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\site-packages\click\core.py", line 1434, in invoke
    return ctx.invoke(self.callback, **ctx.params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\site-packages\click\core.py", line 783, in invoke
    return __callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\site-packages\click\decorators.py", line 33, in new_func
    return f(get_current_context(), *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\evohome\evohome-async-master/src\evohomeasync2\__init__.py", line 153, in get_schedules
    asyncio.run(get_schedules(ctx.obj[SZ_EVO], loc_idx))
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\asyncio\base_events.py", line 664, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "E:\evohome\evohome-async-master/src\evohomeasync2\__init__.py", line 145, in get_schedules
    schedules = await tcs._get_schedules()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\evohome\evohome-async-master/src\evohomeasync2\controlsystem.py", line 302, in _get_schedules
    schedule = await zone.get_schedule()
               ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\evohome\evohome-async-master/src\evohomeasync2\zone.py", line 131, in get_schedule
    schedule: _EvoDictT = await self._broker.get(
                          ^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\evohome\evohome-async-master/src\evohomeasync2\broker.py", line 237, in get
    raise RequestFailed(hint, status=exc.status) from exc
evohomeasync2.exceptions.RequestFailed: Bad request (invalid data/json?)

2023-11-26 23:33:00 ERROR asyncio Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x000001BAC46485F0>
2023-11-26 23:33:00 ERROR asyncio Unclosed connector
connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0x000001BAC466CDD0>, 123780.171)]']
connector: <aiohttp.connector.TCPConnector object at 0x000001BAC4648590>
zxdavb commented 10 months ago

OK, it looks like you have a ghost zone.

I can easily enough work around this, but I cannot really test it at present (my mocked server isn't sophisticated enough):

for zone in self._zones:
    try:
        schedule = await zone.get_schedule()
    except InvalidSchedule:
        self._logger.warning(
            f"Ignoring schedule of {zone.zoneId} ({zone.name}): missing/invalid"
        )
        schedule = {}

Can you pull down the latest master? If that works, I'll push a new version up to PyPi.

I would appreciate it that you keep the Ghost Zone, until I've finished testing this feature.

mptheone commented 10 months ago

Hello,

i tried with your latest master but follwoing error:

Traceback (most recent call last):
  File "E:\evohome\evohome-async-master\evohome-async-master\client.py", line 19, in <module>
    main()
  File "E:\evohome\evohome-async-master\evohome-async-master/src\evohomeasync2\__init__.py", line 192, in main
    cli(obj={})  # default for ctx.obj is None
    ^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\site-packages\click\core.py", line 1157, in __call__
    return self.main(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\site-packages\click\core.py", line 1078, in main
    rv = self.invoke(ctx)
         ^^^^^^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\site-packages\click\core.py", line 1688, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\site-packages\click\core.py", line 1434, in invoke
    return ctx.invoke(self.callback, **ctx.params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\site-packages\click\core.py", line 783, in invoke
    return __callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\site-packages\click\decorators.py", line 33, in new_func
    return f(get_current_context(), *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\evohome\evohome-async-master\evohome-async-master/src\evohomeasync2\__init__.py", line 153, in get_schedules
    asyncio.run(get_schedules(ctx.obj[SZ_EVO], loc_idx))
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\asyncio\base_events.py", line 664, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "E:\evohome\evohome-async-master\evohome-async-master/src\evohomeasync2\__init__.py", line 144, in get_schedules
    tcs: ControlSystem = _get_tcs(evo, loc_idx)
                         ^^^^^^^^^^^^^^^^^^^^^^
  File "E:\evohome\evohome-async-master\evohome-async-master/src\evohomeasync2\__init__.py", line 93, in _get_tcs
    return evo.locations[int(loc_idx)]._gateways[0]._control_systems[0]
           ~~~~~~~~~~~~~^^^^^^^^^^^^^^
IndexError: list index out of range

and in the json file i have:

client.py: Starting backup...
2023-11-27 14:58:58 ERROR asyncio Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x000002335B682F30>
2023-11-27 14:58:58 ERROR asyncio Unclosed connector
connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0x000002335B6A0F50>, 179338.031)]']
connector: <aiohttp.connector.TCPConnector object at 0x000002335B683170>
zxdavb commented 10 months ago

You do not include the command that you are using. Could you kindly do so: it would be helpful, but do redact the username/password.

Your JSON file is because you are redirecting STDOUT:

python client.py -u username@gmail.com -p password get-schedules > schedules.json

... so you could try instead:

python client.py -u username@gmail.com -p password get-schedules -f schedules.json

This error:

  File "E:\evohome\evohome-async-master\evohome-async-master/src\evohomeasync2\__init__.py", line 93, in _get_tcs
    return evo.locations[int(loc_idx)]._gateways[0]._control_systems[0]
           ~~~~~~~~~~~~~^^^^^^^^^^^^^^
IndexError: list index out of range

... suggest to me you're using:

python client.py -u username@gmail.com -p password get-schedules --loc-idx 1

How many locations do you have - most have only one per account.

Try:

python client.py -u username@gmail.com -p password get-schedules --loc-idx 0

... or drop the --loc-idx option altogether?

mptheone commented 10 months ago

hello,

i tried all zour comments. I have only one location and 6 thermostats and one is a ghost, or an fake entry. Also i am succesfull able to login via APP or webbrowser same username and PW.

here is the latest comment i tried.

E:\evohome\evohome-async-master\evohome-async-master>python client.py -u xxxxx -p xxxxxx get-schedules --loc-idx 0

client.py: Starting backup...
Traceback (most recent call last):
  File "E:\evohome\evohome-async-master\evohome-async-master/src\evohomeasync2\broker.py", line 196, in _obtain_access_token
    response.raise_for_status()
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\site-packages\aiohttp\client_reqrep.py", line 1059, in raise_for_status
    raise ClientResponseError(
aiohttp.client_exceptions.ClientResponseError: 429, message='Too Many Requests', url=URL('https://tccna.honeywell.com/Auth/OAuth/Token')

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "E:\evohome\evohome-async-master\evohome-async-master\client.py", line 19, in <module>
    main()
  File "E:\evohome\evohome-async-master\evohome-async-master/src\evohomeasync2\__init__.py", line 192, in main
    cli(obj={})  # default for ctx.obj is None
    ^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\site-packages\click\core.py", line 1157, in __call__
    return self.main(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\site-packages\click\core.py", line 1078, in main
    rv = self.invoke(ctx)
         ^^^^^^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\site-packages\click\core.py", line 1688, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\site-packages\click\core.py", line 1434, in invoke
    return ctx.invoke(self.callback, **ctx.params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\site-packages\click\core.py", line 783, in invoke
    return __callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\site-packages\click\decorators.py", line 33, in new_func
    return f(get_current_context(), *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\evohome\evohome-async-master\evohome-async-master/src\evohomeasync2\__init__.py", line 153, in get_schedules
    asyncio.run(get_schedules(ctx.obj[SZ_EVO], loc_idx))
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\asyncio\base_events.py", line 664, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "E:\evohome\evohome-async-master\evohome-async-master/src\evohomeasync2\__init__.py", line 142, in get_schedules
    await evo.login()
  File "E:\evohome\evohome-async-master\evohome-async-master/src\evohomeasync2\client.py", line 166, in login
    await self.user_account()
  File "E:\evohome\evohome-async-master\evohome-async-master/src\evohomeasync2\client.py", line 199, in user_account
    self._user_account = await self.broker.get(
                         ^^^^^^^^^^^^^^^^^^^^^^
  File "E:\evohome\evohome-async-master\evohome-async-master/src\evohomeasync2\broker.py", line 230, in get
    response, content = await self._client(  # type: ignore[assignment]
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\evohome\evohome-async-master\evohome-async-master/src\evohomeasync2\broker.py", line 102, in _client
    headers = await self._headers()
              ^^^^^^^^^^^^^^^^^^^^^
  File "E:\evohome\evohome-async-master\evohome-async-master/src\evohomeasync2\broker.py", line 136, in _headers
    await self._basic_login()
  File "E:\evohome\evohome-async-master\evohome-async-master/src\evohomeasync2\broker.py", line 177, in _basic_login
    await self._obtain_access_token(CREDS_USER_PASSWORD | self._credentials)  # type: ignore[operator]
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\evohome\evohome-async-master\evohome-async-master/src\evohomeasync2\broker.py", line 200, in _obtain_access_token
    raise AuthenticationFailed(hint, status=exc.status) from exc
evohomeasync2.exceptions.AuthenticationFailed: Vendor's API rate limit exceeded (wait a while)
2023-11-27 17:08:27 ERROR asyncio Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x000002353403FBF0>
2023-11-27 17:08:27 ERROR asyncio Unclosed connector
connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0x0000023534C71070>, 187107.875)]']
connector: <aiohttp.connector.TCPConnector object at 0x0000023534C57080>
zxdavb commented 10 months ago

The error is:

evohomeasync2.exceptions.AuthenticationFailed: Vendor's API rate limit exceeded (wait a while)

This is a temporary error. The vendor (Honeywell/Resideo) will only let you authenticate a limited number of times per unit time.

Wait a while, and try again.

zxdavb commented 10 months ago

Hang on, I've noticed a bug...

zxdavb commented 10 months ago

OK, try it now.

mptheone commented 10 months ago

with a new version and which command?

i highly apprecaite your work
zxdavb commented 10 months ago

Make sure you pull the latest commit from master.

zxdavb commented 10 months ago

This is likely the best option for you:

python client.py -u username@gmail.com -p password get-schedules -f schedules.json
mptheone commented 10 months ago

it works fantastic.. i just have to remove the fake room from the set schedule.... awesome now i can create my google calender bridge... thanks!

zxdavb commented 10 months ago

You should be able to remove the ghost zone via the TCC web portal.

zxdavb commented 10 months ago

awesome news... get schedule works.. but then i tried of course set.. which failed. the error message that i tried to often is not true, because after that i tried and get and imemdaitly it created a new json.

> python client.py -u xxxxxx -p xxxx: set-schedules -f schedules.json

client.py: Starting restore...
2023-11-27 20:03:19 WARNING evohomeasync2.client JSON may be invalid (bad schema): PUT temperatureZone/7xxx71/schedule: required key not provided @ data['DailySchedules']
Traceback (most recent call last):

File "E:\evohome\evohome-async-master(1)\evohome-async-master/src\evohomeasync2\broker.py", line 274, in put
  response.raise_for_status()
File "C:\Users\mp\AppData\Local\Programs\Python\Python312\Lib\site-packages\aiohttp\client_reqrep.py", line 1059, in raise_for_status
  raise ClientResponseError(
aiohttp.client_exceptions.ClientResponseError: 400, message='Bad Request', url=URL('https://tccna.honeywell.com/WebAPI/emea/api/v1/temperatureZone/7xxxx1/schedule')

The above exception was the direct cause of the following exception:

File "E:\evohome\evohome-async-master(1)\evohome-async-master/src\evohomeasync2\broker.py", line 278, in put
  raise exc.RequestFailed(hint, status=err.status) from err
evohomeasync2.exceptions.RequestFailed: Bad request (invalid data/json?)
mptheone commented 10 months ago

one interesting thing.. i need to run it multiple times that it goes over the api rate error...

zxdavb commented 10 months ago

one interesting thing.. i need to run it multiple times that it goes over the api rate error...

The vendor is quite strict about it.

zxdavb commented 10 months ago

Regarding your error:

evohomeasync2.exceptions.RequestFailed: Bad request (invalid data/json?)

I am not sure what is happening - set-schedule works perfectly for me.

zxdavb commented 10 months ago

Did you perhaps edit the JSON file?

mptheone commented 10 months ago

if i try to send a 2nd time directly the json it stopped , yeah i had to remove the ghost entry.. but i can work with that state thanks!

zxdavb commented 10 months ago

OK, will close now - I have added a switch (-c) that will cache access tokens, which will help a lot to avoid the API rate limit being exceeded. Will push that soon.

python client.py -u user@gmail.com -p passwd --cache-tokens get-schedules