Skip to content

Commit ce30750

Browse files
alan412lizlooney
andauthored
All components take a single port (#215)
* Beginnings of port possibly having multiple pieces * Update Python components to use Port class * Add types to init * Remove alternate way to construct * Add () to super * Fix __init__ * Check to see if we have an expected port type * add port types * add expected port type * Use expected port type * Only output expected port if it exists * New generated JSON * Making sure ports get passed from robot through * Got rid of typescript warnings * In port.py: When compare port_type with PortType.BASE_COMPOUND, use .value to get the numeric value. Added type hints for function returns. Modified get_type to just return self.type. In component.py: Changed port.get_type to port.get_type(). In mrc_port.ts, in pythonFromBlock: Changed _generator to generator. Added generator.addImport('port'); Changed Port to port.Port in generated python code. Changed PortType to port.PortType in generated python code. Changed MOTOR_PORT to EXPANSION_HUB_MOTOR_PORT. Changed SERVO_PORT to EXPANSION_HUB_SERVO_PORT. Updated external_samples_data.json. * Addressed review comments * Make Port an abstract class with SimplePort and CompoundPort children * Update python generator when it generates the 2 simple ports for a compound port. * Added port to the list of external_samples modules for which to generate json. Regenerated json. * Removed duplicate case for 'SMART_MOTOR_PORT' in function createPort. Changed 'unknown:' to 'unknown'. --------- Co-authored-by: Liz Looney <[email protected]>
1 parent 5c16860 commit ce30750

19 files changed

+593
-366
lines changed

external_samples/color_range_sensor.py

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
# @author [email protected] (Alan Smith)
1818

1919
from typing import Protocol, Self
20-
from component import Component, PortType, InvalidPortException
20+
from component import Component, InvalidPortException
21+
from port import Port, PortType
2122

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

3132

3233
class ColorRangeSensor(Component):
33-
def __init__(self, ports : list[tuple[PortType, int]]):
34-
portType, port = ports[0]
35-
if portType != PortType.I2C_PORT:
36-
raise InvalidPortException
37-
self.port = port
34+
def __init__(self, port : Port):
35+
super().__init__( port, expectedType = PortType.I2C_PORT)
3836

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

57-
def get_connection_port_type(self) -> list[PortType]:
58-
return [PortType.I2C_PORT]
59-
6055
def periodic(self) -> None:
6156
pass
62-
63-
# Alternative constructor to create an instance from an i2c port
64-
@classmethod
65-
def from_i2c_port(cls: type[Self], i2c_port: int) -> Self:
66-
return cls([(PortType.I2C_PORT, i2c_port)])
67-
57+
6858
# Component specific methods
6959

7060
def get_color_rgb(self) -> tuple[int, int, int]:

external_samples/component.py

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,26 @@
1717
# @author [email protected] (Alan Smith)
1818

1919
from abc import ABC, abstractmethod
20-
from enum import Enum
2120
from typing import Protocol
21+
from port import Port, PortType
2222

2323
class EmptyCallable(Protocol):
2424
def __call__(self) -> None:
2525
pass
2626

2727

28-
class PortType(Enum):
29-
CAN_PORT = 1
30-
SMART_IO_PORT = 2
31-
SMART_MOTOR_PORT = 3
32-
SERVO_PORT = 4
33-
I2C_PORT = 5
34-
USB_PORT = 6
35-
36-
3728
class InvalidPortException(Exception):
3829
pass
3930

4031

4132
# This is an abstract class
4233
class Component(ABC):
43-
@abstractmethod
44-
def __init__(self, ports : list[tuple[PortType, int]]):
45-
pass
34+
def __init__(self, port : Port, expectedType : PortType):
35+
"""This has the port it is attached to, and the expected type of the port"""
36+
if port.get_type() != expectedType:
37+
raise InvalidPortException
38+
39+
self.port = port
4640

4741
# Returns the manufacturer of the component
4842
@abstractmethod
@@ -87,11 +81,11 @@ def stop(self) -> None:
8781
def reset(self) -> None:
8882
pass
8983

90-
# Returns a list (can be empty, one, or multiple) of the ports this connects to
91-
# of the PortType enumeration
92-
@abstractmethod
93-
def get_connection_port_type(self) -> list[PortType]:
94-
pass
84+
# Returns the port this connects to of the PortType enumeration
85+
def get_connection_port_type(self) -> PortType | None:
86+
if self.port:
87+
return self.port.get_type()
88+
return None
9589

9690
# This is called periodically when an opmode is running. The component might use this
9791
# to talk to hardware and then call callbacks

external_samples/expansion_hub_motor.py

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,15 @@
2121
__author__ = "[email protected] (Liz Looney)"
2222

2323
from typing import Self
24-
from component import Component, PortType, InvalidPortException
24+
from component import Component, InvalidPortException
25+
from port import Port, PortType
2526
import expansion_hub
2627
import wpimath
2728

28-
# TODO(lizlooney): Update port types.
29-
3029
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)
30+
def __init__(self, port : Port):
31+
super().__init__(port, PortType.EXPANSION_HUB_MOTOR)
32+
self.expansion_hub_motor = expansion_hub.ExpansionHubMotor(self.port.port1.location, self.port.port2.location)
3933

