Git Product home page Git Product logo

purei9_unofficial's Introduction

purei9_unofficial

This project includes a client/library to connect to Electrolux and AEG cleaner robots.

Compatibility

Tested with an AEX RX9 (aka purei9) first Generation and second (aka purei9.2) Generation.

Security

Other than the purei9 app, this tool does not verify the robot's TLS certificate when using it in "local" mode, so beware of MitMs in your LAN, eavedropping on your robot. In case you are curious how the trust model works anyway: The TLS certificate of the robot is self signed and verified against a known public key which is gathered from the purei9 cloud.

Disclaimer

The developer of this software is not affiliated at all with Electrolux. Electrolux, AEG and Purei9 are brand/product names by Electrolux AB which i do not have any rights upon.

Installation

Install via pip

pip install purei9_unofficial 

If you want to use the CLI (not only the library) you additionally need to install tabulate

pip install tabulate 

Usage

The library currently implements 3 interfaces which allows controlling the robot: locally (via a TCP connection on port 3002), and via the 2 different electrolux cloud services. The interface which uses the first version of the electrolux cloud API is the one with the most features implemented currently.

connection via cloud

See your robots status

$ python -m purei9_unofficial cloud -c [email protected]:mypassword status
+--------------------------+---------+----------+-----------+----------+---------+----------+-----------+
|            id            |  name   | localpw  | connected |  status  | battery | firmware | powermode |
+--------------------------+---------+----------+-----------+----------+---------+----------+-----------+
| 900395798357985798375972 | Cleaner | 01234567 |   True    | Sleeping |  High   |  42.19   |   HIGH    |
+--------------------------+---------+----------+-----------+----------+---------+----------+-----------+

Start a cleaning session

$ python -m purei9_unofficial cloud -c [email protected]:mypassword start -r 900395798357985798375972

local connection

First you need to get your local robot pw from the cloud API to talk to the robot on a local connection. Note that this only works if your robot was initalized with the old purei9 App (not the "wellbeing" App).

$ python -m purei9_unofficial cloud -c [email protected]:mypassword status
+--------------------------+---------+----------+-----------+----------+---------+----------+-----------+
|            id            |  name   | localpw  | connected |  status  | battery | firmware | powermode |
+--------------------------+---------+----------+-----------+----------+---------+----------+-----------+
| 900395798357985798375972 | Cleaner | 01234567 |   True    | Sleeping |  High   |  42.19   |   HIGH    |
+--------------------------+---------+----------+-----------+----------+---------+----------+-----------+

reset localpw

If when the above command is run, localpw is blank, you can reset it using the following instructions. This does not appear to interfer with the cloud connection as it does not use the localpw after set up. (Tested firmware version 42.19 on the first generation purei9, use at your own risk)

  1. Flip your purei9 onto its back.
  2. Connect your laptop to its network (Network Name: 3Dvision XXX-XXXX).
  3. Search its network to local its ip address (will be different to its normal ip on your home network).
    $ python -m purei9_unofficial local find
    +---------------+--------------------------+---------+
    |   Address     |         RobotID          |  Name   |
    +---------------+--------------------------+---------+
    | 192.168.6.1   | 900395798357985798375972 | Cleaner |
    +---------------+--------------------------+---------+
    
  4. Run the setlocalpw command, the command below would set it to 01234567
    $ python3 -m purei9_unofficial local -a 192.168.6.1 -l 01234567 setlocalpw
    
  5. Flip the purei9 back over and put on charge.
  6. From here local commands should work.

Note there may be a small delay between the robot being put on its back and the network being available. As well as between putting it on charge and the local commands being available.

You can also use the tool to locate any robots in the network

$ python -m purei9_unofficial local find
+---------------+--------------------------+---------+
|   Address     |         RobotID          |  Name   |
+---------------+--------------------------+---------+
| 192.168.1.101 | 900395798357985798375972 | Cleaner |
+---------------+--------------------------+---------+

Now you can connect to your robot locally, get the status and start/stop it.

$ python -m purei9_unofficial local -a 192.168.1.101 -l 01234567 status
$ python -m purei9_unofficial local -a 192.168.1.101 -l 01234567 start

