standardbots 2.0.0.dev1737138088__tar.gz → 2.0.0.dev1740502639__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 (17) hide show
  1. {standardbots-2.0.0.dev1737138088 → standardbots-2.0.0.dev1740502639}/PKG-INFO +1 -1
  2. standardbots-2.0.0.dev1740502639/README.md +153 -0
  3. {standardbots-2.0.0.dev1737138088 → standardbots-2.0.0.dev1740502639}/setup.py +1 -1
  4. {standardbots-2.0.0.dev1737138088 → standardbots-2.0.0.dev1740502639}/standardbots/auto_generated/apis.py +186 -15
  5. {standardbots-2.0.0.dev1737138088 → standardbots-2.0.0.dev1740502639}/standardbots/auto_generated/models.py +665 -255
  6. {standardbots-2.0.0.dev1737138088 → standardbots-2.0.0.dev1740502639}/standardbots.egg-info/PKG-INFO +1 -1
  7. {standardbots-2.0.0.dev1737138088 → standardbots-2.0.0.dev1740502639}/standardbots.egg-info/SOURCES.txt +6 -1
  8. {standardbots-2.0.0.dev1737138088 → standardbots-2.0.0.dev1740502639}/standardbots.egg-info/top_level.txt +1 -0
  9. standardbots-2.0.0.dev1740502639/tests/fixtures/__init__.py +0 -0
  10. standardbots-2.0.0.dev1740502639/tests/fixtures/client_fixt.py +25 -0
  11. standardbots-2.0.0.dev1740502639/tests/fixtures/routines_fixt.py +48 -0
  12. standardbots-2.0.0.dev1740502639/tests/test_apis.py +2994 -0
  13. {standardbots-2.0.0.dev1737138088 → standardbots-2.0.0.dev1740502639}/setup.cfg +0 -0
  14. {standardbots-2.0.0.dev1737138088 → standardbots-2.0.0.dev1740502639}/standardbots/__init__.py +0 -0
  15. {standardbots-2.0.0.dev1737138088 → standardbots-2.0.0.dev1740502639}/standardbots/auto_generated/__init__.py +0 -0
  16. {standardbots-2.0.0.dev1737138088 → standardbots-2.0.0.dev1740502639}/standardbots.egg-info/dependency_links.txt +0 -0
  17. {standardbots-2.0.0.dev1737138088 → standardbots-2.0.0.dev1740502639}/standardbots.egg-info/requires.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: standardbots
3
- Version: 2.0.0.dev1737138088
3
+ Version: 2.0.0.dev1740502639
4
4
  Summary: Standard Bots RO1 Robotics API
5
5
  Home-page:
6
6
  Author: Standard Bots Support
