Skip to content

Commit

Permalink
FAST: Nano hardware rules working, various bug fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
toomanybrians committed Sep 26, 2023
1 parent 1b5302f commit 5925b19
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 107 deletions.
1 change: 1 addition & 0 deletions mpf/platforms/fast/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* Digital driver, confirm? Initial config?
* Remove hw led fade time from nano?
* implement soft reset for EXP
* verify enable for neuron


* Delayed pulse. Add platform_setting for this.
Expand Down
5 changes: 4 additions & 1 deletion mpf/platforms/fast/communicators/net_nano.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ def _process_sa(self, msg):
super()._process_sa(f'00,{raw_switch_data}')

def _process_boot_message(self, msg):
if msg == '02':
if msg == '00': # rebooting
self.machine.stop("FAST NET Nano rebooted")

if msg == '02': # reboot done
self._process_reboot_done()
# TODO what else? Mark all configs as dirty? Log and warn if this was unexpected?
39 changes: 32 additions & 7 deletions mpf/platforms/fast/fast_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def __init__(self, communicator: FastSerialCommunicator, hw_number: int) -> None
self.number = hw_number # must be int to work with the rest of MPF
self.hw_number = Util.int_to_hex_string(hw_number) # hex version the FAST hw actually uses
self.autofire_config = None
self.platform_settings = dict()

