diff --git a/external_samples/color_range_sensor.py b/external_samples/color_range_sensor.py index 1534347d..11e45bf8 100644 --- a/external_samples/color_range_sensor.py +++ b/external_samples/color_range_sensor.py @@ -17,7 +17,8 @@ # @author alan@porpoiseful.com (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: @@ -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" @@ -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]: diff --git a/external_samples/component.py b/external_samples/component.py index e6992a4d..f0da9e0a 100644 --- a/external_samples/component.py +++ b/external_samples/component.py @@ -17,32 +17,26 @@ # @author alan@porpoiseful.com (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 @@ -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 diff --git a/external_samples/expansion_hub_motor.py b/external_samples/expansion_hub_motor.py index 50fb03e2..e9fadc5c 100644 --- a/external_samples/expansion_hub_motor.py +++ b/external_samples/expansion_hub_motor.py @@ -21,21 +21,15 @@ __author__ = "lizlooney@google.com (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" @@ -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 diff --git a/external_samples/expansion_hub_servo.py b/external_samples/expansion_hub_servo.py index 5f7d6c64..7ebae626 100644 --- a/external_samples/expansion_hub_servo.py +++ b/external_samples/expansion_hub_servo.py @@ -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" @@ -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 diff --git a/external_samples/port.py b/external_samples/port.py new file mode 100644 index 00000000..ba71747d --- /dev/null +++ b/external_samples/port.py @@ -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 alan@porpoiseful.com (Alan Smith) +from abc import ABC, abstractmethod +from enum import Enum +from typing import Self + +class PortType(Enum): + 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})" diff --git a/external_samples/rev_touch_sensor.py b/external_samples/rev_touch_sensor.py index 9f9ab179..bf6b3a1e 100644 --- a/external_samples/rev_touch_sensor.py +++ b/external_samples/rev_touch_sensor.py @@ -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" @@ -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() @@ -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): diff --git a/external_samples/servo.py b/external_samples/servo.py index d5683660..242b5dd2 100644 --- a/external_samples/servo.py +++ b/external_samples/servo.py @@ -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" @@ -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: diff --git a/external_samples/smart_motor.py b/external_samples/smart_motor.py index db153979..5f8c0c17 100644 --- a/external_samples/smart_motor.py +++ b/external_samples/smart_motor.py @@ -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" @@ -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 diff --git a/external_samples/spark_mini.py b/external_samples/spark_mini.py index 1e3790fd..9d55ef7a 100644 --- a/external_samples/spark_mini.py +++ b/external_samples/spark_mini.py @@ -22,15 +22,17 @@ from typing import Self from component import Component, PortType, InvalidPortException +from port import Port, PortType + import wpilib import wpimath import wpiutil class SparkMini(Component): - def __init__(self, ports : list[tuple[PortType, int]]): - portType, port = ports[0] - if portType != PortType.SMART_MOTOR_PORT: - raise InvalidPortException + def __init__(self, port : Port): + # TODO(alan412): I don't think this is the correct type + super().__init__(port, PortType.SMART_MOTOR_PORT) + # TODO(lizlooney): When we upgrade to 2027 robotpy, change PWMSparkMax to SparkMini. self.spark_mini = wpilib.PWMSparkMax(port) # wpilib.SparkMini(port) @@ -56,16 +58,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 diff --git a/external_samples/sparkfun_led_stick.py b/external_samples/sparkfun_led_stick.py index 27f1b39e..62868602 100644 --- a/external_samples/sparkfun_led_stick.py +++ b/external_samples/sparkfun_led_stick.py @@ -16,14 +16,12 @@ from typing import Self from component import Component, PortType, InvalidPortException +from port import Port, PortType import wpilib class SparkFunLEDStick(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, PortType.I2C_PORT) def get_manufacturer(self) -> str: return "SparkFun" @@ -46,16 +44,8 @@ def stop(self) -> None: 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)]) # SparkFunLEDStick methods diff --git a/python_tools/generate_json.py b/python_tools/generate_json.py index e2ffbc93..e31cb2e6 100644 --- a/python_tools/generate_json.py +++ b/python_tools/generate_json.py @@ -61,6 +61,7 @@ import component import expansion_hub_motor import expansion_hub_servo +import port import rev_touch_sensor import servo import smart_motor @@ -123,6 +124,7 @@ def main(argv): component, expansion_hub_motor, expansion_hub_servo, + port, rev_touch_sensor, servo, smart_motor, diff --git a/python_tools/json_util.py b/python_tools/json_util.py index 60ae5984..d8c3f9f1 100644 --- a/python_tools/json_util.py +++ b/python_tools/json_util.py @@ -59,6 +59,7 @@ _KEY_ARGUMENT_DEFAULT_VALUE = 'defaultValue' _KEY_ALIASES = 'aliases' _KEY_SUBCLASSES = 'subclasses' +_KEY_PORT_EXPECTED_TYPE = 'expectedPortType' def ignoreModule(module_name: str) -> bool: @@ -359,6 +360,10 @@ def _processClass(self, cls): constructor_data[_KEY_FUNCTION_ARGS] = args constructor_data[_KEY_FUNCTION_DECLARING_CLASS_NAME] = declaring_class_name constructor_data[_KEY_FUNCTION_RETURN_TYPE] = declaring_class_name + expectedPortType = python_util.getPortTypeFromConstructor(value) + if expectedPortType: + constructor_data[_KEY_PORT_EXPECTED_TYPE] = python_util.getPortTypeFromConstructor(value) + constructors.append(constructor_data) class_data[_KEY_CONSTRUCTORS] = constructors diff --git a/python_tools/python_util.py b/python_tools/python_util.py index 366d8851..b67664c7 100644 --- a/python_tools/python_util.py +++ b/python_tools/python_util.py @@ -579,3 +579,23 @@ def collectSubclasses(classes: list[type]) -> dict[str, list[str]]: if subclass_name not in subclass_names: subclass_names.append(subclass_name) return dict_class_name_to_subclass_names + + +def getPortTypeFromConstructor(constructor) -> str: + """Extract portType value from constructor that calls super() with portType parameter.""" + if not mightBeConstructor(constructor): + return None + + try: + source = inspect.getsource(constructor) + # Look for super().__init__(xxx PortType.XXXX) pattern using regex + pattern = r'super\(\)\.__init__\([^,]*,.*PortType\.(\w+)\)' + match = re.search(pattern, source) + if match: + port_type = match.group(1).strip() + return port_type + except: + # If we can't parse the source, return empty string + pass + + return "" \ No newline at end of file diff --git a/src/blocks/mrc_component.ts b/src/blocks/mrc_component.ts index 9c233eb9..9c4ced8a 100644 --- a/src/blocks/mrc_component.ts +++ b/src/blocks/mrc_component.ts @@ -26,13 +26,11 @@ import { MRC_STYLE_COMPONENTS } from '../themes/styles' import { createFieldNonEditableText } from '../fields/FieldNonEditableText'; import { Editor } from '../editor/editor'; import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; -import { getAllowedTypesForSetCheck, getClassData, getModuleData, getSubclassNames } from './utils/python'; +import { getAllowedTypesForSetCheck, getClassData, getSubclassNames } from './utils/python'; import * as toolboxItems from '../toolbox/items'; import * as storageModule from '../storage/module'; import * as storageModuleContent from '../storage/module_content'; -import * as storageNames from '../storage/names'; -import { createPortShadow } from './mrc_port'; -import { createNumberShadowValue } from './utils/value'; +import { createPort } from './mrc_port'; import { ClassData, FunctionData } from './utils/python_json_types'; import { renameMethodCallers } from './mrc_call_python_function' @@ -166,14 +164,16 @@ const COMPONENT = { ports: ports, }; }, - getArgName: function (this: ComponentBlock, i: number): string { - return this.getFieldValue(FIELD_NAME) + '__' + this.mrcArgs[i].name; + getArgName: function (this: ComponentBlock, _: number): string { + return this.getFieldValue(FIELD_NAME) + '__' + 'port'; }, + + getComponentPorts: function (this: ComponentBlock, ports: {[argName: string]: string}): void { // Collect the ports for this component block. for (let i = 0; i < this.mrcArgs.length; i++) { const argName = this.getArgName(i); - ports[argName] = this.mrcArgs[i].type; + ports[argName] = this.mrcArgs[i].name; } }, /** @@ -197,20 +197,16 @@ export const pythonFromBlock = function ( if (block.mrcImportModule) { generator.addImport(block.mrcImportModule); } - let code = 'self.' + block.getFieldValue(FIELD_NAME) + ' = ' + block.getFieldValue(FIELD_TYPE); - if (block.mrcStaticFunctionName) { - code += '.' + block.mrcStaticFunctionName; - } - code += '('; + let code = 'self.' + block.getFieldValue(FIELD_NAME) + ' = ' + block.getFieldValue(FIELD_TYPE) + "("; for (let i = 0; i < block.mrcArgs.length; i++) { if (i != 0) { code += ', '; } if (generator.getModuleType() === storageModule.ModuleType.ROBOT) { - code += block.mrcArgs[i].name + ' = ' + generator.valueToCode(block, 'ARG' + i, Order.NONE); + code += generator.valueToCode(block, 'ARG' + i, Order.NONE); } else { - code += block.mrcArgs[i].name + ' = ' + block.getArgName(i); + code += block.getArgName(i); } } code += ')\n' + 'self.hardware.append(self.' + block.getFieldValue(FIELD_NAME) + ')\n'; @@ -229,15 +225,10 @@ export function getAllPossibleComponents( throw new Error('Could not find classData for ' + componentType); } - const componentName = ( - 'my_' + - storageNames.pascalCaseToSnakeCase( - componentType.substring(componentType.lastIndexOf('.') + 1))); + const componentName = 'my_' + classData.moduleName; - classData.staticMethods.forEach(staticFunctionData => { - if (staticFunctionData.returnType === componentType) { - contents.push(createComponentBlock(componentName, classData, staticFunctionData, moduleType)); - } + classData.constructors.forEach(constructor => { + contents.push(createComponentBlock(componentName, classData, constructor, moduleType)); }); }); @@ -247,52 +238,27 @@ export function getAllPossibleComponents( function createComponentBlock( componentName: string, classData: ClassData, - staticFunctionData: FunctionData, + constructorData: FunctionData, moduleType: storageModule.ModuleType): toolboxItems.Block { const extraState: ComponentExtraState = { importModule: classData.moduleName, - staticFunctionName: staticFunctionData.functionName, + //TODO(ags): Remove this because we know what the constructor name is + staticFunctionName: constructorData.functionName, params: [], }; const fields: {[key: string]: any} = {}; fields[FIELD_NAME] = componentName; fields[FIELD_TYPE] = classData.className; const inputs: {[key: string]: any} = {}; - for (let i = 0; i < staticFunctionData.args.length; i++) { - const argData = staticFunctionData.args[i]; + + if (constructorData.expectedPortType) { extraState.params!.push({ - 'name': argData.name, - 'type': argData.type, + 'name': constructorData.expectedPortType, + 'type': 'Port', }); - if (moduleType == storageModule.ModuleType.ROBOT) { - if (argData.type === 'int') { - const portType = getPortTypeForArgument(argData.name); - if (portType) { - inputs['ARG' + i] = createPortShadow(portType, 1); - } else { - inputs['ARG' + i] = createNumberShadowValue(1); - } - } + if ( moduleType == storageModule.ModuleType.ROBOT ) { + inputs['ARG0'] = createPort(constructorData.expectedPortType); } } return new toolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null); -} - -function getPortTypeForArgument(argName: string): string | null { - const argNameLower = argName.toLowerCase(); - const moduleData = getModuleData('component'); - if (moduleData) { - for (const enumData of moduleData.enums) { - if (enumData.enumClassName === 'component.PortType') { - for (const value of enumData.enumValues) { - if (argNameLower === value.toLowerCase()) { - return value; - } - } - break; - } - } - } - - return null; -} +} \ No newline at end of file diff --git a/src/blocks/mrc_mechanism.ts b/src/blocks/mrc_mechanism.ts index 4eac8de0..a57fb02a 100644 --- a/src/blocks/mrc_mechanism.ts +++ b/src/blocks/mrc_mechanism.ts @@ -31,9 +31,9 @@ import * as toolboxItems from '../toolbox/items'; import * as storageModule from '../storage/module'; import * as storageModuleContent from '../storage/module_content'; import * as storageNames from '../storage/names'; -import * as value from './utils/value'; import { renameMethodCallers } from './mrc_call_python_function' import { renameMechanismName as renameMechanismNameInEventHandlers } from './mrc_event_handler' +import { createPort } from './mrc_port'; export const BLOCK_NAME = 'mrc_mechanism'; export const OUTPUT_NAME = 'mrc_mechansim'; @@ -296,8 +296,7 @@ export function createMechanismBlock( name: port, type: parameterType, }); - const defaultValue = (parameterType === 'int') ? '0' : ''; - inputs['ARG' + i] = value.valueForFunctionArgInput(parameterType, defaultValue); + inputs['ARG' + i] = createPort(parameterType); i++; } }); diff --git a/src/blocks/mrc_port.ts b/src/blocks/mrc_port.ts index b4dbeab8..2c355171 100644 --- a/src/blocks/mrc_port.ts +++ b/src/blocks/mrc_port.ts @@ -29,25 +29,80 @@ import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; export const BLOCK_NAME = 'mrc_port'; export const OUTPUT_NAME = 'mrc_port'; +export type MrcPortType = { + portType: string, + portNumber: number, +}; type PortBlock = Blockly.Block & PortMixin; interface PortMixin extends PortMixinType { + portType_ : string, + ports_ : MrcPortType[], } type PortMixinType = typeof PORT; +type PortExtraState = { + //TODO(Alan) - Figure out how to not have this duplicated for a simple port + portType: string, + ports: MrcPortType[], +} + const PORT = { /** * Block initialization. */ init: function (this: PortBlock): void { this.setStyle(MRC_STYLE_PORTS); - this.appendDummyInput() - .appendField(createFieldNonEditableText(''), 'TYPE') - .appendField(new Blockly.FieldTextInput(''), 'PORT_NUM'); - - //this.setOutput(true, OUTPUT_NAME); this.setOutput(true); }, + + /** + * Save the ports to the block's extra state. + */ + saveExtraState: function (this: PortBlock): PortExtraState { + const state: PortExtraState = { + portType: this.portType_, + ports: this.ports_ + }; + + return state; + }, + + /** + * Load the ports from the block's extra state. + */ + loadExtraState: function (this: PortBlock, state: PortExtraState): void { + this.portType_ = state.portType || ''; + this.ports_ = state.ports || []; + this.updateShape_(); + }, + + /** + * Update the block's shape based on the current ports. + */ + updateShape_: function (this: PortBlock): void { + // Remove all existing inputs + for (let i = this.inputList.length - 1; i >= 0; i--) { + const input = this.inputList[i]; + if (input && (input.name.startsWith('PORT_'))) { + this.removeInput(input.name, true); + } + } + + // Initialize ports if not set + if (!this.ports_) { + this.ports_ = [{ portType: '', portNumber: 0 }]; + } + + // Add inputs for each port + for (let i = 0; i < this.ports_.length; i++) { + const port = this.ports_[i]; + this.appendDummyInput('PORT_' + i) + .appendField(createFieldNonEditableText(port.portType), 'TYPE_' + i) + .appendField(new Blockly.FieldTextInput(port.portNumber.toString()), 'PORT_NUM_' + i) + .setAlign(Blockly.inputs.Align.RIGHT); + } + }, } export const setup = function () { @@ -56,20 +111,104 @@ export const setup = function () { export const pythonFromBlock = function ( block: PortBlock, - _generator: ExtendedPythonGenerator) { - // TODO (Alan) : Specify the type here as well - let code = block.getFieldValue('PORT_NUM'); + generator: ExtendedPythonGenerator) { + generator.addImport('port'); + + const ports: string[] = []; + + for (let i = 0; i < block.inputList.length; i++) { + const input = block.inputList[i]; + if (input.name.startsWith('PORT_')) { + const portNumField = input.fieldRow.find(field => field.name === 'PORT_NUM_' + i); + if (portNumField) { + ports.push(portNumField.getValue() as string); + } + } + } + let code = 'port.'; + + if (ports.length === 1) { + code += `SimplePort(port_type = port.PortType.${block.portType_}, location = ${ports[0]})`; + + } else if (ports.length === 2) { + let port1Type = 'UNKNOWN'; + let port2Type = 'UNKNOWN'; + + switch (block.portType_) { + case 'USB_HUB': + port1Type = 'USB_PORT'; + port2Type = 'USB_PORT'; + break; + case 'EXPANSION_HUB_MOTOR': + port1Type = 'USB_PORT'; + port2Type = 'EXPANSION_HUB_MOTOR_PORT'; + break; + case 'EXPANSION_HUB_SERVO': + port1Type = 'USB_PORT'; + port2Type = 'EXPANSION_HUB_SERVO_PORT'; + break; + } + code += `CompoundPort(port_type = port.PortType.${block.portType_},`; + code += `\\ \n${generator.INDENT}port1 = port.SimplePort(port_type = port.PortType.${port1Type}, location = ${ports[0]}), `; + code += `\\ \n${generator.INDENT}port2 = port.SimplePort(port_type = port.PortType.${port2Type}, location = ${ports[1]}))`; + } return [code, Order.ATOMIC]; } -export function createPortShadow(portType: string, portNum: Number) { +export function createPort(portType : string) { + // Based off of the port type, create the right number and type of ports + const ports : MrcPortType[] = []; + switch(portType){ + case 'CAN_PORT': + ports.push({ portType: 'can', portNumber: 1 }); + break; + case 'SMART_IO_PORT': + ports.push({ portType: 'smartio', portNumber: 1 }); + break; + case 'SMART_MOTOR_PORT': + ports.push({ portType: 'MotionCore port', portNumber: 1 }); + break; + case 'SERVO_PORT': + ports.push({ portType: 'servo', portNumber: 1 }); + break; + case 'I2C_PORT': + ports.push({ portType: 'i2c', portNumber: 1 }); + break; + case 'USB_PORT': + ports.push({ portType: 'usb', portNumber: 1 }); + break; + case 'EXPANSION_HUB_MOTOR_PORT': + ports.push({ portType: 'motor', portNumber: 1 }); + break; + case 'EXPANSION_HUB_SERVO_PORT': + ports.push({ portType: 'servo', portNumber: 1 }); + break; + case 'USB_HUB': + ports.push({ portType: 'usb in', portNumber: 1 }); + ports.push({ portType: 'usb out', portNumber: 1 }); + break; + case 'EXPANSION_HUB_MOTOR': + ports.push({ portType: 'usb in', portNumber: 1 }); + ports.push({ portType: 'motor', portNumber: 1 }); + break; + case 'EXPANSION_HUB_SERVO': + ports.push({ portType: 'usb in', portNumber: 1 }); + ports.push({ portType: 'servo', portNumber: 1 }); + break; + default: + ports.push({ portType: 'unknown' + portType, portNumber: 1 }); + break; + } return { - shadow: { + block: { type: 'mrc_port', - fields: { - TYPE: portType, - PORT_NUM: portNum, + extraState: { + portType: portType, + ports: ports.map(port => ({ + portType: port.portType, + portNumber: port.portNumber + })) }, }, }; diff --git a/src/blocks/utils/generated/external_samples_data.json b/src/blocks/utils/generated/external_samples_data.json index 08c503d3..ff1306f2 100644 --- a/src/blocks/utils/generated/external_samples_data.json +++ b/src/blocks/utils/generated/external_samples_data.json @@ -19,11 +19,12 @@ "args": [ { "defaultValue": "", - "name": "ports", - "type": "list[tuple[component.PortType, int]]" + "name": "port", + "type": "port.Port" } ], "declaringClassName": "color_range_sensor.ColorRangeSensor", + "expectedPortType": "I2C_PORT", "functionName": "__init__", "returnType": "color_range_sensor.ColorRangeSensor", "tooltip": "" @@ -67,7 +68,7 @@ ], "declaringClassName": "color_range_sensor.ColorRangeSensor", "functionName": "get_connection_port_type", - "returnType": "list[component.PortType]", + "returnType": "port.PortType | None", "tooltip": "" }, { @@ -295,21 +296,7 @@ ], "instanceVariables": [], "moduleName": "color_range_sensor", - "staticMethods": [ - { - "args": [ - { - "defaultValue": "", - "name": "i2c_port", - "type": "int" - } - ], - "declaringClassName": "color_range_sensor.ColorRangeSensor", - "functionName": "from_i2c_port", - "returnType": "color_range_sensor.ColorRangeSensor", - "tooltip": "" - } - ] + "staticMethods": [] }, { "className": "color_range_sensor.DistanceCallable", @@ -329,14 +316,19 @@ "args": [ { "defaultValue": "", - "name": "ports", - "type": "list[tuple[component.PortType, int]]" + "name": "port", + "type": "port.Port" + }, + { + "defaultValue": "", + "name": "expectedType", + "type": "port.PortType" } ], "declaringClassName": "component.Component", "functionName": "__init__", "returnType": "component.Component", - "tooltip": "" + "tooltip": "This has the port it is attached to, and the expected type of the port" } ], "enums": [], @@ -351,7 +343,7 @@ ], "declaringClassName": "component.Component", "functionName": "get_connection_port_type", - "returnType": "list[component.PortType]", + "returnType": "port.PortType | None", "tooltip": "" }, { @@ -536,11 +528,12 @@ "args": [ { "defaultValue": "", - "name": "ports", - "type": "list[tuple[component.PortType, int]]" + "name": "port", + "type": "port.Port" } ], "declaringClassName": "expansion_hub_motor.ExpansionHubMotor", + "expectedPortType": "EXPANSION_HUB_MOTOR", "functionName": "__init__", "returnType": "expansion_hub_motor.ExpansionHubMotor", "tooltip": "" @@ -623,7 +616,7 @@ ], "declaringClassName": "expansion_hub_motor.ExpansionHubMotor", "functionName": "get_connection_port_type", - "returnType": "list[component.PortType]", + "returnType": "port.PortType | None", "tooltip": "" }, { @@ -929,26 +922,7 @@ ], "instanceVariables": [], "moduleName": "expansion_hub_motor", - "staticMethods": [ - { - "args": [ - { - "defaultValue": "", - "name": "hub_number", - "type": "int" - }, - { - "defaultValue": "", - "name": "motor_number", - "type": "int" - } - ], - "declaringClassName": "expansion_hub_motor.ExpansionHubMotor", - "functionName": "from_hub_number_and_motor_number", - "returnType": "expansion_hub_motor.ExpansionHubMotor", - "tooltip": "" - } - ] + "staticMethods": [] }, { "className": "expansion_hub_servo.ExpansionHubServo", @@ -958,11 +932,12 @@ "args": [ { "defaultValue": "", - "name": "ports", - "type": "list[tuple[component.PortType, int]]" + "name": "port", + "type": "port.Port" } ], "declaringClassName": "expansion_hub_servo.ExpansionHubServo", + "expectedPortType": "EXPANSION_HUB_SERVO", "functionName": "__init__", "returnType": "expansion_hub_servo.ExpansionHubServo", "tooltip": "" @@ -980,7 +955,7 @@ ], "declaringClassName": "expansion_hub_servo.ExpansionHubServo", "functionName": "get_connection_port_type", - "returnType": "list[component.PortType]", + "returnType": "list[port.PortType]", "tooltip": "" }, { @@ -1219,26 +1194,175 @@ ], "instanceVariables": [], "moduleName": "expansion_hub_servo", - "staticMethods": [ + "staticMethods": [] + }, + { + "className": "port.CompoundPort", + "classVariables": [], + "constructors": [ { "args": [ { "defaultValue": "", - "name": "hub_number", - "type": "int" + "name": "port_type", + "type": "port.PortType" }, { "defaultValue": "", - "name": "servo_number", - "type": "int" + "name": "port1", + "type": "port.Port" + }, + { + "defaultValue": "", + "name": "port2", + "type": "port.Port" } ], - "declaringClassName": "expansion_hub_servo.ExpansionHubServo", - "functionName": "from_hub_number_and_servo_number", - "returnType": "expansion_hub_servo.ExpansionHubServo", + "declaringClassName": "port.CompoundPort", + "functionName": "__init__", + "returnType": "port.CompoundPort", + "tooltip": "\n Create a compound port from two other ports.\n\n Args:\n port_type: PortType for this port (must be a compound type)\n port1: First Port for compound ports\n port2: Second Port for compound ports\n " + } + ], + "enums": [], + "instanceMethods": [ + { + "args": [ + { + "defaultValue": "", + "name": "self", + "type": "port.CompoundPort" + } + ], + "declaringClassName": "port.CompoundPort", + "functionName": "get_all_ports", + "returnType": "list[tuple[port.PortType, int]]", + "tooltip": "Return a list of all simple ports contained in this compound port." + }, + { + "args": [ + { + "defaultValue": "", + "name": "self", + "type": "port.CompoundPort" + } + ], + "declaringClassName": "port.CompoundPort", + "functionName": "get_type", + "returnType": "port.PortType", + "tooltip": "Returns the port type" + } + ], + "instanceVariables": [], + "moduleName": "port", + "staticMethods": [] + }, + { + "className": "port.Port", + "classVariables": [], + "constructors": [ + { + "args": [ + { + "defaultValue": "", + "name": "port_type", + "type": "port.PortType" + } + ], + "declaringClassName": "port.Port", + "functionName": "__init__", + "returnType": "port.Port", "tooltip": "" } - ] + ], + "enums": [], + "instanceMethods": [ + { + "args": [ + { + "defaultValue": "", + "name": "self", + "type": "port.Port" + } + ], + "declaringClassName": "port.Port", + "functionName": "get_all_ports", + "returnType": "list[tuple[port.PortType, int]]", + "tooltip": "Return a list of all simple ports contained in this port." + }, + { + "args": [ + { + "defaultValue": "", + "name": "self", + "type": "port.Port" + } + ], + "declaringClassName": "port.Port", + "functionName": "get_type", + "returnType": "port.PortType", + "tooltip": "Returns the port type" + } + ], + "instanceVariables": [], + "moduleName": "port", + "staticMethods": [] + }, + { + "className": "port.SimplePort", + "classVariables": [], + "constructors": [ + { + "args": [ + { + "defaultValue": "", + "name": "port_type", + "type": "port.PortType" + }, + { + "defaultValue": "", + "name": "location", + "type": "int" + } + ], + "declaringClassName": "port.SimplePort", + "functionName": "__init__", + "returnType": "port.SimplePort", + "tooltip": "\n Create a simple port with a type and location.\n\n Args:\n port_type: PortType for this port (must be a simple type)\n location: int location for this port\n " + } + ], + "enums": [], + "instanceMethods": [ + { + "args": [ + { + "defaultValue": "", + "name": "self", + "type": "port.SimplePort" + } + ], + "declaringClassName": "port.SimplePort", + "functionName": "get_all_ports", + "returnType": "list[tuple[port.PortType, int]]", + "tooltip": "Return a list containing this simple port." + }, + { + "args": [ + { + "defaultValue": "", + "name": "self", + "type": "port.SimplePort" + } + ], + "declaringClassName": "port.SimplePort", + "functionName": "get_type", + "returnType": "port.PortType", + "tooltip": "Returns the port type" + } + ], + "instanceVariables": [], + "moduleName": "port", + "staticMethods": [] }, { "className": "rev_touch_sensor.RevTouchSensor", @@ -1248,11 +1372,12 @@ "args": [ { "defaultValue": "", - "name": "ports", - "type": "list[tuple[component.PortType, int]]" + "name": "port", + "type": "port.Port" } ], "declaringClassName": "rev_touch_sensor.RevTouchSensor", + "expectedPortType": "SMART_IO_PORT", "functionName": "__init__", "returnType": "rev_touch_sensor.RevTouchSensor", "tooltip": "" @@ -1270,7 +1395,7 @@ ], "declaringClassName": "rev_touch_sensor.RevTouchSensor", "functionName": "get_connection_port_type", - "returnType": "list[component.PortType]", + "returnType": "port.PortType | None", "tooltip": "" }, { @@ -1455,21 +1580,7 @@ ], "instanceVariables": [], "moduleName": "rev_touch_sensor", - "staticMethods": [ - { - "args": [ - { - "defaultValue": "", - "name": "smart_io_port", - "type": "int" - } - ], - "declaringClassName": "rev_touch_sensor.RevTouchSensor", - "functionName": "from_smart_io_port", - "returnType": "rev_touch_sensor.RevTouchSensor", - "tooltip": "" - } - ] + "staticMethods": [] }, { "className": "servo.Servo", @@ -1479,11 +1590,12 @@ "args": [ { "defaultValue": "", - "name": "ports", - "type": "list[tuple[component.PortType, int]]" + "name": "port", + "type": "port.Port" } ], "declaringClassName": "servo.Servo", + "expectedPortType": "SERVO_PORT", "functionName": "__init__", "returnType": "servo.Servo", "tooltip": "" @@ -1501,7 +1613,7 @@ ], "declaringClassName": "servo.Servo", "functionName": "get_connection_port_type", - "returnType": "list[component.PortType]", + "returnType": "port.PortType | None", "tooltip": "" }, { @@ -1673,21 +1785,7 @@ ], "instanceVariables": [], "moduleName": "servo", - "staticMethods": [ - { - "args": [ - { - "defaultValue": "", - "name": "servo_port", - "type": "int" - } - ], - "declaringClassName": "servo.Servo", - "functionName": "from_servo_port", - "returnType": "servo.Servo", - "tooltip": "" - } - ] + "staticMethods": [] }, { "className": "smart_motor.SmartMotor", @@ -1697,11 +1795,12 @@ "args": [ { "defaultValue": "", - "name": "ports", - "type": "list[tuple[component.PortType, int]]" + "name": "port", + "type": "port.Port" } ], "declaringClassName": "smart_motor.SmartMotor", + "expectedPortType": "SMART_MOTOR_PORT", "functionName": "__init__", "returnType": "smart_motor.SmartMotor", "tooltip": "" @@ -1732,7 +1831,7 @@ ], "declaringClassName": "smart_motor.SmartMotor", "functionName": "get_connection_port_type", - "returnType": "list[component.PortType]", + "returnType": "port.PortType | None", "tooltip": "" }, { @@ -1930,21 +2029,7 @@ ], "instanceVariables": [], "moduleName": "smart_motor", - "staticMethods": [ - { - "args": [ - { - "defaultValue": "", - "name": "smart_motor_port", - "type": "int" - } - ], - "declaringClassName": "smart_motor.SmartMotor", - "functionName": "from_smart_motor_port", - "returnType": "smart_motor.SmartMotor", - "tooltip": "" - } - ] + "staticMethods": [] }, { "className": "spark_mini.SparkMini", @@ -1954,11 +2039,12 @@ "args": [ { "defaultValue": "", - "name": "ports", - "type": "list[tuple[component.PortType, int]]" + "name": "port", + "type": "port.Port" } ], "declaringClassName": "spark_mini.SparkMini", + "expectedPortType": "SMART_MOTOR_PORT", "functionName": "__init__", "returnType": "spark_mini.SparkMini", "tooltip": "" @@ -2077,7 +2163,7 @@ ], "declaringClassName": "spark_mini.SparkMini", "functionName": "get_connection_port_type", - "returnType": "list[component.PortType]", + "returnType": "port.PortType | None", "tooltip": "" }, { @@ -2419,19 +2505,6 @@ "functionName": "check_motors", "returnType": "None", "tooltip": "Check the motors to see if any have timed out.\n\nThis static method is called periodically to poll all the motors and stop\nany that have timed out." - }, - { - "args": [ - { - "defaultValue": "", - "name": "smart_motor_port", - "type": "int" - } - ], - "declaringClassName": "spark_mini.SparkMini", - "functionName": "from_smart_motor_port", - "returnType": "spark_mini.SparkMini", - "tooltip": "" } ] }, @@ -2443,11 +2516,12 @@ "args": [ { "defaultValue": "", - "name": "ports", - "type": "list[tuple[component.PortType, int]]" + "name": "port", + "type": "port.Port" } ], "declaringClassName": "sparkfun_led_stick.SparkFunLEDStick", + "expectedPortType": "I2C_PORT", "functionName": "__init__", "returnType": "sparkfun_led_stick.SparkFunLEDStick", "tooltip": "" @@ -2465,7 +2539,7 @@ ], "declaringClassName": "sparkfun_led_stick.SparkFunLEDStick", "functionName": "get_connection_port_type", - "returnType": "list[component.PortType]", + "returnType": "port.PortType | None", "tooltip": "" }, { @@ -2668,21 +2742,7 @@ ], "instanceVariables": [], "moduleName": "sparkfun_led_stick", - "staticMethods": [ - { - "args": [ - { - "defaultValue": "", - "name": "i2c_port", - "type": "int" - } - ], - "declaringClassName": "sparkfun_led_stick.SparkFunLEDStick", - "functionName": "from_i2c_port", - "returnType": "sparkfun_led_stick.SparkFunLEDStick", - "tooltip": "" - } - ] + "staticMethods": [] } ], "modules": [ @@ -2693,21 +2753,7 @@ "moduleVariables": [] }, { - "enums": [ - { - "enumClassName": "component.PortType", - "enumValues": [ - "CAN_PORT", - "I2C_PORT", - "SERVO_PORT", - "SMART_IO_PORT", - "SMART_MOTOR_PORT", - "USB_PORT" - ], - "moduleName": "component", - "tooltip": "" - } - ], + "enums": [], "functions": [], "moduleName": "component", "moduleVariables": [] @@ -2724,6 +2770,32 @@ "moduleName": "expansion_hub_servo", "moduleVariables": [] }, + { + "enums": [ + { + "enumClassName": "port.PortType", + "enumValues": [ + "BASE_COMPOUND", + "CAN_PORT", + "EXPANSION_HUB_MOTOR", + "EXPANSION_HUB_MOTOR_PORT", + "EXPANSION_HUB_SERVO", + "EXPANSION_HUB_SERVO_PORT", + "I2C_PORT", + "SERVO_PORT", + "SMART_IO_PORT", + "SMART_MOTOR_PORT", + "USB_HUB", + "USB_PORT" + ], + "moduleName": "port", + "tooltip": "" + } + ], + "functions": [], + "moduleName": "port", + "moduleVariables": [] + }, { "enums": [], "functions": [], @@ -2765,6 +2837,10 @@ "smart_motor.SmartMotor", "spark_mini.SparkMini", "sparkfun_led_stick.SparkFunLEDStick" + ], + "port.Port": [ + "port.CompoundPort", + "port.SimplePort" ] } } diff --git a/src/blocks/utils/python_json_types.ts b/src/blocks/utils/python_json_types.ts index 407893d6..2c2bbc1f 100644 --- a/src/blocks/utils/python_json_types.ts +++ b/src/blocks/utils/python_json_types.ts @@ -59,6 +59,8 @@ export class FunctionData { returnType: string = ''; args: ArgData[] = []; declaringClassName?: string = ''; + // TODO: Make this less hacky... + expectedPortType?: string = ''; } export class ArgData { diff --git a/src/toolbox/test_category.ts b/src/toolbox/test_category.ts index f46179a3..04bc1701 100644 --- a/src/toolbox/test_category.ts +++ b/src/toolbox/test_category.ts @@ -20,6 +20,17 @@ export function getCategory(): toolboxItems.Category { addBuiltInFunctionBlocks([printFunction], contents); + contents.push({ + kind: 'block', + type: 'mrc_port', + extraState: { + ports: [ + { portType: 'USB Port', portNumber: 1 }, + { portType: 'Expansion Hub Motor Port', portNumber: 2 }, + ], + }, + }); + return { kind: 'category', name: Blockly.Msg['MRC_CATEGORY_TEST'],