@@ -0,0 +1,153 @@
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
3
+
4
+ # ENABLE API ON YOUR LOCAL MACHINE
5
+
6
+ Follow the steps here https://www.notion.so/standardbots/Using-the-REST-API-b2c778d47969444dac61483f0117acad
7
+
8
+ # CONFIG
9
+
10
+ At the top of the file, use your token
11
+
12
+ # RUN
13
+
14
+ To run a script move into the `sdks/python` folder and run `python playground/filename.py`
15
+
16
+ # To create a test build
17
+
18
+ 1. Update version in `setup.py`
19
+ 2. Run `python3 setup.py sdist bdist_wheel`
20
+
21
+ # Tests
22
+
23
+ ## Setup
24
+
25
+ To set up tests:
26
+
27
+ ```bash
28
+ cd sdks/python
29
+ ```
30
+
31
+ Note: Make sure to select proper python interpreter.
32
+ Sometimes there is an error related to missing "dotenv" module, to solve this install it separately
33
+
34
+ ```
35
+ python3 -m pip install python-dotenv
36
+ python3 -m pip install -r requirements-dev.txt
37
+ ```
38
+
39
+ ### Create sample data
40
+
41
+ #### Sample routine
42
+
43
+ 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 ).
44
+
45
+ The name of routine should be "Test Public API"
46
+
47
+ #### Sample globals
48
+
49
+ - _Variable._ Add global variable called "test_public_api_global" with any value.
50
+ - _Space._ Create a global space called "Test global space" of any kind.
51
+
52
+
53
+ ## Running
54
+
55
+ Here is a basic test command:
56
+
57
+ ```bash
58
+ SB_API_URL=http://34.162.0.32:3000
59
+ SB_API_TOKEN=...
60
+
61
+ python3 -m pytest ./tests --cov=standardbots --token=$SB_API_TOKEN --api-url=$SB_API_URL
62
+ ```
63
+
64
+ You may also set up a `.env` file at `sdks/python/.env` with the following contents:
65
+
66
+ ```bash
67
+ export SB_API_URL=http://34.162.0.32:3000
68
+ export SB_API_TOKEN=...
69
+ ```
70
+
71
+ Then you can just do:
72
+
73
+ ```bash
74
+ python3 -m pytest ./tests --cov=standardbots
75
+ ```
76
+
77
+ ### Robot state and testing (Markers)
78
+
79
+ 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.
80
+
81
+ At start of testing, robot should:
82
+
83
+ - _NOT_ be e-stopped.
84
+
85
+
86
+ The basic idea here is:
87
+
88
+ - These special tests will not be run by default.
89
+ - You may pass a flag (e.g. `--routine-running`) when the bot is in the correct state to run the tests.
90
+ - Markers usage:
91
+ - When `--routine-running` flag is passed:
92
+ 1. Tests with the flag are run.
93
+ 2. Tests without the flag are not run.
94
+ - When `--gripper=<type>` flag is passed(`type` is value from `GripperKindEnum` enum):
95
+ 1. Tests expect that this specific gripper is connected
96
+ 2. Tests without the flag are not run.
97
+ - When `--camera-disconnected` flag is passed:
98
+ 1. Tests with the flag are run.
99
+ 2. Tests without the flag are not run.
100
+
101
+ We use [pytest markers](https://docs.pytest.org/en/7.1.x/example/markers.html) to do this.
102
+
103
+ #### Routine running
104
+
105
+ The special sample routine ("Test Public API") should be running prior to running these tests. Then do:
106
+
107
+ ```bash
108
+ python3 -m pytest ./tests --cov=standardbots --routine-running
109
+ ```
110
+
111
+ #### E-stop
112
+
113
+ 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.
114
+
115
+ When e-stop test runs, cannot have bot in a failure state (pre-test will fail).
116
+
117
+ ## Troubleshooting
118
+
119
+ ### Tests are hanging
120
+
121
+ The first test appears to start but then nothing happens for several seconds:
122
+
123
+ ```bash
124
+ $ python3 -m pytest ./tests --cov=standardbots
125
+ ========================================================================================================== test session starts ===========================================================================================================
126
+ platform linux -- Python 3.10.12, pytest-6.2.5, py-1.10.0, pluggy-0.13.0
127
+ rootdir: /workspaces/sb/sdks/python, configfile: pytest.ini
128
+ plugins: ament-pep257-0.12.11, ament-xmllint-0.12.11, launch-testing-1.0.6, launch-testing-ros-0.19.7, ament-flake8-0.12.11, ament-lint-0.12.11, ament-copyright-0.12.11, colcon-core-0.18.1, cov-6.0.0
129
+ collected 110 items
130
+
131
+ tests/test_apis.py
132
+ ```
133
+
134
+ Fixes:
135
+
136
+ - _Make sure you can log into remote control._ Ensure that botman is connected.
137
+ - _Ensure that the robot URL is up-to-date._ Botman url will often change when you reboot.
138
+
139
+ ### Custom sensors
140
+
141
+ To test custom sensors:
142
+ - go to the menu on the left bottom corner;
143
+ - Click on 'Equipment';
144
+ - Add Gripper > Custom Gripper;
145
+ - Go to the Sensors tab and click 'Add Sensor';
146
+ - Keep the default values as they are (name: 'Sensor 1', kind: 'Control Box IO', sensor value: 'low');
147
+ - Hit 'Save' and make sure the Custom Gripper is enabled.
148
+
149
+ Then run:
150
+
151
+ ```bash
152
+ python3 -m pytest ./tests --cov=standardbots --custom-sensors
153
+ ```
@@ -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-dev1737138088"
16
+ VERSION = "2.0.0-dev1740502639"
17
17
  # To install the library, run the following
18
18
  #
19
19
  # python setup.py install
@@ -76,6 +76,95 @@ class RequestManager:
76
76
 
77
77
  class Default:
78
78
  _request_manager: RequestManager
79
+ class Calibration:
80
+ def __init__(self, request_manager: RequestManager):
81
+ self._request_manager = request_manager
82
+
83
+
84
+ def get_active_calibration(
85
+ self,
86
+ ) -> Response[
87
+ Union[
88
+ models.ActiveCalibrationContainer,
89
+ models.ErrorResponse,
90
+ None
91
+ ],
92
+ models.ActiveCalibrationContainer
93
+ ]:
94
+ """
95
+ Get the active calibration for the robot.
96
+
97
+ """
98
+ path = "/api/v1/calibration/active"
99
+ try:
100
+ response = self._request_manager.request(
101
+ "GET",
102
+ path,
103
+ headers=self._request_manager.json_headers(),
104
+ )
105
+ parsed = None
106
+ if response.status == 200:
107
+ parsed = models.parse_active_calibration_container(json.loads(response.data))
108
+
109
+ is_user_error = response.status >= 400 and response.status <= 500
110
+ is_unavailable = response.status == 503
111
+ if parsed is None and (is_user_error or is_unavailable):
112
+ parsed = models.parse_error_response(json.loads(response.data))
113
+
114
+ return Response(
115
+ parsed,
116
+ response.status,
117
+ response
118
+ )
119
+ except urllib3.exceptions.MaxRetryError:
120
+ return Response(
121
+ models.ErrorResponse(
122
+ error=models.ErrorEnum.InternalServerError,
123
+ message="Connection Refused"
124
+ ),
125
+ 503,
126
+ None
127
+ )
128
+ def set_active_calibration(
129
+ self,
130
+ body: models.ActiveCalibrationContainer,
131
+ ) -> Response[
132
+ None,
133
+ None
134
+ ]:
135
+ """
136
+ Set the active calibration for the robot.
137
+
138
+ """
139
+ path = "/api/v1/calibration/active"
140
+ try:
141
+ response = self._request_manager.request(
142
+ "POST",
143
+ path,
144
+ headers=self._request_manager.json_headers(),
145
+ body=json.dumps(models.serialize_active_calibration_container(body)),
146
+ )
147
+ parsed = None
148
+
149
+ is_user_error = response.status >= 400 and response.status <= 500
150
+ is_unavailable = response.status == 503
151
+ if parsed is None and (is_user_error or is_unavailable):
152
+ parsed = models.parse_error_response(json.loads(response.data))
153
+
154
+ return Response(
155
+ parsed,
156
+ response.status,
157
+ response
158
+ )
159
+ except urllib3.exceptions.MaxRetryError:
160
+ return Response(
161
+ models.ErrorResponse(
162
+ error=models.ErrorEnum.InternalServerError,
163
+ message="Connection Refused"
164
+ ),
165
+ 503,
166
+ None
167
+ )
79
168
  class Equipment:
80
169
  def __init__(self, request_manager: RequestManager):
81
170
  self._request_manager = request_manager
@@ -83,7 +172,7 @@ class Default:
83
172
  def onrobot_2fg7_move(
84
173
  self,
85
174
  value: Union[int, float],
86
- direction: Union[str, models.LinearGripDirectionEnum] = models.LinearGripDirectionEnum.Inward,
175
+ direction: Union[str, models.LinearGripDirectionEnum] = models.LinearGripDirectionEnum.Internal,
87
176
  unit_kind: Union[str, models.LinearUnitKind] = models.LinearUnitKind.Millimeters
88
177
  ):
89
178
  """Move the robot to the onrobot_2fg7 position.
@@ -105,7 +194,7 @@ class Default:
105
194
  def onrobot_2fg7_grip(
106
195
  self,
107
196
  value: Union[int, float],
108
- direction: Union[str, models.LinearGripDirectionEnum] = models.LinearGripDirectionEnum.Inward,
197
+ direction: Union[str, models.LinearGripDirectionEnum] = models.LinearGripDirectionEnum.Internal,
109
198
  unit_kind: Union[str, models.LinearUnitKind] = models.LinearUnitKind.Millimeters,
110
199
  force: Union[int, float] = 0.0,
111
200
  force_unit: Union[str, models.ForceUnitKind] = models.ForceUnitKind.Newtons
@@ -188,7 +277,7 @@ class Default:
188
277
  return self.control_gripper(
189
278
  body=models.GripperCommandRequest(
190
279
  kind=models.GripperKindEnum.DhCgi,
191
- dh_pgc=models.DHCGIGripperCommandRequest(
280
+ dh_cgi=models.DHCGIGripperCommandRequest(
192
281
  target_diameter, target_force, target_speed
193
282
  ),
194
283
  ),
@@ -382,12 +471,14 @@ class Default:
382
471
  None
383
472
  )
384
473
 
474
+ calibration: Calibration
385
475
  equipment: Equipment
386
476
  sensors: Sensors
387
477
  space: Space
388
478
 
389
479
  def __init__(self, request_manager: RequestManager):
390
480
  self._request_manager = request_manager
481
+ self.calibration = Default.Calibration(request_manager)
391
482
  self.equipment = Default.Equipment(request_manager)
392
483
  self.sensors = Default.Sensors(request_manager)
393
484
  self.space = Default.Space(request_manager)
@@ -425,6 +516,7 @@ class Movement:
425
516
  Union[
426
517
  models.BrakesState,
427
518
  models.ErrorResponse,
519
+ models.ErrorResponse,
428
520
  None
429
521
  ],
430
522
  models.BrakesState
@@ -444,6 +536,8 @@ class Movement:
444
536
  parsed = None
445
537
  if response.status == 200:
446
538
  parsed = models.parse_brakes_state(json.loads(response.data))
539
+ if response.status == 500:
540
+ parsed = models.parse_error_response(json.loads(response.data))
447
541
 
448
542
  is_user_error = response.status >= 400 and response.status <= 500
449
543
  is_unavailable = response.status == 503
@@ -713,7 +807,7 @@ class Camera:
713
807
  None
714
808
  ]:
715
809
  """
716
- Retrieve the latest RGB frame from the camera.
810
+ Retrieve the latest RGB frame from the camera as base64 string. In JPEG format.
717
811
  """
718
812
  path = "/api/v1/camera/frame/rgb"
719
813
  try:
@@ -747,8 +841,12 @@ class Camera:
747
841
  def get_camera_intrinsics_color(
748
842
  self,
749
843
  ) -> Response[
750
- None,
751
- None
844
+ Union[
845
+ models.CameraIntrinsics,
846
+ models.ErrorResponse,
847
+ None
848
+ ],
849
+ models.CameraIntrinsics
752
850
  ]:
753
851
  """
754
852
  Retrieve the intrinsic parameters for the color camera.
@@ -761,6 +859,8 @@ class Camera:
761
859
  headers=self._request_manager.json_headers(),
762
860
  )
