Skip to content

Commit

Permalink
Enable multiple signal config of each type
Browse files Browse the repository at this point in the history
Signed-off-by: Xiyu Oh <[email protected]>
  • Loading branch information
xiyuoh committed Dec 24, 2024
1 parent e9c0700 commit 9d49b7e
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 74 deletions.
22 changes: 17 additions & 5 deletions configs/mir_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,26 @@ plugins:
actions: ["wait_until"]
timeout: 60 # Optional, in seconds. Timeout if there is no move-off signal received, after which robot will move off regardless. Default to 60 seconds.
update_gap: 30 # Optional, in seconds. The fleet adapter will log an update using this interval. Default to 30 seconds.
signal_type: # Defines the default configs for various move off signal types. If this field is not provided, the robot will wait the full duration of the default timeout. Signal configs can be overridden by task description.
signals: # Configure various move off signal types. If this field is not provided in the fleet config or task description, the robot will wait the full duration of the default timeout.
# Currently three signal types are supported: [mission, plc, custom].
mission:
sample_mission_1: # Only a unique name
signal_type: "mission" # Specifies the actual type
mission_name: "some_mission_name" # If this mission is found on the robot, it will be queued when the action starts, and the robot will move off when the mission is completed.
retry_count: 10 # Optional field for signal type "mission". The fleet adapter will re-attempt queueing the mission for this number of tries.
resubmit_on_abort: False # Optional field for signal type "mission". Set to True to resubmit mission if the mission gets aborted by the robot. Default to False.
plc:
sample_mission_2:
signal_type: "mission" # Specifies the actual type
mission_name: "some_other_mission_name"
sample_plc_1:
signal_type: "plc" # Specifies the actual type
register: 20 # Fill in with PLC register number. Robot moves off when this PLC register returns True
custom:
sample_plc_2:
signal_type: "plc" # Specifies the actual type
register: 21
custom_1:
signal_type: "custom" # Specifies the actual type
module: "fleet_adapter_mir_actions.rmf_move_off_on_alert" # Fill in with path to custom module written. Robot moves off when module provides the user-defined move off signal.
default: "custom" # Specifies a default signal type out of those listed above. If the task description does not provide any signal config, the fleet adapter will use this default signal type.
custom_2:
signal_type: "custom" # Specifies the actual type
module: "fleet_adapter_mir_actions.rmf_move_off_on_something_else"
default_signal: "custom_1" # Specifies a default signal type out of those listed above. If the task description does not provide any signal config, the fleet adapter will use this default signal type.
188 changes: 119 additions & 69 deletions fleet_adapter_mir_actions/fleet_adapter_mir_actions/rmf_wait_until.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,86 +26,82 @@ def __init__(self, context: ActionContext):
MirActionFactory.__init__(self, context)
self.move_off = None
# Raise error if config file is invalid
if 'signal_type' in context.action_config:
signal_type = context.action_config['signal_type']
if 'mission' in signal_type:
if 'mission_name' not in signal_type['mission']:
raise KeyError(
f'WaitUntil MirAction requires a default mission name '
f'for signal type [mission], but mission name is not '
f'provided in the action config! Unable to '
f'instantiate an ActionFactory.')
if 'retry_count' in signal_type['mission']:
if signal_type['mission']['retry_count'] < 0:
raise ValueError(
f'WaitUntil MirAction takes in a retry count for '
f'signal type [mission], but retry count provided '
f'is invalid! Unable to instantiate an '
f'ActionFactory.')
if 'plc' in signal_type:
plc_register = signal_type['plc'].get('register')
if plc_register is None:
raise KeyError(
f'WaitUntil MirAction requires a default PLC register '
f'for signal type [plc], but no PLC register was '
f'provided in the action config! Unable to '
f'instantiate an ActionFactory.')
elif not isinstance(plc_register, int):
raise TypeError(
f'WaitUntil MirAction requires a default PLC register '
f'number for signal type [plc], but the value '
f'provided [{plc_register}] is not an integer! '
f'Unable to instantiate an ActionFactory.')
# If user provides a custom MoveOff module, import it
if 'custom' in signal_type:
if 'module' not in signal_type['custom']:
raise KeyError(
f'WaitUntil MirAction requires a custom module for '
f'signal type [custom], but path to module is not '
f'provided in the action config! Unable to '
f'instantiate an ActionFactory.')
try:
move_off_module = context.action_config['module']
move_off_plugin = importlib.import_module(move_off_module)
self.move_off = move_off_plugin.MoveOff(self.context)
except ImportError:
self.context.node.get_logger().warn(
f'Unable to import module {move_off_module}! The '
f'WaitUntil ActionFactory will be instantiated '
f'without supporting any user-defined signal module.'
supported_signal_types = {'mission', 'plc', 'custom'}
valid_signals = 0
signals = context.action_config.get('signals')
if signals is not None:
for signal_name, signal_config in signals.items():
signal_type = signal_config.get('signal_type')
if signal_type is None:
self.context.node.error(
f'WaitUntil MirAction signal config requires a '
f'defined signal_type, but signal_type is not '
f'provided for {signal_name}! Skipping configuration '
f'for {signal_name}.'
)
supported_signal_types = {'mission', 'plc', 'custom'}
default_signal = signal_type.get('default')
if default_signal is None:
self.context.node.get_logger().warn(
f'WaitUntil ActionFactory instantiated for robot '
f'[{self.context.name}], but no default signal type has '
f'been provided in the action config! If no signal type '
f'is populated in the task description, there will be no '
f'move off signal available and the robot will wait for '
f'the full duration of the timeout when this action is '
f'triggered.'
)
elif default_signal not in supported_signal_types:
continue
elif signal_type not in supported_signal_types:
self.context.node.error(
f'WaitUntil MirAction signal config requires a '
f'defined signal_type, but signal_type provided '
f'{signal_name} is not supported! We currently '
f'support the following signal types: '
f'{supported_signal_types}. Skipping configuration '
f'for {signal_name}.'
)
continue

# Pass signal config into verify functions to validate types
# and catch missing values
match signal_type:
case 'mission':
if self.verify_mission(
signal_config.get('mission_name'),
signal_config.get('retry_count'),
signal_config.get('resubmit_on_abort')
):
valid_signals += 1
case 'plc':
if self.verify_plc(signal_config.get('register')):
valid_signals += 1
case 'custom':
if self.verify_custom_module(
signal_config.get('module')):
valid_signals += 1
if valid_signals < 1:
# If the user did not provide any valid signal config, log a
# warning to remind users to provide signal type config in any task
# description they submit
self.context.node.get_logger().warn(
f'WaitUntil ActionFactory is instantiated for robot '
f'[{self.context.name}], but no valid signals config has been '
f'provided in the action config! Any move off signal config '
f'will have to be provided in the task description submitted '
f'to RMF.'
)
return

# Register default signal type
default_signal = context.action_config.get('default')
if default_signal is not None:
if default_signal not in supported_signal_types:
raise ValueError(
f'User provided a default signal type {default_signal} '
f'in the action config for WaitUntil MirAction, but the '
f'default signal type is not supported!')
elif default_signal not in signal_type:
elif default_signal not in signals.keys():
raise ValueError(
f'User provided a default signal type {default_signal} '
f'in the action config for WaitUntil MirAction, but the '
f'default signal type is not configured!')
else:
# If the user did not provide any default signal config, log a
# warning to remind users to provide signal type config in any task
# description they submit
self.context.node.get_logger().warn(
f'WaitUntil ActionFactory is instantiated for robot '
f'[{self.context.name}], but no signal_type config has been '
f'provided in the action config! Any move off signal config '
f'will have to be provided in the task description submitted '
f'to RMF.'
f'WaitUntil ActionFactory instantiated for robot '
f'[{self.context.name}], but no default signal type has been '
f'provided in the action config! If no signal type is '
f'populated in the task description, there will be no move '
f'off signal available and the robot will wait for the full '
f'duration of the timeout when this action is triggered.'
)

def supports_action(self, category: str) -> bool:
Expand All @@ -126,6 +122,60 @@ def perform_action(
return WaitUntil(
description, execution, self.context, self.move_off)

def verify_mission(
self,
signal_name: str,
mission_name: str | None,
retry_count: int | None,
resubmit_on_abort: bool | None) -> bool:
if mission_name is None:
self.context.node.error(
f'WaitUntil MirAction requires a mission name for the '
f'[mission] signal type, but mission name is not provided in '
f'the action config! Skipping configuration for {signal_name}.'
)
return False
if retry_count is not None and retry_count < 0:
self.context.node.warn(
f'WaitUntil MirAction takes in a retry count for the '
f'[mission] signal type, but the value provided '
f'[{retry_count}] is invalid! Ignoring provided value and '
f'using the default retry count of 10 for {signal_name}.'
)
return True

def verify_plc(self, signal_name: str, plc_register: int | None) -> bool:
if plc_register is None:
self.context.node.error(
f'WaitUntil MirAction requires a default PLC register for '
f'signal type [plc], but no PLC register was provided in the '
f'action config! Skipping configuration for {signal_name}.'
)
return False
return True

def verify_custom_module(
self,
signal_name: str,
module: str | None) -> bool:
if module is None:
self.context.node.error(
f'WaitUntil MirAction requires a custom module for the '
f'[custom] signal type, but path to module is not provided in '
f'the action config! Skipping configuration for {signal_name}.'
)
return False
try:
move_off_plugin = importlib.import_module(module)
self.move_off = move_off_plugin.MoveOff(self.context)
except ImportError:
self.context.node.get_logger().warn(
f'Unable to import module {module}! Skipping configuration '
f'for {signal_name}.'
)
return False
return True


class WaitUntil(MirAction):
def __init__(
Expand Down

0 comments on commit 9d49b7e

Please sign in to comment.