4034
def get_manufacturer(self) -> str:
4135
return "REV Robotics"
@@ -62,17 +56,9 @@ def stop(self) -> None:
6256
def reset(self) -> None:
6357
pass
6458

65-
def get_connection_port_type(self) -> list[PortType]:
66-
return [PortType.USB_PORT, PortType.USB_PORT]
67-
6859
def periodic(self) -> None:
6960
pass
7061

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-
7662
# Component specific methods
7763

7864
# Methods from expansion_hub.ExpansionHubMotor

external_samples/expansion_hub_servo.py

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,13 @@
2222

2323
from typing import Self
2424
from component import Component, PortType, InvalidPortException
25+
from port import Port, PortType
2526
import expansion_hub
2627

27-
# TODO(lizlooney): Update port types.
28-
2928
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)
29+
def __init__(self, port : Port):
30+
super().__init__(port, PortType.EXPANSION_HUB_SERVO)
31+
self.expansion_hub_servo = expansion_hub.ExpansionHubServo(self.port.port1.location, self.port.port2.location)
3832

3933
def get_manufacturer(self) -> str:
4034
return "REV Robotics"
@@ -68,11 +62,6 @@ def get_connection_port_type(self) -> list[PortType]:
6862
def periodic(self) -> None:
6963
pass
7064

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-
7665
# Component specific methods
7766

7867
# Methods from expansion_hub.ExpansionHubServo

external_samples/port.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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 class to handle port types
17+
# @author [email protected] (Alan Smith)
18+
from abc import ABC, abstractmethod
19+
from enum import Enum
20+
from typing import Self
21+
22+
class PortType(Enum):
23+
CAN_PORT = 1
24+
SMART_IO_PORT = 2
25+
SMART_MOTOR_PORT = 3
26+
SERVO_PORT = 4
27+
I2C_PORT = 5
28+
USB_PORT = 6
29+
EXPANSION_HUB_MOTOR_PORT = 7 # This is the port on the expansion hub
30+
EXPANSION_HUB_SERVO_PORT = 8 # This is the port on the expansion hub
31+
32+
BASE_COMPOUND = 256
33+
USB_HUB = BASE_COMPOUND + 1
34+
EXPANSION_HUB_MOTOR = BASE_COMPOUND + 2 # This is combination expansion hub and motor
35+
EXPANSION_HUB_SERVO = BASE_COMPOUND + 3 # This is combination expansion hub and servo
36+
37+
class Port(ABC):
38+
"""Abstract base class for all port types."""
39+
40+
def __init__(self, port_type: PortType):
41+
self.type = port_type
42+
43+
@abstractmethod
44+
def get_all_ports(self) -> list[tuple[PortType, int]]:
45+
"""Return a list of all simple ports contained in this port."""
46+
pass
47+
48+
def get_type(self) -> PortType:
49+
"""Returns the port type"""
50+
return self.type
51+
52+
class SimplePort(Port):
53+
def __init__(self, port_type: PortType, location: int):
54+
"""
55+
Create a simple port with a type and location.
56+
57+
Args:
58+
port_type: PortType for this port (must be a simple type)
59+
location: int location for this port
60+
"""
61+
if port_type.value >= PortType.BASE_COMPOUND.value:
62+
raise ValueError("Port must be of a simple type")
63+
super().__init__(port_type)
64+
self.location = location
65+
66+
def get_all_ports(self) -> list[tuple[PortType, int]]:
67+
"""Return a list containing this simple port."""
68+
return [(self.type, self.location)]
69+
70+
def __str__(self) -> str:
71+
return f"SimplePort({self.type}, {self.location})"
72+
73+
class CompoundPort(Port):
74+
def __init__(self, port_type: PortType, port1: Port, port2: Port):
75+
"""
76+
Create a compound port from two other ports.
77+
78+
Args:
79+
port_type: PortType for this port (must be a compound type)
80+
port1: First Port for compound ports
81+
port2: Second Port for compound ports
82+
"""
83+
if port_type.value < PortType.BASE_COMPOUND.value:
84+
raise ValueError("Port must be of a compound type")
85+
super().__init__(port_type)
86+
self.port1 = port1
87+
self.port2 = port2
88+
89+
def get_all_ports(self) -> list[tuple[PortType, int]]:
90+
"""Return a list of all simple ports contained in this compound port."""
91+
return self.port1.get_all_ports() + self.port2.get_all_ports()
92+
93+
def __str__(self) -> str:
94+
return f"CompoundPort({self.type}: {self.port1}, {self.port2})"

