sr-robot3 2024.0.1__tar.gz → 2025.0.0__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.
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/.github/workflows/test_build.yml +11 -11
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/PKG-INFO +7 -5
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/pyproject.toml +6 -4
- sr_robot3-2025.0.0/sr/robot3/_version.py +16 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr/robot3/arduino.py +87 -17
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr/robot3/astoria.py +7 -2
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr/robot3/camera.py +92 -24
- sr_robot3-2025.0.0/sr/robot3/kch.py +414 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr/robot3/motor_board.py +42 -10
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr/robot3/mqtt.py +23 -8
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr/robot3/power_board.py +53 -13
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr/robot3/robot.py +97 -27
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr/robot3/serial_wrapper.py +13 -5
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr/robot3/servo_board.py +44 -11
- sr_robot3-2025.0.0/sr/robot3/simulator/camera.py +75 -0
- sr_robot3-2025.0.0/sr/robot3/simulator/time_server.py +94 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr/robot3/timeout.py +29 -3
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr/robot3/utils.py +40 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr_robot3.egg-info/PKG-INFO +7 -5
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr_robot3.egg-info/SOURCES.txt +10 -0
- sr_robot3-2025.0.0/sr_robot3.egg-info/requires.txt +28 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/tests/conftest.py +1 -1
- sr_robot3-2025.0.0/tests/test_data/empty/.gitkeep +0 -0
- sr_robot3-2025.0.0/tests/test_data/timeout_scripts/catch-base-exception.py +10 -0
- sr_robot3-2025.0.0/tests/test_data/timeout_scripts/catch-exception.py +10 -0
- sr_robot3-2025.0.0/tests/test_data/timeout_scripts/hot-loop.py +7 -0
- sr_robot3-2025.0.0/tests/test_data/timeout_scripts/sleep.py +6 -0
- sr_robot3-2025.0.0/tests/test_data/timeout_scripts/try-finally.py +11 -0
- sr_robot3-2025.0.0/tests/test_data/timeout_scripts_extra/early-exit.py +3 -0
- sr_robot3-2025.0.0/tests/test_data/timeout_scripts_extra/exception.py +5 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/tests/test_power_board.py +1 -1
- sr_robot3-2025.0.0/tests/test_timeout.py +99 -0
- sr-robot3-2024.0.1/sr/robot3/_version.py +0 -4
- sr-robot3-2024.0.1/sr/robot3/kch.py +0 -188
- sr-robot3-2024.0.1/sr_robot3.egg-info/requires.txt +0 -20
- sr-robot3-2024.0.1/tests/test_timeout.py +0 -55
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/.flake8 +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/.github/dependabot.yml +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/.gitignore +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/LICENSE +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/Makefile +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/README.md +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/setup.cfg +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr/__init__.py +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr/robot3/__init__.py +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr/robot3/calibrations/__init__.py +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr/robot3/exceptions.py +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr/robot3/game_specific.py +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr/robot3/logging.py +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr/robot3/marker.py +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr/robot3/py.typed +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr/robot3/raw_serial.py +0 -0
- /sr-robot3-2024.0.1/stubs/RPi/__init__.pyi → /sr_robot3-2025.0.0/sr/robot3/simulator/__init__.py +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr_robot3.egg-info/dependency_links.txt +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/sr_robot3.egg-info/top_level.txt +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/stubs/RPi/GPIO.pyi +0 -0
- /sr-robot3-2024.0.1/tests/test_data/empty/.gitkeep → /sr_robot3-2025.0.0/stubs/RPi/__init__.pyi +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/tests/__init__.py +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/tests/test_arduino.py +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/tests/test_data/bad/metadata.json +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/tests/test_data/marker_detections.json +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/tests/test_data/marker_locations.csv +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/tests/test_data/missing_key/metadata.json +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/tests/test_data/nested/valid/metadata.json +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/tests/test_data/not_object/metadata.json +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/tests/test_data/valid/metadata.json +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/tests/test_logging.py +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/tests/test_markers.py +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/tests/test_motor_board.py +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/tests/test_raw_serial.py +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/tests/test_serial_wrapper.py +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/tests/test_servo_board.py +0 -0
- {sr-robot3-2024.0.1 → sr_robot3-2025.0.0}/tests/test_sr_robot.py +0 -0
@@ -1,6 +1,8 @@
|
|
1
1
|
name: Lint & build
|
2
2
|
|
3
|
-
on:
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
workflow_dispatch:
|
4
6
|
|
5
7
|
jobs:
|
6
8
|
test:
|
@@ -8,23 +10,23 @@ jobs:
|
|
8
10
|
fail-fast: false
|
9
11
|
matrix:
|
10
12
|
os: [ubuntu-latest]
|
11
|
-
py_version: ["3.8", "3.9", "3.10", "3.11"]
|
13
|
+
py_version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
12
14
|
include:
|
13
15
|
- os: windows-latest
|
14
16
|
py_version: "3.8"
|
15
17
|
- os: windows-latest
|
16
|
-
py_version: "3.
|
18
|
+
py_version: "3.12"
|
17
19
|
- os: macos-latest
|
18
20
|
py_version: "3.8"
|
19
21
|
- os: macos-latest
|
20
|
-
py_version: "3.
|
22
|
+
py_version: "3.12"
|
21
23
|
runs-on: ${{ matrix.os }}
|
22
24
|
steps:
|
23
|
-
- uses: actions/checkout@
|
25
|
+
- uses: actions/checkout@v4
|
24
26
|
with:
|
25
27
|
fetch-depth: 0
|
26
28
|
- name: Set up Python
|
27
|
-
uses: actions/setup-python@
|
29
|
+
uses: actions/setup-python@v5
|
28
30
|
with:
|
29
31
|
python-version: ${{ matrix.py_version }}
|
30
32
|
- name: Install dependencies
|
@@ -50,11 +52,11 @@ jobs:
|
|
50
52
|
runs-on: ubuntu-latest
|
51
53
|
needs: test
|
52
54
|
steps:
|
53
|
-
- uses: actions/checkout@
|
55
|
+
- uses: actions/checkout@v4
|
54
56
|
with:
|
55
57
|
fetch-depth: 0
|
56
58
|
- name: Set up Python 3.8
|
57
|
-
uses: actions/setup-python@
|
59
|
+
uses: actions/setup-python@v5
|
58
60
|
with:
|
59
61
|
python-version: "3.8"
|
60
62
|
- name: Install dependencies
|
@@ -66,7 +68,7 @@ jobs:
|
|
66
68
|
run: |
|
67
69
|
make build
|
68
70
|
- name: Save built package
|
69
|
-
uses: actions/upload-artifact@
|
71
|
+
uses: actions/upload-artifact@v4
|
70
72
|
with:
|
71
73
|
name: package
|
72
74
|
path: |
|
@@ -74,5 +76,3 @@ jobs:
|
|
74
76
|
- name: Publish to PyPi
|
75
77
|
if: ${{ github.ref_type == 'tag' }}
|
76
78
|
uses: pypa/gh-action-pypi-publish@release/v1
|
77
|
-
with:
|
78
|
-
print_hash: true
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sr-robot3
|
3
|
-
Version:
|
3
|
+
Version: 2025.0.0
|
4
4
|
Summary: Student Robotics API for Python 3
|
5
5
|
Author-email: Student Robotics <kit-team@studentrobotics.org>
|
6
6
|
License: MIT License
|
@@ -38,19 +38,21 @@ Requires-Python: >=3.8
|
|
38
38
|
Description-Content-Type: text/markdown
|
39
39
|
License-File: LICENSE
|
40
40
|
Requires-Dist: pyserial<4,>=3
|
41
|
-
Requires-Dist: april_vision
|
42
|
-
Requires-Dist: paho-mqtt<2
|
41
|
+
Requires-Dist: april_vision==2.2.0
|
42
|
+
Requires-Dist: paho-mqtt<3,>=2
|
43
43
|
Requires-Dist: pydantic<2,>=1.9.1
|
44
44
|
Requires-Dist: typing-extensions; python_version < "3.10"
|
45
|
+
Requires-Dist: tomli<3,>=2.0.1; python_version < "3.11"
|
45
46
|
Provides-Extra: dev
|
46
47
|
Requires-Dist: flake8; extra == "dev"
|
47
48
|
Requires-Dist: isort; extra == "dev"
|
48
|
-
Requires-Dist: mypy; extra == "dev"
|
49
|
+
Requires-Dist: mypy==1.10.0; python_version < "3.9" and extra == "dev"
|
50
|
+
Requires-Dist: mypy<2,>=1.7; python_version >= "3.9" and extra == "dev"
|
49
51
|
Requires-Dist: build; extra == "dev"
|
50
52
|
Requires-Dist: types-pyserial; extra == "dev"
|
51
53
|
Requires-Dist: pytest; extra == "dev"
|
52
54
|
Requires-Dist: pytest-cov; extra == "dev"
|
53
|
-
Requires-Dist:
|
55
|
+
Requires-Dist: paho-mqtt<3,>=2; extra == "dev"
|
54
56
|
Provides-Extra: vision
|
55
57
|
Requires-Dist: opencv-python-headless<5,>=4; extra == "vision"
|
56
58
|
|
@@ -53,10 +53,11 @@ dynamic = ["version"]
|
|
53
53
|
requires-python = ">=3.8"
|
54
54
|
dependencies = [
|
55
55
|
"pyserial >=3,<4",
|
56
|
-
"april_vision
|
57
|
-
"paho-mqtt >=
|
56
|
+
"april_vision==2.2.0",
|
57
|
+
"paho-mqtt >=2,<3",
|
58
58
|
"pydantic >=1.9.1,<2",
|
59
59
|
"typing-extensions; python_version<'3.10'",
|
60
|
+
"tomli >=2.0.1,<3; python_version<'3.11'",
|
60
61
|
]
|
61
62
|
classifiers = [
|
62
63
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
@@ -75,11 +76,12 @@ Documentation = "https://docs.studentrobotics.org"
|
|
75
76
|
dev = [
|
76
77
|
"flake8",
|
77
78
|
"isort",
|
78
|
-
"mypy",
|
79
|
+
"mypy==1.10.0; python_version<'3.9'",
|
80
|
+
"mypy>=1.7,<2; python_version>='3.9'",
|
79
81
|
"build",
|
80
82
|
"types-pyserial",
|
81
83
|
"pytest",
|
82
84
|
"pytest-cov",
|
83
|
-
"
|
85
|
+
"paho-mqtt >=2,<3"
|
84
86
|
]
|
85
87
|
vision = ["opencv-python-headless >=4,<5"]
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# file generated by setuptools_scm
|
2
|
+
# don't change, don't track in version control
|
3
|
+
TYPE_CHECKING = False
|
4
|
+
if TYPE_CHECKING:
|
5
|
+
from typing import Tuple, Union
|
6
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
7
|
+
else:
|
8
|
+
VERSION_TUPLE = object
|
9
|
+
|
10
|
+
version: str
|
11
|
+
__version__: str
|
12
|
+
__version_tuple__: VERSION_TUPLE
|
13
|
+
version_tuple: VERSION_TUPLE
|
14
|
+
|
15
|
+
__version__ = version = '2025.0.0'
|
16
|
+
__version_tuple__ = version_tuple = (2025, 0, 0)
|
@@ -11,10 +11,18 @@ from serial.tools.list_ports import comports
|
|
11
11
|
from .exceptions import IncorrectBoardError
|
12
12
|
from .logging import log_to_debug
|
13
13
|
from .serial_wrapper import SerialWrapper
|
14
|
-
from .utils import
|
14
|
+
from .utils import (
|
15
|
+
IN_SIMULATOR, Board, BoardIdentity,
|
16
|
+
get_simulator_boards, get_USB_identity, map_to_float,
|
17
|
+
)
|
15
18
|
|
16
19
|
logger = logging.getLogger(__name__)
|
17
20
|
BAUDRATE = 115200
|
21
|
+
if IN_SIMULATOR:
|
22
|
+
# Place each command on a new line in the simulator to simplify the implementation
|
23
|
+
ENDLINE = '\n'
|
24
|
+
else:
|
25
|
+
ENDLINE = ''
|
18
26
|
|
19
27
|
SUPPORTED_VID_PIDS = {
|
20
28
|
(0x2341, 0x0043), # Arduino Uno rev 3
|
@@ -58,7 +66,7 @@ class Arduino(Board):
|
|
58
66
|
"""
|
59
67
|
The Arduino board interface.
|
60
68
|
|
61
|
-
This is intended to be used with Arduino Uno boards running the
|
69
|
+
This is intended to be used with Arduino Uno boards running the SR firmware.
|
62
70
|
|
63
71
|
:param serial_port: The serial port to connect to.
|
64
72
|
:param initial_identity: The identity of the board, as reported by the USB descriptor.
|
@@ -127,17 +135,46 @@ class Arduino(Board):
|
|
127
135
|
f"expected {err.expected_type!r}. Ignoring this device")
|
128
136
|
return None
|
129
137
|
except Exception:
|
130
|
-
if initial_identity is not None
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
+
if initial_identity is not None:
|
139
|
+
if initial_identity.board_type == 'manual':
|
140
|
+
logger.warning(
|
141
|
+
f"Manually specified Arduino at port {serial_port!r} "
|
142
|
+
"could not be identified. Ignoring this device")
|
143
|
+
elif initial_identity.manufacturer == 'sbot_simulator':
|
144
|
+
logger.warning(
|
145
|
+
f"Simulator specified arduino at port {serial_port!r} "
|
146
|
+
"could not be identified. Ignoring this device")
|
147
|
+
return None
|
148
|
+
|
149
|
+
logger.warning(
|
150
|
+
f"Found Arduino-like serial port at {serial_port!r}, "
|
151
|
+
"but it could not be identified. Ignoring this device")
|
138
152
|
return None
|
139
153
|
return board
|
140
154
|
|
155
|
+
@classmethod
|
156
|
+
def _get_simulator_boards(cls) -> MappingProxyType[str, Arduino]:
|
157
|
+
"""
|
158
|
+
Get the simulator boards.
|
159
|
+
|
160
|
+
:return: A mapping of board serial numbers to Arduinos
|
161
|
+
"""
|
162
|
+
boards = {}
|
163
|
+
# The filter here is the name of the emulated board in the simulator
|
164
|
+
for board_info in get_simulator_boards('Arduino'):
|
165
|
+
|
166
|
+
# Create board identity from the info given
|
167
|
+
initial_identity = BoardIdentity(
|
168
|
+
manufacturer='sbot_simulator',
|
169
|
+
board_type=board_info.type_str,
|
170
|
+
asset_tag=board_info.serial_number,
|
171
|
+
)
|
172
|
+
if (board := cls._get_valid_board(board_info.url, initial_identity)) is None:
|
173
|
+
continue
|
174
|
+
|
175
|
+
boards[board._identity.asset_tag] = board
|
176
|
+
return MappingProxyType(boards)
|
177
|
+
|
141
178
|
@classmethod
|
142
179
|
def _get_supported_boards(
|
143
180
|
cls,
|
@@ -152,6 +189,9 @@ class Arduino(Board):
|
|
152
189
|
:param ignored_serials: A list of serial number to ignore during board discovery
|
153
190
|
:return: A mapping of board serial numbers to Arduinos
|
154
191
|
"""
|
192
|
+
if IN_SIMULATOR:
|
193
|
+
return cls._get_simulator_boards()
|
194
|
+
|
155
195
|
boards = {}
|
156
196
|
if ignored_serials is None:
|
157
197
|
ignored_serials = []
|
@@ -192,7 +232,7 @@ class Arduino(Board):
|
|
192
232
|
|
193
233
|
:return: The identity of the board.
|
194
234
|
"""
|
195
|
-
response = self._serial.query('v', endl=
|
235
|
+
response = self._serial.query('v', endl=ENDLINE)
|
196
236
|
response_fields = response.split(':')
|
197
237
|
|
198
238
|
# The arduino firmware cannot access the serial number reported in the USB descriptor
|
@@ -224,7 +264,9 @@ class Arduino(Board):
|
|
224
264
|
:param command: The command to send to the board.
|
225
265
|
:return: The response from the board.
|
226
266
|
"""
|
227
|
-
|
267
|
+
if IN_SIMULATOR:
|
268
|
+
logger.warning("The command method is not fully supported in the simulator")
|
269
|
+
return self._serial.query(command, endl=ENDLINE)
|
228
270
|
|
229
271
|
def map_pin_number(self, pin_number: int) -> str:
|
230
272
|
"""
|
@@ -241,6 +283,34 @@ class Arduino(Board):
|
|
241
283
|
raise ValueError("Invalid pin provided") from None
|
242
284
|
return chr(pin_number + ord('a'))
|
243
285
|
|
286
|
+
@log_to_debug
|
287
|
+
def ultrasound_measure(
|
288
|
+
self,
|
289
|
+
pulse_pin: int,
|
290
|
+
echo_pin: int,
|
291
|
+
) -> int:
|
292
|
+
"""
|
293
|
+
Measure the distance to an object using an ultrasound sensor.
|
294
|
+
|
295
|
+
The sensor can only measure distances up to 4m.
|
296
|
+
|
297
|
+
:param pulse_pin: The pin to send the ultrasound pulse from.
|
298
|
+
:param echo_pin: The pin to read the ultrasound echo from.
|
299
|
+
:raises ValueError: If either of the pins are invalid
|
300
|
+
:return: The distance measured by the ultrasound sensor in mm.
|
301
|
+
"""
|
302
|
+
try: # bounds check
|
303
|
+
pulse_id = self.map_pin_number(pulse_pin)
|
304
|
+
except ValueError:
|
305
|
+
raise ValueError("Invalid pulse pin provided") from None
|
306
|
+
try:
|
307
|
+
echo_id = self.map_pin_number(echo_pin)
|
308
|
+
except ValueError:
|
309
|
+
raise ValueError("Invalid echo pin provided") from None
|
310
|
+
|
311
|
+
response = self._serial.query(f'u{pulse_id}{echo_id}', endl=ENDLINE)
|
312
|
+
return int(response)
|
313
|
+
|
244
314
|
def __repr__(self) -> str:
|
245
315
|
return f"<{self.__class__.__qualname__}: {self._serial}>"
|
246
316
|
|
@@ -303,7 +373,7 @@ class Pin:
|
|
303
373
|
mode_char = MODE_CHAR_MAP.get(value)
|
304
374
|
if mode_char is None:
|
305
375
|
raise IOError(f'Pin mode {value} is not supported')
|
306
|
-
self._serial.write(self._build_command(mode_char), endl=
|
376
|
+
self._serial.write(self._build_command(mode_char), endl=ENDLINE)
|
307
377
|
self._mode = value
|
308
378
|
|
309
379
|
@log_to_debug
|
@@ -318,7 +388,7 @@ class Pin:
|
|
318
388
|
self._check_if_disabled()
|
319
389
|
if self.mode not in DIGITAL_READ_MODES:
|
320
390
|
raise IOError(f'Digital read is not supported in {self.mode}')
|
321
|
-
response = self._serial.query(self._build_command('r'), endl=
|
391
|
+
response = self._serial.query(self._build_command('r'), endl=ENDLINE)
|
322
392
|
return response == 'h'
|
323
393
|
|
324
394
|
@log_to_debug
|
@@ -334,9 +404,9 @@ class Pin:
|
|
334
404
|
if self.mode not in DIGITAL_WRITE_MODES:
|
335
405
|
raise IOError(f'Digital write is not supported in {self.mode}')
|
336
406
|
if value:
|
337
|
-
self._serial.write(self._build_command('h'), endl=
|
407
|
+
self._serial.write(self._build_command('h'), endl=ENDLINE)
|
338
408
|
else:
|
339
|
-
self._serial.write(self._build_command('l'), endl=
|
409
|
+
self._serial.write(self._build_command('l'), endl=ENDLINE)
|
340
410
|
|
341
411
|
@log_to_debug
|
342
412
|
def analog_read(self) -> float:
|
@@ -357,7 +427,7 @@ class Pin:
|
|
357
427
|
raise IOError(f'Analog read is not supported in {self.mode}')
|
358
428
|
if not self._supports_analog:
|
359
429
|
raise IOError('Pin does not support analog read')
|
360
|
-
response = self._serial.query(self._build_command('a'), endl=
|
430
|
+
response = self._serial.query(self._build_command('a'), endl=ENDLINE)
|
361
431
|
# map the response from the ADC range to the voltage range
|
362
432
|
return map_to_float(int(response), ADC_MIN, ADC_MAX, 0.0, 5.0)
|
363
433
|
|
@@ -9,8 +9,9 @@ from threading import Event, Lock
|
|
9
9
|
from time import sleep
|
10
10
|
from typing import Any, ClassVar, NewType, Optional, Tuple
|
11
11
|
|
12
|
+
import paho.mqtt.client as mqtt
|
12
13
|
from paho.mqtt.client import Client as MQTT
|
13
|
-
from paho.mqtt.client import MQTTMessage
|
14
|
+
from paho.mqtt.client import MQTTMessage
|
14
15
|
from pydantic import BaseModel, ValidationError
|
15
16
|
|
16
17
|
from .mqtt import MQTTClient
|
@@ -278,7 +279,11 @@ def init_mqtt(config: AstoriaConfig, client_name: str = 'sr-robot3') -> 'MQTTCli
|
|
278
279
|
host=config.mqtt.host,
|
279
280
|
port=config.mqtt.port,
|
280
281
|
client_name=client_name,
|
281
|
-
mqtt_version=
|
282
|
+
mqtt_version=(
|
283
|
+
mqtt.MQTTProtocolVersion.MQTTv311
|
284
|
+
if config.mqtt.force_protocol_version_3_1 else
|
285
|
+
mqtt.MQTTProtocolVersion.MQTTv5
|
286
|
+
),
|
282
287
|
topic_prefix=config.mqtt.topic_prefix,
|
283
288
|
)
|
284
289
|
return client
|
@@ -1,9 +1,11 @@
|
|
1
1
|
"""An implementation of a camera board using the april_vision library."""
|
2
|
+
from __future__ import annotations
|
3
|
+
|
2
4
|
import logging
|
3
5
|
from pathlib import Path
|
4
6
|
from typing import Callable, Dict, Iterable, List, Optional, Union
|
5
7
|
|
6
|
-
from april_vision import CalibratedCamera, Frame
|
8
|
+
from april_vision import CalibratedCamera, Frame, FrameSource
|
7
9
|
from april_vision import Marker as AprilMarker
|
8
10
|
from april_vision import (
|
9
11
|
Processor, USBCamera, __version__, calibrations,
|
@@ -13,8 +15,11 @@ from april_vision.helpers import Base64Sender
|
|
13
15
|
from numpy.typing import NDArray
|
14
16
|
|
15
17
|
from .marker import Marker
|
16
|
-
from .utils import
|
18
|
+
from .utils import (
|
19
|
+
IN_SIMULATOR, Board, BoardIdentity, BoardInfo, get_simulator_boards,
|
20
|
+
)
|
17
21
|
|
22
|
+
PathLike = Union[Path, str]
|
18
23
|
LOGGER = logging.getLogger(__name__)
|
19
24
|
|
20
25
|
robot_calibrations = calibrations.copy()
|
@@ -30,9 +35,11 @@ class AprilCamera(Board):
|
|
30
35
|
in order to determine the spatial positon and orientation of the markers
|
31
36
|
that it has detected.
|
32
37
|
|
33
|
-
:param
|
34
|
-
:param
|
38
|
+
:param camera_source: The source of the camera frames.
|
39
|
+
:param calibration: The intrinsic calibration of the camera.
|
35
40
|
:param serial_num: The serial number of the camera.
|
41
|
+
:param name: The name of the camera.
|
42
|
+
:param vidpid: The VID:PID of the camera.
|
36
43
|
"""
|
37
44
|
__slots__ = ('_serial_num', '_cam')
|
38
45
|
|
@@ -56,28 +63,82 @@ class AprilCamera(Board):
|
|
56
63
|
|
57
64
|
:return: A dict of cameras, keyed by their name and index.
|
58
65
|
"""
|
66
|
+
if IN_SIMULATOR:
|
67
|
+
return {
|
68
|
+
camera_info.serial_number: cls.from_webots_camera(camera_info)
|
69
|
+
for camera_info in get_simulator_boards('CameraBoard')
|
70
|
+
}
|
71
|
+
|
59
72
|
return {
|
60
73
|
(serial := f"{camera_data.name} - {camera_data.index}"):
|
61
|
-
cls(camera_data.index, camera_data=camera_data, serial_num=serial)
|
74
|
+
cls.from_id(camera_data.index, camera_data=camera_data, serial_num=serial)
|
62
75
|
for camera_data in find_cameras(robot_calibrations)
|
63
76
|
}
|
64
77
|
|
65
|
-
def __init__(
|
78
|
+
def __init__(
|
79
|
+
self, camera_source: FrameSource,
|
80
|
+
calibration: tuple[float, float, float, float] | None,
|
81
|
+
serial_num: str,
|
82
|
+
name: str,
|
83
|
+
vidpid: str = "",
|
84
|
+
) -> None:
|
85
|
+
# The processor handles the detection and pose estimation
|
86
|
+
self._cam = Processor(
|
87
|
+
camera_source,
|
88
|
+
calibration=calibration,
|
89
|
+
name=name,
|
90
|
+
vidpid=vidpid,
|
91
|
+
mask_unknown_size_tags=True,
|
92
|
+
)
|
93
|
+
self._serial_num = serial_num
|
94
|
+
|
95
|
+
@classmethod
|
96
|
+
def from_webots_camera(cls, camera_info: BoardInfo) -> 'AprilCamera':
|
97
|
+
"""
|
98
|
+
Create a camera from a webots camera.
|
99
|
+
|
100
|
+
:param camera_info: The information about the virtual camera,
|
101
|
+
including the url to connect to.
|
102
|
+
:return: The camera object.
|
103
|
+
"""
|
104
|
+
from .simulator.camera import WebotsRemoteCameraSource
|
105
|
+
|
106
|
+
camera_source = WebotsRemoteCameraSource(camera_info)
|
107
|
+
return cls(
|
108
|
+
camera_source,
|
109
|
+
calibration=camera_source.calibration,
|
110
|
+
serial_num=camera_info.serial_number,
|
111
|
+
name=camera_info.serial_number,
|
112
|
+
)
|
113
|
+
|
114
|
+
@classmethod
|
115
|
+
def from_id(
|
116
|
+
cls,
|
117
|
+
camera_id: int,
|
118
|
+
camera_data: CalibratedCamera,
|
119
|
+
serial_num: str,
|
120
|
+
) -> 'AprilCamera':
|
121
|
+
"""
|
122
|
+
Create a camera from an ID.
|
123
|
+
|
124
|
+
:param camera_id: The ID of the camera to create.
|
125
|
+
:param camera_data: The calibration data for the camera.
|
126
|
+
:param serial_num: The serial number of the camera.
|
127
|
+
:return: The camera object.
|
128
|
+
"""
|
66
129
|
# The camera source handles the connection between the camera and the processor
|
67
130
|
camera_source = USBCamera.from_calibration_file(
|
68
131
|
camera_id,
|
69
132
|
calibration_file=camera_data.calibration,
|
70
133
|
vidpid=camera_data.vidpid,
|
71
134
|
)
|
72
|
-
|
73
|
-
self._cam = Processor(
|
135
|
+
return cls(
|
74
136
|
camera_source,
|
75
137
|
calibration=camera_source.calibration,
|
138
|
+
serial_num=serial_num,
|
76
139
|
name=camera_data.name,
|
77
140
|
vidpid=camera_data.vidpid,
|
78
|
-
mask_unknown_size_tags=True,
|
79
141
|
)
|
80
|
-
self._serial_num = serial_num
|
81
142
|
|
82
143
|
def identify(self) -> BoardIdentity:
|
83
144
|
"""
|
@@ -103,34 +164,41 @@ class AprilCamera(Board):
|
|
103
164
|
"""
|
104
165
|
self._cam.close()
|
105
166
|
|
106
|
-
def see(
|
167
|
+
def see(
|
168
|
+
self,
|
169
|
+
*,
|
170
|
+
frame: Optional[NDArray] = None,
|
171
|
+
save: Optional[PathLike] = None,
|
172
|
+
) -> List[Marker]:
|
107
173
|
"""
|
108
174
|
Capture an image and identify fiducial markers.
|
109
175
|
|
110
176
|
:param frame: An image to detect markers in, instead of capturing a new one,
|
177
|
+
:param save: If given, save the annotated frame to the path.
|
178
|
+
This is given a JPEG extension if none is provided.
|
111
179
|
:returns: list of markers that the camera could see.
|
112
180
|
"""
|
181
|
+
if frame is None:
|
182
|
+
frame = self._cam.capture()
|
183
|
+
|
113
184
|
markers = self._cam.see(frame=frame)
|
185
|
+
|
186
|
+
if save:
|
187
|
+
self._cam.save(save, frame=frame, detections=markers)
|
114
188
|
return [Marker.from_april_vision_marker(marker) for marker in markers]
|
115
189
|
|
116
|
-
def capture(self) -> NDArray:
|
190
|
+
def capture(self, *, save: Optional[PathLike] = None) -> NDArray:
|
117
191
|
"""
|
118
192
|
Get the raw image data from the camera.
|
119
193
|
|
194
|
+
:param save: If given, save the annotated frame to the path.
|
195
|
+
This is given a JPEG extension if none is provided.
|
120
196
|
:returns: Camera pixel data
|
121
197
|
"""
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
Save an annotated image to a path.
|
127
|
-
|
128
|
-
:param path: The path to save the image to,
|
129
|
-
this is given a JPEG extension if none is provided.
|
130
|
-
:param frame: An image to annotate and save, instead of capturing a new one,
|
131
|
-
defaults to None
|
132
|
-
"""
|
133
|
-
self._cam.save(path, frame=frame)
|
198
|
+
raw_frame = self._cam.capture()
|
199
|
+
if save:
|
200
|
+
self._cam.save(save, frame=raw_frame, annotated=False)
|
201
|
+
return raw_frame
|
134
202
|
|
135
203
|
def _set_marker_sizes(
|
136
204
|
self,
|