-
Notifications
You must be signed in to change notification settings - Fork 9
Quick pass at sample components #97
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
04af614
d8365ee
6e0362d
388dd1e
2858717
ce3c119
e39579a
6a36371
5032c3e
8ae7c5d
16b4ea5
a1b1bac
f34a922
9097128
a1175de
f1799cf
7e94387
2ab2b1c
841cc4e
f2905fd
7192398
ce19ae0
920aa01
6dda055
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| # @license | ||
| # Copyright 2025 Porpoiseful LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| # @fileoverview This is a sample for a color/range sensor | ||
| # @author alan@porpoiseful.com (Alan Smith) | ||
|
|
||
| from component import Component, PortType, InvalidPortException | ||
| from collections.abc import Protocol | ||
|
|
||
| class DistanceCallable(Protocol): | ||
| def __call__(self, distance : float) -> None: | ||
| pass | ||
| class ColorCallable(Protocol): | ||
| def __call__(self, hue : int, saturation : int, value : int) -> None: | ||
| pass | ||
|
|
||
| class ColorRangeSensor(Component): | ||
| def __init__(self, ports : list[tuple[PortType, int]]): | ||
| portType, port = ports[0] | ||
| if portType != PortType.I2C_PORT: | ||
| raise InvalidPortException | ||
| self.port = port | ||
| def get_manufacturer(self) -> str: | ||
| return "REV Robotics" | ||
| def get_name(self) -> str: | ||
| return "Color Sensor v3" | ||
| def get_part_number(self) -> str: | ||
| return "REV-31-1557" | ||
| def get_url(self) -> str: | ||
| return "https://www.revrobotics.com/rev-31-1557" | ||
| def get_version(self) -> tuple[int, int, str]: | ||
| return (1, 0, "") | ||
| def stop(self) -> None: | ||
| # send stop command to sensor | ||
| pass | ||
| def reset(self) -> None: | ||
| pass | ||
| def get_connection_port_type(self) -> list[PortType]: | ||
| return [PortType.I2C_PORT] | ||
| def periodic(self) -> None: | ||
| pass | ||
|
|
||
| # Component specific methods | ||
| def get_color_rgb(self) -> tuple[int, int, int]: | ||
| '''gets the color in rgb (red, green, blue)''' | ||
| pass | ||
| def get_color_hsv(self) -> tuple[int, int, int]: | ||
| '''gets the color in hsv (hue, saturation, value)''' | ||
| pass | ||
| def get_distance_mm(self) -> float: | ||
| '''gets the distance of the object seen''' | ||
| pass | ||
|
|
||
| def register_when_less_than_distance(self, distance : float, | ||
| callback: DistanceCallable) -> None: | ||
| '''Event when item is seen closer than a distance''' | ||
| self.less_than_distance_callback = callback | ||
|
|
||
| def register_when_hue_in_range(self, min_hue : int, | ||
| max_hue : int, | ||
| callback: ColorCallable) -> None: | ||
| '''Event when hue is in range''' | ||
| self.hue_in_range_callback = callback | ||
|
|
||
| def register_when_saturation_in_range(self, min_saturation : int, | ||
| max_saturation : int, | ||
| callback : ColorCallable) -> None: | ||
| '''Event when saturation is in range''' | ||
| self.saturation_in_range_callback = callback |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| # @license | ||
| # Copyright 2025 Porpoiseful LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| # @fileoverview This is an abstract class for all components | ||
| # @author alan@porpoiseful.com (Alan Smith) | ||
|
|
||
| from abc import ABC, abstractmethod | ||
| from enum import Enum | ||
| from collections.abc import Protocol | ||
|
|
||
| class EmptyCallable(Protocol): | ||
alan412 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| def __call__(self) -> None: | ||
| pass | ||
| class PortType(Enum): | ||
| CAN_PORT = 1 | ||
| SMART_IO_PORT = 2 | ||
| SMART_MOTOR_PORT = 3 | ||
| SERVO_PORT = 4 | ||
| I2C_PORT = 5 | ||
| USB_PORT = 6 | ||
|
|
||
| class InvalidPortException(Exception): | ||
| pass | ||
|
|
||
| # This is an abstract class | ||
| class Component(ABC): | ||
| @abstractmethod | ||
| def __init__(self, ports : list[tuple[PortType, int]]): | ||
| pass | ||
| # This is the manufacturer of the component | ||
| @abstractmethod | ||
| def get_manufacturer(self) -> str: | ||
| pass | ||
| # This is the name of the component | ||
| @abstractmethod | ||
| def get_name(self) -> str: | ||
| pass | ||
| # This is the part number of the component | ||
| @abstractmethod | ||
| def get_part_number(self) -> str: | ||
| pass | ||
| # This is the URL of the component | ||
| @abstractmethod | ||
| def get_url(self) -> str: | ||
| pass | ||
| # This is the version of the software (returned as a (major, minor, revision) tuple where | ||
| # major and minor are positive integers | ||
| # revision can be an empty string or specify small changes that are less than a | ||
| # minor revision | ||
| @abstractmethod | ||
| def get_version(self) -> tuple[int, int, str]: | ||
| pass | ||
|
|
||
| # This stops all movement (if any) for the component | ||
| @abstractmethod | ||
| def stop(self) -> None: | ||
| pass | ||
|
|
||
| # This performs any reset required (if any) at the beginning of each opmode | ||
| # This might remove any registered callbacks | ||
| @abstractmethod | ||
| def reset(self) -> None: | ||
| pass | ||
|
|
||
| # This returns a list (can be empty, one, or multiple) of the ports this connects to | ||
| # of the PortType enumeration | ||
| @abstractmethod | ||
| def get_connection_port_type(self) -> list[PortType]: | ||
| pass | ||
|
|
||
| # This is called periodically when an opmode is running. The component might use this | ||
| # to talk to hardware and then call callbacks | ||
| @abstractmethod | ||
| def periodic(self) -> None: | ||
alan412 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| pass | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| # @license | ||
| # Copyright 2025 Porpoiseful LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| # @fileoverview This is a sample for a touch sensor | ||
| # @author alan@porpoiseful.com (Alan Smith) | ||
|
|
||
| from component import Component, PortType, InvalidPortException, EmptyCallable | ||
|
|
||
| class RevTouchSensor(Component): | ||
| def __init__(self, ports : list[tuple[PortType, int]]): | ||
| self.is_pressed = None | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like is_pressed should be a boolean, so the value should be False instead of None.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It can't be, because when the system boots up you don't know if the touch sensor is currently pressed or not. If you start out saying it isn't, then you'll get an immediate callback for pressed even though that isn't accurate. So you have to start out before your first read with something that knows it hasn't read hardware yet. Leaving this comment open to see if you agree or not.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK. That makes sense. |
||
| portType, port = ports[0] | ||
| if portType != PortType.SMART_IO_PORT: | ||
| raise InvalidPortException | ||
| self.port = port | ||
| def get_manufacturer(self) -> str: | ||
| return "REV Robotics" | ||
| def get_name(self) -> str: | ||
| return "Touch Sensor" | ||
| def get_part_number(self) -> str: | ||
| return "REV-31-1425" | ||
| def get_url(self) -> str: | ||
| return "https://www.revrobotics.com/rev-31-1425/" | ||
| def get_version(self) -> tuple[int, int, str]: | ||
| return (1, 0, "") | ||
| def stop(self) -> None: | ||
| pass | ||
| def reset(self) -> None: | ||
| self.pressed_callback = None | ||
| self.released_callback = None | ||
| pass | ||
| def get_connection_port_type(self) -> list[PortType]: | ||
| return [PortType.SMART_IO_PORT] | ||
| def periodic(self) -> None: | ||
| old = self.is_pressed | ||
| self._read_hardware() | ||
| if old != self.is_pressed: | ||
| if self.is_pressed and self.pressed_callback: | ||
| self.pressed_callback() | ||
| elif old and self.released_callback: | ||
| self.released_callback() | ||
| def _read_hardware(self): | ||
| # here read hardware to get the current value of the sensor and set self.is_pressed | ||
| pass | ||
|
|
||
| def is_pressed(self) -> bool: | ||
| '''Returns if the touch sensor is pressed or not''' | ||
| return self.is_pressed | ||
|
|
||
| # Events | ||
| def register_when_pressed(self, callback: EmptyCallable) -> None: | ||
| '''Event when touch sensor is pressed (after being not pressed)''' | ||
| self.pressed_callback = callback | ||
|
|
||
|
|
||
| def register_when_released(self, callback: EmptyCallable) -> None: | ||
| '''Event when touch sensor is released (after being pressed)''' | ||
| self.released_callback = callback | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| # @license | ||
| # Copyright 2025 Porpoiseful LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| # @fileoverview This is a sample for a servo | ||
| # @author alan@porpoiseful.com (Alan Smith) | ||
|
|
||
| from component import Component, PortType, InvalidPortException | ||
|
|
||
| class Servo(Component): | ||
| def __init__(self, ports : list[tuple[PortType, int]]): | ||
| portType, port = ports[0] | ||
| if portType != PortType.SERVO_PORT: | ||
| raise InvalidPortException | ||
| self.port = port | ||
| def get_manufacturer(self) -> str: | ||
| return "REV Robotics" | ||
| def get_name(self) -> str: | ||
| return "SRS Servo" | ||
| def get_part_number(self) -> str: | ||
| return "REV-41-1097" | ||
| def get_url(self) -> str: | ||
| return "https://www.revrobotics.com/rev-41-1097/" | ||
| def get_version(self) -> tuple[int, int, str]: | ||
| return (1, 0, "") | ||
| def stop(self) -> None: | ||
| # De-energize servo port | ||
| pass | ||
| def reset(self) -> None: | ||
| pass | ||
| def get_connection_port_type(self) -> list[PortType]: | ||
| return [PortType.SERVO_PORT] | ||
| def periodic(self) -> None: | ||
| pass | ||
|
|
||
| # Component specific methods | ||
| def set_position(self, pos: float) -> None: | ||
| '''Set the servo to a position between 0 and 1''' | ||
| # sends to the hardware the position of the servo | ||
| pass | ||
| def set_angle_degrees(self, angle: float) -> None: | ||
| '''Set the servo to an angle between 0 and 270''' | ||
| self.set_position(angle / 270.0) | ||
|
|
||
|
|
||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| # @license | ||
| # Copyright 2025 Porpoiseful LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| # @fileoverview This is a sample for a smart motor | ||
| # @author alan@porpoiseful.com (Alan Smith) | ||
| from component import Component, PortType, InvalidPortException | ||
|
|
||
| class SmartMotor(Component): | ||
| def __init__(self, ports : list[tuple[PortType, int]]): | ||
| portType, port = ports[0] | ||
| if portType != PortType.SMART_MOTOR_PORT: | ||
| raise InvalidPortException | ||
| self.port = port | ||
| def get_manufacturer(self) -> str: | ||
| return "REV Robotics" | ||
| def get_name(self) -> str: | ||
| return "DC Motor" | ||
| def get_part_number(self) -> str: | ||
| return "REV-xx-xxxx" | ||
| def get_url(self) -> str: | ||
| return "https://www.revrobotics.com/rev-xx-xxxx" | ||
| def get_version(self) -> tuple[int, int, str]: | ||
| return (1, 0, "") | ||
| def stop(self) -> None: | ||
| # send stop command to motor | ||
| pass | ||
| def reset(self) -> None: | ||
| pass | ||
| def get_connection_port_type(self) -> list[PortType]: | ||
| return [PortType.SMART_MOTOR_PORT] | ||
| def periodic(self) -> None: | ||
| pass | ||
|
|
||
| # Component specific methods | ||
| def set_speed(self, speed: float) -> None: | ||
| '''Set the motor to a speed between -1 and 1''' | ||
| # TODO: send to the hardware the speed of the motor | ||
| pass | ||
|
|
||
| def set_angle_degrees(self, angle: float) -> None: | ||
| '''Set the motor to an angle between 0 and 360''' | ||
| pass | ||
|
|
||
| def get_num_relative_encoder_ticks(self) -> int: | ||
| '''Get the number of relative motor ticks since reset of encoder''' | ||
| pass | ||
|
|
||
| def get_angle_degrees(self) -> float: | ||
| '''Get the angle position of the motor''' | ||
| pass | ||
|
|
||
| def reset_relative_encoder(self) -> None: | ||
| '''Reset the relative encoder value to 0''' | ||
| pass | ||
|
|
Uh oh!
There was an error while loading. Please reload this page.