robowrap 0.8.0__py3-none-any.whl
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.
- robowrap/__init__.py +21 -0
- robowrap/_sdk.py +67 -0
- robowrap/arm.py +113 -0
- robowrap/camera.py +373 -0
- robowrap/gun.py +131 -0
- robowrap/helperFuncs.py +109 -0
- robowrap/modules.py +303 -0
- robowrap/robowrap.py +373 -0
- robowrap-0.8.0.dist-info/METADATA +82 -0
- robowrap-0.8.0.dist-info/RECORD +12 -0
- robowrap-0.8.0.dist-info/WHEEL +5 -0
- robowrap-0.8.0.dist-info/top_level.txt +1 -0
robowrap/__init__.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .arm import Arm, Gripper
|
|
2
|
+
from .camera import Camera
|
|
3
|
+
from .gun import Gun
|
|
4
|
+
from .modules import AiModule, Armor, Battery, Chassis, DistanceSensor, SensorAdaptor, Servo, Uart
|
|
5
|
+
from .robowrap import RoboMaster
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"AiModule",
|
|
9
|
+
"Arm",
|
|
10
|
+
"Armor",
|
|
11
|
+
"Battery",
|
|
12
|
+
"Camera",
|
|
13
|
+
"Chassis",
|
|
14
|
+
"DistanceSensor",
|
|
15
|
+
"Gripper",
|
|
16
|
+
"Gun",
|
|
17
|
+
"RoboMaster",
|
|
18
|
+
"SensorAdaptor",
|
|
19
|
+
"Servo",
|
|
20
|
+
"Uart",
|
|
21
|
+
]
|
robowrap/_sdk.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from robomaster import led as sdk_led
|
|
7
|
+
from robomaster import robot as sdk_robot
|
|
8
|
+
from robomaster.action import Action as SdkAction
|
|
9
|
+
except ImportError: # pragma: no cover - exercised in local tests without the SDK
|
|
10
|
+
sdk_led = None
|
|
11
|
+
sdk_robot = None
|
|
12
|
+
SdkAction = object
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def create_sdk_robot() -> Any:
|
|
16
|
+
"""Create a RoboMaster EP SDK robot instance."""
|
|
17
|
+
if sdk_robot is None:
|
|
18
|
+
raise ImportError(
|
|
19
|
+
"The DJI RoboMaster SDK is not installed. Install 'robomaster' or inject a "
|
|
20
|
+
"fake robot object when testing."
|
|
21
|
+
)
|
|
22
|
+
return sdk_robot.Robot()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def resolve_robot_mode(mode: str) -> Any:
|
|
26
|
+
"""Map student-friendly robot modes onto SDK constants."""
|
|
27
|
+
normalized = mode.lower()
|
|
28
|
+
if normalized == "free":
|
|
29
|
+
return getattr(sdk_robot, "FREE", "free") if sdk_robot else "free"
|
|
30
|
+
if normalized == "chassis":
|
|
31
|
+
return getattr(sdk_robot, "CHASSIS_LEAD", "chassis_lead") if sdk_robot else "chassis_lead"
|
|
32
|
+
if normalized == "gun":
|
|
33
|
+
return getattr(sdk_robot, "GIMBAL_LEAD", "gimbal_lead") if sdk_robot else "gimbal_lead"
|
|
34
|
+
raise ValueError("mode must be 'free', 'chassis', or 'gun'.")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def normalize_robot_mode(mode: Any) -> str:
|
|
38
|
+
"""Convert SDK robot mode responses back to the student-facing vocabulary."""
|
|
39
|
+
if mode in ("free", getattr(sdk_robot, "FREE", object())):
|
|
40
|
+
return "free"
|
|
41
|
+
if mode in ("chassis_lead", getattr(sdk_robot, "CHASSIS_LEAD", object())):
|
|
42
|
+
return "chassis"
|
|
43
|
+
if mode in ("gimbal_lead", getattr(sdk_robot, "GIMBAL_LEAD", object())):
|
|
44
|
+
return "gun"
|
|
45
|
+
return str(mode)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
LED_COMPONENTS = {
|
|
49
|
+
"front": getattr(sdk_led, "COMP_BOTTOM_FRONT", "front") if sdk_led else "front",
|
|
50
|
+
"back": getattr(sdk_led, "COMP_BOTTOM_BACK", "back") if sdk_led else "back",
|
|
51
|
+
"left": getattr(sdk_led, "COMP_BOTTOM_LEFT", "left") if sdk_led else "left",
|
|
52
|
+
"right": getattr(sdk_led, "COMP_BOTTOM_RIGHT", "right") if sdk_led else "right",
|
|
53
|
+
"gun": getattr(sdk_led, "COMP_TOP_ALL", "gun") if sdk_led else "gun",
|
|
54
|
+
"gun_left": getattr(sdk_led, "COMP_TOP_LEFT", "gun_left") if sdk_led else "gun_left",
|
|
55
|
+
"gun_right": getattr(sdk_led, "COMP_TOP_RIGHT", "gun_right") if sdk_led else "gun_right",
|
|
56
|
+
"all": getattr(sdk_led, "COMP_ALL", "all") if sdk_led else "all",
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
LED_EFFECTS = {
|
|
61
|
+
"on": getattr(sdk_led, "EFFECT_ON", "on") if sdk_led else "on",
|
|
62
|
+
"off": getattr(sdk_led, "EFFECT_OFF", "off") if sdk_led else "off",
|
|
63
|
+
"pulse": getattr(sdk_led, "EFFECT_PULSE", "pulse") if sdk_led else "pulse",
|
|
64
|
+
"flash": getattr(sdk_led, "EFFECT_FLASH", "flash") if sdk_led else "flash",
|
|
65
|
+
"breath": getattr(sdk_led, "EFFECT_BREATH", "breath") if sdk_led else "breath",
|
|
66
|
+
"scrolling": getattr(sdk_led, "EFFECT_SCROLLING", "scrolling") if sdk_led else "scrolling",
|
|
67
|
+
}
|
robowrap/arm.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Optional
|
|
4
|
+
|
|
5
|
+
from .helperFuncs import deprecated_alias, ensure_choice, ensure_int_range
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Arm:
|
|
9
|
+
def __init__(self, robomaster: Any) -> None:
|
|
10
|
+
self.robomaster = robomaster
|
|
11
|
+
self.robot = self.robomaster.robot
|
|
12
|
+
self.gripper = getattr(self.robomaster, "gripper", None)
|
|
13
|
+
|
|
14
|
+
def move(
|
|
15
|
+
self,
|
|
16
|
+
x: int = 0,
|
|
17
|
+
z: int = 0,
|
|
18
|
+
*,
|
|
19
|
+
blocking: bool = True,
|
|
20
|
+
timeout: Optional[float] = None,
|
|
21
|
+
) -> Any:
|
|
22
|
+
"""Move the arm relative to its current position in millimetres."""
|
|
23
|
+
ensure_int_range("x", x, -200, 200, unit="mm")
|
|
24
|
+
ensure_int_range("z", z, -200, 200, unit="mm")
|
|
25
|
+
action = self.robot.robotic_arm.move(x=x, y=z)
|
|
26
|
+
return self.robomaster._complete_action(action, blocking=blocking, timeout=timeout)
|
|
27
|
+
|
|
28
|
+
def move_to(
|
|
29
|
+
self,
|
|
30
|
+
x: int = 0,
|
|
31
|
+
z: int = 0,
|
|
32
|
+
*,
|
|
33
|
+
blocking: bool = True,
|
|
34
|
+
timeout: Optional[float] = None,
|
|
35
|
+
) -> Any:
|
|
36
|
+
"""Move the arm to an absolute position measured from startup in millimetres."""
|
|
37
|
+
ensure_int_range("x", x, -200, 200, unit="mm")
|
|
38
|
+
ensure_int_range("z", z, -200, 200, unit="mm")
|
|
39
|
+
action = self.robot.robotic_arm.moveto(x=x, y=z)
|
|
40
|
+
return self.robomaster._complete_action(action, blocking=blocking, timeout=timeout)
|
|
41
|
+
|
|
42
|
+
def recenter(self, *, blocking: bool = True, timeout: Optional[float] = None) -> Any:
|
|
43
|
+
action = self.robot.robotic_arm.recenter()
|
|
44
|
+
return self.robomaster._complete_action(action, blocking=blocking, timeout=timeout)
|
|
45
|
+
|
|
46
|
+
def reset(self, *, blocking: bool = True, timeout: Optional[float] = None) -> Any:
|
|
47
|
+
"""Return the arm to its startup position."""
|
|
48
|
+
return self.recenter(blocking=blocking, timeout=timeout)
|
|
49
|
+
|
|
50
|
+
def subscribe_position(self, callback: Callable[..., Any], freq: int = 5) -> Any:
|
|
51
|
+
ensure_choice("freq", freq, (1, 5, 10, 20, 50))
|
|
52
|
+
return self.robot.robotic_arm.sub_position(freq=freq, callback=callback)
|
|
53
|
+
|
|
54
|
+
def unsubscribe_position(self) -> Any:
|
|
55
|
+
return self.robot.robotic_arm.unsub_position()
|
|
56
|
+
|
|
57
|
+
def pickup(self) -> None:
|
|
58
|
+
"""Run a simple classroom pickup sequence."""
|
|
59
|
+
if self.gripper is None:
|
|
60
|
+
raise RuntimeError("The gripper wrapper is not available.")
|
|
61
|
+
self.move_to(x=0, z=50)
|
|
62
|
+
self.gripper.open()
|
|
63
|
+
self.robomaster.sleep(1.5)
|
|
64
|
+
self.move(x=175, z=-200)
|
|
65
|
+
self.gripper.close()
|
|
66
|
+
self.robomaster.sleep(1.5)
|
|
67
|
+
self.gripper.pause()
|
|
68
|
+
self.move_to(x=0, z=50)
|
|
69
|
+
|
|
70
|
+
def drop(self) -> None:
|
|
71
|
+
"""Run a simple classroom drop sequence."""
|
|
72
|
+
if self.gripper is None:
|
|
73
|
+
raise RuntimeError("The gripper wrapper is not available.")
|
|
74
|
+
self.move(x=175, z=-200)
|
|
75
|
+
self.gripper.open()
|
|
76
|
+
self.robomaster.sleep(1.5)
|
|
77
|
+
self.move_to(x=0, z=50)
|
|
78
|
+
self.gripper.pause()
|
|
79
|
+
|
|
80
|
+
@deprecated_alias("move_to")
|
|
81
|
+
def moveTo(self, *args: Any, **kwargs: Any) -> Any:
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Gripper:
|
|
86
|
+
def __init__(self, robomaster: Any) -> None:
|
|
87
|
+
self.robomaster = robomaster
|
|
88
|
+
self.robot = self.robomaster.robot
|
|
89
|
+
|
|
90
|
+
def open(self, power: int = 50) -> Any:
|
|
91
|
+
ensure_int_range("power", power, 1, 100)
|
|
92
|
+
return self.robot.gripper.open(power=power)
|
|
93
|
+
|
|
94
|
+
def close(self, power: int = 50) -> Any:
|
|
95
|
+
ensure_int_range("power", power, 1, 100)
|
|
96
|
+
return self.robot.gripper.close(power=power)
|
|
97
|
+
|
|
98
|
+
def pause(self) -> Any:
|
|
99
|
+
return self.robot.gripper.pause()
|
|
100
|
+
|
|
101
|
+
def stop(self) -> Any:
|
|
102
|
+
return self.pause()
|
|
103
|
+
|
|
104
|
+
def reset(self) -> Any:
|
|
105
|
+
"""Stop the gripper motor and leave the claw in a safe passive state."""
|
|
106
|
+
return self.pause()
|
|
107
|
+
|
|
108
|
+
def subscribe_status(self, callback: Callable[..., Any], freq: int = 5) -> Any:
|
|
109
|
+
ensure_choice("freq", freq, (1, 5, 10, 20, 50))
|
|
110
|
+
return self.robot.gripper.sub_status(freq=freq, callback=callback)
|
|
111
|
+
|
|
112
|
+
def unsubscribe_status(self) -> Any:
|
|
113
|
+
return self.robot.gripper.unsub_status()
|
robowrap/camera.py
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Optional
|
|
4
|
+
|
|
5
|
+
try: # pragma: no cover - optional dependency in local tests
|
|
6
|
+
import cv2
|
|
7
|
+
except ImportError: # pragma: no cover - optional dependency in local tests
|
|
8
|
+
cv2 = None
|
|
9
|
+
|
|
10
|
+
try: # pragma: no cover - optional dependency in local tests
|
|
11
|
+
from simple_pid import PID
|
|
12
|
+
except ImportError: # pragma: no cover - exercised in tests without the dependency
|
|
13
|
+
class PID: # type: ignore[override]
|
|
14
|
+
def __init__(self, p: float, i: float, d: float, *, setpoint: float = 0.0, sample_time: float = 0.05) -> None:
|
|
15
|
+
self.Kp = p
|
|
16
|
+
self.Ki = i
|
|
17
|
+
self.Kd = d
|
|
18
|
+
self.setpoint = setpoint
|
|
19
|
+
self.sample_time = sample_time
|
|
20
|
+
self.output_limits = (None, None)
|
|
21
|
+
|
|
22
|
+
def __call__(self, measurement: float) -> float:
|
|
23
|
+
error = self.setpoint - measurement
|
|
24
|
+
output = self.Kp * error
|
|
25
|
+
lower, upper = self.output_limits
|
|
26
|
+
if lower is not None:
|
|
27
|
+
output = max(lower, output)
|
|
28
|
+
if upper is not None:
|
|
29
|
+
output = min(upper, output)
|
|
30
|
+
return output
|
|
31
|
+
|
|
32
|
+
from .helperFuncs import (
|
|
33
|
+
deprecated_alias,
|
|
34
|
+
ensure_choice,
|
|
35
|
+
ensure_color,
|
|
36
|
+
ensure_range,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Camera:
|
|
41
|
+
def __init__(self, robomaster: Any) -> None:
|
|
42
|
+
self.robomaster = robomaster
|
|
43
|
+
self.robot = self.robomaster.robot
|
|
44
|
+
self.camera = self.robot.camera
|
|
45
|
+
self.vision = self.robot.vision
|
|
46
|
+
self.streaming = False
|
|
47
|
+
self.audio_streaming = False
|
|
48
|
+
self.detecting = False
|
|
49
|
+
self.detect_mode = "line"
|
|
50
|
+
self.vision_debug = False
|
|
51
|
+
self.debug_color = (0, 255, 0)
|
|
52
|
+
self.resolution = "360p"
|
|
53
|
+
self.width = 640
|
|
54
|
+
self.height = 360
|
|
55
|
+
self.debug_mode: Any = False
|
|
56
|
+
self.frequency = 30
|
|
57
|
+
self.follow_speed = 0.5
|
|
58
|
+
self.follow_distance = 0.5
|
|
59
|
+
self.at_marker = False
|
|
60
|
+
self._debug_items: list[dict[str, Any]] = []
|
|
61
|
+
self.pid = PID(-330, 0, -28, setpoint=0.0, sample_time=1.0 / self.frequency)
|
|
62
|
+
self.pid.output_limits = (-self.follow_speed / 3.5 * 600, self.follow_speed / 3.5 * 600)
|
|
63
|
+
|
|
64
|
+
def _require_cv2(self) -> None:
|
|
65
|
+
if cv2 is None:
|
|
66
|
+
raise RuntimeError("OpenCV is required for Camera.view(). Install 'opencv-python'.")
|
|
67
|
+
|
|
68
|
+
def _update_resolution_dimensions(self) -> None:
|
|
69
|
+
if self.resolution == "360p":
|
|
70
|
+
self.width, self.height = 640, 360
|
|
71
|
+
elif self.resolution == "540p":
|
|
72
|
+
self.width, self.height = 960, 540
|
|
73
|
+
else:
|
|
74
|
+
self.width, self.height = 1280, 720
|
|
75
|
+
|
|
76
|
+
def set_resolution(self, resolution: str = "360p") -> None:
|
|
77
|
+
resolution = resolution.lower()
|
|
78
|
+
ensure_choice("resolution", resolution, ("360p", "540p", "720p"))
|
|
79
|
+
self.resolution = resolution
|
|
80
|
+
self._update_resolution_dimensions()
|
|
81
|
+
|
|
82
|
+
def start(self) -> Any:
|
|
83
|
+
if self.streaming:
|
|
84
|
+
return True
|
|
85
|
+
result = self.camera.start_video_stream(display=False, resolution=self.resolution)
|
|
86
|
+
self.streaming = bool(result is not False)
|
|
87
|
+
return result
|
|
88
|
+
|
|
89
|
+
def stop_detect(self) -> None:
|
|
90
|
+
if self.detecting:
|
|
91
|
+
self.vision.unsub_detect_info(self.detect_mode)
|
|
92
|
+
self.detecting = False
|
|
93
|
+
|
|
94
|
+
def stop(self) -> Any:
|
|
95
|
+
self.stop_detect()
|
|
96
|
+
if self.audio_streaming:
|
|
97
|
+
self.stop_audio_stream()
|
|
98
|
+
if not self.streaming:
|
|
99
|
+
return True
|
|
100
|
+
result = self.camera.stop_video_stream()
|
|
101
|
+
self.streaming = False
|
|
102
|
+
if cv2 is not None:
|
|
103
|
+
cv2.destroyAllWindows()
|
|
104
|
+
return result
|
|
105
|
+
|
|
106
|
+
def view(self) -> Any:
|
|
107
|
+
self._require_cv2()
|
|
108
|
+
if not self.streaming:
|
|
109
|
+
self.start()
|
|
110
|
+
image = self.camera.read_cv2_image(strategy="newest")
|
|
111
|
+
if image is None:
|
|
112
|
+
return None
|
|
113
|
+
for item in self._debug_items:
|
|
114
|
+
if item["type"] == "box":
|
|
115
|
+
cv2.rectangle(image, item["start"], item["end"], item["color"], 2)
|
|
116
|
+
elif item["type"] == "point":
|
|
117
|
+
cv2.circle(image, item["point"], 2, item["color"], -1)
|
|
118
|
+
cv2.imshow("RoboMaster", image)
|
|
119
|
+
cv2.waitKey(1)
|
|
120
|
+
return image
|
|
121
|
+
|
|
122
|
+
def set_pid(self, p: float = 330, i: float = 0, d: float = 28) -> None:
|
|
123
|
+
self.pid.Kp = -p
|
|
124
|
+
self.pid.Ki = -i
|
|
125
|
+
self.pid.Kd = -d
|
|
126
|
+
|
|
127
|
+
def set_detect_mode(self, mode: str = "line") -> None:
|
|
128
|
+
ensure_choice("mode", mode.lower(), ("person", "gesture", "line", "marker", "robot"))
|
|
129
|
+
self.detect_mode = mode.lower()
|
|
130
|
+
|
|
131
|
+
def set_vision_debug(self, value: bool = True) -> None:
|
|
132
|
+
self.vision_debug = bool(value)
|
|
133
|
+
|
|
134
|
+
def set_debug_color(self, color: tuple[int, int, int] = (255, 0, 0)) -> None:
|
|
135
|
+
self.debug_color = ensure_color("color", color)
|
|
136
|
+
|
|
137
|
+
def reset_vision(self) -> Any:
|
|
138
|
+
self.stop_detect()
|
|
139
|
+
self._debug_items = []
|
|
140
|
+
return self.vision.reset()
|
|
141
|
+
|
|
142
|
+
def _store_boxes(self, items: list[list[Any]]) -> None:
|
|
143
|
+
boxes: list[dict[str, Any]] = []
|
|
144
|
+
for item in items:
|
|
145
|
+
x = int(item[0] * self.width)
|
|
146
|
+
y = int(item[1] * self.height)
|
|
147
|
+
w = int(item[2] * self.width)
|
|
148
|
+
h = int(item[3] * self.height)
|
|
149
|
+
boxes.append(
|
|
150
|
+
{
|
|
151
|
+
"type": "box",
|
|
152
|
+
"start": (x - w // 2, y - h // 2),
|
|
153
|
+
"end": (x + w // 2, y + h // 2),
|
|
154
|
+
"color": self.debug_color,
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
self._debug_items = boxes
|
|
158
|
+
|
|
159
|
+
def _store_line(self, info: list[Any]) -> None:
|
|
160
|
+
points: list[dict[str, Any]] = []
|
|
161
|
+
for point in info[1:]:
|
|
162
|
+
x = int(point[0] * self.width)
|
|
163
|
+
y = int(point[1] * self.height)
|
|
164
|
+
points.append({"type": "point", "point": (x, y), "color": self.debug_color})
|
|
165
|
+
self._debug_items = points
|
|
166
|
+
|
|
167
|
+
def _detect_callback(self, info: Any) -> bool:
|
|
168
|
+
if not info:
|
|
169
|
+
self._debug_items = []
|
|
170
|
+
return False
|
|
171
|
+
if self.detect_mode in {"person", "gesture", "marker", "robot"}:
|
|
172
|
+
self._store_boxes(info)
|
|
173
|
+
elif self.detect_mode == "line":
|
|
174
|
+
self._store_line(info)
|
|
175
|
+
return True
|
|
176
|
+
|
|
177
|
+
def _restart_detection(self, mode: str, callback: Callable[..., Any], color: Optional[str] = None) -> Any:
|
|
178
|
+
ensure_choice("mode", mode, ("person", "gesture", "line", "marker", "robot"))
|
|
179
|
+
if color is not None:
|
|
180
|
+
ensure_choice("color", color, ("red", "green", "blue"))
|
|
181
|
+
if not self.streaming:
|
|
182
|
+
self.start()
|
|
183
|
+
if self.detecting:
|
|
184
|
+
self.stop_detect()
|
|
185
|
+
self.detect_mode = mode
|
|
186
|
+
result = self.vision.sub_detect_info(mode, color, callback)
|
|
187
|
+
self.detecting = bool(result is not False)
|
|
188
|
+
return result
|
|
189
|
+
|
|
190
|
+
def detect(self, name: Optional[str] = None, color: Optional[str] = None) -> Any:
|
|
191
|
+
mode = self.detect_mode if name is None else name.lower()
|
|
192
|
+
return self._restart_detection(mode, self._detect_callback, color=color)
|
|
193
|
+
|
|
194
|
+
def detect_person(self) -> Any:
|
|
195
|
+
return self.detect("person")
|
|
196
|
+
|
|
197
|
+
def detect_gesture(self) -> Any:
|
|
198
|
+
return self.detect("gesture")
|
|
199
|
+
|
|
200
|
+
def detect_line(self, color: str = "red") -> Any:
|
|
201
|
+
return self.detect("line", color=color)
|
|
202
|
+
|
|
203
|
+
def detect_marker(self, color: str = "red") -> Any:
|
|
204
|
+
return self.detect("marker", color=color)
|
|
205
|
+
|
|
206
|
+
def detect_robot(self) -> Any:
|
|
207
|
+
return self.detect("robot")
|
|
208
|
+
|
|
209
|
+
def set_follow_speed(self, speed: float) -> None:
|
|
210
|
+
ensure_range("speed", speed, 0.05, 3.5, unit="m/s")
|
|
211
|
+
self.follow_speed = speed
|
|
212
|
+
self.pid.output_limits = (-self.follow_speed / 3.5 * 600, self.follow_speed / 3.5 * 600)
|
|
213
|
+
|
|
214
|
+
def set_follow_distance(self, distance: float) -> None:
|
|
215
|
+
ensure_range("distance", distance, 0.0, 1.0)
|
|
216
|
+
self.follow_distance = distance
|
|
217
|
+
|
|
218
|
+
def _follow_callback(self, info: Any) -> bool:
|
|
219
|
+
self._detect_callback(info)
|
|
220
|
+
if self.detect_mode != "line":
|
|
221
|
+
return False
|
|
222
|
+
if info == [0] or not info:
|
|
223
|
+
self.robomaster.stop()
|
|
224
|
+
return False
|
|
225
|
+
follow_point = min(5, len(info) - 2)
|
|
226
|
+
tangent_angle = info[follow_point + 1][2]
|
|
227
|
+
turn_speed = self.pid(tangent_angle)
|
|
228
|
+
self.robomaster.set_speed(x=self.follow_speed, z=turn_speed)
|
|
229
|
+
return True
|
|
230
|
+
|
|
231
|
+
def follow(self, name: Optional[str] = None, color: str = "red") -> Any:
|
|
232
|
+
mode = self.detect_mode if name is None else name.lower()
|
|
233
|
+
if mode != "line":
|
|
234
|
+
raise NotImplementedError("Only line following is implemented in the student wrapper.")
|
|
235
|
+
return self._restart_detection(mode, self._follow_callback, color=color)
|
|
236
|
+
|
|
237
|
+
def follow_line(self, color: str = "red") -> Any:
|
|
238
|
+
return self.follow("line", color=color)
|
|
239
|
+
|
|
240
|
+
def move_to_marker(
|
|
241
|
+
self,
|
|
242
|
+
marker_type: str = "1",
|
|
243
|
+
*,
|
|
244
|
+
color: str = "red",
|
|
245
|
+
error: float = 0.06,
|
|
246
|
+
speed: float = 1.0,
|
|
247
|
+
min_speed: float = 0.02,
|
|
248
|
+
target_x: float = 0.0,
|
|
249
|
+
target_y: float = 0.5,
|
|
250
|
+
) -> Any:
|
|
251
|
+
ensure_choice("color", color, ("red", "green", "blue"))
|
|
252
|
+
ensure_range("error", error, 0.0, 1.0)
|
|
253
|
+
ensure_range("speed", speed, 0.05, 3.5, unit="m/s")
|
|
254
|
+
ensure_range("min_speed", min_speed, 0.0, 1.0, unit="m/s")
|
|
255
|
+
ensure_range("target_x", target_x, -1.0, 1.0)
|
|
256
|
+
ensure_range("target_y", target_y, -1.0, 1.0)
|
|
257
|
+
marker_type = str(marker_type)
|
|
258
|
+
self.at_marker = False
|
|
259
|
+
|
|
260
|
+
def _callback(info: Any) -> bool:
|
|
261
|
+
self._detect_callback(info)
|
|
262
|
+
if not info:
|
|
263
|
+
return False
|
|
264
|
+
for marker in info:
|
|
265
|
+
if str(marker[4]) != marker_type:
|
|
266
|
+
continue
|
|
267
|
+
x = marker[0] * 2 - 1
|
|
268
|
+
y = marker[1] * 2 - 1
|
|
269
|
+
if abs(x - target_x) > error:
|
|
270
|
+
lateral = (x - target_x) * speed
|
|
271
|
+
if 0 < abs(lateral) < min_speed:
|
|
272
|
+
lateral = min_speed if lateral > 0 else -min_speed
|
|
273
|
+
self.robomaster.set_speed(y=lateral)
|
|
274
|
+
return False
|
|
275
|
+
if abs(y - target_y) > error:
|
|
276
|
+
forward = (target_y - y) * speed
|
|
277
|
+
if 0 < abs(forward) < min_speed:
|
|
278
|
+
forward = min_speed if forward > 0 else -min_speed
|
|
279
|
+
self.robomaster.set_speed(x=forward)
|
|
280
|
+
return False
|
|
281
|
+
self.robomaster.stop()
|
|
282
|
+
self.stop_detect()
|
|
283
|
+
self.at_marker = True
|
|
284
|
+
return True
|
|
285
|
+
return False
|
|
286
|
+
|
|
287
|
+
return self._restart_detection("marker", _callback, color=color)
|
|
288
|
+
|
|
289
|
+
def take_photo(self) -> Any:
|
|
290
|
+
return self.camera.take_photo()
|
|
291
|
+
|
|
292
|
+
def start_audio_stream(self) -> Any:
|
|
293
|
+
result = self.camera.start_audio_stream()
|
|
294
|
+
self.audio_streaming = bool(result is not False)
|
|
295
|
+
return result
|
|
296
|
+
|
|
297
|
+
def stop_audio_stream(self) -> Any:
|
|
298
|
+
result = self.camera.stop_audio_stream()
|
|
299
|
+
self.audio_streaming = False
|
|
300
|
+
return result
|
|
301
|
+
|
|
302
|
+
def read_audio_frame(self, timeout: float = 1.0) -> Any:
|
|
303
|
+
ensure_range("timeout", timeout, 0.01, 60.0, unit="seconds")
|
|
304
|
+
return self.camera.read_audio_frame(timeout=timeout)
|
|
305
|
+
|
|
306
|
+
def record_audio(self, save_file: str = "output.wav", seconds: float = 5.0, sample_rate: int = 48000) -> Any:
|
|
307
|
+
ensure_range("seconds", seconds, 0.1, 600.0, unit="seconds")
|
|
308
|
+
ensure_choice("sample_rate", sample_rate, (8000, 16000, 32000, 44100, 48000))
|
|
309
|
+
return self.camera.record_audio(save_file=save_file, seconds=seconds, sample_rate=sample_rate)
|
|
310
|
+
|
|
311
|
+
@deprecated_alias("set_resolution")
|
|
312
|
+
def setResolution(self, *args: Any, **kwargs: Any) -> Any:
|
|
313
|
+
return None
|
|
314
|
+
|
|
315
|
+
@deprecated_alias("stop_detect")
|
|
316
|
+
def stopDetect(self, *args: Any, **kwargs: Any) -> Any:
|
|
317
|
+
return None
|
|
318
|
+
|
|
319
|
+
@deprecated_alias("set_pid")
|
|
320
|
+
def setPID(self, *args: Any, **kwargs: Any) -> Any:
|
|
321
|
+
return None
|
|
322
|
+
|
|
323
|
+
@deprecated_alias("set_detect_mode")
|
|
324
|
+
def setDetectMode(self, *args: Any, **kwargs: Any) -> Any:
|
|
325
|
+
return None
|
|
326
|
+
|
|
327
|
+
@deprecated_alias("set_vision_debug")
|
|
328
|
+
def setVisionDebug(self, *args: Any, **kwargs: Any) -> Any:
|
|
329
|
+
return None
|
|
330
|
+
|
|
331
|
+
@deprecated_alias("set_debug_color")
|
|
332
|
+
def setDebugColor(self, *args: Any, **kwargs: Any) -> Any:
|
|
333
|
+
return None
|
|
334
|
+
|
|
335
|
+
@deprecated_alias("detect_person")
|
|
336
|
+
def detectPerson(self, *args: Any, **kwargs: Any) -> Any:
|
|
337
|
+
return None
|
|
338
|
+
|
|
339
|
+
@deprecated_alias("detect_gesture")
|
|
340
|
+
def detectGesture(self, *args: Any, **kwargs: Any) -> Any:
|
|
341
|
+
return None
|
|
342
|
+
|
|
343
|
+
@deprecated_alias("detect_line")
|
|
344
|
+
def detectLine(self, *args: Any, **kwargs: Any) -> Any:
|
|
345
|
+
return None
|
|
346
|
+
|
|
347
|
+
@deprecated_alias("detect_marker")
|
|
348
|
+
def detectMarker(self, *args: Any, **kwargs: Any) -> Any:
|
|
349
|
+
return None
|
|
350
|
+
|
|
351
|
+
@deprecated_alias("detect_robot")
|
|
352
|
+
def detectRobot(self, *args: Any, **kwargs: Any) -> Any:
|
|
353
|
+
return None
|
|
354
|
+
|
|
355
|
+
@deprecated_alias("set_follow_speed")
|
|
356
|
+
def setFollowSpeed(self, *args: Any, **kwargs: Any) -> Any:
|
|
357
|
+
return None
|
|
358
|
+
|
|
359
|
+
@deprecated_alias("set_follow_distance")
|
|
360
|
+
def setFollowDistance(self, *args: Any, **kwargs: Any) -> Any:
|
|
361
|
+
return None
|
|
362
|
+
|
|
363
|
+
@deprecated_alias("follow_line")
|
|
364
|
+
def followLine(self, *args: Any, **kwargs: Any) -> Any:
|
|
365
|
+
return None
|
|
366
|
+
|
|
367
|
+
@deprecated_alias("move_to_marker")
|
|
368
|
+
def moveToMarker(self, *args: Any, **kwargs: Any) -> Any:
|
|
369
|
+
return None
|
|
370
|
+
|
|
371
|
+
@deprecated_alias("view")
|
|
372
|
+
def display(self, *args: Any, **kwargs: Any) -> Any:
|
|
373
|
+
return None
|