external_samples/rev_touch_sensor.py

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,12 @@
1818

1919
from typing import Self
2020
from component import Component, PortType, InvalidPortException, EmptyCallable
21+
from port import Port, PortType
2122

2223
class RevTouchSensor(Component):
23-
def __init__(self, ports : list[tuple[PortType, int]]):
24+
def __init__(self, port : Port):
25+
super().__init__(port, PortType.SMART_IO_PORT)
2426
self.is_pressed = None
25-
portType, port = ports[0]
26-
if portType != PortType.SMART_IO_PORT:
27-
raise InvalidPortException
28-
self.port = port
2927

3028
def get_manufacturer(self) -> str:
3129
return "REV Robotics"
@@ -47,9 +45,6 @@ def reset(self) -> None:
4745
self.released_callback = None
4846
pass
4947

50-
def get_connection_port_type(self) -> list[PortType]:
51-
return [PortType.SMART_IO_PORT]
52-
5348
def periodic(self) -> None:
5449
old = self.is_pressed
5550
self._read_hardware()
@@ -58,12 +53,7 @@ def periodic(self) -> None:
5853
self.pressed_callback()
5954
elif old and self.released_callback:
6055
self.released_callback()
61-
62-
# Alternative constructor to create an instance from a smart io port
63-
@classmethod
64-
def from_smart_io_port(cls: type[Self], smart_io_port: int) -> Self:
65-
return cls([(PortType.SMART_IO_PORT, smart_io_port)])
66-
56+
6757
# Component specific methods
6858

6959
def _read_hardware(self):

external_samples/servo.py

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,11 @@
1818

1919
from typing import Self
2020
from component import Component, PortType, InvalidPortException
21+
from port import Port, PortType
2122

2223
class Servo(Component):
23-
def __init__(self, ports : list[tuple[PortType, int]]):
24-
portType, port = ports[0]
25-
if portType != PortType.SERVO_PORT:
26-
raise InvalidPortException
27-
self.port = port
24+
def __init__(self, port : Port):
25+
super().__init__(port, PortType.SERVO_PORT)
2826

2927
def get_manufacturer(self) -> str:
3028
return "REV Robotics"
@@ -48,17 +46,9 @@ def stop(self) -> None:
4846
def reset(self) -> None:
4947
pass
5048

51-
def get_connection_port_type(self) -> list[PortType]:
52-
return [PortType.SERVO_PORT]
53-
5449
def periodic(self) -> None:
5550
pass
56-
57-
# Alternative constructor to create an instance from an servo port
58-
@classmethod
59-
def from_servo_port(cls: type[Self], servo_port: int) -> Self:
60-
return cls([(PortType.SERVO_PORT, servo_port)])
61-
51+
6252
# Component specific methods
6353

6454
def set_position(self, pos: float) -> None:

external_samples/smart_motor.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,11 @@
1818

1919
from typing import Self
2020
from component import Component, PortType, InvalidPortException
21+
from port import Port, PortType
2122

2223
class SmartMotor(Component):
23-
def __init__(self, ports : list[tuple[PortType, int]]):
24-
portType, port = ports[0]
25-
if portType != PortType.SMART_MOTOR_PORT:
26-
raise InvalidPortException
27-
self.port = port
24+
def __init__(self, port : Port):
25+
super().__init__(port, PortType.SMART_MOTOR_PORT)
2826

2927
def get_manufacturer(self) -> str:
3028
return "REV Robotics"
@@ -48,16 +46,8 @@ def stop(self) -> None:
4846
def reset(self) -> None:
4947
pass
5048

51-
def get_connection_port_type(self) -> list[PortType]:
52-
return [PortType.SMART_MOTOR_PORT]
53-
5449
def periodic(self) -> None:
5550
pass
56-
57-
# Alternative constructor to create an instance from a smart motor port
58-
@classmethod
59-
def from_smart_motor_port(cls: type[Self], smart_motor_port: int) -> Self:
60-
return cls([(PortType.SMART_MOTOR_PORT, smart_motor_port)])
6151

6252
# Component specific methods
6353

0 commit comments

Comments
 (0)