Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fast segment display update hz #1638

Merged
merged 16 commits into from
May 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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.56.0-dev.19'
__version__ = '0.56.0-dev.20'
'''The full version of MPF.'''

__short_version__ = '0.56'
Expand Down
2 changes: 1 addition & 1 deletion mpf/config_spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -620,11 +620,11 @@ fast:
net_buffer: single|int|10
rgb_buffer: single|int|3
dmd_buffer: single|int|3
segment_buffer: single|int|4
console_log: single|enum(none,basic,full)|none
file_log: single|enum(none,basic,full)|basic
firmware_updates: list|subconfig(fast_firmware_update)|None
ignore_rgb_crash: single|bool|false
segment_display_update_hz: single|int|20
fast_firmware_update:
type: single|enum(net,rgb)|
file: single|str|
Expand Down
2 changes: 1 addition & 1 deletion mpf/core/machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,7 @@ async def reset(self) -> None:

These events are posted when MPF boots (after the init_phase events are
posted), and they're also posted subsequently when the machine is reset
(after existing the service mode, for example).
(after exiting the service mode, for example).

This is a queue event. The machine reset phase 3 will not be complete
until the queue is cleared.
Expand Down
63 changes: 48 additions & 15 deletions mpf/platforms/fast/fast.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import asyncio
import os
from copy import deepcopy
from distutils.version import StrictVersion
from packaging import version
from typing import Dict, Set, Optional
from serial import SerialException

Expand Down Expand Up @@ -43,7 +43,8 @@ class FastHardwarePlatform(ServoPlatform, LightsPlatform, DmdPlatform,

__slots__ = ["dmd_connection", "net_connection", "rgb_connection", "seg_connection", "is_retro",
"serial_connections", "fast_leds", "fast_commands", "config", "machine_type", "hw_switch_data",
"io_boards", "flag_led_tick_registered", "_watchdog_task", "_led_task"]
"io_boards", "flag_led_tick_registered", "_watchdog_task", "_led_task", "_seg_task",
"fast_segs"]

def __init__(self, machine):
"""Initialise fast hardware platform.
Expand Down Expand Up @@ -84,7 +85,9 @@ def __init__(self, machine):
self._led_task = None
self.serial_connections = set() # type: Set[FastSerialCommunicator]
self.fast_leds = {}
self.fast_segs = list()
self.flag_led_tick_registered = False
self._seg_task = None
self.hw_switch_data = None
self.io_boards = {} # type: Dict[int, FastIoBoard]

Expand Down Expand Up @@ -155,7 +158,7 @@ def _update_net(self) -> str:
max_firmware = self.net_connection.remote_firmware
update_config = None
for update in self.config['firmware_updates']:
if StrictVersion(update['version']) > StrictVersion(max_firmware) and update['type'] == "net":
if version.parse(update['version']) > version.parse(max_firmware) and update['type'] == "net":
update_config = update

if not update_config:
Expand Down Expand Up @@ -187,6 +190,9 @@ def stop(self):
if self._led_task:
self._led_task.cancel()
self._led_task = None
if self._seg_task:
self._seg_task.cancel()
self._seg_task = None
if self._watchdog_task:
self._watchdog_task.cancel()
self._watchdog_task = None
Expand Down Expand Up @@ -244,8 +250,10 @@ def register_io_board(self, board):

def _update_watchdog(self):
"""Send Watchdog command."""
if self.net_connection:
try:
self.net_connection.send('WD:' + str(hex(self.config['watchdog']))[2:])
except:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do we ignore all errors here? I am certain that this breaks the linter (which is not currently running in CI).

pass

def process_received_message(self, msg: str, remote_processor: str):
"""Send an incoming message from the FAST controller to the proper method for servicing.
Expand All @@ -268,7 +276,7 @@ def process_received_message(self, msg: str, remote_processor: str):
self.log.warning("Received malformed message: %s from %s", msg, remote_processor)
return

# Can't use try since it swallows too many errors for now
# Can't use try since it swallows too many errors for now #TODO
Copy link
Collaborator

Choose a reason for hiding this comment

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

Double comment

if cmd in self.fast_commands:
self.fast_commands[cmd](payload, remote_processor)
else: # pragma: no cover
Expand Down Expand Up @@ -335,22 +343,48 @@ def register_processor_connection(self, name: str, communicator):
self.net_connection = communicator
elif name == 'SEG':
self.seg_connection = communicator

if not self._seg_task:
# Need to wait until the segs are all set up
self.machine.events.add_handler('machine_reset_phase_3', self._start_seg_updates)

elif name == 'RGB':
self.rgb_connection = communicator
self.rgb_connection.send('RF:0')
self.rgb_connection.send('RA:000000') # turn off all LEDs
self.rgb_connection.send('RF:{}'.format(
Util.int_to_hex_string(self.config['hardware_led_fade_time'])))

