Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
907d8f7
Beginnings of port possibly having multiple pieces
alan412 Aug 29, 2025
fb5642c
Update Python components to use Port class
alan412 Aug 30, 2025
cfdf242
Add types to init
alan412 Aug 30, 2025
529b601
Remove alternate way to construct
alan412 Aug 30, 2025
b799da8
Add () to super
alan412 Aug 30, 2025
2445705
Fix __init__
alan412 Aug 30, 2025
dd91671
Check to see if we have an expected port type
alan412 Aug 30, 2025
6c8cd86
add port types
alan412 Aug 30, 2025
9eda6cc
add expected port type
alan412 Aug 30, 2025
a40bc55
Use expected port type
alan412 Aug 31, 2025
6d0a38b
Only output expected port if it exists
alan412 Aug 31, 2025
8c35186
New generated JSON
alan412 Aug 31, 2025
86e148f
Making sure ports get passed from robot through
alan412 Aug 31, 2025
25d14ee
Merge remote-tracking branch 'upstream/main' into pr_multiple_ports
alan412 Aug 31, 2025
30e08e7
Got rid of typescript warnings
alan412 Aug 31, 2025
d8d2533
In port.py:
lizlooney Aug 31, 2025
605e441
Merge pull request #6 from lizlooney/pr_multiple_ports
alan412 Aug 31, 2025
02e9454
Addressed review comments
alan412 Aug 31, 2025
cd01138
Make Port an abstract class with SimplePort and CompoundPort children
alan412 Sep 4, 2025
555ce33
Update python generator when it generates the 2 simple ports for a co…
lizlooney Sep 4, 2025
65f1563
Added port to the list of external_samples modules for which to gener…
lizlooney Sep 4, 2025
c3e2d21
Removed duplicate case for 'SMART_MOTOR_PORT' in function createPort.
lizlooney Sep 4, 2025
a6b695a
Merge pull request #7 from lizlooney/pr_multiple_ports
alan412 Sep 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 5 additions & 15 deletions external_samples/color_range_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
# @author [email protected] (Alan Smith)

from typing import Protocol, Self
from component import Component, PortType, InvalidPortException
from component import Component, InvalidPortException
from port import Port, PortType

class DistanceCallable(Protocol):
def __call__(self, distance : float) -> None:
Expand All @@ -30,11 +31,8 @@ def __call__(self, hue : int, saturation : int, value : int) -> None:


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 __init__(self, port : Port):
super().__init__( port, expectedType = PortType.I2C_PORT)

def get_manufacturer(self) -> str:
return "REV Robotics"
Expand All @@ -54,17 +52,9 @@ def get_version(self) -> tuple[int, int, int]:
def reset(self) -> None:
pass

def get_connection_port_type(self) -> list[PortType]:
return [PortType.I2C_PORT]

def periodic(self) -> None:
pass

# Alternative constructor to create an instance from an i2c port
@classmethod
def from_i2c_port(cls: type[Self], i2c_port: int) -> Self:
return cls([(PortType.I2C_PORT, i2c_port)])


# Component specific methods

def get_color_rgb(self) -> tuple[int, int, int]:
Expand Down
30 changes: 12 additions & 18 deletions external_samples/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,26 @@
# @author [email protected] (Alan Smith)

from abc import ABC, abstractmethod
from enum import Enum
from typing import Protocol
from port import Port, PortType

class EmptyCallable(Protocol):
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
def __init__(self, port : Port, expectedType : PortType):
"""This has the port it is attached to, and the expected type of the port"""
if port.get_type() != expectedType:
raise InvalidPortException

self.port = port

# Returns the manufacturer of the component
@abstractmethod
Expand Down Expand Up @@ -87,11 +81,11 @@ def stop(self) -> None:
def reset(self) -> None:
pass

# 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
# Returns the port this connects to of the PortType enumeration
def get_connection_port_type(self) -> PortType | None:
if self.port:
return self.port.get_type()
return None

# This is called periodically when an opmode is running. The component might use this
# to talk to hardware and then call callbacks
Expand Down
24 changes: 5 additions & 19 deletions external_samples/expansion_hub_motor.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,15 @@
__author__ = "[email protected] (Liz Looney)"

from typing import Self
from component import Component, PortType, InvalidPortException
from component import Component, InvalidPortException
from port import Port, PortType
import expansion_hub
import wpimath

