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 CHANGED
@@ -10,7 +10,7 @@ PyPI.
10
10
 
11
11
  """
12
12
 
13
- __version__ = '0.58.0.dev4' # Also consider whether MPF-MC pyproject.toml should be updated
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
@@ -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._add_handlers_for_current_state()
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
@@ -161,7 +161,7 @@ logging:
161
161
  data_manager: none # todo subclasses
162
162
  delay_manager: none
163
163
  device_manager: none
164
- event_manager: none
164
+ event_manager: basic
165
165
  extra_balls: none
166
166
  file_manager: none # todo
167
167
  light_controller: none
@@ -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.pkone_extension import PKONEExtensionBoard
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
- from mpf.core.platform import SwitchPlatform, DriverPlatform, LightsPlatform, SwitchSettings, DriverSettings, \
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 the PKONE Nano hardware controller.
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", "pkone_extensions", "pkone_lightshows", "_light_system",
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.pkone_extensions = {} # type: Dict[int, PKONEExtensionBoard]
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 x, y: None, # connected Nano processor
49
- 'PCB': lambda x, y: None, # connected board
50
- 'PWD': lambda x, y: None, # watchdog
51
- 'PWF': lambda x, y: None, # watchdog stop
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 Nano hardware."""
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 Nano - Port: {} at {} baud " \
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 - Extension boards:\n"
133
- for extension in self.pkone_extensions.values():
134
- infos += " -> Address ID: {} (firmware v{}, hardware rev {})\n".format(extension.addr,
135
- extension.firmware_version,
136
- extension.hardware_rev)
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
- 'RGBW' if lightshow.rgbw_firmware else 'RGB',
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 register_extension_board(self, board: PKONEExtensionBoard):
155
- """Register an Extension board."""
156
- if board.addr in self.pkone_extensions or board.addr in self.pkone_lightshows:
157
- raise AssertionError("Duplicate address id: a board has already been "
158
- "registered at address {}".format(board.addr))
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: Extension board address id must be between 0 and 7")
191
+ raise AssertionError("Address out of range: EX2 board address id must be between 0 and 7")
162
192
 
163
- self.pkone_extensions[board.addr] = board
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 board.addr in self.pkone_extensions or board.addr in self.pkone_lightshows:
168
- raise AssertionError("Duplicate address id: a board has already been "
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:].replace('E', '')
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.pkone_extensions:
207
- raise AssertionError("PKONE Extension {} does not exist for coil {}".format(board_id, number))
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.pkone_extensions[board_id].coil_count
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 Extension {board_id} only has {coil_count} coils "
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
- # coil and switch must be on the same extension board (same board address id)
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.pkone_extensions:
350
- raise AssertionError("PKONE Extension {} does not exist for servo {}".format(board_id, number))
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.pkone_extensions[board_id].coil_count
354
- servo_count = self.pkone_extensions[board_id].servo_count
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 Extension {} supports {} servos ({} - {}). "
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
- if board_id not in self.pkone_extensions:
383
- raise AssertionError("PKONE Extension {} does not exist for switch {}".format(board_id, number))
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 self.pkone_extensions[board_id].switch_count < switch_num:
389
- raise AssertionError("PKONE Extension {} only has {} switches. Switch: {}".format(
390
- board_id, self.pkone_extensions[board_id].switch_count, number))
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
- # the message payload is delimited with an 'X' character for the switches on each board
434
- # The first character is the board address ID
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
- switch_number = PKONESwitchNumber(int(msg[0]), int(msg[1:3]))
450
- switch_state = int(msg[-1])
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.rgbw_firmware:
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, _, index = hw_numbers[0].split("-")
615
+ board_address_id, group, index = hw_numbers[0].split("-")
560
616
  lightshow = self.pkone_lightshows[int(board_address_id)]
561
- if lightshow.rgbw_firmware:
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].rgbw_firmware:
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
- """Base class for coils/drivers connected to a PKONE Controller/Extension."""
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 Extension addr."""
41
- if self.number.board_address_id not in self.platform.pkone_extensions.keys():
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 Extension Board {}".format(self.number.board_address_id)
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 Extension board."""
1
+ """PKONE EX2 board."""
2
2
  import logging
3
3
 
4
4
 
5
5
  # pylint: disable-msg=too-few-public-methods
6
- class PKONEExtensionBoard:
6
+ class PKONEEX2Board:
7
7
 
8
- """PKONE Extension board."""
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 Extension board."""
15
- self.log = logging.getLogger('PKONEExtensionBoard {}'.format(addr))
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 # numbers 1 - 35 (31-35 are NC opto switches)
20
- self.coil_count = 10 # numbers 1 - 10
21
- self.servo_count = 4 # numbers 11-14
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 Extension Board {} - Firmware: {}, Hardware Rev: {}, " \
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 simple led (single emitter/color) on a PKONE Extension board. Simple leds are either on or off."""
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.pkone_extensions.keys():
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,