Skip to content

Commit c4e4e89

Browse files
authored
Added components for ExpansionHubServo and ExpansionHubMotor. (#188)
* Added components for ExpansionHubServo and ExpansionHubMotor. Added start and update methods to components. Added code to run_opmode to print stacktrace if an error occurs. * Don't allow user to override define_hardware in the robot. * Removed @AbstractMethod from start, update, and stop in component.py. Removed empty start, update, and stop methods from component subclasses.
1 parent 485a6a7 commit c4e4e89

File tree

13 files changed

+1233
-87
lines changed

13 files changed

+1233
-87
lines changed

external_samples/color_range_sensor.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,6 @@ def get_url(self) -> str:
5151
def get_version(self) -> tuple[int, int, int]:
5252
return (1, 0, 0)
5353

54-
def stop(self) -> None:
55-
# send stop command to sensor
56-
pass
57-
5854
def reset(self) -> None:
5955
pass
6056

external_samples/component.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,13 @@ def get_url(self) -> str:
7171
def get_version(self) -> tuple[int, int, int]:
7272
pass
7373

74+
def start(self) -> None:
75+
pass
76+
77+
def update(self) -> None:
78+
pass
79+
7480
# This stops all movement (if any) for the component
75-
@abstractmethod
7681
def stop(self) -> None:
7782
pass
7883

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""This is the expansion_hub_motor module.
16+
17+
This component wraps the expansion_hub.ExpansionHubMotor class, providing
18+
support for a motor connected to a REV Expansion Hub.
19+
"""
20+
21+
__author__ = "[email protected] (Liz Looney)"
22+
23+
from typing import Self
24+
from component import Component, PortType, InvalidPortException
25+
import expansion_hub
26+
import wpimath
27+
28+
# TODO(lizlooney): Update port types.
29+
30+
class ExpansionHubMotor(Component):
31+
def __init__(self, ports : list[tuple[PortType, int]]):
32+
port_type, hub_number = ports[0]
33+
if port_type != PortType.USB_PORT:
34+
raise InvalidPortException
35+
port_type, motor_number = ports[1]
36+
if port_type != PortType.USB_PORT:
37+
raise InvalidPortException
38+
self.expansion_hub_motor = expansion_hub.ExpansionHubMotor(hub_number, motor_number)
39+
40+
def get_manufacturer(self) -> str:
41+
return "REV Robotics"
42+
43+
def get_name(self) -> str:
44+
return "Expansion Hub Motor"
45+
46+
def get_part_number(self) -> str:
47+
return ""
48+
49+
def get_url(self) -> str:
50+
return ""
51+
52+
def get_version(self) -> tuple[int, int, int]:
53+
return (1, 0, 0)
54+
55+
def start(self) -> None:
56+
self.expansion_hub_motor.setEnabled(True)
57+
58+
def stop(self) -> None:
59+
# TODO: Send stop command to motor.
60+
pass
61+
62+
def reset(self) -> None:
63+
pass
64+
65+
def get_connection_port_type(self) -> list[PortType]:
66+
return [PortType.USB_PORT, PortType.USB_PORT]
67+
68+
def periodic(self) -> None:
69+
pass
70+
71+
# Alternative constructor to create an instance from a hub number and a motor port.
72+
@classmethod
73+
def from_hub_number_and_motor_number(cls: type[Self], hub_number: int, motor_number: int) -> Self:
74+
return cls([(PortType.USB_PORT, hub_number), (PortType.USB_PORT, motor_number)])
75+
76+
# Component specific methods
77+
78+
# Methods from expansion_hub.ExpansionHubMotor
79+
80+
def setPercentagePower(self, power: float):
81+
self.expansion_hub_motor.setPercentagePower(power)
82+
83+
def setVoltage(self, voltage: wpimath.units.volts):
84+
self.expansion_hub_motor.setVoltage(voltage)
85+
86+
def setPositionSetpoint(self, setpoint: float):
87+
self.expansion_hub_motor.setPositionSetpoint(setpoint)
88+
89+
def setVelocitySetpoint(self, setpoint: float):
90+
self.expansion_hub_motor.setVelocitySetpoint(setpoint)
91+
92+
def setEnabled(self, enabled: bool):
93+
self.expansion_hub_motor.setEnabled(enabled)
94+
95+
def setFloatOn0(self, floatOn0: bool):
96+
self.expansion_hub_motor.setFloatOn0(floatOn0)
97+
98+
def getCurrent(self) -> float:
99+
return self.expansion_hub_motor.getCurrent()
100+
101+
def setDistancePerCount(self, perCount: float):
102+
self.expansion_hub_motor.setDistancePerCount(perCount)
103+
104+
def isHubConnected(self) -> bool:
105+
return self.expansion_hub_motor.isHubConnected()
106+
107+
def getEncoder(self) -> float:
108+
return self.expansion_hub_motor.getEncoder()
109+
110+
def getEncoderVelocity(self) -> float:
111+
return self.expansion_hub_motor.getEncoderVelocity()
112+
113+
def setReversed(self, reversed: bool):
114+
self.expansion_hub_motor.setReversed(reversed)
115+
116+
def resetEncoder(self):
117+
self.expansion_hub_motor.resetEncoder()
118+
119+
def getVelocityPidConstants(self) -> expansion_hub.ExpansionHubPidConstants:
120+
return self.expansion_hub_motor.getVelocityPidConstants()
121+
122+
def getPositionPidConstants(self) -> expansion_hub.ExpansionHubPidConstants:
123+
return self.expansion_hub_motor.getPositionPidConstants()
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""This is the expansion_hub_servo module.
16+
17+
This component wraps the expansion_hub.ExpansionHubServo class, providing
18+
support for a servo connected to a REV Expansion Hub.
19+
"""
20+
21+
__author__ = "[email protected] (Liz Looney)"
22+
23+
from typing import Self
24+
from component import Component, PortType, InvalidPortException
25+
import expansion_hub
26+
27+
# TODO(lizlooney): Update port types.
28+
29+
class ExpansionHubServo(Component):
30+
def __init__(self, ports : list[tuple[PortType, int]]):
31+
port_type, hub_number = ports[0]
32+
if port_type != PortType.USB_PORT:
33+
raise InvalidPortException
34+
port_type, servo_number = ports[1]
35+
if port_type != PortType.USB_PORT:
36+
raise InvalidPortException
37+
self.expansion_hub_servo = expansion_hub.ExpansionHubServo(hub_number, servo_number)
38+
39+
def get_manufacturer(self) -> str:
40+
return "REV Robotics"
41+
42+
def get_name(self) -> str:
43+
return "Expansion Hub Servo"
44+
45+
def get_part_number(self) -> str:
46+
return ""
47+
48+
def get_url(self) -> str:
49+
return ""
50+
51+
def get_version(self) -> tuple[int, int, int]:
52+
return (1, 0, 0)
53+
54+
def start(self) -> None:
55+
self.expansion_hub_servo.setEnabled(True)
56+
pass
57+
58+
def stop(self) -> None:
59+
# TODO: Send stop command to servo.
60+
pass
61+
62+
def reset(self) -> None:
63+
pass
64+
65+
def get_connection_port_type(self) -> list[PortType]:
66+
return [PortType.USB_PORT, PortType.USB_PORT]
67+
68+
def periodic(self) -> None:
69+
pass
70+
71+
# Alternative constructor to create an instance from a hub number and a servo port.
72+
@classmethod
73+
def from_hub_number_and_servo_number(cls: type[Self], hub_number: int, servo_number: int) -> Self:
74+
return cls([(PortType.USB_PORT, hub_number), (PortType.USB_PORT, servo_number)])
75+
76+
# Component specific methods
77+
78+
# Methods from expansion_hub.ExpansionHubServo
79+
80+
def set(self, value: float):
81+
self.expansion_hub_servo.set(value)
82+
83+
def setAngle(self, degrees: float):
84+
self.expansion_hub_servo.setAngle(degrees)
85+
86+
def setEnabled(self, enabled: bool):
87+
self.expansion_hub_servo.setEnabled(enabled)
88+
89+
def isHubConnected(self) -> bool:
90+
return self.expansion_hub_servo.isHubConnected()
91+
92+
def setFramePeriod(self, framePeriod: int):
93+
self.expansion_hub_servo.setFramePeriod(framePeriod)
94+
95+
def setPulseWidth(self, pulseWidth: int):
96+
self.expansion_hub_servo.setPulseWidth(pulseWidth)

external_samples/rev_touch_sensor.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,6 @@ def get_url(self) -> str:
4242
def get_version(self) -> tuple[int, int, int]:
4343
return (1, 0, 0)
4444

45-
def stop(self) -> None:
46-
pass
47-
4845
def reset(self) -> None:
4946
self.pressed_callback = None
5047
self.released_callback = None

external_samples/servo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def get_version(self) -> tuple[int, int, int]:
4242
return (1, 0, 0)
4343

4444
def stop(self) -> None:
45-
# De-energize servo port
45+
# TODO: De-energize servo port
4646
pass
4747

4848
def reset(self) -> None:

external_samples/smart_motor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def get_version(self) -> tuple[int, int, int]:
4242
return (1, 0, 0)
4343

4444
def stop(self) -> None:
45-
# send stop command to motor
45+
# TODO: send stop command to motor
4646
pass
4747

4848
def reset(self) -> None:

external_samples/spark_mini.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def get_version(self) -> tuple[int, int, int]:
5050
return (1, 0, 0)
5151

5252
def stop(self) -> None:
53-
# send stop command to motor
53+
# TODO: send stop command to motor
5454
pass
5555

5656
def reset(self) -> None:

python_tools/generate_json.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959
sys.path.append("../external_samples")
6060
import color_range_sensor
6161
import component
62+
import expansion_hub_motor
63+
import expansion_hub_servo
6264
import rev_touch_sensor
6365
import servo
6466
import smart_motor
@@ -119,6 +121,8 @@ def main(argv):
119121
external_samples_modules = [
120122
color_range_sensor,
121123
component,
124+
expansion_hub_motor,
125+
expansion_hub_servo,
122126
rev_touch_sensor,
123127
servo,
124128
smart_motor,

server_python_scripts/run_opmode.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import importlib.util
1414
import inspect
1515
import argparse
16+
import traceback
1617
from pathlib import Path
1718

1819
from blocks_base_classes import OpMode
@@ -151,6 +152,7 @@ def run_opmode(opmode_file, duration=None, loop_frequency=50):
151152

152153
except Exception as e:
153154
print(f"Error running opmode: {e}")
155+
traceback.print_exc()
154156
sys.exit(1)
155157

156158

@@ -206,4 +208,4 @@ def main():
206208

207209

208210
if __name__ == '__main__':
209-
main()
211+
main()

0 commit comments

Comments
 (0)