763
861
  parsed = None
862
+ if response.status == 200:
863
+ parsed = models.parse_camera_intrinsics(json.loads(response.data))
764
864
 
765
865
  is_user_error = response.status >= 400 and response.status <= 500
766
866
  is_unavailable = response.status == 503
@@ -788,7 +888,7 @@ class Camera:
788
888
  None
789
889
  ]:
790
890
  """
791
- Retrieve the latest RGB frame from the camera.
891
+ Retrieve the latest RGB frame from the camera as base64 string. In JPEG format.
792
892
  """
793
893
  path = "/api/v1/camera/stream/rgb"
794
894
  try:
@@ -863,14 +963,64 @@ class Camera:
863
963
  503,
864
964
  None
865
965
  )
966
+ class Status:
967
+ def __init__(self, request_manager: RequestManager):
968
+ self._request_manager = request_manager
969
+
970
+
971
+ def get_camera_status(
972
+ self,
973
+ ) -> Response[
974
+ Union[
975
+ models.CameraStatus,
976
+ models.ErrorResponse,
977
+ None
978
+ ],
979
+ models.CameraStatus
980
+ ]:
981
+ """
982
+ Retrieve the current status of the camera.
983
+ """
984
+ path = "/api/v1/camera/status"
985
+ try:
986
+ response = self._request_manager.request(
987
+ "GET",
988
+ path,
989
+ headers=self._request_manager.json_headers(),
990
+ )
991
+ parsed = None
992
+ if response.status == 200:
993
+ parsed = models.parse_camera_status(json.loads(response.data))
994
+
995
+ is_user_error = response.status >= 400 and response.status <= 500
996
+ is_unavailable = response.status == 503
997
+ if parsed is None and (is_user_error or is_unavailable):
998
+ parsed = models.parse_error_response(json.loads(response.data))
999
+
1000
+ return Response(
1001
+ parsed,
1002
+ response.status,
1003
+ response
1004
+ )
1005
+ except urllib3.exceptions.MaxRetryError:
1006
+ return Response(
1007
+ models.ErrorResponse(
1008
+ error=models.ErrorEnum.InternalServerError,
1009
+ message="Connection Refused"
1010
+ ),
1011
+ 503,
1012
+ None
1013
+ )
866
1014
 
