standardbots 2.20241119.1__tar.gz → 2.20250128.1__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.20241119.1
3
+ Version: 2.20250128.1
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.20241119.1"
16
+ VERSION = "2.20250128.1"
17
17
  # To install the library, run the following
18
18
  #
19
19
  # python setup.py install
@@ -66,7 +66,9 @@ class RequestManager:
66
66
  return {
67
67
  "Content-Type": "application/json",
68
68
  "Authorization": "Bearer " + self.token,
69
+ # Include both for backwards-compatibility purposes.
69
70
  "robot_kind": self.robot_kind.value,
71
+ "robot-kind": self.robot_kind.value,
70
72
  }
71
73
 
72
74
  def close(self):
@@ -74,6 +76,55 @@ class RequestManager:
74
76
 
75
77
  class Default:
76
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
+ )
77
128
  class Equipment:
78
129
  def __init__(self, request_manager: RequestManager):
79
130
  self._request_manager = request_manager
@@ -186,7 +237,7 @@ class Default:
186
237
  return self.control_gripper(
187
238
  body=models.GripperCommandRequest(
188
239
  kind=models.GripperKindEnum.DhCgi,
189
- dh_pgc=models.DHCGIGripperCommandRequest(
240
+ dh_cgi=models.DHCGIGripperCommandRequest(
190
241
  target_diameter, target_force, target_speed
191
242
  ),
192
243
  ),
@@ -380,12 +431,14 @@ class Default:
380
431
  None
381
432
  )
382
433
 
434
+ calibration: Calibration
383
435
  equipment: Equipment
384
436
  sensors: Sensors
385
437
  space: Space
386
438
 
387
439
  def __init__(self, request_manager: RequestManager):
388
440
  self._request_manager = request_manager
441
+ self.calibration = Default.Calibration(request_manager)
389
442
  self.equipment = Default.Equipment(request_manager)
390
443
  self.sensors = Default.Sensors(request_manager)
391
444
  self.space = Default.Space(request_manager)
@@ -711,7 +764,7 @@ class Camera:
711
764
  None
712
765
  ]:
713
766
  """
714
- Retrieve the latest RGB frame from the camera.
767
+ Retrieve the latest RGB frame from the camera. In JPEG format.
715
768
  """
716
769
  path = "/api/v1/camera/frame/rgb"
717
770
  try:
@@ -930,6 +983,113 @@ class Faults:
930
983
  self._request_manager = request_manager
931
984
  self.user_faults = Faults.UserFaults(request_manager)
932
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
+
933
1093
  class ChatGPT:
934
1094
  _request_manager: RequestManager
935
1095
  class Data:
@@ -1134,63 +1294,6 @@ class IO:
1134
1294
  self.control = IO.Control(request_manager)
1135
1295
  self.status = IO.Status(request_manager)
1136
1296
 
1137
- class General:
1138
- _request_manager: RequestManager
1139
- class Joints:
1140
- def __init__(self, request_manager: RequestManager):
1141
- self._request_manager = request_manager
1142
-
1143
-
1144
- def get_joints_state(
1145
- self,
1146
- ) -> Response[
1147
- Union[
1148
- models.JointsStateResponse,
1149
- models.ErrorResponse,
1150
- None
1151
- ],
1152
- models.JointsStateResponse
1153
- ]:
1154
- """
1155
- Retrieves information about the state of each joint
1156
- """
1157
- path = "/api/v1/joints"
1158
- try:
1159
- response = self._request_manager.request(
1160
- "GET",
1161
- path,
1162
- headers=self._request_manager.json_headers(),
1163
- )
1164
- parsed = None
1165
- if response.status == 200:
1166
- parsed = models.parse_joints_state_response(json.loads(response.data))
1167
-
1168
- is_user_error = response.status >= 400 and response.status <= 500
1169
- is_unavailable = response.status == 503
1170
- if parsed is None and (is_user_error or is_unavailable):
1171
- parsed = models.parse_error_response(json.loads(response.data))
1172
-
1173
- return Response(
1174
- parsed,
1175
- response.status,
1176
- response
1177
- )
1178
- except urllib3.exceptions.MaxRetryError:
1179
- return Response(
1180
- models.ErrorResponse(
1181
- error=models.ErrorEnum.InternalServerError,
1182
- message="Connection Refused"
1183
- ),
1184
- 503,
1185
- None
1186
- )
1187
-
1188
- joints: Joints
1189
-
1190
- def __init__(self, request_manager: RequestManager):
1191
- self._request_manager = request_manager
1192
- self.joints = General.Joints(request_manager)
1193
-
1194
1297
  class Poses:
1195
1298
  _request_manager: RequestManager
1196
1299
  class ConstructPose:
@@ -1660,15 +1763,15 @@ class Recovery:
1660
1763
  self,
1661
1764
  ) -> Response[
1662
1765
  Union[
1663
- models.RecoveryStatusResponse,
1766
+ models.FailureStateResponse,
1664
1767
  models.ErrorResponse,
1665
1768
  models.ErrorResponse,
1666
1769
  None
1667
1770
  ],
1668
- models.RecoveryStatusResponse
1771
+ models.FailureStateResponse
1669
1772
  ]:
1670
1773
  """
1671
- 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.
1672
1775
 
1673
1776
  """
1674
1777
  path = "/api/v1/recovery/recover"
@@ -1680,7 +1783,7 @@ class Recovery:
1680
1783
  )
1681
1784
  parsed = None
1682
1785
  if response.status == 200:
1683
- parsed = models.parse_recovery_status_response(json.loads(response.data))
1786
+ parsed = models.parse_failure_state_response(json.loads(response.data))
1684
1787
  if response.status == 400:
1685
1788
  parsed = models.parse_error_response(json.loads(response.data))
1686
1789
 
@@ -2491,9 +2594,9 @@ class StandardBotsRobot(Default):
2491
2594
  movement: Movement
2492
2595
  camera: Camera
2493
2596
  faults: Faults
2597
+ general: General
2494
2598
  chat_gpt: ChatGPT
2495
2599
  io: IO
2496
- general: General
2497
2600
  poses: Poses
2498
2601
  recovery: Recovery
2499
2602
  ros: ROS
@@ -2515,9 +2618,9 @@ class StandardBotsRobot(Default):
2515
2618
  self.movement = Movement(self._request_manager)
2516
2619
  self.camera = Camera(self._request_manager)
2517
2620
  self.faults = Faults(self._request_manager)
2621
+ self.general = General(self._request_manager)
2518
2622
  self.chat_gpt = ChatGPT(self._request_manager)
2519
2623
  self.io = IO(self._request_manager)
2520
- self.general = General(self._request_manager)
2521
2624
  self.poses = Poses(self._request_manager)
2522
2625
  self.recovery = Recovery(self._request_manager)
2523
2626
  self.ros = ROS(self._request_manager)