Skip to content

Commit f08c2ab

Browse files
authored
Merge pull request #97 from alan412/sample_components
Quick pass at sample components
2 parents e55dd98 + 6dda055 commit f08c2ab

File tree

5 files changed

+362
-0
lines changed

5 files changed

+362
-0
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# @license
2+
# Copyright 2025 Porpoiseful LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
# @fileoverview This is a sample for a color/range sensor
17+
# @author [email protected] (Alan Smith)
18+
19+
from component import Component, PortType, InvalidPortException
20+
from collections.abc import Protocol
21+
22+
class DistanceCallable(Protocol):
23+
def __call__(self, distance : float) -> None:
24+
pass
25+
class ColorCallable(Protocol):
26+
def __call__(self, hue : int, saturation : int, value : int) -> None:
27+
pass
28+
29+
class ColorRangeSensor(Component):
30+
def __init__(self, ports : list[tuple[PortType, int]]):
31+
portType, port = ports[0]
32+
if portType != PortType.I2C_PORT:
33+
raise InvalidPortException
34+
self.port = port
35+
def get_manufacturer(self) -> str:
36+
return "REV Robotics"
37+
def get_name(self) -> str:
38+
return "Color Sensor v3"
39+
def get_part_number(self) -> str:
40+
return "REV-31-1557"
41+
def get_url(self) -> str:
42+
return "https://www.revrobotics.com/rev-31-1557"
43+
def get_version(self) -> tuple[int, int, str]:
44+
return (1, 0, "")
45+
def stop(self) -> None:
46+
# send stop command to sensor
47+
pass
48+
def reset(self) -> None:
49+
pass
50+
def get_connection_port_type(self) -> list[PortType]:
51+
return [PortType.I2C_PORT]
52+
def periodic(self) -> None:
53+
pass
54+
55+
# Component specific methods
56+
def get_color_rgb(self) -> tuple[int, int, int]:
57+
'''gets the color in rgb (red, green, blue)'''
58+
pass
59+
def get_color_hsv(self) -> tuple[int, int, int]:
60+
'''gets the color in hsv (hue, saturation, value)'''
61+
pass
62+
def get_distance_mm(self) -> float:
63+
'''gets the distance of the object seen'''
64+
pass
65+
66+
def register_when_less_than_distance(self, distance : float,
67+
callback: DistanceCallable) -> None:
68+
'''Event when item is seen closer than a distance'''
69+
self.less_than_distance_callback = callback
70+
71+
def register_when_hue_in_range(self, min_hue : int,
72+
max_hue : int,
73+
callback: ColorCallable) -> None:
74+
'''Event when hue is in range'''
75+
self.hue_in_range_callback = callback
76+
77+
def register_when_saturation_in_range(self, min_saturation : int,
78+
max_saturation : int,
79+
callback : ColorCallable) -> None:
80+
'''Event when saturation is in range'''
81+
self.saturation_in_range_callback = callback

external_samples/component.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# @license
2+
# Copyright 2025 Porpoiseful LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
# @fileoverview This is an abstract class for all components
17+
# @author [email protected] (Alan Smith)
18+
19+
from abc import ABC, abstractmethod
20+
from enum import Enum
21+
from collections.abc import Protocol
22+
23+
class EmptyCallable(Protocol):
24+
def __call__(self) -> None:
25+
pass
26+
class PortType(Enum):
27+
CAN_PORT = 1
28+
SMART_IO_PORT = 2
29+
SMART_MOTOR_PORT = 3
30+
SERVO_PORT = 4
31+
I2C_PORT = 5
32+
USB_PORT = 6
33+
34+
class InvalidPortException(Exception):
35+
pass
36+
37+
# This is an abstract class
38+
class Component(ABC):
39+
@abstractmethod
40+
def __init__(self, ports : list[tuple[PortType, int]]):
41+
pass
42+
# This is the manufacturer of the component
43+
@abstractmethod
44+
def get_manufacturer(self) -> str:
45+
pass
46+
# This is the name of the component
47+
@abstractmethod
48+
def get_name(self) -> str:
49+
pass
50+
# This is the part number of the component
51+
@abstractmethod
52+
def get_part_number(self) -> str:
53+
pass
54+
# This is the URL of the component
55+
@abstractmethod
56+
def get_url(self) -> str:
57+
pass
58+
# This is the version of the software (returned as a (major, minor, revision) tuple where
59+
# major and minor are positive integers
60+
# revision can be an empty string or specify small changes that are less than a
61+
# minor revision
62+
@abstractmethod
63+
def get_version(self) -> tuple[int, int, str]:
64+
pass
65+
66+
# This stops all movement (if any) for the component
67+
@abstractmethod
68+
def stop(self) -> None:
69+
pass
70+
71+
# This performs any reset required (if any) at the beginning of each opmode
72+
# This might remove any registered callbacks
73+
@abstractmethod
74+
def reset(self) -> None:
75+
pass
76+
77+
# This returns a list (can be empty, one, or multiple) of the ports this connects to
78+
# of the PortType enumeration
79+
@abstractmethod
80+
def get_connection_port_type(self) -> list[PortType]:
81+
pass
82+
83+
# This is called periodically when an opmode is running. The component might use this
84+
# to talk to hardware and then call callbacks
85+
@abstractmethod
86+
def periodic(self) -> None:
87+
pass
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# @license
2+
# Copyright 2025 Porpoiseful LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
# @fileoverview This is a sample for a touch sensor
17+
# @author [email protected] (Alan Smith)
18+
19+
from component import Component, PortType, InvalidPortException, EmptyCallable
20+
21+
class RevTouchSensor(Component):
22+
def __init__(self, ports : list[tuple[PortType, int]]):
23+
self.is_pressed = None
24+
portType, port = ports[0]
25+
if portType != PortType.SMART_IO_PORT:
26+
raise InvalidPortException
27+
self.port = port
28+
def get_manufacturer(self) -> str:
29+
return "REV Robotics"
30+
def get_name(self) -> str:
31+
return "Touch Sensor"
32+
def get_part_number(self) -> str:
33+
return "REV-31-1425"
34+
def get_url(self) -> str:
35+
return "https://www.revrobotics.com/rev-31-1425/"
36+
def get_version(self) -> tuple[int, int, str]:
37+
return (1, 0, "")
38+
def stop(self) -> None:
39+
pass
40+
def reset(self) -> None:
41+
self.pressed_callback = None
42+
self.released_callback = None
43+
pass
44+
def get_connection_port_type(self) -> list[PortType]:
45+
return [PortType.SMART_IO_PORT]
46+
def periodic(self) -> None:
47+
old = self.is_pressed
48+
self._read_hardware()
49+
if old != self.is_pressed:
50+
if self.is_pressed and self.pressed_callback:
51+
self.pressed_callback()
52+
elif old and self.released_callback:
53+
self.released_callback()
54+
def _read_hardware(self):
55+
# here read hardware to get the current value of the sensor and set self.is_pressed
56+
pass
57+
58+
def is_pressed(self) -> bool:
59+
'''Returns if the touch sensor is pressed or not'''
60+
return self.is_pressed
61+
62+
# Events
63+
def register_when_pressed(self, callback: EmptyCallable) -> None:
64+
'''Event when touch sensor is pressed (after being not pressed)'''
65+
self.pressed_callback = callback
66+
67+
68+
def register_when_released(self, callback: EmptyCallable) -> None:
69+
'''Event when touch sensor is released (after being pressed)'''
70+
self.released_callback = callback

