pyechorobotics 1.2.0__tar.gz → 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyechorobotics-1.2.0/src/pyechorobotics.egg-info → pyechorobotics-2.0.0}/PKG-INFO +7 -7
- {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/README.md +6 -6
- {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/pyproject.toml +1 -1
- {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/src/echoroboticsapi/__init__.py +0 -1
- {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/src/echoroboticsapi/api.py +105 -5
- {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/src/echoroboticsapi/models.py +6 -2
- {pyechorobotics-1.2.0 → pyechorobotics-2.0.0/src/pyechorobotics.egg-info}/PKG-INFO +7 -7
- {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/test/test_api.py +94 -17
- {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/test/test_smart_fetch.py +4 -2
- {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/LICENSE.md +0 -0
- {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/setup.cfg +0 -0
- {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/setup.py +0 -0
- {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/src/echoroboticsapi/smart_fetch.py +0 -0
- {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/src/echoroboticsapi/smart_mode.py +0 -0
- {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/src/pyechorobotics.egg-info/SOURCES.txt +0 -0
- {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/src/pyechorobotics.egg-info/dependency_links.txt +0 -0
- {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/src/pyechorobotics.egg-info/requires.txt +0 -0
- {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/src/pyechorobotics.egg-info/top_level.txt +0 -0
- {pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/test/test_smart_mode.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyechorobotics
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Access API for echorobotics robot lawn mowers
|
|
5
5
|
Author-email: functionpointer <suspendfunction@gmail.com>
|
|
6
6
|
License: GNU General Public License v3 or later (GPLv3+)
|
|
@@ -39,8 +39,8 @@ import logging
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
async def main():
|
|
42
|
-
async with aiohttp.ClientSession(
|
|
43
|
-
api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"])
|
|
42
|
+
async with aiohttp.ClientSession() as session:
|
|
43
|
+
api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"], email="your-email", password="your-password")
|
|
44
44
|
print(await api.last_statuses())
|
|
45
45
|
print(await api.set_mode("chargeAndWork"))
|
|
46
46
|
|
|
@@ -66,8 +66,8 @@ import logging
|
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
async def main():
|
|
69
|
-
async with aiohttp.ClientSession(
|
|
70
|
-
api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"])
|
|
69
|
+
async with aiohttp.ClientSession() as session:
|
|
70
|
+
api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"], email="your-email", password="your-password")
|
|
71
71
|
smartmode = echoroboticsapi.SmartMode("your-robot-id")
|
|
72
72
|
api.register_smart_mode(smartmode)
|
|
73
73
|
|
|
@@ -102,8 +102,8 @@ import logging
|
|
|
102
102
|
|
|
103
103
|
|
|
104
104
|
async def main():
|
|
105
|
-
async with aiohttp.ClientSession(
|
|
106
|
-
api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"])
|
|
105
|
+
async with aiohttp.ClientSession() as session:
|
|
106
|
+
api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"], email="your-email", password="your-password")
|
|
107
107
|
smartmode = echoroboticsapi.SmartMode("your-robot-id")
|
|
108
108
|
api.register_smart_mode(smartmode)
|
|
109
109
|
smartfetch = echoroboticsapi.SmartFetch(api)
|
|
@@ -21,8 +21,8 @@ import logging
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
async def main():
|
|
24
|
-
async with aiohttp.ClientSession(
|
|
25
|
-
api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"])
|
|
24
|
+
async with aiohttp.ClientSession() as session:
|
|
25
|
+
api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"], email="your-email", password="your-password")
|
|
26
26
|
print(await api.last_statuses())
|
|
27
27
|
print(await api.set_mode("chargeAndWork"))
|
|
28
28
|
|
|
@@ -48,8 +48,8 @@ import logging
|
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
async def main():
|
|
51
|
-
async with aiohttp.ClientSession(
|
|
52
|
-
api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"])
|
|
51
|
+
async with aiohttp.ClientSession() as session:
|
|
52
|
+
api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"], email="your-email", password="your-password")
|
|
53
53
|
smartmode = echoroboticsapi.SmartMode("your-robot-id")
|
|
54
54
|
api.register_smart_mode(smartmode)
|
|
55
55
|
|
|
@@ -84,8 +84,8 @@ import logging
|
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
async def main():
|
|
87
|
-
async with aiohttp.ClientSession(
|
|
88
|
-
api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"])
|
|
87
|
+
async with aiohttp.ClientSession() as session:
|
|
88
|
+
api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"], email="your-email", password="your-password")
|
|
89
89
|
smartmode = echoroboticsapi.SmartMode("your-robot-id")
|
|
90
90
|
api.register_smart_mode(smartmode)
|
|
91
91
|
smartfetch = echoroboticsapi.SmartFetch(api)
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
from typing import Any, TYPE_CHECKING, TypedDict
|
|
5
|
+
from warnings import deprecated
|
|
6
|
+
|
|
4
7
|
import pydantic
|
|
5
8
|
from aiohttp import ClientSession, ClientResponse
|
|
6
9
|
from yarl import URL
|
|
10
|
+
|
|
7
11
|
from .models import *
|
|
8
12
|
import logging
|
|
9
13
|
import time
|
|
14
|
+
from abc import ABC, abstractmethod
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return {"UserId": user_id, "UserToken": user_token}
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from . import SmartMode
|
|
14
18
|
|
|
15
19
|
|
|
16
20
|
class LastKnownMode:
|
|
@@ -22,12 +26,27 @@ class LastKnownMode:
|
|
|
22
26
|
self.pending_since = pending_since or time.time()
|
|
23
27
|
|
|
24
28
|
|
|
29
|
+
class AuthInfo(TypedDict):
|
|
30
|
+
access_token: str
|
|
31
|
+
refresh_token: str
|
|
32
|
+
obtained_timestamp: float
|
|
33
|
+
|
|
34
|
+
|
|
25
35
|
class Api:
|
|
26
36
|
"""Class to make authenticated requests."""
|
|
27
37
|
|
|
28
|
-
def __init__(
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
websession: ClientSession,
|
|
41
|
+
robot_ids: RobotId | list[RobotId],
|
|
42
|
+
email: str,
|
|
43
|
+
password: str,
|
|
44
|
+
):
|
|
29
45
|
"""Initialize the auth."""
|
|
30
46
|
self.websession = websession
|
|
47
|
+
self.email = email
|
|
48
|
+
self.password = password
|
|
49
|
+
self.auth_info: AuthInfo | None = None
|
|
31
50
|
if not isinstance(robot_ids, list):
|
|
32
51
|
robot_ids = [robot_ids]
|
|
33
52
|
self.robot_ids = robot_ids
|
|
@@ -36,6 +55,80 @@ class Api:
|
|
|
36
55
|
self.logger = logging.getLogger("echoroboticsapi")
|
|
37
56
|
self.smart_modes: dict[RobotId, "SmartMode"] = {}
|
|
38
57
|
|
|
58
|
+
async def loginv2(self) -> AuthInfo:
|
|
59
|
+
data = {"Email": self.email, "Password": self.password}
|
|
60
|
+
url = URL(f"https://myrobot.echorobotics.com/api/authentication/loginv2")
|
|
61
|
+
result = await self.request(
|
|
62
|
+
method="POST",
|
|
63
|
+
add_auth=False,
|
|
64
|
+
url=url,
|
|
65
|
+
json=data,
|
|
66
|
+
)
|
|
67
|
+
result.raise_for_status()
|
|
68
|
+
json_result = await result.json()
|
|
69
|
+
ret: AuthInfo = {
|
|
70
|
+
"access_token": json_result["Token"],
|
|
71
|
+
"refresh_token": json_result["RefreshToken"],
|
|
72
|
+
"obtained_timestamp": time.time(),
|
|
73
|
+
}
|
|
74
|
+
self.logger.info(
|
|
75
|
+
"loginv2 %s. Token len=%s; RefreshToken len=%s",
|
|
76
|
+
result.status,
|
|
77
|
+
len(ret["access_token"]),
|
|
78
|
+
len(ret["refresh_token"]),
|
|
79
|
+
)
|
|
80
|
+
return ret
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def token_refresh_duration(self):
|
|
84
|
+
return 5 * 60 # 5 minutes
|
|
85
|
+
|
|
86
|
+
async def get_access_token(self) -> str:
|
|
87
|
+
"""Returns a valid access token
|
|
88
|
+
Performs API call to the refresh endpoint if needed.
|
|
89
|
+
"""
|
|
90
|
+
if self.auth_info is None:
|
|
91
|
+
self.auth_info = await self.loginv2()
|
|
92
|
+
if self.auth_info is None:
|
|
93
|
+
return ""
|
|
94
|
+
if (
|
|
95
|
+
remaining := (
|
|
96
|
+
self.auth_info["obtained_timestamp"] + self.token_refresh_duration
|
|
97
|
+
)
|
|
98
|
+
- time.time()
|
|
99
|
+
) <= 0:
|
|
100
|
+
# have to refresh
|
|
101
|
+
url = URL(f"https://myrobot.echorobotics.com/api/authentication/refresh")
|
|
102
|
+
# url = URL(f"http://httpbin.org/cookies")
|
|
103
|
+
result = await self.request(
|
|
104
|
+
method="POST",
|
|
105
|
+
add_auth=False,
|
|
106
|
+
url=url,
|
|
107
|
+
headers={"Authorization": f"Bearer {self.auth_info['access_token']}"},
|
|
108
|
+
cookies={
|
|
109
|
+
"br_rt": self.auth_info["refresh_token"],
|
|
110
|
+
# "br_at": self.auth_info["access_token"],
|
|
111
|
+
},
|
|
112
|
+
json={},
|
|
113
|
+
)
|
|
114
|
+
result.raise_for_status()
|
|
115
|
+
json = await result.json()
|
|
116
|
+
new_auth_info: AuthInfo = {
|
|
117
|
+
"access_token": json["Token"],
|
|
118
|
+
"refresh_token": json["RefreshToken"],
|
|
119
|
+
"obtained_timestamp": time.time(),
|
|
120
|
+
}
|
|
121
|
+
self.auth_info = new_auth_info
|
|
122
|
+
self.logger.info(
|
|
123
|
+
"auth refresh %s. access_token len=%s, refresh_token len=%s",
|
|
124
|
+
result.status,
|
|
125
|
+
len(new_auth_info["access_token"]),
|
|
126
|
+
len(new_auth_info["refresh_token"]),
|
|
127
|
+
)
|
|
128
|
+
else:
|
|
129
|
+
self.logger.debug("auth refresh not needed, %.2f s remaining", remaining)
|
|
130
|
+
return self.auth_info["access_token"]
|
|
131
|
+
|
|
39
132
|
def _set_mode_use_current_sleep_times(self):
|
|
40
133
|
yield from [3, 2, 2, 2]
|
|
41
134
|
while True:
|
|
@@ -278,10 +371,17 @@ class Api:
|
|
|
278
371
|
await self.smart_modes[robot_id].notify_history_list_received(resp)
|
|
279
372
|
return resp
|
|
280
373
|
|
|
281
|
-
async def request(
|
|
374
|
+
async def request(
|
|
375
|
+
self, method: str, url: URL, add_auth: bool = True, **kwargs
|
|
376
|
+
) -> ClientResponse:
|
|
282
377
|
"""Make a request."""
|
|
378
|
+
if headers := kwargs.pop("headers", {}):
|
|
379
|
+
headers = dict(headers)
|
|
380
|
+
if add_auth:
|
|
381
|
+
headers["authorization"] = f"Bearer {await self.get_access_token()}"
|
|
283
382
|
return await self.websession.request(
|
|
284
383
|
method,
|
|
285
384
|
url,
|
|
385
|
+
headers=headers,
|
|
286
386
|
**kwargs,
|
|
287
387
|
)
|
|
@@ -11,12 +11,16 @@ from pydantic import (
|
|
|
11
11
|
validator,
|
|
12
12
|
RootModel,
|
|
13
13
|
ConfigDict,
|
|
14
|
+
StringConstraints,
|
|
14
15
|
)
|
|
15
|
-
from typing import Literal, Self
|
|
16
|
+
from typing import Literal, Self, Annotated
|
|
16
17
|
from dateutil.parser import isoparse as dateutil_isoparse
|
|
17
18
|
from enum import Enum
|
|
18
19
|
|
|
19
|
-
RobotId =
|
|
20
|
+
RobotId = Annotated[
|
|
21
|
+
str,
|
|
22
|
+
StringConstraints(strip_whitespace=True, to_upper=True),
|
|
23
|
+
]
|
|
20
24
|
Mode = Literal["chargeAndWork", "chargeAndStay", "work"]
|
|
21
25
|
Status = Literal[
|
|
22
26
|
"Offline",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyechorobotics
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Access API for echorobotics robot lawn mowers
|
|
5
5
|
Author-email: functionpointer <suspendfunction@gmail.com>
|
|
6
6
|
License: GNU General Public License v3 or later (GPLv3+)
|
|
@@ -39,8 +39,8 @@ import logging
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
async def main():
|
|
42
|
-
async with aiohttp.ClientSession(
|
|
43
|
-
api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"])
|
|
42
|
+
async with aiohttp.ClientSession() as session:
|
|
43
|
+
api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"], email="your-email", password="your-password")
|
|
44
44
|
print(await api.last_statuses())
|
|
45
45
|
print(await api.set_mode("chargeAndWork"))
|
|
46
46
|
|
|
@@ -66,8 +66,8 @@ import logging
|
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
async def main():
|
|
69
|
-
async with aiohttp.ClientSession(
|
|
70
|
-
api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"])
|
|
69
|
+
async with aiohttp.ClientSession() as session:
|
|
70
|
+
api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"], email="your-email", password="your-password")
|
|
71
71
|
smartmode = echoroboticsapi.SmartMode("your-robot-id")
|
|
72
72
|
api.register_smart_mode(smartmode)
|
|
73
73
|
|
|
@@ -102,8 +102,8 @@ import logging
|
|
|
102
102
|
|
|
103
103
|
|
|
104
104
|
async def main():
|
|
105
|
-
async with aiohttp.ClientSession(
|
|
106
|
-
api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"])
|
|
105
|
+
async with aiohttp.ClientSession() as session:
|
|
106
|
+
api = echoroboticsapi.Api(session, robot_ids=["your-robot-id"], email="your-email", password="your-password")
|
|
107
107
|
smartmode = echoroboticsapi.SmartMode("your-robot-id")
|
|
108
108
|
api.register_smart_mode(smartmode)
|
|
109
109
|
smartfetch = echoroboticsapi.SmartFetch(api)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
1
3
|
import datetime
|
|
2
4
|
import json
|
|
3
5
|
import urllib.parse
|
|
@@ -26,20 +28,64 @@ async def client_session():
|
|
|
26
28
|
|
|
27
29
|
|
|
28
30
|
@pytest.fixture
|
|
29
|
-
def
|
|
30
|
-
|
|
31
|
+
def api_not_logged_in(
|
|
32
|
+
client_session: aiohttp.ClientSession,
|
|
33
|
+
robot_id: echoroboticsapi.RobotId,
|
|
34
|
+
user_email: str,
|
|
35
|
+
user_password: str,
|
|
36
|
+
):
|
|
37
|
+
ret = echoroboticsapi.Api(
|
|
38
|
+
client_session, robot_id, email=user_email, password=user_password
|
|
39
|
+
)
|
|
31
40
|
|
|
32
41
|
def fast():
|
|
33
42
|
while True:
|
|
34
43
|
yield 0.01
|
|
35
44
|
|
|
36
45
|
ret._set_mode_use_current_sleep_times = fast
|
|
37
|
-
|
|
46
|
+
return ret
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@pytest.fixture
|
|
50
|
+
def api(api_not_logged_in: echoroboticsapi.Api):
|
|
51
|
+
api_not_logged_in.auth_info = {
|
|
52
|
+
"access_token": "abc",
|
|
53
|
+
"refresh_token": "abc",
|
|
54
|
+
"obtained_timestamp": time.time(),
|
|
55
|
+
}
|
|
56
|
+
yield api_not_logged_in
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pytest.fixture
|
|
60
|
+
def expected_headers():
|
|
61
|
+
return {"authorization": "Bearer abc"}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.mark.asyncio
|
|
65
|
+
async def test_loginv2(
|
|
66
|
+
robot_id: echoroboticsapi.RobotId,
|
|
67
|
+
api_not_logged_in: echoroboticsapi.Api,
|
|
68
|
+
mock_aioresponse,
|
|
69
|
+
user_email: str,
|
|
70
|
+
user_password: str,
|
|
71
|
+
):
|
|
72
|
+
mock_payload = {"Token": "abc", "RefreshToken": "abc"}
|
|
73
|
+
expected_url = f"https://myrobot.echorobotics.com/api/authentication/loginv2"
|
|
74
|
+
mock_aioresponse.post(expected_url, payload=mock_payload)
|
|
75
|
+
|
|
76
|
+
api_not_logged_in.auth_info = await api_not_logged_in.loginv2()
|
|
77
|
+
assert api_not_logged_in.auth_info["access_token"] == "abc"
|
|
78
|
+
mock_aioresponse.assert_called_once_with(
|
|
79
|
+
expected_url,
|
|
80
|
+
method="POST",
|
|
81
|
+
headers={},
|
|
82
|
+
json={"Email": user_email, "Password": user_password},
|
|
83
|
+
)
|
|
38
84
|
|
|
39
85
|
|
|
40
86
|
@pytest.mark.asyncio
|
|
41
87
|
async def test_get_config(
|
|
42
|
-
robot_id: RobotId, api: echoroboticsapi.Api, mock_aioresponse
|
|
88
|
+
robot_id: RobotId, api: echoroboticsapi.Api, mock_aioresponse, expected_headers
|
|
43
89
|
):
|
|
44
90
|
mock_json = '{"IsError": true, "IsInProgress": false, "Message": "configurator.messages.robotOffline", "Data": null, "Descriptors": null, "ConfigId": 0, "ConfigVersionId": 0, "ConfigDateTime": "0001-01-01T00:00:00", "ConfigValidated": false}'
|
|
45
91
|
expected_url = f"https://myrobot.echorobotics.com/api/RobotConfig/GetConfig/{robot_id}?reload=False"
|
|
@@ -48,12 +94,14 @@ async def test_get_config(
|
|
|
48
94
|
resp = await api.get_config(reload=False, robot_id=robot_id)
|
|
49
95
|
|
|
50
96
|
assert resp
|
|
51
|
-
mock_aioresponse.assert_called_once_with(
|
|
97
|
+
mock_aioresponse.assert_called_once_with(
|
|
98
|
+
expected_url, method="GET", headers=expected_headers
|
|
99
|
+
)
|
|
52
100
|
|
|
53
101
|
|
|
54
102
|
@pytest.mark.asyncio
|
|
55
103
|
async def test_get_config_in_progress_without_config_version_id(
|
|
56
|
-
robot_id: RobotId, api: echoroboticsapi.Api, mock_aioresponse
|
|
104
|
+
robot_id: RobotId, api: echoroboticsapi.Api, mock_aioresponse, expected_headers
|
|
57
105
|
):
|
|
58
106
|
mock_json = '{"IsError": false, "IsInProgress": true, "Message": "configurator.apiresponse.get.willNowContactRobot", "ConfigId": 0, "ConfigDateTime": "0001-01-01T00:00:00", "ConfigValidated": false}'
|
|
59
107
|
expected_url = f"https://myrobot.echorobotics.com/api/RobotConfig/GetConfig/{robot_id}?reload=True"
|
|
@@ -63,12 +111,17 @@ async def test_get_config_in_progress_without_config_version_id(
|
|
|
63
111
|
|
|
64
112
|
assert resp.is_in_progress
|
|
65
113
|
assert resp.config_version_id is None
|
|
66
|
-
mock_aioresponse.assert_called_once_with(
|
|
114
|
+
mock_aioresponse.assert_called_once_with(
|
|
115
|
+
expected_url, method="GET", headers=expected_headers
|
|
116
|
+
)
|
|
67
117
|
|
|
68
118
|
|
|
69
119
|
@pytest.mark.asyncio
|
|
70
120
|
async def test_laststatuses(
|
|
71
|
-
api: echoroboticsapi.Api,
|
|
121
|
+
api: echoroboticsapi.Api,
|
|
122
|
+
mock_aioresponse,
|
|
123
|
+
robot_id: echoroboticsapi.RobotId,
|
|
124
|
+
expected_headers,
|
|
72
125
|
):
|
|
73
126
|
mock_json = r'{"QueryDate":"2025-01-24T18:50:19.3556944Z","Robots":["robot_id"],"StatusesInfo":[{"Robot":"robot_id","Status":"Off","MacAddress":"ac1f0fb92010","Date":"2024-12-20T15:17:14.861Z","Delta":"5 Wochen","EstimatedBatteryLevel":99.99,"Position":{"Longitude":6.1,"Latitude":5.5,"DateTime":"2024-11-04T13:17:48.461Z"},"QueryTime":"2025-01-24T18:50:19.3556944Z","HasValues":true,"IsOnline":false}],"RobotOfflineDelayInSeconds":1500}'
|
|
74
127
|
mock_json = mock_json.replace("robot_id", robot_id)
|
|
@@ -76,10 +129,11 @@ async def test_laststatuses(
|
|
|
76
129
|
mock_aioresponse.post(expected_url, body=mock_json)
|
|
77
130
|
last_statuses = await api.last_statuses()
|
|
78
131
|
|
|
79
|
-
mock_aioresponse.
|
|
132
|
+
mock_aioresponse.assert_called_with(
|
|
80
133
|
expected_url,
|
|
81
134
|
method="POST",
|
|
82
135
|
json=[robot_id],
|
|
136
|
+
headers=expected_headers,
|
|
83
137
|
)
|
|
84
138
|
assert last_statuses
|
|
85
139
|
assert last_statuses.statuses_info[0].robot == robot_id
|
|
@@ -89,7 +143,10 @@ async def test_laststatuses(
|
|
|
89
143
|
|
|
90
144
|
@pytest.mark.asyncio
|
|
91
145
|
async def test_history_list(
|
|
92
|
-
api: echoroboticsapi.Api,
|
|
146
|
+
api: echoroboticsapi.Api,
|
|
147
|
+
mock_aioresponse,
|
|
148
|
+
robot_id: echoroboticsapi.RobotId,
|
|
149
|
+
expected_headers,
|
|
93
150
|
):
|
|
94
151
|
mock_json = [
|
|
95
152
|
{
|
|
@@ -403,13 +460,17 @@ async def test_history_list(
|
|
|
403
460
|
mock_aioresponse.assert_called_once_with(
|
|
404
461
|
expected_url,
|
|
405
462
|
method="GET",
|
|
463
|
+
headers=expected_headers,
|
|
406
464
|
)
|
|
407
465
|
assert len(history_list) > 3
|
|
408
466
|
|
|
409
467
|
|
|
410
468
|
@pytest.mark.asyncio
|
|
411
469
|
async def test_current(
|
|
412
|
-
api: echoroboticsapi.Api,
|
|
470
|
+
api: echoroboticsapi.Api,
|
|
471
|
+
mock_aioresponse,
|
|
472
|
+
robot_id: echoroboticsapi.RobotId,
|
|
473
|
+
expected_headers,
|
|
413
474
|
):
|
|
414
475
|
mock_json = r'{"SerialNumber":"robot_id","ActionId":null,"Status":0,"Message":null}'
|
|
415
476
|
mock_json = mock_json.replace("robot_id", robot_id)
|
|
@@ -424,12 +485,16 @@ async def test_current(
|
|
|
424
485
|
mock_aioresponse.assert_called_once_with(
|
|
425
486
|
expected_url,
|
|
426
487
|
method="GET",
|
|
488
|
+
headers=expected_headers,
|
|
427
489
|
)
|
|
428
490
|
|
|
429
491
|
|
|
430
492
|
@pytest.mark.asyncio
|
|
431
493
|
async def test_set_mode_bad_contact(
|
|
432
|
-
api: echoroboticsapi.Api,
|
|
494
|
+
api: echoroboticsapi.Api,
|
|
495
|
+
mock_aioresponse,
|
|
496
|
+
robot_id: echoroboticsapi.RobotId,
|
|
497
|
+
expected_headers,
|
|
433
498
|
):
|
|
434
499
|
mock_json_old = (
|
|
435
500
|
rf'{{"SerialNumber":"{robot_id}","ActionId":null,"Status":0,"Message":null}}'
|
|
@@ -448,12 +513,17 @@ async def test_set_mode_bad_contact(
|
|
|
448
513
|
status_code = await api.set_mode("work", robot_id, use_current=True)
|
|
449
514
|
|
|
450
515
|
assert status_code == -1
|
|
451
|
-
mock_aioresponse.assert_called_with(
|
|
516
|
+
mock_aioresponse.assert_called_with(
|
|
517
|
+
expected_url, method="GET", headers=expected_headers
|
|
518
|
+
)
|
|
452
519
|
|
|
453
520
|
|
|
454
521
|
@pytest.mark.asyncio
|
|
455
522
|
async def test_set_mode_bad_contact_timeout(
|
|
456
|
-
api: echoroboticsapi.Api,
|
|
523
|
+
api: echoroboticsapi.Api,
|
|
524
|
+
mock_aioresponse,
|
|
525
|
+
robot_id: echoroboticsapi.RobotId,
|
|
526
|
+
expected_headers,
|
|
457
527
|
):
|
|
458
528
|
mock_json_old = (
|
|
459
529
|
rf'{{"SerialNumber":"{robot_id}","ActionId":null,"Status":0,"Message":null}}'
|
|
@@ -472,12 +542,17 @@ async def test_set_mode_bad_contact_timeout(
|
|
|
472
542
|
)
|
|
473
543
|
|
|
474
544
|
assert status_code == -1
|
|
475
|
-
mock_aioresponse.assert_called_with(
|
|
545
|
+
mock_aioresponse.assert_called_with(
|
|
546
|
+
expected_url, method="GET", headers=expected_headers
|
|
547
|
+
)
|
|
476
548
|
|
|
477
549
|
|
|
478
550
|
@pytest.mark.asyncio
|
|
479
551
|
async def test_set_mode_working(
|
|
480
|
-
api: echoroboticsapi.Api,
|
|
552
|
+
api: echoroboticsapi.Api,
|
|
553
|
+
mock_aioresponse,
|
|
554
|
+
robot_id: echoroboticsapi.RobotId,
|
|
555
|
+
expected_headers,
|
|
481
556
|
):
|
|
482
557
|
mock_json_old = (
|
|
483
558
|
rf'{{"SerialNumber":"{robot_id}","ActionId":null,"Status":0,"Message":null}}'
|
|
@@ -500,4 +575,6 @@ async def test_set_mode_working(
|
|
|
500
575
|
status_code = await api.set_mode("work", robot_id, use_current=True)
|
|
501
576
|
|
|
502
577
|
assert status_code == 200
|
|
503
|
-
mock_aioresponse.assert_called_with(
|
|
578
|
+
mock_aioresponse.assert_called_with(
|
|
579
|
+
expected_url, method="GET", headers=expected_headers
|
|
580
|
+
)
|
|
@@ -13,8 +13,10 @@ ALL_STATUSES = list(typing.get_args(echoroboticsapi.Status))
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
@pytest.fixture
|
|
16
|
-
def api(robot_id: RobotId):
|
|
17
|
-
api = echoroboticsapi.Api(
|
|
16
|
+
def api(robot_id: RobotId, user_email: str, user_password: str):
|
|
17
|
+
api = echoroboticsapi.Api(
|
|
18
|
+
websession=None, robot_ids=[robot_id], email=user_email, password=user_password
|
|
19
|
+
)
|
|
18
20
|
yield api
|
|
19
21
|
|
|
20
22
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyechorobotics-1.2.0 → pyechorobotics-2.0.0}/src/pyechorobotics.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|