def _start_seg_updates(self, **kwargs):

for s in self.machine.device_manager.collections["segment_displays"]:
self.fast_segs.append(s.hw_display)

self.fast_segs.sort(key=lambda x: x.number)

if self.fast_segs:
self._seg_task = self.machine.clock.schedule_interval(self._update_segs,
1 / self.machine.config['fast'][
Copy link
Collaborator

Choose a reason for hiding this comment

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

Invalid indent. Please run a linter.

'segment_display_update_hz'])

def _update_segs(self, **kwargs):

for s in self.fast_segs:

if s.next_text:
self.seg_connection.send(f'PA:{s.hex_id},{s.next_text.convert_to_str()[0:7]}')
s.next_text = None

if s.next_color:
self.seg_connection.send(('PC:{},{}').format(s.hex_id, s.next_color))
s.next_color = None

def update_leds(self):
"""Update all the LEDs connected to a FAST controller.

This is done once per game loop for efficiency (i.e. all LEDs are sent as a single
update rather than lots of individual ones).

Also, every LED is updated every loop, even if it doesn't change. This
is in case some interference causes a LED to change color. Since we
update every loop, it will only be the wrong color for one tick.
"""
dirty_leds = [led for led in self.fast_leds.values() if led.dirty]

Expand Down Expand Up @@ -774,7 +808,6 @@ async def configure_segment_display(self, number: str, display_size: int, platfo
"""Configure a segment display."""
self.debug_log("Configuring FAST segment display.")
del platform_settings
del display_size
if not self.seg_connection:
raise AssertionError("A request was made to configure a FAST "
"Segment Display but no connection is "
Expand All @@ -793,7 +826,7 @@ def get_switch_config_section(cls):
"""Return switch config section."""
return "fast_switches"

def _check_switch_coil_combincation(self, switch, coil):
def _check_switch_coil_combination(self, switch, coil):
# V2 hardware can write rules across node boards
if not self.net_connection.is_legacy:
return
Expand Down Expand Up @@ -824,7 +857,7 @@ def set_pulse_on_hit_and_release_rule(self, enable_switch, coil):
"Driver: %s", enable_switch.hw_switch.number,
coil.hw_driver.number)

self._check_switch_coil_combincation(enable_switch, coil)
self._check_switch_coil_combination(enable_switch, coil)

driver = coil.hw_driver

Expand Down Expand Up @@ -857,8 +890,8 @@ def set_pulse_on_hit_and_release_and_disable_rule(self, enable_switch: SwitchSet
"%s, Driver: %s", enable_switch.hw_switch.number,
coil.hw_driver.number)

self._check_switch_coil_combincation(enable_switch, coil)
self._check_switch_coil_combincation(eos_switch, coil)
self._check_switch_coil_combination(enable_switch, coil)
self._check_switch_coil_combination(eos_switch, coil)

driver = coil.hw_driver

Expand Down Expand Up @@ -890,7 +923,7 @@ def set_pulse_on_hit_rule(self, enable_switch: SwitchSettings, coil: DriverSetti
"Driver: %s", enable_switch.hw_switch.number,
coil.hw_driver.number)

self._check_switch_coil_combincation(enable_switch, coil)
self._check_switch_coil_combination(enable_switch, coil)

driver = coil.hw_driver

Expand All @@ -912,7 +945,7 @@ def set_pulse_on_hit_and_enable_and_release_rule(self, enable_switch: SwitchSett
"Switch: %s, Driver: %s",
enable_switch.hw_switch.number, coil.hw_driver.number)

self._check_switch_coil_combincation(enable_switch, coil)
self._check_switch_coil_combination(enable_switch, coil)

driver = coil.hw_driver

Expand Down
14 changes: 7 additions & 7 deletions mpf/platforms/fast/fast_segment_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,29 @@ class FASTSegmentDisplay(SegmentDisplayPlatformInterface):

"""FAST segment display."""

__slots__ = ["serial", "hex_id"]
__slots__ = ["serial", "hex_id", "next_color", "next_text"]

def __init__(self, index, communicator):
"""Initialise alpha numeric display."""
super().__init__(index)
self.serial = communicator
self.hex_id = Util.int_to_hex_string(index * 7)
self.next_color = None
self.next_text = None

def set_text(self, text: ColoredSegmentDisplayText, flashing: FlashingType, flash_mask: str) -> None:
"""Set digits to display."""
del flashing
del flash_mask
colors = text.get_colors()
self.serial.send(f'PA:{self.hex_id},{text.convert_to_str()[0:7]}')
self.next_text = text
if colors:
self._set_color(colors)

def _set_color(self, colors: List[RGBColor]) -> None:
"""Set display color."""
self.serial.platform.info_log("Color: {}".format(colors))
#self.serial.platform.info_log("Color: {}".format(colors))
if len(colors) == 1:
colors = (RGBColor(colors[0]).hex + ',') * 7
self.next_color = (RGBColor(colors[0]).hex + ',') * 7
else:
colors = ','.join([RGBColor(color).hex for color in colors])
self.serial.send(('PC:{},{}').format(
self.hex_id, colors))
self.next_color = ','.join([RGBColor(color).hex for color in colors]) + ','
31 changes: 16 additions & 15 deletions mpf/platforms/fast/fast_serial_communicator.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Fast serial communicator."""
import asyncio
from distutils.version import StrictVersion
from packaging import version
from mpf.platforms.fast import fast_defines

from mpf.core.utility_functions import Util
Expand All @@ -12,14 +12,14 @@

# The following minimum firmware versions are to prevent breaking changes
# in MPF from running on boards that have not been updated.
DMD_MIN_FW = '0.88' # Minimum FW for a DMD
NET_MIN_FW = '2.0' # Minimum FW for a V2 controller
NET_LEGACY_MIN_FW = '0.88' # Minimum FW for a V1 controller
RGB_MIN_FW = '2.0' # Minimum FW for an RGB LED controller
RGB_LEGACY_MIN_FW = '0.87'
IO_MIN_FW = '1.09' # Minimum FW for an IO board linked to a V2 controller
IO_LEGACY_MIN_FW = '0.87' # Minimum FW for an IO board linked to a V1 controller
SEG_MIN_FW = '0.10' # Minimum FW for a Segment Display
DMD_MIN_FW = version.parse('0.88') # Minimum FW for a DMD
NET_MIN_FW = version.parse('2.0') # Minimum FW for a V2 controller
NET_LEGACY_MIN_FW = version.parse('0.88') # Minimum FW for a V1 controller
RGB_MIN_FW = version.parse('2.0') # Minimum FW for an RGB LED controller
RGB_LEGACY_MIN_FW = version.parse('0.87')
IO_MIN_FW = version.parse('1.09') # Minimum FW for an IO board linked to a V2 controller
IO_LEGACY_MIN_FW = version.parse('0.87') # Minimum FW for an IO board linked to a V1 controller
SEG_MIN_FW = version.parse('0.10') # Minimum FW for a Segment Display

LEGACY_ID = 'FP-CPU-0' # Start of an id for V1 controller
RETRO_ID = 'FP-SBI' # Start of an id for a Retro controller
Expand Down Expand Up @@ -143,7 +143,7 @@ async def _identify_connection(self):

if self.remote_model.startswith(RETRO_ID):
self.is_retro = True
elif StrictVersion(self.remote_firmware) < StrictVersion(V2_FW):
elif self.remote_firmware < V2_FW:
self.is_legacy = True

self.platform.log.info("Connected! Processor: %s, "
Expand Down Expand Up @@ -191,13 +191,11 @@ async def _identify_connection(self):
elif self.remote_processor == 'SEG':
min_version = SEG_MIN_FW
# latest_version = SEG_LATEST_FW
self.max_messages_in_flight = self.platform.config['segment_buffer']
self.platform.debug_log("Setting SEG buffer size: %s",
self.max_messages_in_flight)
self.max_messages_in_flight = 0 # SEG doesn't have ACK messages
else:
raise AttributeError(f"Unrecognized FAST processor type: {self.remote_processor}")

if StrictVersion(min_version) > StrictVersion(self.remote_firmware):
if version.parse(self.remote_firmware) < min_version:
raise AssertionError(f'Firmware version mismatch. MPF requires the {self.remote_processor} processor '
f'to be firmware {min_version}, but yours is {self.remote_firmware}')

Expand Down Expand Up @@ -315,7 +313,7 @@ async def query_fast_io_boards(self):
node_id, model, fw, int(sw, 16), int(dr, 16))

min_fw = IO_LEGACY_MIN_FW if self.is_legacy else IO_MIN_FW
if StrictVersion(min_fw) > str(fw):
if min_fw > version.parse(fw):
self.platform.log.critical("Firmware version mismatch. MPF requires the IO boards "
"to be firmware %s, but your Board %s (%s) is firmware %s",
min_fw, node_id, model, fw)
Expand Down Expand Up @@ -343,6 +341,9 @@ def _send(self, msg):
if debug and msg[0] != "W":
self.platform.log.debug("Send: %s", "".join(" 0x%02x" % b for b in msg))

elif not self.max_messages_in_flight: # For processors that don't use this
self.writer.write(msg.encode() + b'\r')
self.platform.log.debug("Sending without message flight tracking: %s", msg)
else:
self.messages_in_flight += 1
if self.messages_in_flight > self.max_messages_in_flight:
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
'pyserial-asyncio==0.5;platform_system!="Windows"',
'sortedcontainers==2.3.0',
'psutil==5.7.3',
'packaging',
],

extras_require={
Expand Down