standardbots 2.20241120.1__tar.gz → 2.20250128.2__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.

@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: standardbots
3
- Version: 2.20241120.1
3
+ Version: 2.20250128.2
4
4
  Summary: Standard Bots RO1 Robotics API
5
5
  Home-page:
6
6
  Author: Standard Bots Support
@@ -10,6 +10,13 @@ Requires-Python: >=3.7
10
10
  Requires-Dist: setuptools>=21.0.0
11
11
  Requires-Dist: typing_extensions>=4.3.0
12
12
  Requires-Dist: urllib3>=1.26.7
13
+ Dynamic: author
14
+ Dynamic: author-email
15
+ Dynamic: description
16
+ Dynamic: keywords
17
+ Dynamic: requires-dist
18
+ Dynamic: requires-python
19
+ Dynamic: summary
13
20
 
14
21
  Standard Bots RO1 Robotics API. # noqa: E501
15
22
 
@@ -0,0 +1,141 @@
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
+ pip install -r requirements.txt
31
+ pip install -r requirements-dev.txt
32
+ ```
33
+
34
+ ### Create sample data
35
+
36
+ #### Sample routine
37
+
38
+ 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 ).
39
+
40
+ The name of routine should be "Test Public API"
41
+
42
+ #### Sample globals
43
+
44
+ - _Variable._ Add global variable called "test_public_api_global" with any value.
45
+ - _Space._ Create a global space called "Test global space" of any kind.
46
+
47
+
48
+ ## Running
49
+
50
+ Here is a basic test command:
51
+
52
+ ```bash
53
+ SB_API_URL=http://34.162.0.32:3000
54
+ SB_API_TOKEN=...
55
+
56
+ python3 -m pytest ./tests --cov=standardbots --token=$SB_API_TOKEN --api-url=$SB_API_URL
57
+ ```
58
+
59
+ You may also set up a `.env` file at `sdks/python/.env` with the following contents:
60
+
61
+ ```bash
62
+ export SB_API_URL=http://34.162.0.32:3000
63
+ export SB_API_TOKEN=...
64
+ ```
65
+
66
+ Then you can just do:
67
+
68
+ ```bash
69
+ python3 -m pytest ./tests --cov=standardbots
70
+ ```
71
+
72
+ ### Robot state and testing (Markers)
73
+
74
+ 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.
75
+
76
+ At start of testing, robot should:
77
+
78
+ - _NOT_ be e-stopped.
79
+
80
+
81
+ The basic idea here is:
82
+
83
+ - These special tests will not be run by default.
84
+ - You may pass a flag (e.g. `--routine-running`) when the bot is in the correct state to run the tests.
85
+ - When the flag is passed:
86
+ 1. Tests with the flag are run.
87
+ 2. Tests without the flag are not run.
88
+
89
+ We use [pytest markers](https://docs.pytest.org/en/7.1.x/example/markers.html) to do this.
90
+
91
+ #### Routine running
92
+
93
+ The special sample routine ("Test Public API") should be running prior to running these tests. Then do:
94
+
95
+ ```bash
96
+ python3 -m pytest ./tests --cov=standardbots --routine-running
97
+ ```
98
+
99
+ #### E-stop
100
+
101
+ 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.
102
+
103
+ When e-stop test runs, cannot have bot in a failure state (pre-test will fail).
104
+
105
+ ## Troubleshooting
106
+
107
+ ### Tests are hanging
108
+
109
+ The first test appears to start but then nothing happens for several seconds:
110
+
111
+ ```bash
112
+ $ python3 -m pytest ./tests --cov=standardbots
113
+ ========================================================================================================== test session starts ===========================================================================================================
114
+ platform linux -- Python 3.10.12, pytest-6.2.5, py-1.10.0, pluggy-0.13.0
115
+ rootdir: /workspaces/sb/sdks/python, configfile: pytest.ini
116
+ 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
117
+ collected 110 items
118
+
119
+ tests/test_apis.py
120
+ ```
121
+
122
+ Fixes:
123
+
124
+ - _Make sure you can log into remote control._ Ensure that botman is connected.
125
+ - _Ensure that the robot URL is up-to-date._ Botman url will often change when you reboot.
126
+
127
+ ### Custom sensors
128
+
129
+ To test custom sensors:
130
+ - go to the menu on the left bottom corner;
131
+ - Click on 'Equipment';
132
+ - Add Gripper > Custom Gripper;
133
+ - Go to the Sensors tab and click 'Add Sensor';
134
+ - Keep the default values as they are (name: 'Sensor 1', kind: 'Control Box IO', sensor value: 'low');
135
+ - Hit 'Save' and make sure the Custom Gripper is enabled.
136
+
137
+ Then run:
138
+
139
+ ```bash
140
+ python3 -m pytest ./tests --cov=standardbots --custom-sensors
141
+ ```
@@ -13,7 +13,7 @@
13
13
  from setuptools import setup, find_packages # noqa: H301
14
14
 
15
15
  NAME = "standardbots"
16
- VERSION = "2.20241120.1"
16
+ VERSION = "2.20250128.2"
17
17
  # To install the library, run the following
18
18
  #
19
19
  # python setup.py install
@@ -76,6 +76,55 @@ 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.ActiveCalibrationResponse,
89
+ models.ErrorResponse,
90
+ None
91
+ ],
92
+ models.ActiveCalibrationResponse
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_response(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
+ )
79
128
  class Equipment:
80
129
  def __init__(self, request_manager: RequestManager):
81
130
  self._request_manager = request_manager
@@ -188,7 +237,7 @@ class Default:
188
237
  return self.control_gripper(
189
238
  body=models.GripperCommandRequest(
190
239
  kind=models.GripperKindEnum.DhCgi,
191
- dh_pgc=models.DHCGIGripperCommandRequest(
240
+ dh_cgi=models.DHCGIGripperCommandRequest(
192
241
  target_diameter, target_force, target_speed
193
242
  ),
194
243
  ),
@@ -382,12 +431,14 @@ class Default:
382
431
  None
383
432
  )
384
433
 
434
+ calibration: Calibration
385
435
  equipment: Equipment
386
436
  sensors: Sensors
387
437
  space: Space
388
438
 
389
439
  def __init__(self, request_manager: RequestManager):
390
440
  self._request_manager = request_manager
441
+ self.calibration = Default.Calibration(request_manager)
391
442
  self.equipment = Default.Equipment(request_manager)
392
443
  self.sensors = Default.Sensors(request_manager)
393
444
  self.space = Default.Space(request_manager)
@@ -713,7 +764,7 @@ class Camera:
713
764
  None
714
765
  ]:
715
766
  """
716
- Retrieve the latest RGB frame from the camera.
767
+ Retrieve the latest RGB frame from the camera. In JPEG format.
717
768
  """
718
769
  path = "/api/v1/camera/frame/rgb"
719
770
  try:
@@ -932,6 +983,113 @@ class Faults:
932
983
  self._request_manager = request_manager
933
984
  self.user_faults = Faults.UserFaults(request_manager)
934
985
 
986
+ class General:
987
+ _request_manager: RequestManager
988
+ class BotIdentity:
989
+ def __init__(self, request_manager: RequestManager):
990
+ self._request_manager = request_manager
991
+
992
+
993
+ def bot_identity(
994
+ self,
995
+ ) -> Response[
996
+ Union[
997
+ models.BotIdentityData,
998
+ models.ErrorResponse,
999
+ None
1000
+ ],
1001
+ models.BotIdentityData
1002
+ ]:
1003
+ """
1004
+ Get information about the robot&#x27;s identity.
1005
+ """
1006
+ path = "/api/v1/identity/bot_identity"
1007
+ try:
1008
+ response = self._request_manager.request(
1009
+ "GET",
1010
+ path,
1011
+ headers=self._request_manager.json_headers(),
1012
+ )
1013
+ parsed = None
1014
+ if response.status == 200:
1015
+ parsed = models.parse_bot_identity_data(json.loads(response.data))
1016
+
1017
+ is_user_error = response.status >= 400 and response.status <= 500
1018
+ is_unavailable = response.status == 503
1019
+ if parsed is None and (is_user_error or is_unavailable):
1020
+ parsed = models.parse_error_response(json.loads(response.data))
1021
+
1022
+ return Response(
1023
+ parsed,
1024
+ response.status,
1025
+ response
1026
+ )
1027
+ except urllib3.exceptions.MaxRetryError:
1028
+ return Response(
1029
+ models.ErrorResponse(
1030
+ error=models.ErrorEnum.InternalServerError,
1031
+ message="Connection Refused"
1032
+ ),
1033
+ 503,
1034
+ None
1035
+ )
1036
+ class Joints:
1037
+ def __init__(self, request_manager: RequestManager):
1038
+ self._request_manager = request_manager
1039
+
1040
+
1041
+ def get_joints_state(
1042
+ self,
1043
+ ) -> Response[
1044
+ Union[
1045
+ models.JointsStateResponse,
1046
+ models.ErrorResponse,
1047
+ None
1048
+ ],
1049
+ models.JointsStateResponse
1050
+ ]:
1051
+ """
1052
+ Retrieves information about the state of each joint
1053
+ """
1054
+ path = "/api/v1/joints"
1055
+ try:
1056
+ response = self._request_manager.request(
1057
+ "GET",
1058
+ path,
1059
+ headers=self._request_manager.json_headers(),
1060
+ )
1061
+ parsed = None
1062
+ if response.status == 200:
1063
+ parsed = models.parse_joints_state_response(json.loads(response.data))
1064
+
1065
+ is_user_error = response.status >= 400 and response.status <= 500
1066
+ is_unavailable = response.status == 503
1067
+ if parsed is None and (is_user_error or is_unavailable):
1068
+ parsed = models.parse_error_response(json.loads(response.data))
1069
+
1070
+ return Response(
1071
+ parsed,
1072
+ response.status,
1073
+ response
1074
+ )
1075
+ except urllib3.exceptions.MaxRetryError:
1076
+ return Response(
1077
+ models.ErrorResponse(
1078
+ error=models.ErrorEnum.InternalServerError,
1079
+ message="Connection Refused"
1080
+ ),
1081
+ 503,
1082
+ None
1083
+ )
1084
+
1085
+ bot_identity: BotIdentity
1086
+ joints: Joints
1087
+
1088
+ def __init__(self, request_manager: RequestManager):
1089
+ self._request_manager = request_manager
1090
+ self.bot_identity = General.BotIdentity(request_manager)
1091
+ self.joints = General.Joints(request_manager)
1092
+
935
1093
  class ChatGPT:
936
1094
  _request_manager: RequestManager
937
1095
  class Data:
@@ -1136,63 +1294,6 @@ class IO:
1136
1294
  self.control = IO.Control(request_manager)
1137
1295
  self.status = IO.Status(request_manager)
1138
1296
 
1139
- class General:
1140
- _request_manager: RequestManager
1141
- class Joints:
1142
- def __init__(self, request_manager: RequestManager):
1143
- self._request_manager = request_manager
1144
-
1145
-
1146
- def get_joints_state(
1147
- self,
1148
- ) -> Response[
1149
- Union[
1150
- models.JointsStateResponse,
1151
- models.ErrorResponse,
1152
- None
1153
- ],
1154
- models.JointsStateResponse
1155
- ]:
1156
- """
1157
- Retrieves information about the state of each joint
1158
- """
1159
- path = "/api/v1/joints"
1160
- try:
1161
- response = self._request_manager.request(
1162
- "GET",
1163
- path,
1164
- headers=self._request_manager.json_headers(),
1165
- )
1166
- parsed = None
1167
- if response.status == 200:
1168
- parsed = models.parse_joints_state_response(json.loads(response.data))
1169
-
1170
- is_user_error = response.status >= 400 and response.status <= 500
1171
- is_unavailable = response.status == 503
1172
- if parsed is None and (is_user_error or is_unavailable):
1173
- parsed = models.parse_error_response(json.loads(response.data))
1174
-
1175
- return Response(
1176
- parsed,
1177
- response.status,
1178
- response
1179
- )
1180
- except urllib3.exceptions.MaxRetryError:
1181
- return Response(
1182
- models.ErrorResponse(
1183
- error=models.ErrorEnum.InternalServerError,
1184
- message="Connection Refused"
1185
- ),
1186
- 503,
1187
- None
1188
- )
1189
-
1190
- joints: Joints
1191
-
1192
- def __init__(self, request_manager: RequestManager):
1193
- self._request_manager = request_manager
1194
- self.joints = General.Joints(request_manager)
1195
-
1196
1297
  class Poses:
1197
1298
  _request_manager: RequestManager
1198
1299
  class ConstructPose:
@@ -1662,15 +1763,15 @@ class Recovery:
1662
1763
  self,
1663
1764
  ) -> Response[
1664
1765
  Union[
1665
- models.RecoveryStatusResponse,
1766
+ models.FailureStateResponse,
1666
1767
  models.ErrorResponse,
1667
1768
  models.ErrorResponse,
1668
1769
  None
1669
1770
  ],
1670
- models.RecoveryStatusResponse
1771
+ models.FailureStateResponse
1671
1772
  ]:
1672
1773
  """
1673
- Recovers the robot from a fault state.
1774
+ Attempts to recover the robot from a fault state. Inspect the response to determine if additional recovery actions are required.
1674
1775
 
1675
1776
  """
1676
1777
  path = "/api/v1/recovery/recover"
@@ -1682,7 +1783,7 @@ class Recovery:
1682
1783
  )
1683
1784
  parsed = None
1684
1785
  if response.status == 200:
1685
- parsed = models.parse_recovery_status_response(json.loads(response.data))
1786
+ parsed = models.parse_failure_state_response(json.loads(response.data))
1686
1787
  if response.status == 400:
1687
1788
  parsed = models.parse_error_response(json.loads(response.data))
1688
1789
 
@@ -2493,9 +2594,9 @@ class StandardBotsRobot(Default):
2493
2594
  movement: Movement
2494
2595
  camera: Camera
2495
2596
  faults: Faults
2597
+ general: General
2496
2598
  chat_gpt: ChatGPT
2497
2599
  io: IO
2498
- general: General
2499
2600
  poses: Poses
2500
2601
  recovery: Recovery
2501
2602
  ros: ROS
@@ -2517,9 +2618,9 @@ class StandardBotsRobot(Default):
2517
2618
  self.movement = Movement(self._request_manager)
2518
2619
  self.camera = Camera(self._request_manager)
2519
2620
  self.faults = Faults(self._request_manager)
2621
+ self.general = General(self._request_manager)
2520
2622
  self.chat_gpt = ChatGPT(self._request_manager)
2521
2623
  self.io = IO(self._request_manager)
2522
- self.general = General(self._request_manager)
2523
2624
  self.poses = Poses(self._request_manager)
2524
2625
  self.recovery = Recovery(self._request_manager)
2525
2626
  self.ros = ROS(self._request_manager)