867
1015
  data: Data
868
1016
  settings: Settings
1017
+ status: Status
869
1018
 
870
1019
  def __init__(self, request_manager: RequestManager):
871
1020
  self._request_manager = request_manager
872
1021
  self.data = Camera.Data(request_manager)
873
1022
  self.settings = Camera.Settings(request_manager)
1023
+ self.status = Camera.Status(request_manager)
874
1024
 
875
1025
  class Faults:
876
1026
  _request_manager: RequestManager
@@ -1927,8 +2077,13 @@ class RoutineEditor:
1927
2077
  body: models.PlayRoutineRequest,
1928
2078
  routine_id: str,
1929
2079
  ) -> Response[
1930
- None,
1931
- None
2080
+ Union[
2081
+ models.PlayRoutineResponse,
2082
+ models.ErrorResponse,
2083
+ models.ErrorResponse,
2084
+ None
2085
+ ],
2086
+ models.PlayRoutineResponse
1932
2087
  ]:
1933
2088
  """
1934
2089
  Play a routine
@@ -1943,6 +2098,10 @@ class RoutineEditor:
1943
2098
  body=json.dumps(models.serialize_play_routine_request(body)),
1944
2099
  )
1945
2100
  parsed = None
2101
+ if response.status == 200:
2102
+ parsed = models.parse_play_routine_response(json.loads(response.data))
2103
+ if response.status == 400:
2104
+ parsed = models.parse_error_response(json.loads(response.data))
1946
2105
 
1947
2106
  is_user_error = response.status >= 400 and response.status <= 500
1948
2107
  is_unavailable = response.status == 503
@@ -1967,11 +2126,15 @@ class RoutineEditor:
1967
2126
  self,
1968
2127
  routine_id: str,
1969
2128
  ) -> Response[
1970
- None,
2129
+ Union[
2130
+ models.ErrorResponse,
2131
+ models.ErrorResponse,
2132
+ None
2133
+ ],
1971
2134
  None
1972
2135
  ]:
1973
2136
  """