self.baseline_driver_config = FastDriverConfig(number=self.hw_number, trigger='00',
switch_id='00', mode='00',
Expand Down Expand Up @@ -73,6 +74,7 @@ def set_initial_config(self, mpf_config: DriverConfig, platform_settings):
This will not be called for drivers that are not in the MPF config.
"""

self.platform_settings = platform_settings
self.current_driver_config = self.convert_mpf_config_to_fast(mpf_config, platform_settings)
self.baseline_driver_config = copy(self.current_driver_config)

Expand Down Expand Up @@ -171,9 +173,8 @@ def get_board_name(self):
return f"FAST Retro ({self.communicator.platform.machine_type.upper()})"

coil_index = 0
number = Util.hex_string_to_int(self.number)
for board_obj in self.communicator.platform.io_boards.values():
if coil_index <= number < coil_index + board_obj.driver_count:
if coil_index <= self.number < coil_index + board_obj.driver_count:
return f"FAST Board {str(board_obj.node_id)}"
coil_index += board_obj.driver_count

Expand Down Expand Up @@ -224,6 +225,9 @@ def disable(self):
self.communicator.send_and_forget(f'{self.communicator.TRIGGER_CMD}:{self.hw_number},02')

def set_hardware_rule(self, mode, switch, coil_settings, **kwargs):

self._check_switch_coil_combination(switch, coil_settings.hw_driver)

reconfigured = False
trigger_needed = False
switch_needed = False
Expand All @@ -247,13 +251,15 @@ def set_hardware_rule(self, mode, switch, coil_settings, **kwargs):
new_settings['pwm2_power'] = None
new_settings['pwm2_ms'] = None

if coil_settings.recycle:
if True:
if coil_settings.recycle is True:
if self.platform_settings['recycle_ms']: # MPF autofire rules will use True, so pull it from the config if specified.
new_settings['recycle_ms'] = Util.int_to_hex_string(self.platform_settings['recycle_ms'])
else:
new_settings['recycle_ms'] = '00'
else: # int
new_settings['recycle_ms'] = Util.int_to_hex_string(coil_settings.recycle)
else:
elif not coil_settings.recycle: # False or None
new_settings['recycle_ms'] = '00'
else:
new_settings['recycle_ms'] = Util.int_to_hex_string(coil_settings.recycle)

# Update the current_driver_config with any new settings
for idx, param in enumerate(self.mode_param_mapping[mode]):
Expand Down Expand Up @@ -300,6 +306,21 @@ def set_hardware_rule(self, mode, switch, coil_settings, **kwargs):
else:
self.communicator.send_and_forget(f'{self.communicator.TRIGGER_CMD}:{self.hw_number},00')

def _check_switch_coil_combination(self, switch, coil):
# TODO move this to the communicator or something? Since it's only Nano?

# V2 hardware can write rules across node boards
if self.communicator.config['controller'] != 'nano':
return

# first 8 switches always work
if 0 <= switch.hw_switch.number <= 7:
return

if self.get_board_name() != switch.hw_switch.get_board_name():
raise AssertionError(f"Driver {coil.number} and switch {switch.hw_switch.number} "
"are on different boards. Cannot apply rule!")

def is_new_config_needed(self, current, new):
# figures out if bits other than 6 and 7 changed, meaning we need a full new DL command not just TL update
current_num = int(current, 16)
Expand Down Expand Up @@ -337,6 +358,10 @@ def enable(self, pulse_settings: PulseSettings, hold_settings: HoldSettings):
pwm1_power = Util.float_to_pwm8_hex_string(pulse_settings.power)
pwm2_power = Util.float_to_pwm8_hex_string(hold_settings.power)

if mode != '18':
mode = '18'
reconfigured = True

if self.current_driver_config.param1 != pwm1_ms:
self.current_driver_config.param1 = pwm1_ms
reconfigured = True
Expand Down
3 changes: 1 addition & 2 deletions mpf/platforms/fast/fast_switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,8 @@ def get_board_name(self):
return f"FAST Retro ({self.communicator.platform.machine_type.upper()})"

switch_index = 0
number = Util.hex_string_to_int(self.number)
for board_obj in self.communicator.platform.io_boards.values():
if switch_index <= number < switch_index + board_obj.switch_count:
if switch_index <= self.number < switch_index + board_obj.switch_count:
return f"FAST Board {str(board_obj.node_id)}"
switch_index += board_obj.switch_count

Expand Down
3 changes: 2 additions & 1 deletion mpf/tests/machine_files/fast/config/nano.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,10 @@ coils:
number: 3208-6
default_pulse_ms: 23
max_hold_power: 1.0
allow_enable: true
c_slingshot_test: # DN:07,81,00,10,0A,FF,00,00,00
number: 3208-7
c_pulse_pwm32_mask: # DN:11,81,00,10,0A,AA,00,92,00
c_pulse_pwm_mask: # DN:11,81,00,10,0A,AA,00,92,00
number: io1616_1-5
default_pulse_power: 0.53
default_hold_power: 0.40
Expand Down
139 changes: 43 additions & 96 deletions mpf/tests/test_Fast_Nano.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,19 +329,16 @@ def setUp(self):
self.assertEqual(16, self.machine.default_platform.io_boards[3].switch_count)
self.assertEqual(16, self.machine.default_platform.io_boards[3].driver_count)

def test_coils(self):
def DISABLED_test_coils(self):
self._test_pulse()
# self._test_long_pulse()
# self._test_timed_enable()
# self._test_default_timed_enable()
# self._test_enable_exception()
# self._test_allow_enable()
# self._test_pwm_ssm()
# self._test_coil_configure()
self._test_long_pulse()
self._test_timed_enable()
self._test_default_timed_enable()
self._test_enable_exception()
self._test_allow_enable()
self._test_pwm_ssm()

# test hardware scan


info_str = (
'NET: FP-CPU-002-2 v01.05\n'
'RGB: FP-CPU-002-2 v01.00\n'
Expand All @@ -353,38 +350,8 @@ def test_coils(self):
'Board 3 - Model: FP-I/O-1616, Firmware: 01.05, Switches: 16, Drivers: 16\n'
)

print(self.machine.default_platform.get_info_string())

self.assertEqual(info_str, self.machine.default_platform.get_info_string())

def _test_coil_configure(self):
self.assertEqual("FAST Board 0", self.machine.coils["c_test"].hw_driver.get_board_name())
self.assertEqual("FAST Board 3", self.machine.coils["c_flipper_hold"].hw_driver.get_board_name())
# last driver on board
self.net_cpu.expected_commands = {
"DN:2B,00,00,00": "DN:P"
}
coil = self.machine.default_platform.configure_driver(self.machine.coils["c_test"].hw_driver.config, '1616_2-15',
{"connection": "network", "recycle_ms": 10})
self.assertEqual('2B', coil.number)
self.advance_time_and_run(.1)
self.assertFalse(self.net_cpu.expected_commands)

# board 0 has 8 drivers. configuring driver 9 should not work
with self.assertRaises(AssertionError):
self.machine.default_platform.configure_driver(self.machine.coils["c_test"].hw_driver.config, '3208-8',
{"connection": "network", "recycle_ms": 10})

# test error for invalid board
with self.assertRaises(AssertionError):
self.machine.default_platform.configure_driver(self.machine.coils["c_test"].hw_driver.config, 'brian-0',
{"connection": "network", "recycle_ms": 10})

# test error for driver number too high
with self.assertRaises(AssertionError):
self.machine.default_platform.configure_driver(self.machine.coils["c_test"].hw_driver.config, '3208-9',
{"connection": "network", "recycle_ms": 10})

def _test_pulse(self):
self.net_cpu.expected_commands = {
"TN:04,01": "TN:P"
Expand All @@ -395,27 +362,14 @@ def _test_pulse(self):
self.assertFalse(self.net_cpu.expected_commands)

def _test_long_pulse(self):
# enable command
# driver is configured for mode 70, so this should be a regular trigger
self.net_cpu.expected_commands = {
"TN:12,01": "TN:P"
}
self.machine.coils["c_long_pulse"].pulse()
self.advance_time_and_run(.1)
self.assertFalse(self.net_cpu.expected_commands)

# disable command
self.net_cpu.expected_commands = {
"TN:12,02": "TN:P"
}

self.advance_time_and_run(1)
# pulse_ms is 2000ms, so after 1s, this should not be sent
self.assertTrue(self.net_cpu.expected_commands)

self.advance_time_and_run(1)
# but after 2s, it should be
self.assertFalse(self.net_cpu.expected_commands)

def _test_timed_enable(self):
# enable command
self.net_cpu.expected_commands = {
Expand All @@ -442,15 +396,15 @@ def _test_enable_exception(self):

def _test_allow_enable(self):
self.net_cpu.expected_commands = {
"DN:06,C1,00,18,17,FF,FF,00": "DN:P"
"DN:06,C1,00,18,17,FF,FF,00,00": "DN:P"
}
self.machine.coils["c_test_allow_enable"].enable()
self.advance_time_and_run(.1)
self.assertFalse(self.net_cpu.expected_commands)

def _test_pwm_ssm(self):
self.net_cpu.expected_commands = {
"DN:13,C1,00,18,0A,FF,84224244,00": "DN:P"
"DN:13,C1,00,18,0A,FF,88,00,00": "DN:P"
}
self.machine.coils["c_hold_ssm"].enable()
self.advance_time_and_run(.1)
Expand All @@ -467,36 +421,10 @@ def DISABLED_test_rules(self):
self._test_enable_exception_hw_rule()
self._test_two_rules_one_switch()
self._test_hw_rule_pulse()
self._test_hw_rule_pulse_pwm32()
self._test_hw_rule_pulse_pwm()
self._test_hw_rule_pulse_inverted_switch()
self._test_hw_rule_same_board()

def _test_hw_rule_same_board(self):
self.net_cpu.expected_commands = {
"DN:21,01,07,10,0A,FF,00,00,14": "DN:P"
}
# coil and switch are on different boards but first 8 switches always work
self.machine.autofire_coils["ac_different_boards"].enable()
self.advance_time_and_run(.1)
self.assertFalse(self.net_cpu.expected_commands)

# switch and coil on board 3. should work
self.net_cpu.expected_commands = {
"DN:21,01,39,10,0A,FF,00,00,14": "DN:P",
"SN:39,01,02,02": "SN:P"
}
self.machine.autofire_coils["ac_board_3"].enable()
self.advance_time_and_run(.1)
self.assertFalse(self.net_cpu.expected_commands)

self.net_cpu.expected_commands = {
"DN:10,01,03,10,0A,89,00,00,14": "DN:P",
}
# coil and switch are on different boards
with self.assertRaises(AssertionError):
self.machine.autofire_coils["ac_broken_combination"].enable()
self.advance_time_and_run(.1)

def _test_enable_exception_hw_rule(self):
# enable coil which does not have allow_enable
with self.assertRaises(AssertionError):
Expand All @@ -507,9 +435,8 @@ def _test_enable_exception_hw_rule(self):

def _test_two_rules_one_switch(self):
self.net_cpu.expected_commands = {
"SN:03,01,02,02": "SN:P",
"DN:04,01,03,10,17,FF,00,00,1B": "DN:P",
"DN:06,01,03,10,17,FF,00,00,2E": "DN:P"
"TN:04,00,03": "TN:P",
"TN:06,00,03": "TN:P"
}
self.post_event("ac_same_switch")
self.hit_and_release_switch("s_flipper")
Expand All @@ -518,44 +445,64 @@ def _test_two_rules_one_switch(self):

def _test_hw_rule_pulse(self):
self.net_cpu.expected_commands = {
"DN:07,01,16,10,0A,FF,00,00,14": "DN:P", # hw rule
"SN:16,01,02,02": "SN:P" # debounce quick on switch
"TN:07,00,16": "TN:P", # hw rule
}
self.machine.autofire_coils["ac_slingshot_test"].enable()
self.advance_time_and_run(.1)
self.assertFalse(self.net_cpu.expected_commands)

self.net_cpu.expected_commands = {
"DN:07,81": "DN:P"
"TN:07,02": "TN:P"
}
self.machine.autofire_coils["ac_slingshot_test"].disable()
self.advance_time_and_run(.1)
self.assertFalse(self.net_cpu.expected_commands)

def _test_hw_rule_pulse_pwm32(self):
def _test_hw_rule_pulse_pwm(self):
self.net_cpu.expected_commands = {
"DN:11,89,00,10,0A,AAAAAAAA,00,00,00": "DN:P"
"TN:11,01": "TN:P"
}
self.machine.coils["c_pulse_pwm32_mask"].pulse()
self.machine.coils["c_pulse_pwm_mask"].pulse()
self.advance_time_and_run(.1)
self.assertFalse(self.net_cpu.expected_commands)

self.net_cpu.expected_commands = {
"DN:11,C1,00,18,0A,AAAAAAAA,4A4A4A4A,00": "DN:P"
"DN:11,C1,00,18,0A,AA,92,00,00": "DN:P"
}
self.machine.coils["c_pulse_pwm32_mask"].enable()
self.machine.coils["c_pulse_pwm_mask"].enable()
self.advance_time_and_run(.1)
self.assertFalse(self.net_cpu.expected_commands)

def _test_hw_rule_pulse_inverted_switch(self):
self.net_cpu.expected_commands = {
"DN:07,11,1A,10,0A,FF,00,00,14": "DN:P",
"SN:1A,01,02,02": "SN:P"
"DN:07,11,1A,10,0A,FF,00,00,00": "DN:P",
}
self.machine.autofire_coils["ac_inverted_switch"].enable()
self.advance_time_and_run(.1)
self.assertFalse(self.net_cpu.expected_commands)

def _test_hw_rule_same_board(self):
self.net_cpu.expected_commands = {
"TN:21,00,07": "DN:P"
}
# coil and switch are on different boards but first 8 switches always work
self.machine.autofire_coils["ac_different_boards"].enable()
self.advance_time_and_run(.1)
self.assertFalse(self.net_cpu.expected_commands)

# switch and coil on board 3. should work
self.net_cpu.expected_commands = {
"TN:21,00,39": "DN:P",
}
self.machine.autofire_coils["ac_board_3"].enable()
self.advance_time_and_run(.1)
self.assertFalse(self.net_cpu.expected_commands)

# coil and switch are on different boards
with self.assertRaises(AssertionError):
self.machine.autofire_coils["ac_broken_combination"].enable()
self.advance_time_and_run(.1)

def _switch_hit_cb(self, **kwargs):
self.switch_hit = True

Expand Down

0 comments on commit 5925b19

Please sign in to comment.