standardbots 2.0.0.dev1752004997__tar.gz → 2.0.0.dev1753213530__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.
Potentially problematic release.
This version of standardbots might be problematic. Click here for more details.
- {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/PKG-INFO +1 -1
- {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/README.md +38 -22
- {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/setup.py +1 -1
- {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/standardbots/auto_generated/apis.py +44 -0
- {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/standardbots/auto_generated/models.py +147 -5
- {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/standardbots.egg-info/PKG-INFO +1 -1
- {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/tests/test_apis.py +48 -19
- {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/setup.cfg +0 -0
- {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/standardbots/__init__.py +0 -0
- {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/standardbots/auto_generated/__init__.py +0 -0
- {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/standardbots.egg-info/SOURCES.txt +0 -0
- {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/standardbots.egg-info/dependency_links.txt +0 -0
- {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/standardbots.egg-info/requires.txt +0 -0
- {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/standardbots.egg-info/top_level.txt +0 -0
- {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/tests/fixtures/__init__.py +0 -0
- {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/tests/fixtures/client_fixt.py +0 -0
- {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/tests/fixtures/robot_fixt.py +0 -0
- {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/tests/fixtures/routines_fixt.py +0 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
You can test the API by using the playground, add your own file or use the
|
|
2
|
-
When a client has an issue, and they send their code.
|
|
1
|
+
You can test the API by using the playground, add your own file or use the
|
|
2
|
+
existing python scripts. When a client has an issue, and they send their code.
|
|
3
|
+
It's helpful to test their code to see possible issues
|
|
3
4
|
|
|
4
5
|
# ENABLE API ON YOUR LOCAL MACHINE
|
|
5
6
|
|
|
6
|
-
Follow the steps here
|
|
7
|
+
Follow the steps here
|
|
8
|
+
https://www.notion.so/standardbots/Using-the-REST-API-b2c778d47969444dac61483f0117acad
|
|
7
9
|
|
|
8
10
|
# CONFIG
|
|
9
11
|
|
|
@@ -11,7 +13,8 @@ At the top of the file, use your token
|
|
|
11
13
|
|
|
12
14
|
# RUN
|
|
13
15
|
|
|
14
|
-
To run a script move into the `sdks/python` folder and run
|
|
16
|
+
To run a script move into the `sdks/python` folder and run
|
|
17
|
+
`python playground/filename.py`
|
|
15
18
|
|
|
16
19
|
# To create a test build
|
|
17
20
|
|
|
@@ -48,8 +51,8 @@ To set up tests:
|
|
|
48
51
|
cd sdks/python
|
|
49
52
|
```
|
|
50
53
|
|
|
51
|
-
Note: Make sure to select proper python interpreter.
|
|
52
|
-
|
|
54
|
+
Note: Make sure to select proper python interpreter. Sometimes there is an error
|
|
55
|
+
related to missing "dotenv" module, to solve this install it separately
|
|
53
56
|
|
|
54
57
|
```
|
|
55
58
|
python3 -m pip install python-dotenv
|
|
@@ -60,16 +63,18 @@ python3 -m pip install -r requirements-dev.txt
|
|
|
60
63
|
|
|
61
64
|
#### Sample routine
|
|
62
65
|
|
|
63
|
-
You need to add the sample routine in
|
|
66
|
+
You need to add the sample routine in
|
|
67
|
+
[sdks/python/tests/fixtures/test_public_api_routine.json](./tests/fixtures/test_public_api_routine.json)
|
|
68
|
+
to your target test environment (i.e. upload the routine to ).
|
|
64
69
|
|
|
65
70
|
The name of routine should be "Test Public API"
|
|
66
71
|
|
|
67
72
|
#### Sample globals
|
|
68
73
|
|
|
69
|
-
- _Variable._ Add global variable called "test_public_api_global" with any
|
|
74
|
+
- _Variable._ Add global variable called "test_public_api_global" with any
|
|
75
|
+
value.
|
|
70
76
|
- _Space._ Create a global space called "Test global space" of any kind.
|
|
71
77
|
|
|
72
|
-
|
|
73
78
|
## Running
|
|
74
79
|
|
|
75
80
|
Here is a basic test command:
|
|
@@ -81,7 +86,8 @@ SB_API_TOKEN=...
|
|
|
81
86
|
python3 -m pytest ./tests --cov=standardbots --token=$SB_API_TOKEN --api-url=$SB_API_URL
|
|
82
87
|
```
|
|
83
88
|
|
|
84
|
-
You may also set up a `.env` file at `sdks/python/.env` with the following
|
|
89
|
+
You may also set up a `.env` file at `sdks/python/.env` with the following
|
|
90
|
+
contents:
|
|
85
91
|
|
|
86
92
|
```bash
|
|
87
93
|
export SB_API_URL=http://34.162.0.32:3000
|
|
@@ -96,38 +102,43 @@ python3 -m pytest ./tests --cov=standardbots
|
|
|
96
102
|
|
|
97
103
|
### Robot state and testing (Markers)
|
|
98
104
|
|
|
99
|
-
We need the bot to be in a certain state to run certain tests. For example, we
|
|
105
|
+
We need the bot to be in a certain state to run certain tests. For example, we
|
|
106
|
+
need a routine to be running in order to stop the routine. Camera should be
|
|
107
|
+
disconnected by default.
|
|
100
108
|
|
|
101
109
|
At start of testing, robot should:
|
|
102
110
|
|
|
103
111
|
- _NOT_ be e-stopped.
|
|
104
112
|
|
|
105
|
-
|
|
106
113
|
The basic idea here is:
|
|
107
114
|
|
|
108
115
|
- These special tests will not be run by default.
|
|
109
|
-
- You may pass a flag (e.g. `--camera-
|
|
116
|
+
- You may pass a flag (e.g. `--camera-connected`) when the bot is in the correct
|
|
117
|
+
state to run the tests.
|
|
110
118
|
- Markers usage:
|
|
111
|
-
- When `--gripper=<type>` flag is passed(`type` is value from `GripperKindEnum`
|
|
119
|
+
- When `--gripper=<type>` flag is passed(`type` is value from `GripperKindEnum`
|
|
120
|
+
enum):
|
|
112
121
|
1. Tests expect that this specific gripper is connected
|
|
113
122
|
2. Tests without the flag are not run.
|
|
114
|
-
- When `--camera-
|
|
123
|
+
- When `--camera-connected` flag is passed:
|
|
115
124
|
1. Tests with the flag are run.
|
|
116
125
|
2. Tests without the flag are not run.
|
|
117
126
|
|
|
118
|
-
We use [pytest markers](https://docs.pytest.org/en/7.1.x/example/markers.html)
|
|
127
|
+
We use [pytest markers](https://docs.pytest.org/en/7.1.x/example/markers.html)
|
|
128
|
+
to do this.
|
|
119
129
|
|
|
120
130
|
#### Camera disconnected
|
|
121
131
|
|
|
122
132
|
The wrist camera should be disabled. Then run:
|
|
123
133
|
|
|
124
134
|
```bash
|
|
125
|
-
python3 -m pytest ./tests --cov=standardbots --camera-
|
|
135
|
+
python3 -m pytest ./tests --cov=standardbots --camera-connected
|
|
126
136
|
```
|
|
127
137
|
|
|
128
138
|
#### E-stop
|
|
129
139
|
|
|
130
|
-
No marker needed for e-stop. However, we do rely on active recovery of e-stop
|
|
140
|
+
No marker needed for e-stop. However, we do rely on active recovery of e-stop
|
|
141
|
+
and getting the failure state in order to do these tests.
|
|
131
142
|
|
|
132
143
|
When e-stop test runs, cannot have bot in a failure state (pre-test will fail).
|
|
133
144
|
|
|
@@ -151,16 +162,19 @@ tests/test_apis.py
|
|
|
151
162
|
Fixes:
|
|
152
163
|
|
|
153
164
|
- _Make sure you can log into remote control._ Ensure that botman is connected.
|
|
154
|
-
- _Ensure that the robot URL is up-to-date._ Botman url will often change when
|
|
165
|
+
- _Ensure that the robot URL is up-to-date._ Botman url will often change when
|
|
166
|
+
you reboot.
|
|
155
167
|
|
|
156
168
|
### Custom sensors
|
|
157
169
|
|
|
158
170
|
To test custom sensors:
|
|
171
|
+
|
|
159
172
|
- go to the menu on the left bottom corner;
|
|
160
173
|
- Click on 'Equipment';
|
|
161
174
|
- Add Gripper > Custom Gripper;
|
|
162
175
|
- Go to the Sensors tab and click 'Add Sensor';
|
|
163
|
-
- Keep the default values as they are (name: 'Sensor 1', kind: 'Control Box IO',
|
|
176
|
+
- Keep the default values as they are (name: 'Sensor 1', kind: 'Control Box IO',
|
|
177
|
+
sensor value: 'low');
|
|
164
178
|
- Hit 'Save' and make sure the Custom Gripper is enabled.
|
|
165
179
|
|
|
166
180
|
Then run:
|
|
@@ -173,11 +187,13 @@ python3 -m pytest ./tests --cov=standardbots --gripper=custom_sensors
|
|
|
173
187
|
|
|
174
188
|
> See command: `botctl publicapi test:setup-bot`.
|
|
175
189
|
|
|
176
|
-
We now have a common robot database state that can be used for testing. While
|
|
190
|
+
We now have a common robot database state that can be used for testing. While
|
|
191
|
+
this isn't necessary for use, it does provide a common state for testing.
|
|
177
192
|
|
|
178
193
|
### How to create a new backup
|
|
179
194
|
|
|
180
|
-
In the place where you are running the stack and want to create the backup (e.g.
|
|
195
|
+
In the place where you are running the stack and want to create the backup (e.g.
|
|
196
|
+
on the control box):
|
|
181
197
|
|
|
182
198
|
```bash
|
|
183
199
|
DB_USER=sb
|
|
@@ -284,6 +284,50 @@ class Default:
|
|
|
284
284
|
)
|
|
285
285
|
|
|
286
286
|
|
|
287
|
+
def get_equipment(
|
|
288
|
+
self,
|
|
289
|
+
) -> Response[
|
|
290
|
+
Union[
|
|
291
|
+
models.GetEquipmentConfigResponse,
|
|
292
|
+
models.ErrorResponse,
|
|
293
|
+
None
|
|
294
|
+
],
|
|
295
|
+
models.GetEquipmentConfigResponse
|
|
296
|
+
]:
|
|
297
|
+
"""
|
|
298
|
+
Get the currently-configured equipment
|
|
299
|
+
|
|
300
|
+
"""
|
|
301
|
+
path = "/api/v1/equipment"
|
|
302
|
+
try:
|
|
303
|
+
response = self._request_manager.request(
|
|
304
|
+
"GET",
|
|
305
|
+
path,
|
|
306
|
+
headers=self._request_manager.json_headers(),
|
|
307
|
+
)
|
|
308
|
+
parsed = None
|
|
309
|
+
if response.status == 200:
|
|
310
|
+
parsed = models.parse_get_equipment_config_response(json.loads(response.data))
|
|
311
|
+
|
|
312
|
+
is_user_error = response.status >= 400 and response.status <= 500
|
|
313
|
+
is_unavailable = response.status == 503
|
|
314
|
+
if parsed is None and (is_user_error or is_unavailable):
|
|
315
|
+
parsed = models.parse_error_response(json.loads(response.data))
|
|
316
|
+
|
|
317
|
+
return Response(
|
|
318
|
+
parsed,
|
|
319
|
+
response.status,
|
|
320
|
+
response
|
|
321
|
+
)
|
|
322
|
+
except urllib3.exceptions.MaxRetryError:
|
|
323
|
+
return Response(
|
|
324
|
+
models.ErrorResponse(
|
|
325
|
+
error=models.ErrorEnum.InternalServerError,
|
|
326
|
+
message="Connection Refused"
|
|
327
|
+
),
|
|
328
|
+
503,
|
|
329
|
+
None
|
|
330
|
+
)
|
|
287
331
|
def control_gripper(
|
|
288
332
|
self,
|
|
289
333
|
body: models.GripperCommandRequest,
|
|
@@ -1186,18 +1186,106 @@ def serialize_environment_variable(data: EnvironmentVariable) -> object:
|
|
|
1186
1186
|
}
|
|
1187
1187
|
|
|
1188
1188
|
@dataclass
|
|
1189
|
-
class
|
|
1190
|
-
"""
|
|
1189
|
+
class EquipmentConfig:
|
|
1190
|
+
"""Equipment configuration
|
|
1191
|
+
"""
|
|
1192
|
+
id: Union[str, None] = None
|
|
1193
|
+
name: Union[str, None] = None
|
|
1194
|
+
kind: Union[str, None] = None
|
|
1195
|
+
is_enabled: Union[bool, None] = None
|
|
1196
|
+
config: Union[str, None] = None
|
|
1197
|
+
|
|
1198
|
+
def validate_id(self, value: str) -> Tuple[bool, str]:
|
|
1199
|
+
if value is None:
|
|
1200
|
+
return [False, "id is required for EquipmentConfig"]
|
|
1201
|
+
|
|
1202
|
+
if not isinstance(value, str):
|
|
1203
|
+
return [False, "id must be of type str for EquipmentConfig, got " + type(value).__name__]
|
|
1204
|
+
|
|
1205
|
+
return [True, ""]
|
|
1206
|
+
|
|
1207
|
+
def validate_name(self, value: str) -> Tuple[bool, str]:
|
|
1208
|
+
if value is None:
|
|
1209
|
+
return [True, ""]
|
|
1210
|
+
|
|
1211
|
+
if not isinstance(value, str):
|
|
1212
|
+
return [False, "name must be of type str for EquipmentConfig, got " + type(value).__name__]
|
|
1213
|
+
|
|
1214
|
+
return [True, ""]
|
|
1215
|
+
|
|
1216
|
+
def validate_kind(self, value: str) -> Tuple[bool, str]:
|
|
1217
|
+
if value is None:
|
|
1218
|
+
return [False, "kind is required for EquipmentConfig"]
|
|
1219
|
+
|
|
1220
|
+
if not isinstance(value, str):
|
|
1221
|
+
return [False, "kind must be of type str for EquipmentConfig, got " + type(value).__name__]
|
|
1222
|
+
|
|
1223
|
+
return [True, ""]
|
|
1224
|
+
|
|
1225
|
+
def validate_is_enabled(self, value: bool) -> Tuple[bool, str]:
|
|
1226
|
+
if value is None:
|
|
1227
|
+
return [False, "is_enabled is required for EquipmentConfig"]
|
|
1228
|
+
|
|
1229
|
+
if not isinstance(value, bool):
|
|
1230
|
+
return [False, "is_enabled must be of type bool for EquipmentConfig, got " + type(value).__name__]
|
|
1231
|
+
|
|
1232
|
+
return [True, ""]
|
|
1233
|
+
|
|
1234
|
+
def validate_config(self, value: str) -> Tuple[bool, str]:
|
|
1235
|
+
if value is None:
|
|
1236
|
+
return [False, "config is required for EquipmentConfig"]
|
|
1237
|
+
|
|
1238
|
+
if not isinstance(value, str):
|
|
1239
|
+
return [False, "config must be of type str for EquipmentConfig, got " + type(value).__name__]
|
|
1240
|
+
|
|
1241
|
+
return [True, ""]
|
|
1191
1242
|
|
|
1192
1243
|
def __post_init__(self):
|
|
1193
1244
|
# Type check incoming model - raise error if invalid (required or wrong type)
|
|
1245
|
+
is_valid, error_str = self.validate_id(self.id)
|
|
1246
|
+
if not is_valid:
|
|
1247
|
+
raise TypeError(error_str)
|
|
1248
|
+
is_valid, error_str = self.validate_name(self.name)
|
|
1249
|
+
if not is_valid:
|
|
1250
|
+
raise TypeError(error_str)
|
|
1251
|
+
is_valid, error_str = self.validate_kind(self.kind)
|
|
1252
|
+
if not is_valid:
|
|
1253
|
+
raise TypeError(error_str)
|
|
1254
|
+
is_valid, error_str = self.validate_is_enabled(self.is_enabled)
|
|
1255
|
+
if not is_valid:
|
|
1256
|
+
raise TypeError(error_str)
|
|
1257
|
+
is_valid, error_str = self.validate_config(self.config)
|
|
1258
|
+
if not is_valid:
|
|
1259
|
+
raise TypeError(error_str)
|
|
1194
1260
|
|
|
1195
|
-
def
|
|
1196
|
-
return
|
|
1261
|
+
def parse_equipment_config(data: object):
|
|
1262
|
+
return EquipmentConfig(
|
|
1263
|
+
id=parse_str(data["id"]) if "id" in data and data.get("id") is not None else None,
|
|
1264
|
+
name=parse_str(data["name"]) if "name" in data and data.get("name") is not None else None,
|
|
1265
|
+
kind=parse_str(data["kind"]) if "kind" in data and data.get("kind") is not None else None,
|
|
1266
|
+
is_enabled=parse_bool(data["is_enabled"]) if "is_enabled" in data and data.get("is_enabled") is not None else None,
|
|
1267
|
+
config=parse_str(data["config"]) if "config" in data and data.get("config") is not None else None,
|
|
1197
1268
|
)
|
|
1198
1269
|
|
|
1270
|
+
def serialize_equipment_config(data: EquipmentConfig) -> object:
|
|
1271
|
+
return {
|
|
1272
|
+
"id": serialize_str(data.id),
|
|
1273
|
+
"name": None if data.name is None else serialize_str(data.name),
|
|
1274
|
+
"kind": serialize_str(data.kind),
|
|
1275
|
+
"is_enabled": serialize_bool(data.is_enabled),
|
|
1276
|
+
"config": serialize_str(data.config),
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
EquipmentKeyAssignments = Dict[str, str]
|
|
1280
|
+
|
|
1281
|
+
def parse_equipment_key_assignments(data: object) -> EquipmentKeyAssignments:
|
|
1282
|
+
return {
|
|
1283
|
+
parse_str(key): parse_str(value) for key, value in data.items()
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1199
1286
|
def serialize_equipment_key_assignments(data: EquipmentKeyAssignments) -> object:
|
|
1200
1287
|
return {
|
|
1288
|
+
serialize_str(key): serialize_str(value) for key, value in data.items()
|
|
1201
1289
|
}
|
|
1202
1290
|
|
|
1203
1291
|
class ErrorEnum(Enum):
|
|
@@ -4058,6 +4146,14 @@ def parse_environment_variables_list(data: object) -> EnvironmentVariablesList:
|
|
|
4058
4146
|
def serialize_environment_variables_list(data: EnvironmentVariablesList) -> List[object]:
|
|
4059
4147
|
return [serialize_environment_variable(item) for item in data]
|
|
4060
4148
|
|
|
4149
|
+
EquipmentConfigList = List[EquipmentConfig]
|
|
4150
|
+
|
|
4151
|
+
def parse_equipment_config_list(data: object) -> EquipmentConfigList:
|
|
4152
|
+
return [parse_equipment_config(item) for item in data]
|
|
4153
|
+
|
|
4154
|
+
def serialize_equipment_config_list(data: EquipmentConfigList) -> List[object]:
|
|
4155
|
+
return [serialize_equipment_config(item) for item in data]
|
|
4156
|
+
|
|
4061
4157
|
@dataclass
|
|
4062
4158
|
class StartRecordingRequest:
|
|
4063
4159
|
"""Request to start recording movement and camera data"""
|
|
@@ -4077,7 +4173,7 @@ class StartRecordingRequest:
|
|
|
4077
4173
|
if value is None:
|
|
4078
4174
|
return [True, ""]
|
|
4079
4175
|
|
|
4080
|
-
if not isinstance(value,
|
|
4176
|
+
if not (isinstance(value, dict) and all((isinstance(k, str) and isinstance(val, str)) for k, val in value.items())):
|
|
4081
4177
|
return [False, "equipmentKeyAssignments must be of type EquipmentKeyAssignments for StartRecordingRequest, got " + type(value).__name__]
|
|
4082
4178
|
|
|
4083
4179
|
return [True, ""]
|
|
@@ -6668,6 +6764,37 @@ def serialize_routine(data: Routine) -> object:
|
|
|
6668
6764
|
"environment_variables": None if data.environment_variables is None else serialize_environment_variables_list(data.environment_variables),
|
|
6669
6765
|
}
|
|
6670
6766
|
|
|
6767
|
+
@dataclass
|
|
6768
|
+
class GetEquipmentConfigResponse:
|
|
6769
|
+
"""Response to get the currently-configured equipment
|
|
6770
|
+
"""
|
|
6771
|
+
equipment: Union[EquipmentConfigList, None] = None
|
|
6772
|
+
|
|
6773
|
+
def validate_equipment(self, value: EquipmentConfigList) -> Tuple[bool, str]:
|
|
6774
|
+
if value is None:
|
|
6775
|
+
return [True, ""]
|
|
6776
|
+
|
|
6777
|
+
if not (isinstance(value, list) and all(isinstance(x, EquipmentConfig) for x in value)):
|
|
6778
|
+
return [False, "equipment must be of type EquipmentConfigList for GetEquipmentConfigResponse, got " + type(value).__name__]
|
|
6779
|
+
|
|
6780
|
+
return [True, ""]
|
|
6781
|
+
|
|
6782
|
+
def __post_init__(self):
|
|
6783
|
+
# Type check incoming model - raise error if invalid (required or wrong type)
|
|
6784
|
+
is_valid, error_str = self.validate_equipment(self.equipment)
|
|
6785
|
+
if not is_valid:
|
|
6786
|
+
raise TypeError(error_str)
|
|
6787
|
+
|
|
6788
|
+
def parse_get_equipment_config_response(data: object):
|
|
6789
|
+
return GetEquipmentConfigResponse(
|
|
6790
|
+
equipment=parse_equipment_config_list(data["equipment"]) if "equipment" in data and data.get("equipment") is not None else None,
|
|
6791
|
+
)
|
|
6792
|
+
|
|
6793
|
+
def serialize_get_equipment_config_response(data: GetEquipmentConfigResponse) -> object:
|
|
6794
|
+
return {
|
|
6795
|
+
"equipment": None if data.equipment is None else serialize_equipment_config_list(data.equipment),
|
|
6796
|
+
}
|
|
6797
|
+
|
|
6671
6798
|
@dataclass
|
|
6672
6799
|
class CalibrationData:
|
|
6673
6800
|
"""The result of a calibration"""
|
|
@@ -8059,6 +8186,7 @@ class RecorderConfig:
|
|
|
8059
8186
|
bots: Union[RecorderBotDetailsList, None] = None
|
|
8060
8187
|
offset: Union[OffsetConfig, None] = None
|
|
8061
8188
|
sensors: Union[SensorConfigList, None] = None
|
|
8189
|
+
equipmentKeyAssignments: Union[EquipmentKeyAssignments, None] = None
|
|
8062
8190
|
|
|
8063
8191
|
def validate_isSecondary(self, value: bool) -> Tuple[bool, str]:
|
|
8064
8192
|
if value is None:
|
|
@@ -8114,6 +8242,15 @@ class RecorderConfig:
|
|
|
8114
8242
|
|
|
8115
8243
|
return [True, ""]
|
|
8116
8244
|
|
|
8245
|
+
def validate_equipmentKeyAssignments(self, value: EquipmentKeyAssignments) -> Tuple[bool, str]:
|
|
8246
|
+
if value is None:
|
|
8247
|
+
return [True, ""]
|
|
8248
|
+
|
|
8249
|
+
if not (isinstance(value, dict) and all((isinstance(k, str) and isinstance(val, str)) for k, val in value.items())):
|
|
8250
|
+
return [False, "equipmentKeyAssignments must be of type EquipmentKeyAssignments for RecorderConfig, got " + type(value).__name__]
|
|
8251
|
+
|
|
8252
|
+
return [True, ""]
|
|
8253
|
+
|
|
8117
8254
|
def __post_init__(self):
|
|
8118
8255
|
# Type check incoming model - raise error if invalid (required or wrong type)
|
|
8119
8256
|
is_valid, error_str = self.validate_isSecondary(self.isSecondary)
|
|
@@ -8134,6 +8271,9 @@ class RecorderConfig:
|
|
|
8134
8271
|
is_valid, error_str = self.validate_sensors(self.sensors)
|
|
8135
8272
|
if not is_valid:
|
|
8136
8273
|
raise TypeError(error_str)
|
|
8274
|
+
is_valid, error_str = self.validate_equipmentKeyAssignments(self.equipmentKeyAssignments)
|
|
8275
|
+
if not is_valid:
|
|
8276
|
+
raise TypeError(error_str)
|
|
8137
8277
|
|
|
8138
8278
|
def parse_recorder_config(data: object):
|
|
8139
8279
|
return RecorderConfig(
|
|
@@ -8143,6 +8283,7 @@ def parse_recorder_config(data: object):
|
|
|
8143
8283
|
bots=parse_recorder_bot_details_list(data["bots"]) if "bots" in data and data.get("bots") is not None else None,
|
|
8144
8284
|
offset=parse_offset_config(data["offset"]) if "offset" in data and data.get("offset") is not None else None,
|
|
8145
8285
|
sensors=parse_sensor_config_list(data["sensors"]) if "sensors" in data and data.get("sensors") is not None else None,
|
|
8286
|
+
equipmentKeyAssignments=parse_equipment_key_assignments(data["equipmentKeyAssignments"]) if "equipmentKeyAssignments" in data and data.get("equipmentKeyAssignments") is not None else None,
|
|
8146
8287
|
)
|
|
8147
8288
|
|
|
8148
8289
|
def serialize_recorder_config(data: RecorderConfig) -> object:
|
|
@@ -8153,6 +8294,7 @@ def serialize_recorder_config(data: RecorderConfig) -> object:
|
|
|
8153
8294
|
"bots": None if data.bots is None else serialize_recorder_bot_details_list(data.bots),
|
|
8154
8295
|
"offset": None if data.offset is None else serialize_offset_config(data.offset),
|
|
8155
8296
|
"sensors": None if data.sensors is None else serialize_sensor_config_list(data.sensors),
|
|
8297
|
+
"equipmentKeyAssignments": None if data.equipmentKeyAssignments is None else serialize_equipment_key_assignments(data.equipmentKeyAssignments),
|
|
8156
8298
|
}
|
|
8157
8299
|
|
|
8158
8300
|
@dataclass
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Tests for Standard Bots Python SDKs."""
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
import time
|
|
4
5
|
|
|
5
6
|
import pytest
|
|
@@ -266,6 +267,37 @@ class TestPostRoutineEditorRoutinePlay:
|
|
|
266
267
|
# TODO: can add tests when there is failure in routine(collision etc)
|
|
267
268
|
|
|
268
269
|
|
|
270
|
+
class TestGetEquipment:
|
|
271
|
+
"""Tests: [GET] `/api/v1/equipment`
|
|
272
|
+
|
|
273
|
+
Not covered here: Ensuring that updates to equipment config and enabled/disabled status are reflected in the response.
|
|
274
|
+
"""
|
|
275
|
+
|
|
276
|
+
def test_get_equipment(self, client_live: StandardBotsRobot) -> None:
|
|
277
|
+
"""Get equipment test"""
|
|
278
|
+
client = client_live
|
|
279
|
+
|
|
280
|
+
with client.connection():
|
|
281
|
+
res = client.equipment.get_equipment()
|
|
282
|
+
|
|
283
|
+
assert not res.isNotOk()
|
|
284
|
+
assert res.status == 200
|
|
285
|
+
assert isinstance(res.data, models.GetEquipmentConfigResponse)
|
|
286
|
+
assert res.data.equipment is not None
|
|
287
|
+
assert isinstance(res.data.equipment, list)
|
|
288
|
+
if len(res.data.equipment) > 0:
|
|
289
|
+
assert isinstance(res.data.equipment[0], models.EquipmentConfig)
|
|
290
|
+
|
|
291
|
+
equipment = res.data.equipment[0]
|
|
292
|
+
assert equipment.id is not None
|
|
293
|
+
assert equipment.kind is not None
|
|
294
|
+
assert equipment.is_enabled is not None
|
|
295
|
+
assert equipment.config is not None
|
|
296
|
+
|
|
297
|
+
# Can be parsed as JSON
|
|
298
|
+
assert json.loads(equipment.config) is not None
|
|
299
|
+
|
|
300
|
+
|
|
269
301
|
class TestGetEquipmentEndEffectorConfiguration:
|
|
270
302
|
"""Tests: [GET] `/api/v1/equipment/end-effector/configuration`"""
|
|
271
303
|
|
|
@@ -959,12 +991,12 @@ class TestPostMovementBrakesEmergencyStop:
|
|
|
959
991
|
break
|
|
960
992
|
assert data.failure.kind == "EStopTriggered"
|
|
961
993
|
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
994
|
+
# ######################################
|
|
995
|
+
# Recover from e-stop
|
|
996
|
+
# ######################################
|
|
965
997
|
|
|
966
|
-
|
|
967
|
-
|
|
998
|
+
with client.connection():
|
|
999
|
+
res_status = client.recovery.recover.recover()
|
|
968
1000
|
|
|
969
1001
|
def test_estop_sim(self, client_sim: StandardBotsRobot) -> None:
|
|
970
1002
|
"""Basic test: sim mode
|
|
@@ -1292,7 +1324,7 @@ class TestPostMovementPositionArm:
|
|
|
1292
1324
|
z=0.707,
|
|
1293
1325
|
w=0.707,
|
|
1294
1326
|
)
|
|
1295
|
-
initial_tooltip_position = models.Position(x=0.0, y=0.3687, z=1.
|
|
1327
|
+
initial_tooltip_position = models.Position(x=0.0, y=0.3687, z=1.3618)
|
|
1296
1328
|
target_tooltip_position = models.Position(x=0.1, y=0.4, z=1.3)
|
|
1297
1329
|
pose_body = models.ArmPositionUpdateRequest(
|
|
1298
1330
|
kind=models.ArmPositionUpdateRequestKindEnum.TooltipPosition,
|
|
@@ -1595,7 +1627,6 @@ class TestGetRecoveryStatus:
|
|
|
1595
1627
|
class TestGetCameraStreamRGB:
|
|
1596
1628
|
"""Tests: [GET] `/api/v1/camera/stream/rgb`"""
|
|
1597
1629
|
|
|
1598
|
-
@pytest.mark.camera_disconnected
|
|
1599
1630
|
def test_camera_stream_rgb_camera_disconnected(
|
|
1600
1631
|
self, client_live: StandardBotsRobot
|
|
1601
1632
|
) -> None:
|
|
@@ -1607,6 +1638,7 @@ class TestGetCameraStreamRGB:
|
|
|
1607
1638
|
assert res.isNotOk()
|
|
1608
1639
|
assert res.status == 503
|
|
1609
1640
|
|
|
1641
|
+
@pytest.mark.camera_connected
|
|
1610
1642
|
def test_basic(self, client_live: StandardBotsRobot) -> None:
|
|
1611
1643
|
"""Basic test to get camera steam RGB"""
|
|
1612
1644
|
client = client_live
|
|
@@ -1642,7 +1674,6 @@ class TestGetCameraStreamRGB:
|
|
|
1642
1674
|
class TestPostCameraSettings:
|
|
1643
1675
|
"""Tests: [POST] `/api/v1/camera/settings`"""
|
|
1644
1676
|
|
|
1645
|
-
@pytest.mark.camera_disconnected
|
|
1646
1677
|
def test_post_camera_settings_camera_disconnected(
|
|
1647
1678
|
self, client_live: StandardBotsRobot
|
|
1648
1679
|
) -> None:
|
|
@@ -1662,6 +1693,7 @@ class TestPostCameraSettings:
|
|
|
1662
1693
|
assert res.isNotOk()
|
|
1663
1694
|
assert res.status == 503
|
|
1664
1695
|
|
|
1696
|
+
@pytest.mark.camera_connected
|
|
1665
1697
|
def test_post_camera_settings(self, client_live: StandardBotsRobot) -> None:
|
|
1666
1698
|
"""Basic test to update settings"""
|
|
1667
1699
|
client = client_live
|
|
@@ -1683,7 +1715,6 @@ class TestPostCameraSettings:
|
|
|
1683
1715
|
class TestGetCameraFrameRGB:
|
|
1684
1716
|
"""Tests: [GET] `/api/v1/camera/frame/rgb`"""
|
|
1685
1717
|
|
|
1686
|
-
@pytest.mark.camera_disconnected
|
|
1687
1718
|
def test_strem_rgb_camera_disconnected(
|
|
1688
1719
|
self, client_live: StandardBotsRobot
|
|
1689
1720
|
) -> None:
|
|
@@ -1705,6 +1736,7 @@ class TestGetCameraFrameRGB:
|
|
|
1705
1736
|
assert res.isNotOk()
|
|
1706
1737
|
assert res.status == 503
|
|
1707
1738
|
|
|
1739
|
+
@pytest.mark.camera_connected
|
|
1708
1740
|
def test_get_color_frame_invalid_payload(
|
|
1709
1741
|
self, client_live: StandardBotsRobot
|
|
1710
1742
|
) -> None:
|
|
@@ -1717,6 +1749,7 @@ class TestGetCameraFrameRGB:
|
|
|
1717
1749
|
assert res.status == 400
|
|
1718
1750
|
assert res.data.error == models.ErrorEnum.RequestFailedValidation
|
|
1719
1751
|
|
|
1752
|
+
@pytest.mark.camera_connected
|
|
1720
1753
|
def test_get_color_frame(self, client_live: StandardBotsRobot) -> None:
|
|
1721
1754
|
"""Test get color frame"""
|
|
1722
1755
|
client = client_live
|
|
@@ -1746,7 +1779,6 @@ class TestGetCameraFrameRGB:
|
|
|
1746
1779
|
class TestGetCameraIntrinsicsRGB:
|
|
1747
1780
|
"""Tests: [GET] `/api/v1/camera/intrinsics/rgb`"""
|
|
1748
1781
|
|
|
1749
|
-
@pytest.mark.camera_disconnected
|
|
1750
1782
|
def test_get_camera_intrinsics_camera_disconnected(
|
|
1751
1783
|
self, client_live: StandardBotsRobot
|
|
1752
1784
|
) -> None:
|
|
@@ -1757,6 +1789,7 @@ class TestGetCameraIntrinsicsRGB:
|
|
|
1757
1789
|
assert res.isNotOk()
|
|
1758
1790
|
assert res.status == 503
|
|
1759
1791
|
|
|
1792
|
+
@pytest.mark.camera_connected
|
|
1760
1793
|
def test_get_camera_intrinsics(self, client_live: StandardBotsRobot) -> None:
|
|
1761
1794
|
"""Test to get camera intrinsics"""
|
|
1762
1795
|
|
|
@@ -1772,7 +1805,6 @@ class TestGetCameraIntrinsicsRGB:
|
|
|
1772
1805
|
class TestGetCameraStatus:
|
|
1773
1806
|
"""Tests: [GET] `/api/v1/camera/status`"""
|
|
1774
1807
|
|
|
1775
|
-
@pytest.mark.camera_disconnected
|
|
1776
1808
|
def test_get_camera_status_camera_disconnected(
|
|
1777
1809
|
self, client_live: StandardBotsRobot
|
|
1778
1810
|
) -> None:
|
|
@@ -1785,6 +1817,7 @@ class TestGetCameraStatus:
|
|
|
1785
1817
|
assert isinstance(res.data, models.CameraStatus)
|
|
1786
1818
|
assert not res.data.connected
|
|
1787
1819
|
|
|
1820
|
+
@pytest.mark.camera_connected
|
|
1788
1821
|
def test_get_camera_status(self, client_live: StandardBotsRobot) -> None:
|
|
1789
1822
|
"""Test to get camera status"""
|
|
1790
1823
|
|
|
@@ -1800,6 +1833,7 @@ class TestGetCameraStatus:
|
|
|
1800
1833
|
class TestGetConnectedCameras:
|
|
1801
1834
|
"""Tests: [GET] `/api/v1/cameras/connected`"""
|
|
1802
1835
|
|
|
1836
|
+
@pytest.mark.camera_connected
|
|
1803
1837
|
def test_get_connected_cameras(self, client_live: StandardBotsRobot) -> None:
|
|
1804
1838
|
"""Test case when camera is disconnected"""
|
|
1805
1839
|
|
|
@@ -2652,9 +2686,7 @@ class TestPostPosesJointPose:
|
|
|
2652
2686
|
client = client_live
|
|
2653
2687
|
with client.connection():
|
|
2654
2688
|
body = models.JointPoseRequest(
|
|
2655
|
-
pose=models.JointAngles(
|
|
2656
|
-
j0=20.0, j1=-30.0, j2=71.0, j3=-130.0, j4=-43.0, j5=60.0
|
|
2657
|
-
)
|
|
2689
|
+
pose=models.JointAngles(j0=1.0, j1=-1.0, j2=1.0, j3=1.0, j4=1.0, j5=1.0)
|
|
2658
2690
|
)
|
|
2659
2691
|
res = client.poses.construct_pose.joint_pose(body)
|
|
2660
2692
|
assert not res.isNotOk()
|
|
@@ -3290,10 +3322,7 @@ class TestPostMovementPositionArmControlled:
|
|
|
3290
3322
|
assert heartbeat_data.event is not None
|
|
3291
3323
|
assert heartbeat_data.event.kind == models.ArmPositionUpdateKindEnum.Failure
|
|
3292
3324
|
assert heartbeat_data.event.failure is not None
|
|
3293
|
-
assert
|
|
3294
|
-
heartbeat_data.event.failure.reason
|
|
3295
|
-
== "Failed to generate a motion plan"
|
|
3296
|
-
)
|
|
3325
|
+
assert heartbeat_data.event.failure.reason == "Error while running motion"
|
|
3297
3326
|
|
|
3298
3327
|
def test_move_then_stop_heartbeat(
|
|
3299
3328
|
self,
|
|
@@ -3361,7 +3390,7 @@ class TestPostMovementPositionArmControlled:
|
|
|
3361
3390
|
)
|
|
3362
3391
|
|
|
3363
3392
|
assert movement_started > 0, "Movement never started"
|
|
3364
|
-
time.sleep(
|
|
3393
|
+
time.sleep(3)
|
|
3365
3394
|
|
|
3366
3395
|
# Check that the arm has stopped
|
|
3367
3396
|
res = client.recovery.recover.get_status()
|
|
File without changes
|
{standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/standardbots/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/tests/fixtures/__init__.py
RENAMED
|
File without changes
|
{standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/tests/fixtures/client_fixt.py
RENAMED
|
File without changes
|
{standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753213530}/tests/fixtures/robot_fixt.py
RENAMED
|
File without changes
|
|
File without changes
|