More usage

Common options

$ python -m purei9_unofficial --help
usage: purei9_unofficial/__main__.py [-h] [-d] [-o {table,json}] [-s] {cloud,local} ...

positional arguments:
  {cloud,local}         command
    cloud               Connect to electrolux purei9 cloud (old API).
    local               Connect to robot(s) via local network.

optional arguments:
  -h, --help            show this help message and exit
  -d, --debug           enable debug logging
  -o {table,json}, --output {table,json}
			output format
  -s, --store-credentials
			Store/Use crendetials from /home/philipp/.local/share/purei9_unofficial

Cloud

$ python -m purei9_unofficial cloud --help
usage: purei9_unofficial/__main__.py cloud [-h] [-v {1,2}] [-c CREDENTIALS] [-t TOKEN] {status,start,home,pause,stop,maps,history,mode} ...

positional arguments:
  {status,start,home,pause,stop,maps,history,mode}
			subcommand, default=status
    status              Get status of all robots.
    start               Tell a robot to start cleaning.
    home                Tell a robot to go home.
    pause               Tell a robot to pause cleaning.
    stop                Tell a robot to stop cleaning.
    maps                List maps and zones (experimental).
    history             List history (experimental).
    mode                Set a robots powermode.

optional arguments:
  -h, --help            show this help message and exit
  -v {1,2}, --apiversion {1,2}
			Cloud API version, v1=purei9, v2=wellbeing
  -c CREDENTIALS, --credentials CREDENTIALS
			elecrolux cloud credentails in username:password format
  -t TOKEN, --token TOKEN
			electrolux v2 API token

Local

$ python -m purei9_unofficial local --help
usage: purei9_unofficial/__main__.py local [-h] [-a ADDRESS] [-l LOCALPW] {find,status,wifi,start,home,pause,stop,mode} ...

positional arguments:
  {find,status,wifi,start,home,pause,stop,mode}
			subcommand, default=find
    find                Find all robots in the local subnet.
    setlocalpw          Set localpw to localpw specified with -l (only works on setup mode).
    status              Get status of the robot.
    wifi                Get available wifi networks for the robot.
    start               Tell the robot to start cleaning (note: toggles between play/pause).
    home                Tell the robot to go home.
    pause               Tell the robot to pause cleaning (note: toggles between play/pause).
    stop                Tell the robot to stop cleaning.
    mode                Set a robots powermode.

optional arguments:
  -h, --help            show this help message and exit

Credentials:
  Required for all commands except "find".

  -a ADDRESS, --address ADDRESS
			robot ip address
  -l LOCALPW, --localpw LOCALPW
			robot localpw (get via "cloud -v1 status")

Library usage

If you want to use the library instead, here is some example code which assumes (1) you only have one robot in your electrolux account and (2) the robot is located in the same network as the computer you are running the code on. You can also have a look at the CLI implementation.

from purei9_unofficial.cloud import CloudClient
from purei9_unofficial.local import RobotClient, find_robots

# Get the list of robots in the cloud account
cloudclient  = CloudClient("account_email", "account_password")
robots       = cloudclient.getRobots()

# Get the local robot password to authenticate at our robot
localpw      = robots[0].getlocalpw()

# Get the robots in our network
local_robots = find_robots()

# Create a RobotClient to connect to it
robotclient  = RobotClient(local_robots[0].address)
robotclient.connect(localpw)

# Gets the status
print(robotclient.getstatus())

purei9_unofficial's People

Contributors

ekman avatar example123 avatar natoopotato avatar phype 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

purei9_unofficial's Issues

Gather further data

@Phype Many thanks for the project!

Is it possible to request further data like the charge state or the current position and map of the robot?

KeyError: 'notConnected' on reading dustbin status

