Skip to content

Commit

Permalink
Merge branch 'fast-0061-stepper' into fast-0061-stepper-0.80
Browse files Browse the repository at this point in the history
# Conflicts:
#	mpf/_version.py
  • Loading branch information
avanwinkle committed Oct 12, 2024
2 parents 2884054 + e2503c2 commit 32bfade
Show file tree
Hide file tree
Showing 13 changed files with 242 additions and 40 deletions.
2 changes: 1 addition & 1 deletion mpf/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"""

__version__ = '0.80.0.dev4'
__version__ = '0.80.0.dev5'
'''The full version of MPF.'''

__short_version__ = '0.80'
Expand Down
12 changes: 10 additions & 2 deletions mpf/config_spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,9 @@ fast_servos:
min_us: single|int|1000
home_us: single|int|1500
max_runtime: single|ms|2000 # 65535 max
fast_steppers:
__valid_in__: machine # todo add to validator
__allow_others__:
file_shows:
__valid_in__: machine, mode # todo add to validator
__type__: config_dict
Expand Down Expand Up @@ -1785,23 +1788,28 @@ spinners:
steppers:
__valid_in__: machine
__type__: device
named_positions: dict|float:str|None
home_events: event_handler|event_handler:ms|None
home_on_startup: single|bool|true
homing_mode: single|enum(hardware,switch)|hardware
homing_switch: single|machine(switches)|None
homing_direction: single|enum(clockwise,counterclockwise)|clockwise
homing_speed: single|int|None
pos_min: single|int|0
pos_max: single|int|1000
ball_search_min: single|int|0
ball_search_max: single|int|1
ball_search_wait: single|ms|5s
include_in_ball_search: single|bool|true
relative_positions: dict|float:str|None
relative_positions: dict|float:subconfig(stepper_position_settings)|None
reset_position: single|int|0
reset_events: event_handler|event_handler:ms|machine_reset_phase_3, ball_starting, ball_will_end, service_mode_entered
named_positions: dict|float:subconfig(stepper_position_settings)|None
number: single|str|
platform: single|str|None
platform_settings: single|dict|None
stepper_position_settings:
event: single|str|
speed: single|int|None
spike_stepper_settings:
homing_speed: single|int|10
speed: single|int|20
Expand Down
46 changes: 32 additions & 14 deletions mpf/devices/stepper.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@ class Stepper(SystemWideDevice):
class_label = 'stepper'

__slots__ = ["hw_stepper", "_target_position", "_current_position", "_ball_search_started",
"_ball_search_old_target", "_is_homed", "_is_moving", "_move_task", "delay"]
"_ball_search_old_target", "_is_homed", "_is_moving", "_move_task", "delay",
"_target_speed"]

def __init__(self, machine, name):
"""Initialize stepper."""
self.hw_stepper = None # type: Optional[StepperPlatformInterface]
self.platform = None # type: Optional[Stepper]
self._target_position = 0 # in user units
self._current_position = 0 # in user units
self._target_speed = None # in steps per second
self._ball_search_started = False
self._ball_search_old_target = 0
self._is_homed = False
Expand All @@ -51,14 +53,16 @@ async def _initialize(self):
self._target_position = self.config['reset_position']

for position in self.config['named_positions']:
self.machine.events.add_handler(self.config['named_positions'][position],
self.machine.events.add_handler(self.config['named_positions'][position]['event'],
self.event_move_to_position,
position=position)
position=position,
speed=self.config['named_positions'][position]['speed'])

for position in self.config['relative_positions']:
self.machine.events.add_handler(self.config['relative_positions'][position],
self.machine.events.add_handler(self.config['relative_positions'][position]['event'],
self.event_move_to_position,
position=position,
speed=[self.config['relative_positions'][position]['speed']],
is_relative=True)

if not self.platform.features['allow_empty_numbers'] and self.config['number'] is None:
Expand All @@ -82,6 +86,13 @@ async def _initialize(self):

def validate_and_parse_config(self, config, is_mode_config, debug_prefix: str = None):
"""Validate stepper config."""
# If positions are just strings, expand them into strings and speeds
# TODO: Figure out how to use express_config() to map this
for cfg in ('named_positions', 'relative_positions'):
if cfg in config:
for pos, value in config[cfg].items():
if isinstance(value, str):
config[cfg][pos] = { 'event': value }
config = super().validate_and_parse_config(config, is_mode_config, debug_prefix)
platform = self.machine.get_platform_sections(
'stepper_controllers', getattr(config, "platform", None))
Expand All @@ -94,9 +105,13 @@ async def _run(self):
# wait for switches to be initialized
await self.machine.events.wait_for_event("init_phase_3")

# first home the stepper
self.info_log("Initializing stepper and homing.")
await self._home()
if self.config['home_on_startup']:
# first home the stepper
self.info_log("Initializing stepper and homing.")
await self._home()
else:
self.info_log("Initializing stepper but will not home.")
self._is_homed = True

# run the loop at least once
self._is_moving.set()
Expand All @@ -119,23 +134,26 @@ async def _run(self):
self.info_log("Stepper moving relative %s to hit target %s from %s",
delta, target_position, self._current_position)
# move stepper
self.hw_stepper.move_rel_pos(delta)
self.hw_stepper.move_rel_pos(delta, self._target_speed)
# wait for the move to complete
await self.hw_stepper.wait_for_move_completed()
else:
self.info_log("Got command to move (relative) to %s, but already there. Not moving.",
self._target_position)
# set current position
self._current_position = target_position
# Clear the speed override
self._target_speed = None
# post ready event
self._post_ready_event()

def _move_to_absolute_position(self, position):
def _move_to_absolute_position(self, position, speed=None):
"""Move stepper to position."""
self.info_log("Moving to absolute position %s. Current position: %s",
self.info_log("%s: Moving to absolute position %s. Current position: %s",
self.hw_stepper, position, self._current_position)
if self.config['pos_min'] <= position <= self.config['pos_max']:
self._target_position = position
self._target_speed = speed
self._is_moving.set()
else:
raise ValueError("_move_to_absolute_position: position argument beyond limits")
Expand Down Expand Up @@ -201,22 +219,22 @@ def reset(self):
self._move_to_absolute_position(self.config['reset_position'])

@event_handler(5)
def event_move_to_position(self, position=None, is_relative=False, **kwargs):
def event_move_to_position(self, position=None, speed=None, is_relative=False, **kwargs):
"""Event handler for move_to_position event."""
del kwargs
if position is None:
raise AssertionError("move_to_position event is missing a position.")

self.move_to_position(position, is_relative)
self.move_to_position(position, speed, is_relative)

def move_to_position(self, position, is_relative=False):
def move_to_position(self, position, speed=None, is_relative=False):
"""Move stepper to a position."""
self.info_log("Stepper at %s moving to %s position %s", self._current_position,
"relative" if is_relative else "absolute", position)
self._target_position = (self._current_position + position) if is_relative else position
if self._ball_search_started:
return
self._move_to_absolute_position(self._target_position)
self._move_to_absolute_position(self._target_position, speed)

def _ball_search_start(self, **kwargs):
del kwargs
Expand Down
1 change: 1 addition & 0 deletions mpf/modes/service/code/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ def _generate_light_chains(self): # pylint: disable=too-many-locals
return

items.sort(key=lambda x: x.chain)
return items

async def _volume_menu(self, platform=None):
position = 0
Expand Down
22 changes: 21 additions & 1 deletion mpf/platforms/fast/communicators/exp.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""FAST Expansion Board Serial Communicator."""
# mpf/platforms/fast/communicators/exp.py