1974
- Pause a routine
2137
+ Pause a routine. Routine must be running.
1975
2138
  """
1976
2139
  path = "/api/v1/routine-editor/routines/{routine_id}/pause"
1977
2140
  path = path.replace("{routine_id}", str(routine_id))
@@ -1982,6 +2145,8 @@ class RoutineEditor:
1982
2145
  headers=self._request_manager.json_headers(),
1983
2146
  )
1984
2147
  parsed = None
2148
+ if response.status == 400:
2149
+ parsed = models.parse_error_response(json.loads(response.data))
1985
2150
 
1986
2151
  is_user_error = response.status >= 400 and response.status <= 500
1987
2152
  is_unavailable = response.status == 503
@@ -2005,11 +2170,15 @@ class RoutineEditor:
2005
2170
  def stop(
2006
2171
  self,
2007
2172
  ) -> Response[
2008
- None,
2173
+ Union[
2174
+ models.ErrorResponse,
2175
+ models.ErrorResponse,
2176
+ None
2177
+ ],
2009
2178
  None
2010
2179
  ]:
2011
2180
  """
2012
- Stop running routine and all ongoing motions
2181
+ Stop running routine and all ongoing motions. Routine must be running.
2013
2182
  """
2014
2183
  path = "/api/v1/routine-editor/stop"
2015
2184
  try:
@@ -2019,6 +2188,8 @@ class RoutineEditor:
2019
2188
  headers=self._request_manager.json_headers(),
2020
2189
  )
2021
2190
  parsed = None
2191
+ if response.status == 400:
2192
+ parsed = models.parse_error_response(json.loads(response.data))
2022
2193
 
2023
2194
  is_user_error = response.status >= 400 and response.status <= 500
2024
2195
  is_unavailable = response.status == 503
@@ -2293,7 +2464,7 @@ class RoutineEditor:
2293
2464
  models.RuntimeVariable
2294
2465
  ]:
2295
2466
  """
2296
- Returns current state of a variable
2467
+ Returns current state of a variable. Routine must be running.
2297
2468
  """
2298
2469
  path = "/api/v1/routine-editor/variables/{variable_name}"
2299
2470
  path = path.replace("{variable_name}", str(variable_name))
@@ -2339,7 +2510,7 @@ class RoutineEditor:
2339
2510
  models.RuntimeVariable
2340
2511
  ]:
2341
2512
  """
2342
- Update the value of a variable
2513
+ Update the value of a variable. Routine must be running.
2343
2514
  """
2344
2515
  path = "/api/v1/routine-editor/variables/{variable_name}"
2345
2516
  path = path.replace("{variable_name}", str(variable_name))