When using this library through a home-assistant integration (https://github.com/Ekman/home-assistant-pure-i9) I get the following message in the log:

2021-12-25 19:31:25 ERROR (MainThread) [homeassistant.components.vacuum] purei9: Error on device update!
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/helpers/entity_platform.py", line 431, in _async_add_entity
await entity.async_device_update(warning=False)
File "/usr/src/homeassistant/homeassistant/helpers/entity.py", line 684, in async_device_update
await task
File "/usr/local/lib/python3.9/concurrent/futures/thread.py", line 52, in run
result = self.fn(*self.args, **self.kwargs)
File "/config/custom_components/purei9/vacuum.py", line 195, in update
purei9_dustbin = self._robot.getdustbinstatus()
File "/usr/local/lib/python3.9/site-packages/purei9_unofficial/cloudv2.py", line 43, in getdustbinstatus
return DustbinStates[dustbinstatus]
File "/usr/local/lib/python3.9/enum.py", line 432, in __getitem__
return cls._member_map_[name]
KeyError: 'notConnected'

The robot works fine from purei9_unofficial commandline with the following status output:

+--------------------------+----------+------+----------+-----------+----------+---------+---------+----------+-----------+
|            id            |  model   | name | localpw  | connected |  status  | dustbin | battery | firmware | powermode |
+--------------------------+----------+------+----------+-----------+----------+---------+---------+----------+-----------+
| XXXXXXXXXXXXXXXXXXXXXXXX | PUREi9.2 | PI92 | XXXXXXXX |   True    | Sleeping |  unset  |  High   |  42.19   |  MEDIUM   |
+--------------------------+----------+------+----------+-----------+----------+---------+---------+----------+-----------+

The dustbin is installed and the robot works fine through the app,

It seems there are issues interpreting the dustbin status in the getdustbinstatus function?

Local mode seems broken

Local mode doesn't work for me because the robot says its the wrong localpw. Maybe the changed the firmware? I've disabled the tests for local mode for now.

Start/Stop robot via Cloud API

Add support for start/stopping the robot from via the cloud API. The cloud API is not exactly well-organized (in my opionion). Might need to look at the newer "Wellbeeing" App 1, which might use another API.

Unable to fetch maps KeyError: 'interactiveId'

python3 -m purei9_unofficial -d -o json cloud -v 2 -c "[email protected]:password" maps -r 900000000000000000000000
2022-12-12 23:11:26,077 - purei9_unofficial.util - DEBUG - HTTP POST https://api.delta.electrolux.com/api/Clients/Wellbeing
2022-12-12 23:11:26,083 - urllib3.connectionpool - DEBUG - Starting new HTTPS connection (1): api.delta.electrolux.com:443
2022-12-12 23:11:26,352 - urllib3.connectionpool - DEBUG - https://api.delta.electrolux.com:443 "POST /api/Clients/Wellbeing HTTP/1.1" 200 None
2022-12-12 23:11:26,355 - purei9_unofficial.util - DEBUG - HTTP 200 <Response [200]> (sensitive data not shown)
2022-12-12 23:11:26,356 - purei9_unofficial.util - DEBUG - HTTP POST https://api.delta.electrolux.com/api/Users/Login
2022-12-12 23:11:26,359 - urllib3.connectionpool - DEBUG - Starting new HTTPS connection (1): api.delta.electrolux.com:443
2022-12-12 23:11:26,843 - urllib3.connectionpool - DEBUG - https://api.delta.electrolux.com:443 "POST /api/Users/Login HTTP/1.1" 200 None
2022-12-12 23:11:26,847 - purei9_unofficial.util - DEBUG - HTTP 200 <Response [200]> (sensitive data not shown)
2022-12-12 23:11:26,847 - purei9_unofficial.util - DEBUG - HTTP GET https://api.delta.electrolux.com/api/robots/900000000000000000000000/interactivemaps
2022-12-12 23:11:26,851 - urllib3.connectionpool - DEBUG - Starting new HTTPS connection (1): api.delta.electrolux.com:443
2022-12-12 23:11:27,166 - urllib3.connectionpool - DEBUG - https://api.delta.electrolux.com:443 "GET /api/robots/900000000000000000000000/interactivemaps HTTP/1.1" 200 760
2022-12-12 23:11:27,170 - purei9_unofficial.util - DEBUG - HTTP 200 <Response [200]> [{"zones":[{"name":"Room 1","id":"4afbe6a3-b856-4827-a29c-a3d55b1e6615","zoneType":"clean","vertices":[{"x":-4.333697,"y":-10.649171},{"x":-3.6309724,"y":-6.505852},{"x":0.05493144,"y":-6.580045},{"x":0.40908402,"y":-11.150099}],"roomCategory":4,"powerMode":3},{"name":"Room 2","id":"7a0a5662-6124-4db5-baae-0c28356edfb0","zoneType":"clean","vertices":[{"x":0.4653397,"y":-11.110397},{"x":0.4653397,"y":-6.3187327},{"x":3.8381655,"y":-6.318735},{"x":3.8381655,"y":-11.110397}],"roomCategory":8,"powerMode":3}],"name":"Home","rotation":0.0,"interactiveMapMessageUuid":"a6ba1659-4bed-42e4-b9d0-31c939cfdaed","id":"d19f4305-eda5-49f7-b0a0-74967d8a486d","status":0,"sequenceNumber":84,"timestamp":"2022-12-10T17:39:57+00:00","keep":true,"freeze":true}]
Traceback (most recent call last):
File "/usr/lib/python3.9/runpy.py", line 197, in _run_module_as_main
return _run_code(code, main_globals, None,
File "/usr/lib/python3.9/runpy.py", line 87, in _run_code
exec(code, run_globals)
File "/srv/homeassistant/.local/lib/python3.9/site-packages/purei9_unofficial/main.py", line 207, in
for m in rc.getMaps():
File "/srv/homeassistant/.local/lib/python3.9/site-packages/purei9_unofficial/cloudv2.py", line 154, in getMaps
return list(map(lambda x: CloudMap(self, x), r.json()))
File "/srv/homeassistant/.local/lib/python3.9/site-packages/purei9_unofficial/cloudv2.py", line 154, in
return list(map(lambda x: CloudMap(self, x), r.json()))
File "/srv/homeassistant/.local/lib/python3.9/site-packages/purei9_unofficial/cloudv2.py", line 265, in init
self.interactiveid = js["interactiveId"]
KeyError: 'interactiveId'
`

Make `cloud -v 2` the default

Asking the cloud v2 (wellbeein) API by default might help when debugging issues, because that is what the homeassistant intergration does.

Login 403 Error not a valid key=value pair (missing equal-sign) in Authorization header:

~ # python3 -m purei9_unofficial -d -o json cloud -v3 -c "XXXXXX:XXXXXXX" status
2024-08-03 12:27:06,778 - purei9_unofficial.util - DEBUG - HTTP POST https://api.ocp.electrolux.one/one-account-authorization/api/v1/token
2024-08-03 12:27:06,783 - urllib3.connectionpool - DEBUG - Starting new HTTPS connection (1): api.ocp.electrolux.one:443
2024-08-03 12:27:06,933 - urllib3.connectionpool - DEBUG - https://api.ocp.electrolux.one:443 "POST /one-account-authorization/api/v1/token HTTP/11" 200 809
2024-08-03 12:27:06,938 - purei9_unofficial.util - DEBUG - HTTP 200 <Response [200]> (sensitive data not shown)
2024-08-03 12:27:06,940 - purei9_unofficial.util - DEBUG - HTTP POST https://api.ocp.electrolux.one/one-account-authentication/api/v1/authenticate
2024-08-03 12:27:06,950 - urllib3.connectionpool - DEBUG - Starting new HTTPS connection (1): api.ocp.electrolux.one:443
2024-08-03 12:27:07,071 - urllib3.connectionpool - DEBUG - https://api.ocp.electrolux.one:443 "POST /one-account-authentication/api/v1/authenticate HTTP/11" 403 1582
2024-08-03 12:27:07,073 - purei9_unofficial.util - DEBUG - HTTP 403 <Response [403]> {"message":"'eyJraWQiOiIxMGZhMWQwOWY4YjM2OGFjYmE4YmRiNDYxOTFmZmVhODE1MmZiM2YzZjQ5N2RhZjk1OWFjNWIzNDM5ZDI3OGY0IiwiYWxnIjoiUlMyNTYiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjE3MjI2ODA4MjYsImlzcyI6Imh0dHBzOi8vYXBpLm9jcC5lbGVjdHJvbHV4Lm9uZS9vbmUtYWNjb3VudC1hdXRob3JpemF0aW9uIiwiYXVkIjoiaHR0cHM6Ly9hcGkub2NwLmVsZWN0cm9sdXgub25lIiwiZXhwIjoxNzIyNzI0MDI2LCJzdWIiOiJFbHhPbmVBcHAiLCJhenAiOiJFbHhPbmVBcHAiLCJzY29wZSI6IiIsIm9jYyI6bnVsbH0.Ch6hp-POAv4uNO0shn6zJDy1a2oWJ3sEsnW2z-Pc8h81JeInFwm7mGSvzzaUS9-4VNqM6UAFIqIhBWUt5McJ58SY4teJhKIAnRvPmI9RnDABLDdQVl7g-MpnzxGcftUqjrmyVklETgZNQ0E5M61Na9Y3pj83thhdHggY2rYG13MXH2i5jjKcK3Zzz5Jm0QIJmpd-DYV29ZOqNKZIl-f_Ee3QdE9gDuVto1uyPATePAHtZ1WXMuQIe2zvF6RXgvbhEMjlImHzzMT69KVXEG73gfYePHojct7piTJzBfJFNeDWbzEAqovsBWiybcBKsEcToOD_8FnkuqG-ZP1vEBOplg' not a valid key=value pair (missing equal-sign) in Authorization header: 'Bearer eyJraWQiOiIxMGZhMWQwOWY4YjM2OGFjYmE4YmRiNDYxOTFmZmVhODE1MmZiM2YzZjQ5N2RhZjk1OWFjNWIzNDM5ZDI3OGY0IiwiYWxnIjoiUlMyNTYiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjE3MjI2ODA4MjYsImlzcyI6Imh0dHBzOi8vYXBpLm9jcC5lbGVjdHJvbHV4Lm9uZS9vbmUtYWNjb3VudC1hdXRob3JpemF0aW9uIiwiYXVkIjoiaHR0cHM6Ly9hcGkub2NwLmVsZWN0cm9sdXgub25lIiwiZXhwIjoxNzIyNzI0MDI2LCJzdWIiOiJFbHhPbmVBcHAiLCJhenAiOiJFbHhPbmVBcHAiLCJzY29wZSI6IiIsIm9jYyI6bnVsbH0.Ch6hp-POAv4uNO0shn6zJDy1a2oWJ3sEsnW2z-Pc8h81JeInFwm7mGSvzzaUS9-4VNqM6UAFIqIhBWUt5McJ58SY4teJhKIAnRvPmI9RnDABLDdQVl7g-MpnzxGcftUqjrmyVklETgZNQ0E5M61Na9Y3pj83thhdHggY2rYG13MXH2i5jjKcK3Zzz5Jm0QIJmpd-DYV29ZOqNKZIl-f_Ee3QdE9gDuVto1uyPATePAHtZ1WXMuQIe2zvF6RXgvbhEMjlImHzzMT69KVXEG73gfYePHojct7piTJzBfJFNeDWbzEAqovsBWiybcBKsEcToOD_8FnkuqG-ZP1vEBOplg'."}
2024-08-03 12:27:07,073 - purei9_unofficial.util - DEBUG - HTTP POST https://api.ocp.electrolux.one/one-account-authentication/api/v1/authenticate
2024-08-03 12:27:07,077 - urllib3.connectionpool - DEBUG - Starting new HTTPS connection (1): api.ocp.electrolux.one:443
2024-08-03 12:27:07,193 - urllib3.connectionpool - DEBUG - https://api.ocp.electrolux.one:443 "POST /one-account-authentication/api/v1/authenticate HTTP/11" 403 1582
2024-08-03 12:27:07,194 - purei9_unofficial.util - DEBUG - HTTP 403 <Response [403]> {"message":"'eyJraWQiOiIxMGZhMWQwOWY4YjM2OGFjYmE4YmRiNDYxOTFmZmVhODE1MmZiM2YzZjQ5N2RhZjk1OWFjNWIzNDM5ZDI3OGY0IiwiYWxnIjoiUlMyNTYiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjE3MjI2ODA4MjYsImlzcyI6Imh0dHBzOi8vYXBpLm9jcC5lbGVjdHJvbHV4Lm9uZS9vbmUtYWNjb3VudC1hdXRob3JpemF0aW9uIiwiYXVkIjoiaHR0cHM6Ly9hcGkub2NwLmVsZWN0cm9sdXgub25lIiwiZXhwIjoxNzIyNzI0MDI2LCJzdWIiOiJFbHhPbmVBcHAiLCJhenAiOiJFbHhPbmVBcHAiLCJzY29wZSI6IiIsIm9jYyI6bnVsbH0.Ch6hp-POAv4uNO0shn6zJDy1a2oWJ3sEsnW2z-Pc8h81JeInFwm7mGSvzzaUS9-4VNqM6UAFIqIhBWUt5McJ58SY4teJhKIAnRvPmI9RnDABLDdQVl7g-MpnzxGcftUqjrmyVklETgZNQ0E5M61Na9Y3pj83thhdHggY2rYG13MXH2i5jjKcK3Zzz5Jm0QIJmpd-DYV29ZOqNKZIl-f_Ee3QdE9gDuVto1uyPATePAHtZ1WXMuQIe2zvF6RXgvbhEMjlImHzzMT69KVXEG73gfYePHojct7piTJzBfJFNeDWbzEAqovsBWiybcBKsEcToOD_8FnkuqG-ZP1vEBOplg' not a valid key=value pair (missing equal-sign) in Authorization header: 'Bearer eyJraWQiOiIxMGZhMWQwOWY4YjM2OGFjYmE4YmRiNDYxOTFmZmVhODE1MmZiM2YzZjQ5N2RhZjk1OWFjNWIzNDM5ZDI3OGY0IiwiYWxnIjoiUlMyNTYiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjE3MjI2ODA4MjYsImlzcyI6Imh0dHBzOi8vYXBpLm9jcC5lbGVjdHJvbHV4Lm9uZS9vbmUtYWNjb3VudC1hdXRob3JpemF0aW9uIiwiYXVkIjoiaHR0cHM6Ly9hcGkub2NwLmVsZWN0cm9sdXgub25lIiwiZXhwIjoxNzIyNzI0MDI2LCJzdWIiOiJFbHhPbmVBcHAiLCJhenAiOiJFbHhPbmVBcHAiLCJzY29wZSI6IiIsIm9jYyI6bnVsbH0.Ch6hp-POAv4uNO0shn6zJDy1a2oWJ3sEsnW2z-Pc8h81JeInFwm7mGSvzzaUS9-4VNqM6UAFIqIhBWUt5McJ58SY4teJhKIAnRvPmI9RnDABLDdQVl7g-MpnzxGcftUqjrmyVklETgZNQ0E5M61Na9Y3pj83thhdHggY2rYG13MXH2i5jjKcK3Zzz5Jm0QIJmpd-DYV29ZOqNKZIl-f_Ee3QdE9gDuVto1uyPATePAHtZ1WXMuQIe2zvF6RXgvbhEMjlImHzzMT69KVXEG73gfYePHojct7piTJzBfJFNeDWbzEAqovsBWiybcBKsEcToOD_8FnkuqG-ZP1vEBOplg'."}
2024-08-03 12:27:07,194 - purei9_unofficial.util - DEBUG - HTTP POST https://api.ocp.electrolux.one/one-account-authentication/api/v1/authenticate
2024-08-03 12:27:07,198 - urllib3.connectionpool - DEBUG - Starting new HTTPS connection (1): api.ocp.electrolux.one:443
2024-08-03 12:27:07,319 - urllib3.connectionpool - DEBUG - https://api.ocp.electrolux.one:443 "POST /one-account-authentication/api/v1/authenticate HTTP/11" 403 1582
2024-08-03 12:27:07,321 - purei9_unofficial.util - DEBUG - HTTP 403 <Response [403]> {"message":"'eyJraWQiOiIxMGZhMWQwOWY4YjM2OGFjYmE4YmRiNDYxOTFmZmVhODE1MmZiM2YzZjQ5N2RhZjk1OWFjNWIzNDM5ZDI3OGY0IiwiYWxnIjoiUlMyNTYiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjE3MjI2ODA4MjYsImlzcyI6Imh0dHBzOi8vYXBpLm9jcC5lbGVjdHJvbHV4Lm9uZS9vbmUtYWNjb3VudC1hdXRob3JpemF0aW9uIiwiYXVkIjoiaHR0cHM6Ly9hcGkub2NwLmVsZWN0cm9sdXgub25lIiwiZXhwIjoxNzIyNzI0MDI2LCJzdWIiOiJFbHhPbmVBcHAiLCJhenAiOiJFbHhPbmVBcHAiLCJzY29wZSI6IiIsIm9jYyI6bnVsbH0.Ch6hp-POAv4uNO0shn6zJDy1a2oWJ3sEsnW2z-Pc8h81JeInFwm7mGSvzzaUS9-4VNqM6UAFIqIhBWUt5McJ58SY4teJhKIAnRvPmI9RnDABLDdQVl7g-MpnzxGcftUqjrmyVklETgZNQ0E5M61Na9Y3pj83thhdHggY2rYG13MXH2i5jjKcK3Zzz5Jm0QIJmpd-DYV29ZOqNKZIl-f_Ee3QdE9gDuVto1uyPATePAHtZ1WXMuQIe2zvF6RXgvbhEMjlImHzzMT69KVXEG73gfYePHojct7piTJzBfJFNeDWbzEAqovsBWiybcBKsEcToOD_8FnkuqG-ZP1vEBOplg' not a valid key=value pair (missing equal-sign) in Authorization header: 'Bearer eyJraWQiOiIxMGZhMWQwOWY4YjM2OGFjYmE4YmRiNDYxOTFmZmVhODE1MmZiM2YzZjQ5N2RhZjk1OWFjNWIzNDM5ZDI3OGY0IiwiYWxnIjoiUlMyNTYiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjE3MjI2ODA4MjYsImlzcyI6Imh0dHBzOi8vYXBpLm9jcC5lbGVjdHJvbHV4Lm9uZS9vbmUtYWNjb3VudC1hdXRob3JpemF0aW9uIiwiYXVkIjoiaHR0cHM6Ly9hcGkub2NwLmVsZWN0cm9sdXgub25lIiwiZXhwIjoxNzIyNzI0MDI2LCJzdWIiOiJFbHhPbmVBcHAiLCJhenAiOiJFbHhPbmVBcHAiLCJzY29wZSI6IiIsIm9jYyI6bnVsbH0.Ch6hp-POAv4uNO0shn6zJDy1a2oWJ3sEsnW2z-Pc8h81JeInFwm7mGSvzzaUS9-4VNqM6UAFIqIhBWUt5McJ58SY4teJhKIAnRvPmI9RnDABLDdQVl7g-MpnzxGcftUqjrmyVklETgZNQ0E5M61Na9Y3pj83thhdHggY2rYG13MXH2i5jjKcK3Zzz5Jm0QIJmpd-DYV29ZOqNKZIl-f_Ee3QdE9gDuVto1uyPATePAHtZ1WXMuQIe2zvF6RXgvbhEMjlImHzzMT69KVXEG73gfYePHojct7piTJzBfJFNeDWbzEAqovsBWiybcBKsEcToOD_8FnkuqG-ZP1vEBOplg'."}
2024-08-03 12:27:07,321 - purei9_unofficial.util - ERROR - Giving up due to no left retries. Wrong credentials?
Traceback (most recent call last):
  File "/usr/lib/python3.12/site-packages/purei9_unofficial/util.py", line 22, in do_http
    r.raise_for_status()
  File "/usr/lib/python3.12/site-packages/requests/models.py", line 1024, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 403 Client Error: Forbidden for url: https://api.ocp.electrolux.one/one-account-authentication/api/v1/authenticate

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.12/site-packages/purei9_unofficial/util.py", line 22, in do_http
    r.raise_for_status()
  File "/usr/lib/python3.12/site-packages/requests/models.py", line 1024, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 403 Client Error: Forbidden for url: https://api.ocp.electrolux.one/one-account-authentication/api/v1/authenticate

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/usr/lib/python3.12/site-packages/purei9_unofficial/__main__.py", line 164, in <module>
    robots = client.getRobots()
             ^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/purei9_unofficial/cloudv3.py", line 251, in getRobots
    r = do_http("GET", self.apiurl + "/appliances", headers=self._getHeaders())
                                                            ^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/purei9_unofficial/cloudv3.py", line 227, in _getHeaders
    r = do_http("POST", self.authenticationurl + "/authenticate", json={"username":self.username, "password": self.password}, headers={"Authorization": "Bearer " + self.token["accessToken"],
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/purei9_unofficial/util.py", line 26, in do_http
    return do_http(method, url, retries-1, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/purei9_unofficial/util.py", line 26, in do_http
    return do_http(method, url, retries-1, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/site-packages/purei9_unofficial/util.py", line 29, in do_http
    raise r
  File "/usr/lib/python3.12/site-packages/purei9_unofficial/util.py", line 22, in do_http
    r.raise_for_status()
  File "/usr/lib/python3.12/site-packages/requests/models.py", line 1024, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 403 Client Error: Forbidden for url: https://api.ocp.electrolux.one/one-account-authentication/api/v1/authenticate
~ #

Unify BinaryMessage class to parse/write Robot Messages

Currently UDP (IP Discovery) and TCP/TLS (Control) connection code uses different code to parse/format messages. Since they use the same format, this should be unified using the newer (and better written) BinaryMessage class code.

Support for new API

Given that the Wellbeeing app is going to be deprecated at some point, we'll probably need to move to the API used by the newest app. See #21.

Add some tests

Not sure how to do right now, but some tests would be great.

Unable to connect local vacuum because there is no localpw

I am able to run (from hass.io) the following:
python3 -m purei9_unofficial cloud -c username@domain:password status
and I get the response with field values for id, name, localpw, connected, status, battery and firmware.
The problem now for me is that the value for localpw is empty and
python3 -m purei_unofficial local -a <ip_address> status
doesn't work without -l value for local password, tried without it and with -l "" and with -l''

PureI firmware version: 41.16

Start spot cleaning

Implement command for starting a spot cleaning. It is cleaning a area of 1x1 meter area but 2 times.

Help reverse engineering the API

First I'd like to thank @Phype for his awesome work of reverse engineering the API, because it seems like a lot of hard work to accomplish this task.

I've been trying to use this project for a few months now, but I've been unsuccessful. Since I am getting a 404 error trying to get my purei9.2 status, I thought my i9.2 might be registered under another server than the one used on this project (I am from Brazil, and sounds reasonable different regions use different API locations), so I decided to do some digging myself.
The only thing I have been able to discover is that the API domain used by my account is the api.ocp.electrolux.one. I then tried to just replace the domain used on this project by it, but no luck.

I am able to successfully decrypt HTTPS data with from Google Chrome on a rooted Android emulator with PCAPDroid, but the Electrolux app doesn't work even with PCAPDroid CA cert installed at the system level, so I'd like help with that if you may.

I don't want to make a big fuss about this issue, because it seems like there are some oddities exclusive to my region (or maybe to just my account). For example, I was talking with a developer from the Electrolux app and he said my vaccum wasn't supposed to show as white o the app, because the white image is just a development placeholder.

Thank you for your attention.

Local mode on RX8: async robot messages confuse sendrecv()

Using the local variant for controlling two RX8 robots - so far it works, but there are some observations I wanted to share:

  • "Local" works only with Python on openssl 1.x
  • "Local" works flawlessly with localpw (there's another bug filed, I can't confirm local broken for RX8)

While the robot is running and the local connection kept active, the robot seems to send async messages to the open channel when changing status (ex. cleaning -> go for charging), which remain in the socket queue. On next sendrecv(), which appears to expect an empty socket queue, an exception is raised if the received response can't be converted rightly (ex. "power mode can't be 4") because there's no message flag checking implemented. Ever observed this with your robots?
Wondering there needs to be a message dispatcher with callbacks - ever planned async status notifications?

Make `cloud -v 3` the default

After some more testing, i guess we should make the new API the default.
I'll leave it to -v 2 for now for some more testing.

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.