# TODO(lizlooney): Update port types.

class ExpansionHubMotor(Component):
def __init__(self, ports : list[tuple[PortType, int]]):
port_type, hub_number = ports[0]
if port_type != PortType.USB_PORT:
raise InvalidPortException
port_type, motor_number = ports[1]
if port_type != PortType.USB_PORT:
raise InvalidPortException
self.expansion_hub_motor = expansion_hub.ExpansionHubMotor(hub_number, motor_number)
def __init__(self, port : Port):
super().__init__(port, PortType.EXPANSION_HUB_MOTOR)
self.expansion_hub_motor = expansion_hub.ExpansionHubMotor(self.port.port1.location, self.port.port2.location)

def get_manufacturer(self) -> str:
return "REV Robotics"
Expand All @@ -62,17 +56,9 @@ def stop(self) -> None:
def reset(self) -> None:
pass

def get_connection_port_type(self) -> list[PortType]:
return [PortType.USB_PORT, PortType.USB_PORT]

def periodic(self) -> None:
pass

# Alternative constructor to create an instance from a hub number and a motor port.
@classmethod
def from_hub_number_and_motor_number(cls: type[Self], hub_number: int, motor_number: int) -> Self:
return cls([(PortType.USB_PORT, hub_number), (PortType.USB_PORT, motor_number)])

# Component specific methods

# Methods from expansion_hub.ExpansionHubMotor
Expand Down
19 changes: 4 additions & 15 deletions external_samples/expansion_hub_servo.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,13 @@

from typing import Self
from component import Component, PortType, InvalidPortException
from port import Port, PortType
import expansion_hub

# TODO(lizlooney): Update port types.

class ExpansionHubServo(Component):
def __init__(self, ports : list[tuple[PortType, int]]):
port_type, hub_number = ports[0]
if port_type != PortType.USB_PORT:
raise InvalidPortException
port_type, servo_number = ports[1]
if port_type != PortType.USB_PORT:
raise InvalidPortException
self.expansion_hub_servo = expansion_hub.ExpansionHubServo(hub_number, servo_number)
def __init__(self, port : Port):
super().__init__(port, PortType.EXPANSION_HUB_SERVO)
self.expansion_hub_servo = expansion_hub.ExpansionHubServo(self.port.port1.location, self.port.port2.location)

def get_manufacturer(self) -> str:
return "REV Robotics"
Expand Down Expand Up @@ -68,11 +62,6 @@ def get_connection_port_type(self) -> list[PortType]:
def periodic(self) -> None:
pass

# Alternative constructor to create an instance from a hub number and a servo port.
@classmethod
def from_hub_number_and_servo_number(cls: type[Self], hub_number: int, servo_number: int) -> Self:
return cls([(PortType.USB_PORT, hub_number), (PortType.USB_PORT, servo_number)])

# Component specific methods

# Methods from expansion_hub.ExpansionHubServo
Expand Down
94 changes: 94 additions & 0 deletions external_samples/port.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# @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 class to handle port types
# @author [email protected] (Alan Smith)
from abc import ABC, abstractmethod
from enum import Enum
from typing import Self

class PortType(Enum):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a class comment that shows examples of how to use PortType?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am happy to write a comment here, but I couldn't come up with anything about how to use it that didn't seem like "this is the type of port". Suggestions?

CAN_PORT = 1
SMART_IO_PORT = 2
SMART_MOTOR_PORT = 3
SERVO_PORT = 4
I2C_PORT = 5
USB_PORT = 6
EXPANSION_HUB_MOTOR_PORT = 7 # This is the port on the expansion hub
EXPANSION_HUB_SERVO_PORT = 8 # This is the port on the expansion hub

BASE_COMPOUND = 256
USB_HUB = BASE_COMPOUND + 1
EXPANSION_HUB_MOTOR = BASE_COMPOUND + 2 # This is combination expansion hub and motor
EXPANSION_HUB_SERVO = BASE_COMPOUND + 3 # This is combination expansion hub and servo

class Port(ABC):
"""Abstract base class for all port types."""

def __init__(self, port_type: PortType):
self.type = port_type

@abstractmethod
def get_all_ports(self) -> list[tuple[PortType, int]]:
"""Return a list of all simple ports contained in this port."""
pass

