standardbots 2.0.0.dev1752004997__tar.gz → 2.0.0.dev1753212895__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.

Files changed (18) hide show
  1. {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753212895}/PKG-INFO +1 -1
  2. {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753212895}/README.md +38 -22
  3. {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753212895}/setup.py +1 -1
  4. {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753212895}/standardbots/auto_generated/apis.py +44 -0
  5. {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753212895}/standardbots/auto_generated/models.py +147 -5
  6. {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753212895}/standardbots.egg-info/PKG-INFO +1 -1
  7. {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753212895}/tests/test_apis.py +48 -19
  8. {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753212895}/setup.cfg +0 -0
  9. {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753212895}/standardbots/__init__.py +0 -0
  10. {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753212895}/standardbots/auto_generated/__init__.py +0 -0
  11. {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753212895}/standardbots.egg-info/SOURCES.txt +0 -0
  12. {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753212895}/standardbots.egg-info/dependency_links.txt +0 -0
  13. {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753212895}/standardbots.egg-info/requires.txt +0 -0
  14. {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753212895}/standardbots.egg-info/top_level.txt +0 -0
  15. {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753212895}/tests/fixtures/__init__.py +0 -0
  16. {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753212895}/tests/fixtures/client_fixt.py +0 -0
  17. {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753212895}/tests/fixtures/robot_fixt.py +0 -0
  18. {standardbots-2.0.0.dev1752004997 → standardbots-2.0.0.dev1753212895}/tests/fixtures/routines_fixt.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: standardbots
3
- Version: 2.0.0.dev1752004997
3
+ Version: 2.0.0.dev1753212895
4
4
  Summary: Standard Bots RO1 Robotics API
5
5
  Home-page:
6
6
  Author: Standard Bots Support
@@ -1,9 +1,11 @@
1
- You can test the API by using the playground, add your own file or use the existing python scripts.
2
- When a client has an issue, and they send their code. It's helpful to test their code to see possible issues
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 https://www.notion.so/standardbots/Using-the-REST-API-b2c778d47969444dac61483f0117acad
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 `python playground/filename.py`
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
- Sometimes there is an error related to missing "dotenv" module, to solve this install it separately
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 [sdks/python/tests/fixtures/test_public_api_routine.json](./tests/fixtures/test_public_api_routine.json) to your target test environment (i.e. upload the routine to ).
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 value.
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 contents:
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 need a routine to be running in order to stop the routine. Camera should be connected by default.
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-disconnected`) when the bot is in the correct state to run the tests.
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` enum):
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-disconnected` flag is passed:
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) to do this.
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-disconnected
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 and getting the failure state in order to do these tests.
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 you reboot.
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', sensor value: 'low');
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 this isn't necessary for use, it does provide a common state for testing.
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. on the control box):
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
@@ -13,7 +13,7 @@
13
13
  from setuptools import setup, find_packages # noqa: H301
14
14
 
15
15
  NAME = "standardbots"
16
- VERSION = "2.0.0-dev1752004997"
16
+ VERSION = "2.0.0-dev1753212895"
17
17
  # To install the library, run the following
18
18
  #
19
19
  # python setup.py install
@@ -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 EquipmentKeyAssignments:
1190
- """Dictionary mapping equipment IDs to equipment keys"""
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 parse_equipment_key_assignments(data: object):
1196
- return EquipmentKeyAssignments(
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, EquipmentKeyAssignments):
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: standardbots
3
- Version: 2.0.0.dev1752004997
3
+ Version: 2.0.0.dev1753212895
4
4
  Summary: Standard Bots RO1 Robotics API
5
5
  Home-page:
6
6
  Author: Standard Bots Support
@@ -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
- # Recover from e-stop
964
- # ######################################
994
+ # ######################################
995
+ # Recover from e-stop
996
+ # ######################################
965
997
 
966
- with client.connection():
967
- res_status = client.recovery.recover.recover()
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.4618)
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(0.25)
3393
+ time.sleep(3)
3365
3394
 
3366
3395
  # Check that the arm has stopped
3367
3396
  res = client.recovery.recover.get_status()