from functools import partial

from mpf.platforms.fast.fast_defines import EXPANSION_BOARD_FEATURES
from mpf.platforms.fast.fast_exp_board import FastExpansionBoard
from mpf.platforms.fast.communicators.base import FastSerialCommunicator
Expand All @@ -18,14 +20,16 @@ class FastExpCommunicator(FastSerialCommunicator):

IGNORED_MESSAGES = ['XX:F']

__slots__ = ["exp_boards_by_address", "active_board"]
__slots__ = ["exp_boards_by_address", "active_board", "_device_processors"]

def __init__(self, platform, processor, config):
"""Initialize the EXP communicator."""
super().__init__(platform, processor, config)

self.exp_boards_by_address = dict() # keys = board addresses, values = FastExpansionBoard objects
self._device_processors = dict()
self.active_board = None

self.message_processors['BR:'] = self._process_br

async def init(self):
Expand Down Expand Up @@ -104,3 +108,19 @@ def set_led_fade_rate(self, board_address: str, rate: int) -> None:

self.platform.debug_log("%s - Setting LED fade rate to %sms", self, rate)
self.send_and_forget(f'RF@{board_address}:{Util.int_to_hex_string(rate, True)}')

def register_processor(self, message_prefix, board_address, device_id, callback):
if message_prefix not in self.message_processors:
self.message_processors[message_prefix] = partial(self._process_device_msg, message_prefix)
self._device_processors[message_prefix]= dict()
if board_address not in self._device_processors[message_prefix]:
self._device_processors[message_prefix][board_address] = dict()
self._device_processors[message_prefix][board_address][device_id] = callback

