From 04af61425895bd9b67ae2c7dbf3b4d0ab9b8d629 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Mon, 10 Mar 2025 16:48:23 -0400 Subject: [PATCH 01/23] Quick pass at external sample --- external_samples/component.py | 51 ++++++++++++++++++++++++++++ external_samples/rev_touch_sensor.py | 34 +++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 external_samples/component.py create mode 100644 external_samples/rev_touch_sensor.py diff --git a/external_samples/component.py b/external_samples/component.py new file mode 100644 index 00000000..d32a6310 --- /dev/null +++ b/external_samples/component.py @@ -0,0 +1,51 @@ +from abc import ABC, abstractmethod +from enum import Enum + +class PortType(Enum): + CAN_PORT = 1 + SMART_IO_PORT = 2 + SMART_MOTOR_PORT = 3 + SERVO_PORT = 4 + I2C_PORT = 5 + USB_PORT = 6 + +# This is an abstract class +class Component: + # 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 is an optional string + @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 + + # any reset required (if any) after it has stopped before it can be used + @abstractmethod + def reset(self) -> None: + pass + + # This returns a list (can be empty, one, or multipe) of the ports this connects to + # of the PortType enumeration + @abstractmethod + def get_connection_port_type(self) -> list[PortType]: + pass \ No newline at end of file diff --git a/external_samples/rev_touch_sensor.py b/external_samples/rev_touch_sensor.py new file mode 100644 index 00000000..356ba7a8 --- /dev/null +++ b/external_samples/rev_touch_sensor.py @@ -0,0 +1,34 @@ +from component import Component, PortType +from _collections_abc import Callable + +class RevTouchSensor(Component): + # Required methods + 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, None) + def stop(self) -> None: + pass + def reset(self) -> None: + pass + def get_connection_port_type(self) -> list[PortType]: + return [PortType.SMART_IO_PORT] + + # Methods + def is_pressed(self) -> bool: + # Code to communicate using WPILib would go here + return True + + # Events + def whenPressedRegister(callback: Callable[[None, None]]) -> None: + # Code to register callback here + pass + def whenReleasedRegister(callback: Callable[[None, None]]) -> None: + # Code to register callback here + pass \ No newline at end of file From d8365eed849dc5fbc4579e439c0ab41aa28c20ac Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Mon, 10 Mar 2025 16:54:33 -0400 Subject: [PATCH 02/23] change to snake case --- external_samples/rev_touch_sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/external_samples/rev_touch_sensor.py b/external_samples/rev_touch_sensor.py index 356ba7a8..b48d852c 100644 --- a/external_samples/rev_touch_sensor.py +++ b/external_samples/rev_touch_sensor.py @@ -26,9 +26,9 @@ def is_pressed(self) -> bool: return True # Events - def whenPressedRegister(callback: Callable[[None, None]]) -> None: + def when_pressed_register(callback: Callable[[None, None]]) -> None: # Code to register callback here pass - def whenReleasedRegister(callback: Callable[[None, None]]) -> None: + def when_released_register(callback: Callable[[None, None]]) -> None: # Code to register callback here pass \ No newline at end of file From 6e0362d5c12446db8bb4a0faa57f94ed6eb922d3 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Mon, 10 Mar 2025 16:59:20 -0400 Subject: [PATCH 03/23] Fix callable syntax --- external_samples/rev_touch_sensor.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/external_samples/rev_touch_sensor.py b/external_samples/rev_touch_sensor.py index b48d852c..f530263d 100644 --- a/external_samples/rev_touch_sensor.py +++ b/external_samples/rev_touch_sensor.py @@ -26,9 +26,10 @@ def is_pressed(self) -> bool: return True # Events - def when_pressed_register(callback: Callable[[None, None]]) -> None: + def when_pressed_register(callback: Callable[[], None]) -> None: # Code to register callback here pass - def when_released_register(callback: Callable[[None, None]]) -> None: + + def when_released_register(callback: Callable[[], None]) -> None: # Code to register callback here pass \ No newline at end of file From 388dd1eb6cc84d554ce09a3c464d7d6598d9a215 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Mon, 10 Mar 2025 17:04:35 -0400 Subject: [PATCH 04/23] Change register to be first word --- external_samples/rev_touch_sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/external_samples/rev_touch_sensor.py b/external_samples/rev_touch_sensor.py index f530263d..fa8bad92 100644 --- a/external_samples/rev_touch_sensor.py +++ b/external_samples/rev_touch_sensor.py @@ -26,10 +26,10 @@ def is_pressed(self) -> bool: return True # Events - def when_pressed_register(callback: Callable[[], None]) -> None: + def register_when_pressed(callback: Callable[[], None]) -> None: # Code to register callback here pass - def when_released_register(callback: Callable[[], None]) -> None: + def register_when_released(callback: Callable[[], None]) -> None: # Code to register callback here pass \ No newline at end of file From 2858717bb33048a91d7cc4895b65564b526d4821 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Mon, 10 Mar 2025 17:06:55 -0400 Subject: [PATCH 05/23] Add periodic method --- external_samples/component.py | 8 +++++++- external_samples/rev_touch_sensor.py | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/external_samples/component.py b/external_samples/component.py index d32a6310..cee3fd08 100644 --- a/external_samples/component.py +++ b/external_samples/component.py @@ -39,7 +39,8 @@ def get_version(self) -> tuple[int, int, str]: def stop(self) -> None: pass - # any reset required (if any) after it has stopped before it can be used + # any reset required (if any) at the beginning of each opmode + # This might remove any registered callbacks @abstractmethod def reset(self) -> None: pass @@ -48,4 +49,9 @@ def reset(self) -> None: # of the PortType enumeration @abstractmethod def get_connection_port_type(self) -> list[PortType]: + pass + + # This is called periodically when an opmode is running + @abstractmethod + def periodic(self) -> None: pass \ No newline at end of file diff --git a/external_samples/rev_touch_sensor.py b/external_samples/rev_touch_sensor.py index fa8bad92..f146e995 100644 --- a/external_samples/rev_touch_sensor.py +++ b/external_samples/rev_touch_sensor.py @@ -19,6 +19,9 @@ def reset(self) -> None: pass def get_connection_port_type(self) -> list[PortType]: return [PortType.SMART_IO_PORT] + def periodic(self) -> None: + # This would poll the hardware and see if it needs to fire any events + pass # Methods def is_pressed(self) -> bool: From ce3c11974ffc19b58e4047067e732ce3d7e6f087 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Mon, 10 Mar 2025 17:07:38 -0400 Subject: [PATCH 06/23] Changed str in version to be an empty string --- external_samples/rev_touch_sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external_samples/rev_touch_sensor.py b/external_samples/rev_touch_sensor.py index f146e995..09e3c1ff 100644 --- a/external_samples/rev_touch_sensor.py +++ b/external_samples/rev_touch_sensor.py @@ -12,7 +12,7 @@ def get_part_number(self) -> str: def get_url(self) -> str: return "https://www.revrobotics.com/rev-31-1425/" def get_version(self) -> tuple[int, int, str]: - return (1, 0, None) + return (1, 0, "") def stop(self) -> None: pass def reset(self) -> None: From e39579ae6bf2a734a355aea786a334cee8e8b90d Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Tue, 11 Mar 2025 19:29:28 -0400 Subject: [PATCH 07/23] Resolve review comments --- external_samples/component.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/external_samples/component.py b/external_samples/component.py index cee3fd08..c6f5240b 100644 --- a/external_samples/component.py +++ b/external_samples/component.py @@ -45,13 +45,14 @@ def stop(self) -> None: def reset(self) -> None: pass - # This returns a list (can be empty, one, or multipe) of the ports this connects to + # 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 + # 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: pass \ No newline at end of file From 6a363713f3cb4cc5e32513441db516a7eee0aded Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Tue, 11 Mar 2025 19:29:48 -0400 Subject: [PATCH 08/23] Add callback and docstrings --- external_samples/rev_touch_sensor.py | 31 +++++++++++++++++++--------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/external_samples/rev_touch_sensor.py b/external_samples/rev_touch_sensor.py index 09e3c1ff..b0bb86b5 100644 --- a/external_samples/rev_touch_sensor.py +++ b/external_samples/rev_touch_sensor.py @@ -2,6 +2,9 @@ from _collections_abc import Callable class RevTouchSensor(Component): + def __init__(self): + self.is_pressed = None + # Required methods def get_manufacturer(self) -> str: return "REV Robotics" @@ -20,19 +23,27 @@ def reset(self) -> None: def get_connection_port_type(self) -> list[PortType]: return [PortType.SMART_IO_PORT] def periodic(self) -> None: - # This would poll the hardware and see if it needs to fire any events + 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 - # Methods def is_pressed(self) -> bool: - # Code to communicate using WPILib would go here - return True + '''Returns if the touch sensor is pressed or not''' + return self.is_pressed # Events - def register_when_pressed(callback: Callable[[], None]) -> None: - # Code to register callback here - pass + def register_when_pressed(self, callback: Callable[[], None]) -> None: + '''Event when touch sensor is first pressed''' + self.pressed_callback = callback + - def register_when_released(callback: Callable[[], None]) -> None: - # Code to register callback here - pass \ No newline at end of file + def register_when_released(self, callback: Callable[[], None]) -> None: + '''Event when touch sensor is first released''' + self.released_callback = callback \ No newline at end of file From 5032c3e39796cd9cf422d38840cdb410a0d86d44 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Tue, 11 Mar 2025 19:32:53 -0400 Subject: [PATCH 09/23] Fix review comment --- external_samples/rev_touch_sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external_samples/rev_touch_sensor.py b/external_samples/rev_touch_sensor.py index b0bb86b5..6c23b3c4 100644 --- a/external_samples/rev_touch_sensor.py +++ b/external_samples/rev_touch_sensor.py @@ -1,5 +1,5 @@ from component import Component, PortType -from _collections_abc import Callable +from collections.abc import Callable class RevTouchSensor(Component): def __init__(self): From 8ae7c5da0df538a2a3c92656a95b5f255c6637c8 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Tue, 11 Mar 2025 19:45:55 -0400 Subject: [PATCH 10/23] Added InvalidPortException and init --- external_samples/component.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/external_samples/component.py b/external_samples/component.py index c6f5240b..19bac6fb 100644 --- a/external_samples/component.py +++ b/external_samples/component.py @@ -9,8 +9,14 @@ class PortType(Enum): I2C_PORT = 5 USB_PORT = 6 +class InvalidPortException(Exception): + pass + # This is an abstract class class Component: + @abstractmethod + def __init__(self, ports : list[tuple[PortType, int]]): + pass # This is the manufacturer of the component @abstractmethod def get_manufacturer(self) -> str: From 16b4ea53f9710fe4bce83fbaaafc62c287f581ec Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Tue, 11 Mar 2025 19:46:06 -0400 Subject: [PATCH 11/23] Added init --- external_samples/rev_touch_sensor.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/external_samples/rev_touch_sensor.py b/external_samples/rev_touch_sensor.py index 6c23b3c4..649a0e36 100644 --- a/external_samples/rev_touch_sensor.py +++ b/external_samples/rev_touch_sensor.py @@ -1,10 +1,13 @@ -from component import Component, PortType +from component import Component, PortType, InvalidPortException from collections.abc import Callable class RevTouchSensor(Component): - def __init__(self): + def __init__(self, ports : list[tuple[PortType, int]]): self.is_pressed = None - + portType, port = ports[0] + if portType != PortType.SMART_IO_PORT: + raise InvalidPortException + self.port = port # Required methods def get_manufacturer(self) -> str: return "REV Robotics" @@ -19,6 +22,8 @@ def get_version(self) -> tuple[int, int, str]: 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] From a1b1bac3fc1351a8bab9f0904c0e423a9bc78086 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Tue, 11 Mar 2025 19:46:12 -0400 Subject: [PATCH 12/23] Added servo example --- external_samples/servo.py | 42 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 external_samples/servo.py diff --git a/external_samples/servo.py b/external_samples/servo.py new file mode 100644 index 00000000..2073ff92 --- /dev/null +++ b/external_samples/servo.py @@ -0,0 +1,42 @@ +from component import Component, PortType, InvalidPortException +from collections.abc import Callable + +class Servo(Component): + # Required methods + def __init__(self, ports : list[tuple[PortType, int]]): + self.is_pressed = None + 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(self, angle: float) -> None: + '''Set the servo to an angle between 0 and 270''' + self.set_position(angle / 270.0) + + + \ No newline at end of file From f34a9221b5983374d69e0925f6b825640076df3a Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Tue, 11 Mar 2025 21:07:03 -0400 Subject: [PATCH 13/23] Specify degrees for set angle --- external_samples/servo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external_samples/servo.py b/external_samples/servo.py index 2073ff92..fb2e18d8 100644 --- a/external_samples/servo.py +++ b/external_samples/servo.py @@ -34,7 +34,7 @@ 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(self, angle: float) -> None: + def set_angle_degrees(self, angle: float) -> None: '''Set the servo to an angle between 0 and 270''' self.set_position(angle / 270.0) From 90971288977ffc964dd5eacb1eb668c5ba697b28 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Tue, 11 Mar 2025 21:08:02 -0400 Subject: [PATCH 14/23] Add sample Smart Motor component --- external_samples/smart_motor.py | 52 +++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 external_samples/smart_motor.py diff --git a/external_samples/smart_motor.py b/external_samples/smart_motor.py new file mode 100644 index 00000000..8e35878c --- /dev/null +++ b/external_samples/smart_motor.py @@ -0,0 +1,52 @@ +from component import Component, PortType, InvalidPortException +from collections.abc import Callable + +class SmartMotor(Component): + # Required methods + def __init__(self, ports : list[tuple[PortType, int]]): + self.is_pressed = None + 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''' + # sends to the hardware the speed of the motor + + def set_angle_degrees(self, angle: float) -> None: + '''Set the motor to an angle between 0 and 270''' + self.set_position(angle / 270.0) + + 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 + \ No newline at end of file From a1175dec41cdeff322699edd74b854d76777d72a Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Tue, 11 Mar 2025 21:23:11 -0400 Subject: [PATCH 15/23] Added EmptyCallable --- external_samples/component.py | 4 ++++ external_samples/rev_touch_sensor.py | 7 +++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/external_samples/component.py b/external_samples/component.py index 19bac6fb..9225af56 100644 --- a/external_samples/component.py +++ b/external_samples/component.py @@ -1,6 +1,10 @@ from abc import ABC, abstractmethod from enum import Enum +from collections.abc import Callable, Protocol +class EmptyCallable(Protocol): + def __call__(self) -> bool: + pass class PortType(Enum): CAN_PORT = 1 SMART_IO_PORT = 2 diff --git a/external_samples/rev_touch_sensor.py b/external_samples/rev_touch_sensor.py index 649a0e36..30175e8d 100644 --- a/external_samples/rev_touch_sensor.py +++ b/external_samples/rev_touch_sensor.py @@ -1,5 +1,4 @@ -from component import Component, PortType, InvalidPortException -from collections.abc import Callable +from component import Component, PortType, InvalidPortException, EmptyCallable class RevTouchSensor(Component): def __init__(self, ports : list[tuple[PortType, int]]): @@ -44,11 +43,11 @@ def is_pressed(self) -> bool: return self.is_pressed # Events - def register_when_pressed(self, callback: Callable[[], None]) -> None: + def register_when_pressed(self, callback: EmptyCallable) -> None: '''Event when touch sensor is first pressed''' self.pressed_callback = callback - def register_when_released(self, callback: Callable[[], None]) -> None: + def register_when_released(self, callback: EmptyCallable) -> None: '''Event when touch sensor is first released''' self.released_callback = callback \ No newline at end of file From f1799cfc753e8d6967321108cae60a342c24e8e3 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Tue, 11 Mar 2025 21:23:32 -0400 Subject: [PATCH 16/23] remove unused import --- external_samples/smart_motor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/external_samples/smart_motor.py b/external_samples/smart_motor.py index 8e35878c..f65896c6 100644 --- a/external_samples/smart_motor.py +++ b/external_samples/smart_motor.py @@ -1,5 +1,4 @@ from component import Component, PortType, InvalidPortException -from collections.abc import Callable class SmartMotor(Component): # Required methods From 7e943875a7b05e5a04d04b62177c000b5e54374b Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Tue, 11 Mar 2025 21:24:30 -0400 Subject: [PATCH 17/23] Change EmptyCallable to be void --- external_samples/component.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external_samples/component.py b/external_samples/component.py index 9225af56..1936a84f 100644 --- a/external_samples/component.py +++ b/external_samples/component.py @@ -3,7 +3,7 @@ from collections.abc import Callable, Protocol class EmptyCallable(Protocol): - def __call__(self) -> bool: + def __call__(self) -> None: pass class PortType(Enum): CAN_PORT = 1 From 2ab2b1cee5dace2083468427ca0388a0ec6013eb Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Tue, 11 Mar 2025 21:29:01 -0400 Subject: [PATCH 18/23] Add color range sensor --- external_samples/color_range_sensor.py | 63 ++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 external_samples/color_range_sensor.py diff --git a/external_samples/color_range_sensor.py b/external_samples/color_range_sensor.py new file mode 100644 index 00000000..29412c1d --- /dev/null +++ b/external_samples/color_range_sensor.py @@ -0,0 +1,63 @@ +from component import Component, PortType, InvalidPortException +from collections.abc import Callable, 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): + # Required methods + def __init__(self, ports : list[tuple[PortType, int]]): + self.is_pressed = None + 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) -> list[int, int, int]: + '''gets the color in rgb (red, green, blue)''' + pass + def get_color_hsv(self) -> list[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 + + + \ No newline at end of file From 841cc4e908ec3e35fc81f485c7ff751774534d21 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Fri, 14 Mar 2025 07:51:54 -0400 Subject: [PATCH 19/23] Address review comments --- external_samples/color_range_sensor.py | 23 ++++++++++++----------- external_samples/component.py | 9 +++++---- external_samples/rev_touch_sensor.py | 4 ++-- external_samples/servo.py | 2 -- external_samples/smart_motor.py | 1 - 5 files changed, 19 insertions(+), 20 deletions(-) diff --git a/external_samples/color_range_sensor.py b/external_samples/color_range_sensor.py index 29412c1d..83f0bdc3 100644 --- a/external_samples/color_range_sensor.py +++ b/external_samples/color_range_sensor.py @@ -1,5 +1,5 @@ from component import Component, PortType, InvalidPortException -from collections.abc import Callable, Protocol +from collections.abc import Protocol class DistanceCallable(Protocol): def __call__(self, distance : float) -> None: @@ -11,7 +11,6 @@ def __call__(self, hue : int, saturation : int, value : int) -> None: class ColorRangeSensor(Component): # Required methods def __init__(self, ports : list[tuple[PortType, int]]): - self.is_pressed = None portType, port = ports[0] if portType != PortType.I2C_PORT: raise InvalidPortException @@ -37,27 +36,29 @@ def periodic(self) -> None: pass # Component specific methods - def get_color_rgb(self) -> list[int, int, int]: + def get_color_rgb(self) -> tuple[int, int, int]: '''gets the color in rgb (red, green, blue)''' pass - def get_color_hsv(self) -> list[int, int, int]: + 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: + 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: + 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: + 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 - - - \ No newline at end of file + self.saturation_in_range_callback = callback \ No newline at end of file diff --git a/external_samples/component.py b/external_samples/component.py index 1936a84f..8ed6b47a 100644 --- a/external_samples/component.py +++ b/external_samples/component.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod from enum import Enum -from collections.abc import Callable, Protocol +from collections.abc import Protocol class EmptyCallable(Protocol): def __call__(self) -> None: @@ -17,7 +17,7 @@ class InvalidPortException(Exception): pass # This is an abstract class -class Component: +class Component(ABC): @abstractmethod def __init__(self, ports : list[tuple[PortType, int]]): pass @@ -39,7 +39,8 @@ 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 is an optional string + # 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 @@ -49,7 +50,7 @@ def get_version(self) -> tuple[int, int, str]: def stop(self) -> None: pass - # any reset required (if any) at the beginning of each opmode + # This performs any reset required (if any) at the beginning of each opmode # This might remove any registered callbacks @abstractmethod def reset(self) -> None: diff --git a/external_samples/rev_touch_sensor.py b/external_samples/rev_touch_sensor.py index 30175e8d..cf6d3ebb 100644 --- a/external_samples/rev_touch_sensor.py +++ b/external_samples/rev_touch_sensor.py @@ -44,10 +44,10 @@ def is_pressed(self) -> bool: # Events def register_when_pressed(self, callback: EmptyCallable) -> None: - '''Event when touch sensor is first pressed''' + '''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 first released''' + '''Event when touch sensor is released (after being pressed)''' self.released_callback = callback \ No newline at end of file diff --git a/external_samples/servo.py b/external_samples/servo.py index fb2e18d8..cdfc057c 100644 --- a/external_samples/servo.py +++ b/external_samples/servo.py @@ -1,10 +1,8 @@ from component import Component, PortType, InvalidPortException -from collections.abc import Callable class Servo(Component): # Required methods def __init__(self, ports : list[tuple[PortType, int]]): - self.is_pressed = None portType, port = ports[0] if portType != PortType.SERVO_PORT: raise InvalidPortException diff --git a/external_samples/smart_motor.py b/external_samples/smart_motor.py index f65896c6..bc4dd783 100644 --- a/external_samples/smart_motor.py +++ b/external_samples/smart_motor.py @@ -3,7 +3,6 @@ class SmartMotor(Component): # Required methods def __init__(self, ports : list[tuple[PortType, int]]): - self.is_pressed = None portType, port = ports[0] if portType != PortType.SMART_MOTOR_PORT: raise InvalidPortException From f2905fdb8986d17fa5d58179ae50ef6d7615429f Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Fri, 14 Mar 2025 21:06:33 -0400 Subject: [PATCH 20/23] starting to put sample blocks in --- src/toolbox/component_samples_category.ts | 41 +++++++++++++++++++++++ src/toolbox/toolbox.ts | 2 ++ 2 files changed, 43 insertions(+) create mode 100644 src/toolbox/component_samples_category.ts diff --git a/src/toolbox/component_samples_category.ts b/src/toolbox/component_samples_category.ts new file mode 100644 index 00000000..15144003 --- /dev/null +++ b/src/toolbox/component_samples_category.ts @@ -0,0 +1,41 @@ +export const category = +{ + kind: 'category', + name: 'Components', + contents: [ + { + kind: 'category', + name: 'Touch Sensor', + contents: [ + { + kind: "block", + type: "mrc_call_python_function", + extraState: {"functionKind": "instance", + "MODULE_OR_CLASS": "touch", + "returnType": "bool", + "args": [], + "tooltip": "Returns if the touch sensor is pressed or not", + }, + fields: { + FUNC: 'is_pressed', + }, + } + ] + }, + { + kind: 'category', + name: 'Smart Motor', + contents: [ ] + }, + { + kind: 'category', + name: 'Servo', + contents: [ ] + }, + { + kind: 'category', + name: 'Color Range Sensor', + contents: [ ] + } + ], +} \ No newline at end of file diff --git a/src/toolbox/toolbox.ts b/src/toolbox/toolbox.ts index 30281c88..d6bd52fd 100644 --- a/src/toolbox/toolbox.ts +++ b/src/toolbox/toolbox.ts @@ -28,6 +28,7 @@ import {category as textCategory} from './text_category'; import {category as listsCategory} from './lists_category'; import {category as miscCategory} from './misc_category'; import {category as methodsCategory} from './methods_category'; +import {category as componentSampleCategory} from './component_samples_category'; export function getToolboxJSON( opt_includeExportedBlocksFromProject: toolboxItems.ContentsType[], @@ -72,6 +73,7 @@ export function getToolboxJSON( custom: 'VARIABLE', }, methodsCategory, + componentSampleCategory, ]); return { From ce19ae09ec6450ce3bf031d60817e05aa22520fb Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Tue, 18 Mar 2025 17:54:38 -0400 Subject: [PATCH 21/23] Remove non working toolbox for sample components --- src/toolbox/component_samples_category.ts | 41 ----------------------- src/toolbox/toolbox.ts | 1 - 2 files changed, 42 deletions(-) delete mode 100644 src/toolbox/component_samples_category.ts diff --git a/src/toolbox/component_samples_category.ts b/src/toolbox/component_samples_category.ts deleted file mode 100644 index 15144003..00000000 --- a/src/toolbox/component_samples_category.ts +++ /dev/null @@ -1,41 +0,0 @@ -export const category = -{ - kind: 'category', - name: 'Components', - contents: [ - { - kind: 'category', - name: 'Touch Sensor', - contents: [ - { - kind: "block", - type: "mrc_call_python_function", - extraState: {"functionKind": "instance", - "MODULE_OR_CLASS": "touch", - "returnType": "bool", - "args": [], - "tooltip": "Returns if the touch sensor is pressed or not", - }, - fields: { - FUNC: 'is_pressed', - }, - } - ] - }, - { - kind: 'category', - name: 'Smart Motor', - contents: [ ] - }, - { - kind: 'category', - name: 'Servo', - contents: [ ] - }, - { - kind: 'category', - name: 'Color Range Sensor', - contents: [ ] - } - ], -} \ No newline at end of file diff --git a/src/toolbox/toolbox.ts b/src/toolbox/toolbox.ts index 9a8fe51f..a030f89b 100644 --- a/src/toolbox/toolbox.ts +++ b/src/toolbox/toolbox.ts @@ -73,7 +73,6 @@ export function getToolboxJSON( custom: 'VARIABLE', }, methodsCategory, - componentSampleCategory, ]); return { From 920aa013bd2df78d39a02cebd0eef098f0828ac8 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Tue, 18 Mar 2025 18:00:57 -0400 Subject: [PATCH 22/23] Add licenses --- external_samples/color_range_sensor.py | 19 ++++++++++++++++++- external_samples/component.py | 18 ++++++++++++++++++ external_samples/rev_touch_sensor.py | 19 ++++++++++++++++++- external_samples/servo.py | 19 ++++++++++++++++++- external_samples/smart_motor.py | 25 +++++++++++++++++++++---- 5 files changed, 93 insertions(+), 7 deletions(-) diff --git a/external_samples/color_range_sensor.py b/external_samples/color_range_sensor.py index 83f0bdc3..9c6852aa 100644 --- a/external_samples/color_range_sensor.py +++ b/external_samples/color_range_sensor.py @@ -1,3 +1,21 @@ +# @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 @@ -9,7 +27,6 @@ def __call__(self, hue : int, saturation : int, value : int) -> None: pass class ColorRangeSensor(Component): - # Required methods def __init__(self, ports : list[tuple[PortType, int]]): portType, port = ports[0] if portType != PortType.I2C_PORT: diff --git a/external_samples/component.py b/external_samples/component.py index 8ed6b47a..9b76439a 100644 --- a/external_samples/component.py +++ b/external_samples/component.py @@ -1,3 +1,21 @@ +# @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 diff --git a/external_samples/rev_touch_sensor.py b/external_samples/rev_touch_sensor.py index cf6d3ebb..afb4ab3b 100644 --- a/external_samples/rev_touch_sensor.py +++ b/external_samples/rev_touch_sensor.py @@ -1,3 +1,21 @@ +# @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): @@ -7,7 +25,6 @@ def __init__(self, ports : list[tuple[PortType, int]]): if portType != PortType.SMART_IO_PORT: raise InvalidPortException self.port = port - # Required methods def get_manufacturer(self) -> str: return "REV Robotics" def get_name(self) -> str: diff --git a/external_samples/servo.py b/external_samples/servo.py index cdfc057c..88f9e285 100644 --- a/external_samples/servo.py +++ b/external_samples/servo.py @@ -1,7 +1,24 @@ +# @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): - # Required methods def __init__(self, ports : list[tuple[PortType, int]]): portType, port = ports[0] if portType != PortType.SERVO_PORT: diff --git a/external_samples/smart_motor.py b/external_samples/smart_motor.py index bc4dd783..6ed58d99 100644 --- a/external_samples/smart_motor.py +++ b/external_samples/smart_motor.py @@ -1,7 +1,23 @@ +# @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): - # Required methods def __init__(self, ports : list[tuple[PortType, int]]): portType, port = ports[0] if portType != PortType.SMART_MOTOR_PORT: @@ -30,11 +46,12 @@ def periodic(self) -> None: # Component specific methods def set_speed(self, speed: float) -> None: '''Set the motor to a speed between -1 and 1''' - # sends to the hardware the speed of the motor + # 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 270''' - self.set_position(angle / 270.0) + '''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''' From 6dda05515ae8aa8191f7e199177ad12e930516a2 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Tue, 18 Mar 2025 18:01:37 -0400 Subject: [PATCH 23/23] remove no longer needed import --- src/toolbox/toolbox.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/toolbox/toolbox.ts b/src/toolbox/toolbox.ts index a030f89b..9f261611 100644 --- a/src/toolbox/toolbox.ts +++ b/src/toolbox/toolbox.ts @@ -28,7 +28,6 @@ import {category as textCategory} from './text_category'; import {category as listsCategory} from './lists_category'; import {category as miscCategory} from './misc_category'; import {category as methodsCategory} from './methods_category'; -import {category as componentSampleCategory} from './component_samples_category'; export function getToolboxJSON( opt_includeExportedBlocksFromProject: toolboxItems.ContentsType[],