def get_type(self) -> PortType:
"""Returns the port type"""
return self.type

class SimplePort(Port):
def __init__(self, port_type: PortType, location: int):
"""
Create a simple port with a type and location.

Args:
port_type: PortType for this port (must be a simple type)
location: int location for this port
"""
if port_type.value >= PortType.BASE_COMPOUND.value:
raise ValueError("Port must be of a simple type")
super().__init__(port_type)
self.location = location

def get_all_ports(self) -> list[tuple[PortType, int]]:
"""Return a list containing this simple port."""
return [(self.type, self.location)]

def __str__(self) -> str:
return f"SimplePort({self.type}, {self.location})"

class CompoundPort(Port):
def __init__(self, port_type: PortType, port1: Port, port2: Port):
"""
Create a compound port from two other ports.

Args:
port_type: PortType for this port (must be a compound type)
port1: First Port for compound ports
port2: Second Port for compound ports
"""
if port_type.value < PortType.BASE_COMPOUND.value:
raise ValueError("Port must be of a compound type")
super().__init__(port_type)
self.port1 = port1
self.port2 = port2

def get_all_ports(self) -> list[tuple[PortType, int]]:
"""Return a list of all simple ports contained in this compound port."""
return self.port1.get_all_ports() + self.port2.get_all_ports()

def __str__(self) -> str:
return f"CompoundPort({self.type}: {self.port1}, {self.port2})"
18 changes: 4 additions & 14 deletions external_samples/rev_touch_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,12 @@

from typing import Self
from component import Component, PortType, InvalidPortException, EmptyCallable
from port import Port, PortType

class RevTouchSensor(Component):
def __init__(self, ports : list[tuple[PortType, int]]):
def __init__(self, port : Port):
super().__init__(port, PortType.SMART_IO_PORT)
self.is_pressed = None
portType, port = ports[0]
if portType != PortType.SMART_IO_PORT:
raise InvalidPortException
self.port = port

def get_manufacturer(self) -> str:
return "REV Robotics"
Expand All @@ -47,9 +45,6 @@ def reset(self) -> 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()
Expand All @@ -58,12 +53,7 @@ def periodic(self) -> None:
self.pressed_callback()
elif old and self.released_callback:
self.released_callback()

# Alternative constructor to create an instance from a smart io port
@classmethod
def from_smart_io_port(cls: type[Self], smart_io_port: int) -> Self:
return cls([(PortType.SMART_IO_PORT, smart_io_port)])


# Component specific methods

def _read_hardware(self):
Expand Down
18 changes: 4 additions & 14 deletions external_samples/servo.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,11 @@

from typing import Self
from component import Component, PortType, InvalidPortException
from port import Port, PortType

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 __init__(self, port : Port):
super().__init__(port, PortType.SERVO_PORT)

def get_manufacturer(self) -> str:
return "REV Robotics"
Expand All @@ -48,17 +46,9 @@ def stop(self) -> None:
def reset(self) -> None:
pass

def get_connection_port_type(self) -> list[PortType]:
return [PortType.SERVO_PORT]

def periodic(self) -> None:
pass

# Alternative constructor to create an instance from an servo port
@classmethod
def from_servo_port(cls: type[Self], servo_port: int) -> Self:
return cls([(PortType.SERVO_PORT, servo_port)])


# Component specific methods

def set_position(self, pos: float) -> None:
Expand Down
16 changes: 3 additions & 13 deletions external_samples/smart_motor.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,11 @@

from typing import Self
from component import Component, PortType, InvalidPortException
from port import Port, PortType

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 __init__(self, port : Port):
super().__init__(port, PortType.SMART_MOTOR_PORT)

def get_manufacturer(self) -> str:
return "REV Robotics"
Expand All @@ -48,16 +46,8 @@ def stop(self) -> None:
def reset(self) -> None:
pass

def get_connection_port_type(self) -> list[PortType]:
return [PortType.SMART_MOTOR_PORT]

def periodic(self) -> None:
pass

# Alternative constructor to create an instance from a smart motor port
@classmethod
def from_smart_motor_port(cls: type[Self], smart_motor_port: int) -> Self:
return cls([(PortType.SMART_MOTOR_PORT, smart_motor_port)])

# Component specific methods

Expand Down
Loading