def _process_device_msg(self, message_prefix, message):
# Commands like MS: currently don't include the EXP board in the response,
# so there's no way to know which board needs to be informed. Inform them
# all? If multiple boards are running concurrently, it'll get ugly.
device_id = message.split(",")[0]
for board_callback in self._device_processors[message_prefix].values():
board_callback[device_id](message)
2 changes: 1 addition & 1 deletion mpf/platforms/fast/communicators/net_neuron.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ def update_switches_from_hw_data(self):
This will silently sync the switch.hw_state. If the logical state changes,
it will process it like any switch change.
"""
for switch in self.machine.switches.values():
for switch in [sw for sw in self.machine.switches.values() if sw.platform == self.platform]:
hw_state = self.platform.hw_switch_data[switch.hw_switch.number]

if hw_state != switch.hw_state:
Expand Down
38 changes: 37 additions & 1 deletion mpf/platforms/fast/fast.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from mpf.core.platform import (RgbDmdPlatform, DriverConfig, DriverSettings,
LightsPlatform, RepulseSettings,
SegmentDisplayPlatform, ServoPlatform,
StepperPlatform,
SwitchConfig, SwitchSettings)
from mpf.core.utility_functions import Util
from mpf.exceptions.config_file_error import ConfigFileError
Expand All @@ -24,14 +25,15 @@
from mpf.platforms.fast.fast_port_detector import FastPortDetector
from mpf.platforms.fast.fast_segment_display import FASTSegmentDisplay
from mpf.platforms.fast.fast_servo import FastServo
from mpf.platforms.fast.fast_stepper import FastStepper
from mpf.platforms.fast.fast_switch import FASTSwitch
# pylint: disable-msg=too-many-instance-attributes
from mpf.platforms.interfaces.light_platform_interface import LightPlatformInterface
from mpf.platforms.system11 import System11OverlayPlatform


class FastHardwarePlatform(ServoPlatform, LightsPlatform, RgbDmdPlatform,
SegmentDisplayPlatform,
SegmentDisplayPlatform, StepperPlatform,
System11OverlayPlatform):

"""Platform class for the FAST Pinball hardware."""
Expand Down Expand Up @@ -428,6 +430,40 @@ async def configure_servo(self, number: str, config: dict) -> FastServo:

return FastServo(brk_board, port, config)

async def configure_stepper(self, number: str, config: dict) -> FastStepper:
"""Configure a servo.
Args:
----
number: Number of stepper
config: Dict of config settings.
Returns: Stepper object.
"""
# TODO consolidate with similar code in configure_light()
number = number.lower()
parts = number.split("-")

exp_board = self.exp_boards_by_name[parts[0]]

try:
_, port = parts
breakout_id = '0'
except ValueError:
_, breakout_id, port = parts
breakout_id = breakout_id.strip('b')

brk_board = exp_board.breakouts[breakout_id]

# verify this board support servos
assert int(port) <= int(brk_board.features['stepper_ports']) # TODO should this be stored as an int?

if 'platform_settings' in config:
config.update(self.machine.config_validator.validate_config('fast_steppers', config['platform_settings']))
del config['platform_settings']

return FastStepper(brk_board, port, config)

def _parse_switch_number(self, number):
try:
board_str, switch_str = number.split("-")
Expand Down
11 changes: 11 additions & 0 deletions mpf/platforms/fast/fast_defines.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
)

EXPANSION_BOARD_FEATURES = {
'FP-EXP-0061': {
'min_fw': '0.31',
'local_breakouts': ['FP-EXP-0061'],
'breakout_ports': 0,
'default_address': '90'
},
'FP-EXP-0071': {
'min_fw': '0.11',
'local_breakouts': ['FP-EXP-0071'],
Expand Down Expand Up @@ -54,6 +60,11 @@
}

BREAKOUT_FEATURES = {
'FP-EXP-0061': {
'min_fw': '0.33',
'led_ports': 4,
'stepper_ports': 2
},
'FP-EXP-0071': {
'min_fw': '0.11',
'led_ports': 4,
Expand Down
4 changes: 3 additions & 1 deletion mpf/platforms/fast/fast_servo.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""Fast servo implementation."""

from mpf.core.utility_functions import Util
from mpf.platforms.interfaces.servo_platform_interface import ServoPlatformInterface


Expand All @@ -14,7 +16,7 @@ def __init__(self, breakout_board, port, config):
self.exp_connection = breakout_board.communicator

self.base_address = breakout_board.address
self.servo_index = str(int(port) - 1) # Servos are 0-indexed
self.servo_index = Util.int_to_hex_string(int(port) - 1) # Servos are 0-indexed
self.max_runtime = f"{config['max_runtime']:02X}"

self.write_config_to_servo()
Expand Down
Loading

0 comments on commit 32bfade

Please sign in to comment.