mpf 0.58.0.dev4__py3-none-any.whl → 0.58.0.dev7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mpf/_version.py +1 -1
- mpf/config_spec.yaml +3 -0
- mpf/core/logging.py +6 -2
- mpf/devices/state_machine.py +17 -3
- mpf/mpfconfig.yaml +1 -1
- mpf/platforms/pkone/pkone.py +109 -53
- mpf/platforms/pkone/pkone_coil.py +4 -4
- mpf/platforms/pkone/{pkone_extension.py → pkone_ex2.py} +10 -10
- mpf/platforms/pkone/pkone_lights.py +2 -2
- mpf/platforms/pkone/pkone_lightshow.py +19 -2
- mpf/platforms/pkone/pkone_serial_communicator.py +131 -52
- mpf/platforms/pkone/pkone_switch.py +32 -6
- mpf/tests/machine_files/pkone/config/config.yaml +1 -1
- mpf/tests/machine_files/state_machine/modes/game_mode/config/game_mode.yaml +31 -0
- mpf/tests/test_Logging.py +75 -0
- mpf/tests/test_PKONE.py +26 -23
- mpf/tests/test_PKONEStartup.py +193 -0
- mpf/tests/test_StateMachine.py +84 -0
- {mpf-0.58.0.dev4.dist-info → mpf-0.58.0.dev7.dist-info}/METADATA +1 -1
- {mpf-0.58.0.dev4.dist-info → mpf-0.58.0.dev7.dist-info}/RECORD +25 -23
- {mpf-0.58.0.dev4.dist-info → mpf-0.58.0.dev7.dist-info}/licenses/AUTHORS.md +1 -0
- {mpf-0.58.0.dev4.dist-info → mpf-0.58.0.dev7.dist-info}/WHEEL +0 -0
- {mpf-0.58.0.dev4.dist-info → mpf-0.58.0.dev7.dist-info}/entry_points.txt +0 -0
- {mpf-0.58.0.dev4.dist-info → mpf-0.58.0.dev7.dist-info}/licenses/LICENSE.md +0 -0
- {mpf-0.58.0.dev4.dist-info → mpf-0.58.0.dev7.dist-info}/top_level.txt +0 -0
mpf/_version.py
CHANGED
|
@@ -10,7 +10,7 @@ PyPI.
|
|
|
10
10
|
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
__version__ = '0.58.0.
|
|
13
|
+
__version__ = '0.58.0.dev7' # Also consider whether MPF-MC pyproject.toml should be updated
|
|
14
14
|
'''The full version of MPF.'''
|
|
15
15
|
|
|
16
16
|
__short_version__ = '0.58'
|
mpf/config_spec.yaml
CHANGED
|
@@ -1271,6 +1271,7 @@ pin2dmd:
|
|
|
1271
1271
|
resolution: single|enum(128x32,192x64,256x64)|128x32
|
|
1272
1272
|
panel: single|enum(rgb,rbg)|rgb
|
|
1273
1273
|
pkone:
|
|
1274
|
+
lightshow_groups: dict|str:str|None
|
|
1274
1275
|
__valid_in__: machine
|
|
1275
1276
|
__type__: config
|
|
1276
1277
|
port: single|str|
|
|
@@ -1663,6 +1664,7 @@ state_machines:
|
|
|
1663
1664
|
states: dict|str:subconfig(state_machine_states)|
|
|
1664
1665
|
transitions: list|subconfig(state_machine_transitions)|
|
|
1665
1666
|
persist_state: single|bool|false
|
|
1667
|
+
repost_start_events_on_restore: single|bool|false
|
|
1666
1668
|
starting_state: single|str|start
|
|
1667
1669
|
state_machine_transitions:
|
|
1668
1670
|
source: list|str|
|
|
@@ -1673,6 +1675,7 @@ state_machine_states:
|
|
|
1673
1675
|
label: single|str|None
|
|
1674
1676
|
show_when_active: single|subconfig(show_config)|None
|
|
1675
1677
|
events_when_started: list|event_posted|None
|
|
1678
|
+
events_when_restored: list|event_posted|None
|
|
1676
1679
|
events_when_stopped: list|event_posted|None
|
|
1677
1680
|
show_config:
|
|
1678
1681
|
show: single|str|
|
mpf/core/logging.py
CHANGED
|
@@ -95,7 +95,9 @@ class LogMixin:
|
|
|
95
95
|
if not self.log:
|
|
96
96
|
self._logging_not_configured()
|
|
97
97
|
|
|
98
|
-
if self._debug_to_console:
|
|
98
|
+
if self._debug_to_console and self._debug_to_file:
|
|
99
|
+
level = 10
|
|
100
|
+
elif self._debug_to_console:
|
|
99
101
|
level = 12
|
|
100
102
|
elif self._debug_to_file:
|
|
101
103
|
level = 11
|
|
@@ -116,7 +118,9 @@ class LogMixin:
|
|
|
116
118
|
if not self.log:
|
|
117
119
|
self._logging_not_configured()
|
|
118
120
|
|
|
119
|
-
if self._info_to_console or self._debug_to_console:
|
|
121
|
+
if (self._info_to_console or self._debug_to_console) and (self._info_to_file or self._debug_to_file):
|
|
122
|
+
level = 20
|
|
123
|
+
elif self._info_to_console or self._debug_to_console:
|
|
120
124
|
level = 22
|
|
121
125
|
elif self._info_to_file or self._debug_to_file:
|
|
122
126
|
level = 21
|
mpf/devices/state_machine.py
CHANGED
|
@@ -83,9 +83,7 @@ class StateMachine(SystemWideDevice, ModeDevice):
|
|
|
83
83
|
if not self.state:
|
|
84
84
|
self._start_state(self.config['starting_state'])
|
|
85
85
|
else:
|
|
86
|
-
self.
|
|
87
|
-
self._run_show_for_current_state()
|
|
88
|
-
self.notify_virtual_change("state", None, self.state)
|
|
86
|
+
self._restore_state(self.state)
|
|
89
87
|
|
|
90
88
|
def device_removed_from_mode(self, mode: Mode):
|
|
91
89
|
"""Unset internal state to prevent leakage."""
|
|
@@ -128,6 +126,22 @@ class StateMachine(SystemWideDevice, ModeDevice):
|
|
|
128
126
|
self._add_handlers_for_current_state()
|
|
129
127
|
self._run_show_for_current_state()
|
|
130
128
|
|
|
129
|
+
def _restore_state(self, state):
|
|
130
|
+
self.debug_log("Restoring state %s", state)
|
|
131
|
+
|
|
132
|
+
state_config = self.config['states'][state]
|
|
133
|
+
if state_config['events_when_restored']:
|
|
134
|
+
for event_name in state_config['events_when_restored']:
|
|
135
|
+
self.machine.events.post(event_name)
|
|
136
|
+
|
|
137
|
+
elif self.config['repost_start_events_on_restore'] and state_config['events_when_started']:
|
|
138
|
+
for event_name in state_config['events_when_started']:
|
|
139
|
+
self.machine.events.post(event_name)
|
|
140
|
+
|
|
141
|
+
self._add_handlers_for_current_state()
|
|
142
|
+
self._run_show_for_current_state()
|
|
143
|
+
self.notify_virtual_change("state", None, self.state)
|
|
144
|
+
|
|
131
145
|
def _run_show_for_current_state(self):
|
|
132
146
|
assert not self._show
|
|
133
147
|
state_config = self.config['states'][self.state]
|
mpf/mpfconfig.yaml
CHANGED
mpf/platforms/pkone/pkone.py
CHANGED
|
@@ -5,33 +5,37 @@ Contains the hardware interface and drivers for the Penny K Pinball PKONE
|
|
|
5
5
|
platform hardware.
|
|
6
6
|
"""
|
|
7
7
|
import asyncio
|
|
8
|
+
import re
|
|
8
9
|
from copy import deepcopy
|
|
9
10
|
from typing import Optional, Dict, List, Tuple, Set
|
|
10
11
|
|
|
12
|
+
from mpf.core.platform import (
|
|
13
|
+
SwitchPlatform, DriverPlatform, LightsPlatform, SwitchSettings, DriverSettings,
|
|
14
|
+
DriverConfig, SwitchConfig, RepulseSettings, ServoPlatform)
|
|
11
15
|
from mpf.core.platform_batch_light_system import PlatformBatchLightSystem
|
|
12
16
|
from mpf.platforms.pkone.pkone_serial_communicator import PKONESerialCommunicator
|
|
13
|
-
from mpf.platforms.pkone.
|
|
17
|
+
from mpf.platforms.pkone.pkone_ex2 import PKONEEX2Board
|
|
14
18
|
from mpf.platforms.pkone.pkone_lightshow import PKONELightshowBoard
|
|
15
|
-
from mpf.platforms.pkone.pkone_switch import PKONESwitch, PKONESwitchNumber
|
|
19
|
+
from mpf.platforms.pkone.pkone_switch import PKONESwitch, PKONESwitchNumber, PKONESwitchBoard
|
|
16
20
|
from mpf.platforms.pkone.pkone_coil import PKONECoil, PKONECoilNumber
|
|
17
21
|
from mpf.platforms.pkone.pkone_servo import PKONEServo, PKONEServoNumber
|
|
18
22
|
from mpf.platforms.pkone.pkone_lights import PKONESimpleLED, PKONESimpleLEDNumber, PKONELEDChannel
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
DriverConfig, SwitchConfig, RepulseSettings, ServoPlatform
|
|
24
|
+
DUPLICATE_ADDRESS_ERROR = "Duplicate address id: a board has already been registered at address {}"
|
|
22
25
|
|
|
23
26
|
|
|
24
27
|
# pylint: disable-msg=too-many-instance-attributes,too-many-public-methods
|
|
25
28
|
class PKONEHardwarePlatform(SwitchPlatform, DriverPlatform, LightsPlatform, ServoPlatform):
|
|
26
29
|
|
|
27
|
-
"""Platform class for
|
|
30
|
+
"""Platform class for PKONE EX2, Switch and Lightshow hardware.
|
|
28
31
|
|
|
29
32
|
Args:
|
|
30
33
|
----
|
|
31
34
|
machine: The MachineController instance.
|
|
32
35
|
"""
|
|
33
36
|
|
|
34
|
-
__slots__ = ["config", "serial_connections", "
|
|
37
|
+
__slots__ = ["config", "serial_connections", "pkone_ex2_boards", "pkone_switch_boards",
|
|
38
|
+
"pkone_lightshows", "_light_system",
|
|
35
39
|
"_watchdog_task", "hw_switch_data", "controller_connection", "pkone_commands"]
|
|
36
40
|
|
|
37
41
|
def __init__(self, machine) -> None:
|
|
@@ -39,21 +43,27 @@ class PKONEHardwarePlatform(SwitchPlatform, DriverPlatform, LightsPlatform, Serv
|
|
|
39
43
|
super().__init__(machine)
|
|
40
44
|
self.controller_connection = None
|
|
41
45
|
self.serial_connections = set() # type: Set[PKONESerialCommunicator]
|
|
42
|
-
self.
|
|
46
|
+
self.pkone_ex2_boards = {} # type: Dict[int, PKONEEX2Board]
|
|
47
|
+
self.pkone_switch_boards = {} # type: Dict[int, PKONESwitchBoard]
|
|
43
48
|
self.pkone_lightshows = {} # type: Dict[int, PKONELightshowBoard]
|
|
44
49
|
self._light_system = None # type: Optional[PlatformBatchLightSystem]
|
|
45
50
|
self._watchdog_task = None
|
|
46
51
|
self.hw_switch_data = dict()
|
|
47
52
|
|
|
48
|
-
self.pkone_commands = {'PCN': lambda
|
|
49
|
-
'PCB': lambda
|
|
50
|
-
'PWD': lambda
|
|
51
|
-
'
|
|
53
|
+
self.pkone_commands = {'PCN': lambda payload: None, # connected USB controller
|
|
54
|
+
'PCB': lambda payload: None, # connected board
|
|
55
|
+
'PWD': lambda payload: None, # watchdog
|
|
56
|
+
'PWT': self.receive_watchdog_timeout,
|
|
57
|
+
'PWF': lambda payload: None, # watchdog stop
|
|
52
58
|
'PSA': self.receive_all_switches, # all switch states
|
|
53
59
|
'PSW': self.receive_switch, # switch state change
|
|
54
60
|
'PXX': self.receive_error, # error
|
|
55
61
|
}
|
|
56
62
|
|
|
63
|
+
for command in ('PRS', 'PWS', 'PCC', 'PCP', 'PCH', 'PCR', 'PHR', 'PHD', 'PSC',
|
|
64
|
+
'PLS', 'PLO', 'PLB', 'PWB', 'PLW', 'PWW', 'PLC', 'PWC'):
|
|
65
|
+
self.pkone_commands[command] = lambda payload: None
|
|
66
|
+
|
|
57
67
|
# Set platform features. Each platform interface can change
|
|
58
68
|
# these to notify the framework of the specific features it supports.
|
|
59
69
|
self.features['max_pulse'] = 250
|
|
@@ -65,7 +75,7 @@ class PKONEHardwarePlatform(SwitchPlatform, DriverPlatform, LightsPlatform, Serv
|
|
|
65
75
|
self.debug_log("Configuring PKONE hardware.")
|
|
66
76
|
|
|
67
77
|
async def initialize(self):
|
|
68
|
-
"""Initialize connection to PKONE
|
|
78
|
+
"""Initialize the connection to PKONE hardware."""
|
|
69
79
|
await self._connect_to_hardware()
|
|
70
80
|
|
|
71
81
|
# Setup the batch light system
|
|
@@ -123,23 +133,40 @@ class PKONEHardwarePlatform(SwitchPlatform, DriverPlatform, LightsPlatform, Serv
|
|
|
123
133
|
infos += "------------------------\n"
|
|
124
134
|
infos += " - Connected Controllers:\n"
|
|
125
135
|
for connection in sorted(self.serial_connections, key=lambda x: x.port):
|
|
126
|
-
infos += " -> PKONE
|
|
136
|
+
infos += " -> PKONE EX2 USB connection - Port: {} at {} baud " \
|
|
127
137
|
"(firmware v{}, hardware rev {}).\n".format(connection.port,
|
|
128
138
|
connection.baud,
|
|
129
139
|
connection.remote_firmware,
|
|
130
140
|
connection.remote_hardware_rev)
|
|
131
141
|
|
|
132
|
-
infos += "\n -
|
|
133
|
-
for
|
|
134
|
-
infos +=
|
|
135
|
-
|
|
136
|
-
|
|
142
|
+
infos += "\n - EX2 boards:\n"
|
|
143
|
+
for board in self.pkone_ex2_boards.values():
|
|
144
|
+
infos += (
|
|
145
|
+
" -> Address ID: {} (firmware v{}, hardware rev {})\n".format(
|
|
146
|
+
board.addr,
|
|
147
|
+
board.firmware_version,
|
|
148
|
+
board.hardware_rev)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
infos += "\n - Switch boards:\n"
|
|
152
|
+
for board in self.pkone_switch_boards.values():
|
|
153
|
+
infos += (
|
|
154
|
+
" -> Address ID: {} (firmware v{}, hardware rev {})\n".format(
|
|
155
|
+
board.addr,
|
|
156
|
+
board.firmware_version,
|
|
157
|
+
board.hardware_rev)
|
|
158
|
+
)
|
|
137
159
|
|
|
138
160
|
infos += "\n - Lightshow boards:\n"
|
|
139
161
|
for lightshow in self.pkone_lightshows.values():
|
|
162
|
+
firmware_type = 'RGB'
|
|
163
|
+
if lightshow.mixed_firmware:
|
|
164
|
+
firmware_type = 'MIX'
|
|
165
|
+
elif lightshow.rgbw_firmware:
|
|
166
|
+
firmware_type = 'RGBW'
|
|
140
167
|
infos += " -> Address ID: {} ({} firmware v{}, " \
|
|
141
168
|
"hardware rev {})\n".format(lightshow.addr,
|
|
142
|
-
|
|
169
|
+
firmware_type,
|
|
143
170
|
lightshow.firmware_version,
|
|
144
171
|
lightshow.hardware_rev)
|
|
145
172
|
|
|
@@ -151,22 +178,32 @@ class PKONEHardwarePlatform(SwitchPlatform, DriverPlatform, LightsPlatform, Serv
|
|
|
151
178
|
await comm.connect()
|
|
152
179
|
self.serial_connections.add(comm)
|
|
153
180
|
|
|
154
|
-
def
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
181
|
+
def _address_is_registered(self, address):
|
|
182
|
+
return (address in self.pkone_ex2_boards or address in self.pkone_switch_boards or
|
|
183
|
+
address in self.pkone_lightshows)
|
|
184
|
+
|
|
185
|
+
def register_ex2_board(self, board: PKONEEX2Board):
|
|
186
|
+
"""Register an EX2 board."""
|
|
187
|
+
if self._address_is_registered(board.addr):
|
|
188
|
+
raise AssertionError(DUPLICATE_ADDRESS_ERROR.format(board.addr))
|
|
159
189
|
|
|
160
190
|
if board.addr not in range(8):
|
|
161
|
-
raise AssertionError("Address out of range:
|
|
191
|
+
raise AssertionError("Address out of range: EX2 board address id must be between 0 and 7")
|
|
162
192
|
|
|
163
|
-
self.
|
|
193
|
+
self.pkone_ex2_boards[board.addr] = board
|
|
194
|
+
|
|
195
|
+
def register_switch_board(self, board: PKONESwitchBoard):
|
|
196
|
+
"""Register a Switch board."""
|
|
197
|
+
if self._address_is_registered(board.addr):
|
|
198
|
+
raise AssertionError(DUPLICATE_ADDRESS_ERROR.format(board.addr))
|
|
199
|
+
if board.addr not in range(8):
|
|
200
|
+
raise AssertionError("Address out of range: Switch board address id must be between 0 and 7")
|
|
201
|
+
self.pkone_switch_boards[board.addr] = board
|
|
164
202
|
|
|
165
203
|
def register_lightshow_board(self, board: PKONELightshowBoard):
|
|
166
204
|
"""Register a Lightshow board."""
|
|
167
|
-
if
|
|
168
|
-
raise AssertionError(
|
|
169
|
-
"registered at address {}".format(board.addr))
|
|
205
|
+
if self._address_is_registered(board.addr):
|
|
206
|
+
raise AssertionError(DUPLICATE_ADDRESS_ERROR.format(board.addr))
|
|
170
207
|
|
|
171
208
|
if board.addr not in range(4):
|
|
172
209
|
raise AssertionError("Address out of range: Lightshow board address id must be between 0 and 3")
|
|
@@ -182,7 +219,7 @@ class PKONEHardwarePlatform(SwitchPlatform, DriverPlatform, LightsPlatform, Serv
|
|
|
182
219
|
"""
|
|
183
220
|
assert self.log is not None
|
|
184
221
|
cmd = msg[0:3]
|
|
185
|
-
payload = msg[3:].
|
|
222
|
+
payload = msg[3:].removesuffix('E')
|
|
186
223
|
|
|
187
224
|
# Can't use try since it swallows too many errors for now
|
|
188
225
|
if cmd in self.pkone_commands:
|
|
@@ -190,6 +227,12 @@ class PKONEHardwarePlatform(SwitchPlatform, DriverPlatform, LightsPlatform, Serv
|
|
|
190
227
|
else: # pragma: no cover
|
|
191
228
|
self.log.warning("Received unknown serial command %s.", msg)
|
|
192
229
|
|
|
230
|
+
def receive_watchdog_timeout(self, msg):
|
|
231
|
+
"""Stop play when the controller reports that host communication was lost."""
|
|
232
|
+
del msg
|
|
233
|
+
self.machine.stop(
|
|
234
|
+
"PKONE hardware watchdog expired; outputs were disabled. Restart after checking the connection.")
|
|
235
|
+
|
|
193
236
|
def receive_error(self, msg):
|
|
194
237
|
"""Receive an error message from the controller."""
|
|
195
238
|
self.log.error("Received an error message from the controller: %s", msg)
|
|
@@ -203,16 +246,16 @@ class PKONEHardwarePlatform(SwitchPlatform, DriverPlatform, LightsPlatform, Serv
|
|
|
203
246
|
board_id = int(board_id_str)
|
|
204
247
|
coil_num = int(coil_num_str)
|
|
205
248
|
|
|
206
|
-
if board_id not in self.
|
|
207
|
-
raise AssertionError("PKONE
|
|
249
|
+
if board_id not in self.pkone_ex2_boards:
|
|
250
|
+
raise AssertionError("PKONE EX2 {} does not exist for coil {}".format(board_id, number))
|
|
208
251
|
|
|
209
252
|
if coil_num == 0:
|
|
210
253
|
raise AssertionError("PKONE coil numbering begins with 1. Coil: {}".format(number))
|
|
211
254
|
|
|
212
|
-
coil_count = self.
|
|
255
|
+
coil_count = self.pkone_ex2_boards[board_id].coil_count
|
|
213
256
|
if coil_count < coil_num or coil_num < 1:
|
|
214
257
|
raise AssertionError(
|
|
215
|
-
"PKONE
|
|
258
|
+
"PKONE EX2 {board_id} only has {coil_count} coils "
|
|
216
259
|
"({first_coil} - {last_coil}). Coil: {number}".format(
|
|
217
260
|
board_id=board_id, coil_count=coil_count, first_coil=1, last_coil=coil_count, number=number))
|
|
218
261
|
|
|
@@ -245,7 +288,7 @@ class PKONEHardwarePlatform(SwitchPlatform, DriverPlatform, LightsPlatform, Serv
|
|
|
245
288
|
@staticmethod
|
|
246
289
|
def _check_coil_switch_combination(coil: DriverSettings, switch: SwitchSettings):
|
|
247
290
|
"""Check to see if the coil/switch combination is legal for hardware rules."""
|
|
248
|
-
#
|
|
291
|
+
# A hardware rule requires both devices on the same EX2 address.
|
|
249
292
|
if switch.hw_switch.number.board_address_id != coil.hw_driver.number.board_address_id:
|
|
250
293
|
raise AssertionError("Coil {} and switch {} are on different boards. Cannot apply hardware rule!".format(
|
|
251
294
|
coil.hw_driver.number, switch.hw_switch.number))
|
|
@@ -346,14 +389,14 @@ class PKONEHardwarePlatform(SwitchPlatform, DriverPlatform, LightsPlatform, Serv
|
|
|
346
389
|
board_id = int(board_id_str)
|
|
347
390
|
servo_num = int(servo_num_str)
|
|
348
391
|
|
|
349
|
-
if board_id not in self.
|
|
350
|
-
raise AssertionError("PKONE
|
|
392
|
+
if board_id not in self.pkone_ex2_boards:
|
|
393
|
+
raise AssertionError("PKONE EX2 {} does not exist for servo {}".format(board_id, number))
|
|
351
394
|
|
|
352
395
|
# Servos are numbered in sequence immediately after the highest coil number
|
|
353
|
-
driver_count = self.
|
|
354
|
-
servo_count = self.
|
|
396
|
+
driver_count = self.pkone_ex2_boards[board_id].coil_count
|
|
397
|
+
servo_count = self.pkone_ex2_boards[board_id].servo_count
|
|
355
398
|
if servo_num <= driver_count or servo_num > driver_count + servo_count:
|
|
356
|
-
raise AssertionError("PKONE
|
|
399
|
+
raise AssertionError("PKONE EX2 {} supports {} servos ({} - {}). "
|
|
357
400
|
"Servo: {} is not a valid number.".format(
|
|
358
401
|
board_id, servo_count, driver_count + 1, driver_count + servo_count, number))
|
|
359
402
|
|
|
@@ -379,15 +422,16 @@ class PKONEHardwarePlatform(SwitchPlatform, DriverPlatform, LightsPlatform, Serv
|
|
|
379
422
|
board_id = int(board_id_str)
|
|
380
423
|
switch_num = int(switch_num_str)
|
|
381
424
|
|
|
382
|
-
|
|
383
|
-
|
|
425
|
+
board = self.pkone_ex2_boards.get(board_id) or self.pkone_switch_boards.get(board_id)
|
|
426
|
+
if board is None:
|
|
427
|
+
raise AssertionError("PKONE input board {} does not exist for switch {}".format(board_id, number))
|
|
384
428
|
|
|
385
429
|
if switch_num == 0:
|
|
386
430
|
raise AssertionError("PKONE switch numbering begins with 1. Switch: {}".format(number))
|
|
387
431
|
|
|
388
|
-
if
|
|
389
|
-
raise AssertionError("PKONE
|
|
390
|
-
board_id,
|
|
432
|
+
if board.switch_count < switch_num:
|
|
433
|
+
raise AssertionError("PKONE board {} only has {} switch inputs. Switch: {}".format(
|
|
434
|
+
board_id, board.switch_count, number))
|
|
391
435
|
|
|
392
436
|
return PKONESwitchNumber(board_id, switch_num)
|
|
393
437
|
|
|
@@ -430,9 +474,12 @@ class PKONEHardwarePlatform(SwitchPlatform, DriverPlatform, LightsPlatform, Serv
|
|
|
430
474
|
# [PSA opcode] + [[board address id] + 0 or 1 for each switch on the board] + E
|
|
431
475
|
self.debug_log("Received all switch states (PSA): %s", msg)
|
|
432
476
|
|
|
433
|
-
|
|
434
|
-
|
|
477
|
+
if not msg or msg[0] not in '01234567':
|
|
478
|
+
raise AssertionError("Invalid PKONE switch snapshot: {}".format(msg))
|
|
435
479
|
board_address_id = int(msg[0])
|
|
480
|
+
board = self.pkone_ex2_boards.get(board_address_id) or self.pkone_switch_boards.get(board_address_id)
|
|
481
|
+
if board is None or not re.fullmatch('[01]{' + str(board.switch_count) + '}', msg[1:]):
|
|
482
|
+
raise AssertionError("Invalid PKONE switch snapshot: {}".format(msg))
|
|
436
483
|
switch_states = msg[1:]
|
|
437
484
|
|
|
438
485
|
# There is one character for each switch on the board (1 = active, 0 = inactive)
|
|
@@ -446,8 +493,14 @@ class PKONEHardwarePlatform(SwitchPlatform, DriverPlatform, LightsPlatform, Serv
|
|
|
446
493
|
# The PSW message contains the following information:
|
|
447
494
|
# [PSW opcode] + [board address id] + switch number + switch state (0 or 1) + E
|
|
448
495
|
self.debug_log("Received switch state change (PSW): %s", msg)
|
|
449
|
-
|
|
450
|
-
|
|
496
|
+
if not re.fullmatch(r'[0-7][0-9]{2}[01]', msg):
|
|
497
|
+
raise AssertionError("Invalid PKONE switch event: {}".format(msg))
|
|
498
|
+
board_id, number, switch_state = int(msg[0]), int(msg[1:3]), int(msg[3])
|
|
499
|
+
board = self.pkone_ex2_boards.get(board_id) or self.pkone_switch_boards.get(board_id)
|
|
500
|
+
if board is None or not 1 <= number <= board.switch_count:
|
|
501
|
+
raise AssertionError("Unknown PKONE switch: {}".format(msg))
|
|
502
|
+
switch_number = PKONESwitchNumber(board_id, number)
|
|
503
|
+
self.hw_switch_data[switch_number] = switch_state
|
|
451
504
|
self.machine.switch_controller.process_switch_by_num(state=switch_state,
|
|
452
505
|
num=switch_number,
|
|
453
506
|
platform=self)
|
|
@@ -461,7 +514,7 @@ class PKONEHardwarePlatform(SwitchPlatform, DriverPlatform, LightsPlatform, Serv
|
|
|
461
514
|
|
|
462
515
|
# get the lightshow board that will be sent the command (need to know if rgb or rgbw board)
|
|
463
516
|
lightshow = self.pkone_lightshows[first_channel.board_address_id]
|
|
464
|
-
if lightshow.
|
|
517
|
+
if lightshow.channels_for_group(first_channel.group) == 4:
|
|
465
518
|
cmd_opcode = "PWB"
|
|
466
519
|
channel_grouping = 4
|
|
467
520
|
else:
|
|
@@ -546,6 +599,9 @@ class PKONEHardwarePlatform(SwitchPlatform, DriverPlatform, LightsPlatform, Serv
|
|
|
546
599
|
board_address_id, group, index = number.split("-")
|
|
547
600
|
led_channel = PKONELEDChannel(board_address_id, group, index, config, self._light_system)
|
|
548
601
|
lightshow = self.pkone_lightshows[int(board_address_id)]
|
|
602
|
+
channels = lightshow.channels_for_group(group)
|
|
603
|
+
if not 0 <= int(index) < 64 * channels:
|
|
604
|
+
raise AssertionError("PKONE LED channel exceeds this group's 64-pixel range")
|
|
549
605
|
lightshow.add_channel_hw_driver(int(group), led_channel)
|
|
550
606
|
self._light_system.mark_dirty(led_channel)
|
|
551
607
|
return led_channel
|
|
@@ -556,9 +612,9 @@ class PKONEHardwarePlatform(SwitchPlatform, DriverPlatform, LightsPlatform, Serv
|
|
|
556
612
|
"""Determine whether the specified LED is hardware aligned."""
|
|
557
613
|
light = self.machine.lights[led_name]
|
|
558
614
|
hw_numbers = light.get_hw_numbers()
|
|
559
|
-
board_address_id,
|
|
615
|
+
board_address_id, group, index = hw_numbers[0].split("-")
|
|
560
616
|
lightshow = self.pkone_lightshows[int(board_address_id)]
|
|
561
|
-
if lightshow.
|
|
617
|
+
if lightshow.channels_for_group(group) == 4:
|
|
562
618
|
channel_grouping = 4
|
|
563
619
|
else:
|
|
564
620
|
channel_grouping = 3
|
|
@@ -589,7 +645,7 @@ class PKONEHardwarePlatform(SwitchPlatform, DriverPlatform, LightsPlatform, Serv
|
|
|
589
645
|
index = int(number_str)
|
|
590
646
|
|
|
591
647
|
# Determine if there are 3 or 4 channels depending upon firmware on board
|
|
592
|
-
if self.pkone_lightshows[board_address_id].
|
|
648
|
+
if self.pkone_lightshows[int(board_address_id)].channels_for_group(group) == 4:
|
|
593
649
|
# rgbw uses 4 channels per led
|
|
594
650
|
return [
|
|
595
651
|
{
|
|
@@ -16,7 +16,7 @@ PKONECoilConfiguration = namedtuple("PKONECoilConfiguration", ["pulse_settings",
|
|
|
16
16
|
|
|
17
17
|
class PKONECoil(DriverPlatformInterface):
|
|
18
18
|
|
|
19
|
-
"""
|
|
19
|
+
"""A coil/driver connected to a PKONE EX2 board."""
|
|
20
20
|
|
|
21
21
|
__slots__ = ["log", "hardware_rule", "_config_state", "machine", "platform",
|
|
22
22
|
"send", "platform_settings"]
|
|
@@ -37,10 +37,10 @@ class PKONECoil(DriverPlatformInterface):
|
|
|
37
37
|
self.reset()
|
|
38
38
|
|
|
39
39
|
def get_board_name(self):
|
|
40
|
-
"""Return PKONE
|
|
41
|
-
if self.number.board_address_id not in self.platform.
|
|
40
|
+
"""Return PKONE EX2 address."""
|
|
41
|
+
if self.number.board_address_id not in self.platform.pkone_ex2_boards:
|
|
42
42
|
return "PKONE Unknown Board"
|
|
43
|
-
return "PKONE
|
|
43
|
+
return "PKONE EX2 Board {}".format(self.number.board_address_id)
|
|
44
44
|
|
|
45
45
|
def get_recycle_time_ms_for_cmd(self, recycle, pulse_ms) -> int:
|
|
46
46
|
"""Return recycle ms."""
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
"""PKONE
|
|
1
|
+
"""PKONE EX2 board."""
|
|
2
2
|
import logging
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
# pylint: disable-msg=too-few-public-methods
|
|
6
|
-
class
|
|
6
|
+
class PKONEEX2Board:
|
|
7
7
|
|
|
8
|
-
"""PKONE
|
|
8
|
+
"""PKONE EX2 board with switches, optos, coils and servos."""
|
|
9
9
|
|
|
10
10
|
__slots__ = ["log", "addr", "firmware_version", "hardware_rev", "switch_count",
|
|
11
11
|
"coil_count", "servo_count"]
|
|
12
12
|
|
|
13
13
|
def __init__(self, addr, firmware_version, hardware_rev):
|
|
14
|
-
"""Initialize PKONE
|
|
15
|
-
self.log = logging.getLogger('
|
|
14
|
+
"""Initialize a PKONE EX2 board."""
|
|
15
|
+
self.log = logging.getLogger('PKONEEX2Board {}'.format(addr))
|
|
16
16
|
self.addr = addr
|
|
17
17
|
self.firmware_version = firmware_version
|
|
18
18
|
self.hardware_rev = hardware_rev
|
|
19
|
-
self.switch_count = 35 #
|
|
20
|
-
self.coil_count = 10 #
|
|
21
|
-
self.servo_count = 4
|
|
19
|
+
self.switch_count = 35 # inputs 1-30 are switches; 31-35 are optos
|
|
20
|
+
self.coil_count = 10 # outputs 1-10
|
|
21
|
+
self.servo_count = 4 # outputs 11-14
|
|
22
22
|
|
|
23
23
|
def get_description_string(self) -> str:
|
|
24
24
|
"""Return description string."""
|
|
25
|
-
return "PKONE
|
|
26
|
-
"Switches: {}, Coils: {}, Servos: {}".format(
|
|
25
|
+
return "PKONE EX2 Board {} - Firmware: {}, Hardware Rev: {}, " \
|
|
26
|
+
"Switches/Optos: {}, Coils: {}, Servos: {}".format(
|
|
27
27
|
self.addr,
|
|
28
28
|
self.firmware_version,
|
|
29
29
|
self.hardware_rev,
|
|
@@ -17,7 +17,7 @@ PKONESimpleLEDNumber = namedtuple("PKONESimpleLEDNumber", ["board_address_id", "
|
|
|
17
17
|
|
|
18
18
|
class PKONESimpleLED(LightPlatformSoftwareFade):
|
|
19
19
|
|
|
20
|
-
"""A
|
|
20
|
+
"""A general-lighting output on a PKONE Lightshow board."""
|
|
21
21
|
|
|
22
22
|
__slots__ = ["log", "send", "platform"]
|
|
23
23
|
|
|
@@ -46,7 +46,7 @@ class PKONESimpleLED(LightPlatformSoftwareFade):
|
|
|
46
46
|
|
|
47
47
|
def get_board_name(self):
|
|
48
48
|
"""Return PKONE Lightshow addr."""
|
|
49
|
-
if self.number.board_address_id not in self.platform.
|
|
49
|
+
if self.number.board_address_id not in self.platform.pkone_lightshows:
|
|
50
50
|
return "PKONE Unknown Board"
|
|
51
51
|
return "PKONE Lightshow Board {}".format(self.number.board_address_id)
|
|
52
52
|
|
|
@@ -11,15 +11,17 @@ class PKONELightshowBoard:
|
|
|
11
11
|
"""PKONE Lightshow board."""
|
|
12
12
|
|
|
13
13
|
__slots__ = ["log", "addr", "firmware_version", "hardware_rev", "rgbw_firmware", "simple_led_count", "led_groups",
|
|
14
|
-
"max_leds_per_group", "_channel_hw_drivers"]
|
|
14
|
+
"max_leds_per_group", "_channel_hw_drivers", "mixed_firmware", "group_types"]
|
|
15
15
|
|
|
16
16
|
# pylint: disable-msg=too-many-arguments
|
|
17
|
-
def __init__(self, addr, firmware_version, hardware_rev, rgbw_firmware=False):
|
|
17
|
+
def __init__(self, addr, firmware_version, hardware_rev, rgbw_firmware=False, mixed_firmware=False):
|
|
18
18
|
"""Initialize PKONE Lightshow board."""
|
|
19
19
|
self.log = logging.getLogger('PKONELightshowBoard {}'.format(addr))
|
|
20
20
|
self.addr = addr
|
|
21
21
|
self.firmware_version = firmware_version
|
|
22
22
|
self.rgbw_firmware = rgbw_firmware
|
|
23
|
+
self.mixed_firmware = mixed_firmware
|
|
24
|
+
self.group_types = {}
|
|
23
25
|
self.hardware_rev = hardware_rev
|
|
24
26
|
self.simple_led_count = 40 # numbers 1 - 40
|
|
25
27
|
self.led_groups = 8 # numbers 1 - 8
|
|
@@ -29,8 +31,23 @@ class PKONELightshowBoard:
|
|
|
29
31
|
for group in range(1, self.led_groups + 1):
|
|
30
32
|
self._channel_hw_drivers[group] = {}
|
|
31
33
|
|
|
34
|
+
def channels_for_group(self, group):
|
|
35
|
+
"""Return the configured wire width; never guess for a mixed board."""
|
|
36
|
+
group = int(group)
|
|
37
|
+
if not 1 <= group <= 8:
|
|
38
|
+
raise AssertionError("PKONE LED group must be 1-8")
|
|
39
|
+
if not self.mixed_firmware:
|
|
40
|
+
return 4 if self.rgbw_firmware else 3
|
|
41
|
+
if group not in self.group_types:
|
|
42
|
+
raise AssertionError(f"Configure pkone: lightshow_groups: '{self.addr}-{group}' as rgb or rgbw")
|
|
43
|
+
return self.group_types[group]
|
|
44
|
+
|
|
32
45
|
def get_description_string(self) -> str:
|
|
33
46
|
"""Return description string."""
|
|
47
|
+
if self.mixed_firmware:
|
|
48
|
+
return (f"PKONE Lightshow Board {self.addr} - mixed RGB/RGBW firmware: "
|
|
49
|
+
f"{self.firmware_version}, Hardware Rev: {self.hardware_rev}, "
|
|
50
|
+
f"configured groups: {self.group_types}")
|
|
34
51
|
if self.rgbw_firmware:
|
|
35
52
|
return "PKONE Lightshow Board {} - RGBW Firmware: {}, Hardware Rev: {}, Simple LEDs: {}, " \
|
|
36
53
|
"RGBW LED Groups: {} (max {} LEDs per RGBW group)".format(self.addr,
|