external_samples/servo.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# @license
2+
# Copyright 2025 Porpoiseful LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
# @fileoverview This is a sample for a servo
17+
# @author [email protected] (Alan Smith)
18+
19+
from component import Component, PortType, InvalidPortException
20+
21+
class Servo(Component):
22+
def __init__(self, ports : list[tuple[PortType, int]]):
23+
portType, port = ports[0]
24+
if portType != PortType.SERVO_PORT:
25+
raise InvalidPortException
26+
self.port = port
27+
def get_manufacturer(self) -> str:
28+
return "REV Robotics"
29+
def get_name(self) -> str:
30+
return "SRS Servo"
31+
def get_part_number(self) -> str:
32+
return "REV-41-1097"
33+
def get_url(self) -> str:
34+
return "https://www.revrobotics.com/rev-41-1097/"
35+
def get_version(self) -> tuple[int, int, str]:
36+
return (1, 0, "")
37+
def stop(self) -> None:
38+
# De-energize servo port
39+
pass
40+
def reset(self) -> None:
41+
pass
42+
def get_connection_port_type(self) -> list[PortType]:
43+
return [PortType.SERVO_PORT]
44+
def periodic(self) -> None:
45+
pass
46+
47+
# Component specific methods
48+
def set_position(self, pos: float) -> None:
49+
'''Set the servo to a position between 0 and 1'''
50+
# sends to the hardware the position of the servo
51+
pass
52+
def set_angle_degrees(self, angle: float) -> None:
53+
'''Set the servo to an angle between 0 and 270'''
54+
self.set_position(angle / 270.0)
55+
56+
57+

external_samples/smart_motor.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# @license
2+
# Copyright 2025 Porpoiseful LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
# @fileoverview This is a sample for a smart motor
17+
# @author [email protected] (Alan Smith)
18+
from component import Component, PortType, InvalidPortException
19+
20+
class SmartMotor(Component):
21+
def __init__(self, ports : list[tuple[PortType, int]]):
22+
portType, port = ports[0]
23+
if portType != PortType.SMART_MOTOR_PORT:
24+
raise InvalidPortException
25+
self.port = port
26+
def get_manufacturer(self) -> str:
27+
return "REV Robotics"
28+
def get_name(self) -> str:
29+
return "DC Motor"
30+
def get_part_number(self) -> str:
31+
return "REV-xx-xxxx"
32+
def get_url(self) -> str:
33+
return "https://www.revrobotics.com/rev-xx-xxxx"
34+
def get_version(self) -> tuple[int, int, str]:
35+
return (1, 0, "")
36+
def stop(self) -> None:
37+
# send stop command to motor
38+
pass
39+
def reset(self) -> None:
40+
pass
41+
def get_connection_port_type(self) -> list[PortType]:
42+
return [PortType.SMART_MOTOR_PORT]
43+
def periodic(self) -> None:
44+
pass
45+
46+
# Component specific methods
47+
def set_speed(self, speed: float) -> None:
48+
'''Set the motor to a speed between -1 and 1'''
49+
# TODO: send to the hardware the speed of the motor
50+
pass
51+
52+
def set_angle_degrees(self, angle: float) -> None:
53+
'''Set the motor to an angle between 0 and 360'''
54+
pass
55+
56+
def get_num_relative_encoder_ticks(self) -> int:
57+
'''Get the number of relative motor ticks since reset of encoder'''
58+
pass
59+
60+
def get_angle_degrees(self) -> float:
61+
'''Get the angle position of the motor'''
62+
pass
63+
64+
def reset_relative_encoder(self) -> None:
65+
'''Reset the relative encoder value to 0'''
66+
pass
67+

0 commit comments

Comments
 (0)