Пример #1
0
    def __init__(self, machine):
        """Initialise and connect P3-Roc."""
        super().__init__(machine)
        self.log = logging.getLogger('P3-ROC')
        self.debug_log("Configuring P3-ROC hardware.")

        # validate config for p3_roc
        self.machine.config_validator.validate_config(
            "p3_roc", self.machine.config['p_roc'])

        if self.machine_type != self.pinproc.MachineTypePDB:
            raise AssertionError("P3-Roc can only handle PDB driver boards")

        self.connect()

        # Because PDBs can be configured in many different ways, we need to
        # traverse the YAML settings to see how many PDBs are being used.
        # Then we can configure the P3-ROC appropriately to use those PDBs.
        # Only then can we relate the YAML coil/light #'s to P3-ROC numbers for
        # the collections.

        self.debug_log("Configuring P3-ROC for PDB driver boards.")
        self.pdbconfig = PDBConfig(self.proc, self.machine.config,
                                   self.pinproc.DriverCount)

        self.acceleration = [0] * 3
        self.accelerometer_device = None  # type: PROCAccelerometer
Пример #2
0
    def __init__(self, machine):
        """Initialise P-ROC."""
        super().__init__(machine)
        self.log = logging.getLogger('P-ROC')
        self.debug_log("Configuring P-ROC hardware")

        # validate config for p_roc
        self.config = self.machine.config_validator.validate_config(
            "p_roc", self.machine.config['p_roc'])

        self.dmd = None
        self.alpha_display = None

        self.connect()

        self.aux_port = AuxPort(self)
        self.aux_port.reset()
        self._use_extended_matrix = False
        self._use_first_eight_direct_inputs = False

        # Because PDBs can be configured in many different ways, we need to
        # traverse the YAML settings to see how many PDBs are being used.
        # Then we can configure the P-ROC appropriately to use those PDBs.
        # Only then can we relate the YAML coil/light #'s to P-ROC numbers for
        # the collections.
        if self.machine_type == self.pinproc.MachineTypePDB:
            self.debug_log("Configuring P-ROC for PDBs (P-ROC driver boards)")
            self.pdbconfig = PDBConfig(self.proc, self.machine.config,
                                       self.pinproc.DriverCount)

        else:
            self.debug_log("Configuring P-ROC for OEM driver boards")
Пример #3
0
    async def connect(self):
        """Connect to the P3-Roc."""
        await super().connect()

        self.pdbconfig = PDBConfig(self, self.machine.config,
                                   self.pinproc.DriverCount)

        if self.dipswitches & 0x01:
            self.log.info(
                "Burst drivers are configured as outputs (DIP Switch 1 set). "
                "You cannot use IDs 0-3 for PD-16/PD-LED boards.")

            if self.version < 2 or self.revision < 6:
                raise AssertionError(
                    "Local inputs are supported only in FW 2.6+. Disable DIP 1 or update firmware."
                )

        if self.dipswitches & 0x02:
            self.log.info(
                "Burst switches are configured as inputs (DIP Switch 2 set). "
                "You cannot use IDs 0-3 for SW-16 boards.")

            if self.version < 2 or self.revision < 6:
                raise AssertionError(
                    "Local inputs are supported only in FW 2.6+. Disable DIP 2 or update firmware."
                )

            for board in range(0, 4):
                device_type = await self.run_proc_cmd("read_data", 2,
                                                      (1 << 12) + (board << 6))
                if device_type != 0:
                    raise AssertionError(
                        "Invalid P3-Roc configuration. Found SW-16 with ID {} which is invalid "
                        "because burst switches/drivers which are configured as inputs/outputs use "
                        "the same switch position. Either disabled DIP 2 or assign ID >= 4 to "
                        "all your SW-16s.")

        # remove all burst ir mappings
        for driver in range(0, 64):
            self.run_proc_cmd_no_wait("write_data", 0x02, 0x80 + (driver * 2),
                                      0)
            self.run_proc_cmd_no_wait("write_data", 0x02, 0x81 + (driver * 2),
                                      0)

        # disable burst IRs
        burst_config1 = 0
        self.run_proc_cmd_no_wait("write_data", 0x02, 0x01, burst_config1)
Пример #4
0
    async def connect(self):
        """Connect to the P-Roc."""
        await super().connect()

        self.aux_port = AuxPort(self)
        self.aux_port.reset()

        # Because PDBs can be configured in many different ways, we need to
        # traverse the YAML settings to see how many PDBs are being used.
        # Then we can configure the P-ROC appropriately to use those PDBs.
        # Only then can we relate the YAML coil/light #'s to P-ROC numbers for
        # the collections.
        if self.machine_type == self.pinproc.MachineTypePDB:
            self.debug_log("Configuring P-ROC for PDBs (P-ROC driver boards)")
            self.pdbconfig = PDBConfig(self, self.machine.config, self.pinproc.DriverCount)

        else:
            self.debug_log("Configuring P-ROC for OEM driver boards")
Пример #5
0
    def __init__(self, machine):
        """Initialise P-ROC."""
        super(HardwarePlatform, self).__init__(machine)
        self.log = logging.getLogger('P-ROC')
        self.debug_log("Configuring P-ROC hardware")

        # validate config for p_roc
        self.machine.config_validator.validate_config(
            "p_roc", self.machine.config['p_roc'])

        self.dmd = None

        self.connect()

        # Clear out the default program for the aux port since we might need it
        # for a 9th column. Details:
        # http://www.pinballcontrollers.com/forum/index.php?topic=1360
        commands = []
        commands += [self.pinproc.aux_command_disable()]

        for dummy_iterator in range(1, 255):
            commands += [self.pinproc.aux_command_jump(0)]

        self.proc.aux_send_commands(0, commands)
        # End of the clear out the default program for the aux port.

        # Because PDBs can be configured in many different ways, we need to
        # traverse the YAML settings to see how many PDBs are being used.
        # Then we can configure the P-ROC appropriately to use those PDBs.
        # Only then can we relate the YAML coil/light #'s to P-ROC numbers for
        # the collections.
        if self.machine_type == self.pinproc.MachineTypePDB:
            self.debug_log("Configuring P-ROC for PDBs (P-ROC driver boards)")
            self.pdbconfig = PDBConfig(self.proc, self.machine.config,
                                       self.pinproc.DriverCount)

        else:
            self.debug_log("Configuring P-ROC for OEM driver boards")
Пример #6
0
class PRocHardwarePlatform(PROCBasePlatform, DmdPlatform, SegmentDisplaySoftwareFlashPlatform):

    """Platform class for the P-ROC hardware controller.

    Args:
        machine: The MachineController instance.
    """

    __slots__ = ["dmd", "alpha_display", "aux_port", "_use_extended_matrix",
                 "_use_first_eight_direct_inputs"]

    def __init__(self, machine):
        """Initialise P-ROC."""
        super().__init__(machine)
        # validate config for p_roc
        self.config = self.machine.config_validator.validate_config("p_roc", self.machine.config.get('p_roc', {}))
        self._configure_device_logging_and_debug('P-Roc', self.config)

        if self.config['driverboards']:
            self.machine_type = self.pinproc.normalize_machine_type(self.config['driverboards'])
        else:
            self.machine_type = self.pinproc.normalize_machine_type(self.machine.config['hardware']['driverboards'])

        self.dmd = None
        self.alpha_display = None
        self.aux_port = None

        self._use_extended_matrix = False
        self._use_first_eight_direct_inputs = False

    async def connect(self):
        """Connect to the P-Roc."""
        await super().connect()

        self.aux_port = AuxPort(self)
        self.aux_port.reset()

        # Because PDBs can be configured in many different ways, we need to
        # traverse the YAML settings to see how many PDBs are being used.
        # Then we can configure the P-ROC appropriately to use those PDBs.
        # Only then can we relate the YAML coil/light #'s to P-ROC numbers for
        # the collections.
        if self.machine_type == self.pinproc.MachineTypePDB:
            self.debug_log("Configuring P-ROC for PDBs (P-ROC driver boards)")
            self.pdbconfig = PDBConfig(self, self.machine.config, self.pinproc.DriverCount)

        else:
            self.debug_log("Configuring P-ROC for OEM driver boards")

    def _get_default_subtype(self):
        """Return default subtype for P-Roc."""
        return "matrix"

    def __repr__(self):
        """Return string representation."""
        return '<Platform.P-ROC>'

    def get_info_string(self):
        """Dump infos about boards."""
        infos = "Firmware Version: {} Firmware Revision: {} Hardware Board ID: {}\n".format(
            self.version, self.revision, self.hardware_version)
        return infos

    @classmethod
    def get_coil_config_section(cls):
        """Return coil config section."""
        return "p_roc_coils"

    def configure_driver(self, config: DriverConfig, number: str, platform_settings: dict):
        """Create a P-ROC driver.

        Typically drivers are coils or flashers, but for the P-ROC this is
        also used for matrix-based lights.

        Args:
            config: Dictionary of settings for the driver.
            number: Number of this driver
            platform_settings: Platform specific setting for this driver.

        Returns a reference to the PROCDriver object which is the actual object
        you can use to pulse(), patter(), enable(), etc.

        """
        # todo need to add Aux Bus support
        # todo need to add virtual driver support for driver counts > 256

        # Find the P-ROC number for each driver. For P-ROC driver boards, the
        # P-ROC number is specified via the Ax-By-C format. For OEM driver
        # boards configured via driver numbers, libpinproc's decode() method
        # can provide the number.

        if self.machine_type == self.pinproc.MachineTypePDB:
            proc_num = self.pdbconfig.get_proc_coil_number(str(number))
            if proc_num == -1:
                raise AssertionError("Driver {} cannot be controlled by the P-ROC. ".format(str(number)))
        else:
            proc_num = self.pinproc.decode(self.machine_type, str(number))

        polarity = platform_settings.get("polarity", None)
        driver = PROCDriver(proc_num, config, self, number, polarity)
        self._late_init_futures.append(driver.initialise())

        return driver

    def configure_switch(self, number: str, config: SwitchConfig, platform_config: dict):
        """Configure a P-ROC switch.

        Args:
            number: String number of the switch to configure.
            config: SwitchConfig settings.
            platform_config: Platform specific settings.

        Returns: A configured switch object.

        """
        del platform_config
        try:
            if number.startswith("SD") and 0 <= int(number[2:]) <= 7:
                self._use_first_eight_direct_inputs = True
            _, y = number.split('/', 2)
            if int(y) > 7:
                self._use_extended_matrix = True
        except ValueError:
            pass

        if self._use_extended_matrix and self._use_first_eight_direct_inputs:
            raise AssertionError(
                "P-Roc vannot use extended matrix and the first eight direct inputs at the same "
                "time. Either only use SD8 to SD31 or only use matrix X/Y with Y <= 7. Offending "
                "switch: {}".format(number))

        if self.machine_type == self.pinproc.MachineTypePDB:
            proc_num = self.pdbconfig.get_proc_switch_number(str(number))
            if proc_num == -1:
                raise AssertionError("Switch {} cannot be controlled by the P-ROC. ".format(str(number)))

        else:
            proc_num = self.pinproc.decode(self.machine_type, str(number))
        return self._configure_switch(config, proc_num)

    async def get_hw_switch_states(self) -> Dict[str, bool]:
        """Read in and set the initial switch state.

        The P-ROC uses the following values for hw switch states:
        1 - closed (debounced)
        2 - open (debounced)
        3 - closed (not debounced)
        4 - open (not debounced)
        """
        switch_states = await self.run_proc_cmd("switch_get_states")
        states = {}

        for switch, state in enumerate(switch_states):
            states[switch] = bool(state in (1, 3))

        return states

    def configure_dmd(self):
        """Configure a hardware DMD connected to a classic P-ROC."""
        self.dmd = PROCDMD(self, self.machine)
        return self.dmd

    async def configure_segment_display(self, number: str, platform_settings) \
            -> "SegmentDisplaySoftwareFlashPlatformInterface":
        """Configure display."""
        del platform_settings
        number_int = int(number)
        if 0 < number_int >= 4:
            raise AssertionError("Number must be between 0 and 3 for p_roc segment display.")

        if not self.alpha_display:
            self.alpha_display = AuxAlphanumericDisplay(self, self.aux_port)

        display = PRocAlphanumericDisplay(self.alpha_display, number_int)
        self._handle_software_flash(display)
        return display

    def process_events(self, events):
        """Process events from the P-Roc."""
        for event in events:
            event_type = event['type']
            event_value = event['value']
            if event_type == self.pinproc.EventTypeDMDFrameDisplayed:
                pass
            elif event_type == self.pinproc.EventTypeSwitchClosedDebounced:
                self.machine.switch_controller.process_switch_by_num(
                    state=1, num=event_value, platform=self)
            elif event_type == self.pinproc.EventTypeSwitchOpenDebounced:
                self.machine.switch_controller.process_switch_by_num(
                    state=0, num=event_value, platform=self)
            elif event_type == self.pinproc.EventTypeSwitchClosedNondebounced:
                self.machine.switch_controller.process_switch_by_num(
                    state=1, num=event_value, platform=self)
            elif event_type == self.pinproc.EventTypeSwitchOpenNondebounced:
                self.machine.switch_controller.process_switch_by_num(
                    state=0, num=event_value, platform=self)
            else:
                self.log.warning("Received unrecognized event from the P-ROC. "
                                 "Type: %s, Value: %s", event_type, event_value)
Пример #7
0
class P3RocHardwarePlatform(PROCBasePlatform, I2cPlatform,
                            AccelerometerPlatform):
    """Platform class for the P3-ROC hardware controller.

    Args:
        machine: The MachineController instance.
    """

    __slots__ = [
        "_burst_opto_drivers_to_switch_map", "_burst_switches",
        "_bursts_enabled", "acceleration", "accelerometer_device"
    ]

    def __init__(self, machine):
        """Initialise and connect P3-Roc."""
        super().__init__(machine)
        self.log = logging.getLogger('P3-ROC')
        self.debug_log("Configuring P3-ROC hardware.")

        # validate config for p3_roc
        self.config = self.machine.config_validator.validate_config(
            "p3_roc", self.machine.config.get('p_roc', {}))

        self.debug = self.config["debug"]

        if self.machine_type != self.pinproc.MachineTypePDB:
            raise AssertionError("P3-Roc can only handle PDB driver boards")

        # Because PDBs can be configured in many different ways, we need to
        # traverse the YAML settings to see how many PDBs are being used.
        # Then we can configure the P3-ROC appropriately to use those PDBs.
        # Only then can we relate the YAML coil/light #'s to P3-ROC numbers for
        # the collections.

        self.debug_log("Configuring P3-ROC for PDB driver boards.")
        self._burst_opto_drivers_to_switch_map = {}
        self._burst_switches = []
        self._bursts_enabled = False

        self.acceleration = [0] * 3
        self.accelerometer_device = None  # type: PROCAccelerometer

    async def connect(self):
        """Connect to the P3-Roc."""
        await super().connect()

        self.pdbconfig = PDBConfig(self, self.machine.config,
                                   self.pinproc.DriverCount)

        if self.dipswitches & 0x01:
            self.log.info(
                "Burst drivers are configured as outputs (DIP Switch 1 set). "
                "You cannot use IDs 0-3 for PD-16/PD-LED boards.")

            if self.version < 2 or self.revision < 6:
                raise AssertionError(
                    "Local inputs are supported only in FW 2.6+. Disable DIP 1 or update firmware."
                )

        if self.dipswitches & 0x02:
            self.log.info(
                "Burst switches are configured as inputs (DIP Switch 2 set). "
                "You cannot use IDs 0-3 for SW-16 boards.")

            if self.version < 2 or self.revision < 6:
                raise AssertionError(
                    "Local inputs are supported only in FW 2.6+. Disable DIP 2 or update firmware."
                )

            for board in range(0, 4):
                device_type = await self.run_proc_cmd("read_data", 2,
                                                      (1 << 12) + (board << 6))
                if device_type != 0:
                    raise AssertionError(
                        "Invalid P3-Roc configuration. Found SW-16 with ID {} which is invalid "
                        "because burst switches/drivers which are configured as inputs/outputs use "
                        "the same switch position. Either disabled DIP 2 or assign ID >= 4 to "
                        "all your SW-16s.")

        # remove all burst ir mappings
        for driver in range(0, 64):
            self.run_proc_cmd_no_wait("write_data", 0x02, 0x80 + (driver * 2),
                                      0)
            self.run_proc_cmd_no_wait("write_data", 0x02, 0x81 + (driver * 2),
                                      0)

        # disable burst IRs
        burst_config1 = 0
        self.run_proc_cmd_no_wait("write_data", 0x02, 0x01, burst_config1)

    def _get_default_subtype(self):
        """Return default subtype for P3-Roc."""
        return "led"

    def __repr__(self):
        """Return string representation."""
        return '<Platform.P3-ROC>'

    async def configure_i2c(self, number: str):
        """Configure I2C device on P3-Roc."""
        return P3RocI2c(number, self)

    @classmethod
    def scale_accelerometer_to_g(cls, raw_value):
        """Convert internal representation to g."""
        # raw value is 0 to 16384 -> 14 bit
        # scale is -2g to 2g (2 complement)
        if raw_value & (1 << 13):
            raw_value -= 1 << 14

        g_value = float(raw_value) / (1 << 12)

        return g_value

    def configure_accelerometer(self, number, config, callback):
        """Configure the accelerometer on the P3-ROC."""
        del config
        if number != "1":
            raise AssertionError(
                "P3-ROC only has one accelerometer. Use number 1. Found: {}".
                format(number))

        self.accelerometer_device = PROCAccelerometer(callback)
        self._configure_accelerometer()

        return self.accelerometer_device

    def _configure_accelerometer(self):

        # enable polling every 128ms
        enable = 0x0F

        # configure some P3-Roc registers

        self.run_proc_cmd_no_wait("write_data", 6, 0x000, enable)

        # CTRL_REG1 - set to standby
        self.run_proc_cmd_no_wait("write_data", 6, 0x12A, 0)

        # XYZ_DATA_CFG - disable high pass filter, scale 0 to 2g
        self.run_proc_cmd_no_wait("write_data", 6, 0x10E, 0x00)

        # CTRL_REG1 - set device to active and in low noise mode
        # 800HZ output data rate
        self.run_proc_cmd_no_wait("write_data", 6, 0x12A, 0x05)

        # CTRL_REG2 - set no sleep, high resolution mode
        self.run_proc_cmd_no_wait("write_data", 6, 0x12B, 0x02)

        # for auto-polling of accelerometer every 128 ms (8 times a sec). set 0x0F
        # disable polling + IRQ status addr FF_MT_SRC
        self.run_proc_cmd_no_wait("write_data", 6, 0x000, 0x1E0F)
        # flush data to proc
        self.run_proc_cmd_no_wait("flush")

    def get_info_string(self):
        """Dump infos about boards."""
        infos = "Firmware Version: {} Firmware Revision: {} Hardware Board ID: {}\n".format(
            self.version, self.revision, self.hardware_version)

        infos += "SW-16 boards found:\n"

        for board in range(0, 32):
            device_type = self.run_proc_cmd_sync("read_data", 2,
                                                 (1 << 12) + (board << 6))
            board_id = self.run_proc_cmd_sync("read_data", 2,
                                              (1 << 12) + (board << 6) + 1)
            if device_type != 0:
                infos += " - Board: {} Switches: 16 Device Type: {:X} Board ID: {:X}\n".format(
                    board, device_type, board_id)

        return infos

    def configure_driver(self, config: DriverConfig, number: str,
                         platform_settings: dict):
        """Create a P3-ROC driver.

        Typically drivers are coils or flashers, but for the P3-ROC this is
        also used for matrix-based lights.

        Args:
            config: Dictionary of settings for the driver.
            number: Number of this driver.
            platform_settings: Platform specific settings

        Returns a reference to the PROCDriver object which is the actual object you
        can use to pulse(), patter(), enable(), etc.
        """
        # todo need to add virtual driver support for driver counts > 256

        # Find the P3-ROC number for each driver. For P3-ROC driver boards, the
        # P3-ROC number is specified via the Ax-By-C format.

        if number.startswith("direct-"):
            return self._configure_direct_driver(config, number)

        proc_num = self.pdbconfig.get_proc_coil_number(str(number))
        if proc_num == -1:
            raise AssertionError(
                "Driver {} cannot be controlled by the P3-ROC. ".format(
                    str(number)))

        if proc_num < 32 and self.dipswitches & 0x01:
            raise AssertionError(
                "Cannot use PD-16 with ID 0 or 1 when DIP 1 is on the P3-Roc. Turn DIP 1 off or "
                "renumber PD-16s. Driver: {}".format(number))

        proc_driver_object = PROCDriver(proc_num, config, self, number, True)

        return proc_driver_object

    def _configure_direct_driver(self, config, number):
        try:
            _, driver_number = number.split("-", 2)
            driver_number = int(driver_number)
        except (ValueError, TypeError):
            raise AssertionError(
                "Except format direct-X with 0 <= X <= 63. Invalid format. Got: {}"
                .format(number))

        if 0 < driver_number > 63:
            raise AssertionError(
                "Except format direct-X with 0 <= X <= 63. X out of bounds. Got: {}"
                .format(number))

        if not self.dipswitches & 0x01:
            raise AssertionError(
                "Set DIP 1 on the P3-Roc to use burst switches as local outputs"
            )

        return PROCDriver(driver_number, config, self, number, True)

    def configure_switch(self, number: str, config: SwitchConfig,
                         platform_config: dict):
        """Configure a P3-ROC switch.

        Args:
            number: Number of this switch
            config: Dictionary of settings for the switch.
            platform_config: Platform specific settings.

        Returns: A configured switch object.
        """
        del platform_config
        if number.startswith("burst-"):
            return self._configure_burst_opto(config, number)
        if number.startswith("direct-"):
            return self._configure_direct_switch(config, number)

        proc_num = self.pdbconfig.get_proc_switch_number(str(number))
        if 0 <= proc_num < 64 and self.dipswitches & 0x02:
            raise AssertionError(
                "Cannot use SW-16 with ID 0-3 when DIP 2 is on the P3-Roc. Turn DIP 2 off or "
                "renumber SW-16s. Switch: {}".format(number))
        return self._configure_switch(config, proc_num)

    def _configure_direct_switch(self, config, number):
        try:
            _, switch_number = number.split("-", 2)
            switch_number = int(switch_number)
        except (ValueError, TypeError):
            raise AssertionError(
                "Except format direct-X with 0 <= X <= 63. Invalid format. Got: {}"
                .format(number))

        if 0 < switch_number > 63:
            raise AssertionError(
                "Except format direct-X with 0 <= X <= 63. X out of bounds. Got: {}"
                .format(number))

        if not self.dipswitches & 0x02:
            raise AssertionError(
                "Set DIP 2 on the P3-Roc to use burst switches as local inputs"
            )

        return self._configure_switch(config, switch_number)

    # pylint: disable-msg=too-many-locals
    def _configure_burst_opto(self, config, number):
        """Configure burst opto on the P3-Roc.

        From Gerry:
        Iterate txIndex from 0 to 63 and fill in the rxMap for each.
        The rx map can overlap. Doesn't matter. The P3-ROC hardware drives burst tx0 then checks the 5 mapped rx.
        Then it drives burst tx1 and checks its 5 mapped rx. Etc up to tx63.
        Also be sure to set the Max burst tx in Switch Controller Burst Configuration 1 register (as documented in the
        programmer's reference).
        Only other thing I can think that matters is this. There are 64 burst tx (outputs) on the board, but only 32
        actual drivers in the code. 0:31 are duplicated as 32:63. So at any one time, 2 tx pins are active.

        Now for burst rx map configuration:
        // Program Count into 1st address for transmitter
        data = (uint)rx_list.Count;
        Machine.PROC.write_data (switchModule, (uint)(burstConfigOffset + tx*2), data);

        // Prepare rx map
        data = 0;
        foreach (int receiver in rx_list) {
          data = (data << 6) | ((uint)receiver & 0x3f);
        }
        Machine.PROC.write_data (switchModule, (uint)(burstConfigOffset + tx*2+1), data);
        """
        # parse input and driver switches first
        try:
            _, input_switch, driver = number.split("-", 3)
            input_switch = int(input_switch)
            driver = int(driver)
        except ValueError:
            raise AssertionError(
                "Burst Opto {} is invalid. Format should be burst-XX-YY with X=input Y=driver."
            )

        # verify we are not conflicting with local inputs
        if self.dipswitches & 0x03:
            raise AssertionError(
                "Cannot use Burst Optos when local inputs or outputs are used. Disable DIP 1 and 2 "
                "on the P3-Roc.")

        # enable burst IRs
        if not self._bursts_enabled:
            self._bursts_enabled = True
            self.log.info("Enabling all burst opto on the P3-Roc.")
            burst_config0 = self.config['burst_us_per_half_pulse'] & 0x3F
            burst_config0 |= (
                self.config['burst_number_of_pulses_to_drive_output']
                & 0x1F) << 6
            burst_config0 |= (
                self.config['burst_number_of_idle_pulses_before_next']
                & 0x3F) << 12
            burst_config0 |= (
                self.config['burst_number_of_burst_pulses_before_check']
                & 0x3F) << 18
            burst_config0 |= (
                (self.config['burst_ms_between_scans'] - 1) & 0x1F) << 24
            self.proc.write_data(0x02, 0x00, burst_config0)
            self.debug_log("Setting 0x02 0x00 to %s", burst_config0)

            burst_config1 = (1 << 31) | 0x1F

            self.run_proc_cmd_no_wait("write_data", 0x02, 0x01, burst_config1)
            self.debug_log("Setting 0x02 0x01 to %s", burst_config1)

            # enable receiver 63 for all of the optos (works around bug in fpga)
            for driver_num in range(0, 64):
                self.run_proc_cmd_no_wait("write_data", 0x02,
                                          0x80 + (driver_num * 2), 1)
                self.debug_log("Setting 0x02 %s to %s",
                               0x80 + (driver_num * 2), 1)
                self.run_proc_cmd_no_wait("write_data", 0x02,
                                          0x81 + (driver_num * 2), 63)
                self.debug_log("Setting 0x02 %s to %s",
                               0x81 + (driver_num * 2), 63)

        # configure driver for receiver
        if driver not in self._burst_opto_drivers_to_switch_map:
            self._burst_opto_drivers_to_switch_map[driver] = []

        if input_switch in self._burst_opto_drivers_to_switch_map[driver]:
            raise AssertionError(
                "Input {} already configured for driver {} in {}. Make sure to configure each "
                "burst input<->driver combination only once.".format(
                    input_switch, driver, number))

        # tell p3-roc to check this input for that driver
        self._burst_opto_drivers_to_switch_map[driver].append(input_switch)

        if len(self._burst_opto_drivers_to_switch_map[driver]) > 5:
            raise AssertionError(
                "Every burst driver only supports up to 5 drivers. Driver {} exceeded that with "
                "switch {}.".format(driver, number))

        rx_to_check_for_this_transmitter = 0
        for switch in self._burst_opto_drivers_to_switch_map[driver]:
            rx_to_check_for_this_transmitter <<= 6
            rx_to_check_for_this_transmitter += switch

        addr_80 = 0x80 + (driver * 2)
        data_80 = len(self._burst_opto_drivers_to_switch_map[driver])
        self.debug_log("Setting 0x02 %s to %s", addr_80, data_80)
        self.run_proc_cmd_no_wait("write_data", 0x02, addr_80, data_80)

        addr_81 = 0x81 + (driver * 2)
        self.debug_log("Setting 0x02 %s to %s", addr_81,
                       rx_to_check_for_this_transmitter)
        self.run_proc_cmd_no_wait("write_data", 0x02, addr_81,
                                  rx_to_check_for_this_transmitter)

        burst_switch = P3RocBurstOpto(config, number, input_switch, driver)
        self._burst_switches.append(burst_switch)

        return burst_switch

    async def get_hw_switch_states(self):
        """Read in and set the initial switch state.

        The P-ROC uses the following values for hw switch states:
        1 - closed (debounced)
        2 - open (debounced)
        3 - closed (not debounced)
        4 - open (not debounced)
        """
        states = await self.run_proc_cmd("switch_get_states")
        result = {}

        for switch, state in enumerate(states):
            # Note: The P3-ROC will return a state of "3" for switches from non-
            # connected SW-16 boards, so that's why we only check for "1" below
            if state == 1:
                result[switch] = 1
            else:
                result[switch] = 0

        # assume 0 for all bursts initially
        for switch in self._burst_switches:
            result[switch.number] = 0

        return result

    def process_events(self, events):
        """Process events from the P3-Roc."""
        for event in events:
            event_type = event['type']
            event_value = event['value']
            if event_type == self.pinproc.EventTypeSwitchClosedDebounced:
                self.machine.switch_controller.process_switch_by_num(
                    state=1, num=event_value, platform=self)
            elif event_type == self.pinproc.EventTypeSwitchOpenDebounced:
                self.machine.switch_controller.process_switch_by_num(
                    state=0, num=event_value, platform=self)
            elif event_type == self.pinproc.EventTypeSwitchClosedNondebounced:
                self.machine.switch_controller.process_switch_by_num(
                    state=1, num=event_value, platform=self)
            elif event_type == self.pinproc.EventTypeSwitchOpenNondebounced:
                self.machine.switch_controller.process_switch_by_num(
                    state=0, num=event_value, platform=self)

            # The P3-ROC will always send all three values sequentially.
            # Therefore, we will trigger after the Z value
            elif event_type == self.pinproc.EventTypeAccelerometerX:
                self.acceleration[0] = event_value
                if self.debug:
                    self.debug_log("Got Accelerometer value X. Value: %s",
                                   event_value)
            elif event_type == self.pinproc.EventTypeAccelerometerY:
                self.acceleration[1] = event_value
                if self.debug:
                    self.debug_log("Got Accelerometer value Y. Value: %s",
                                   event_value)
            elif event_type == self.pinproc.EventTypeAccelerometerZ:
                self.acceleration[2] = event_value

                # trigger here
                if self.accelerometer_device:
                    self.accelerometer_device.update_acceleration(
                        self.scale_accelerometer_to_g(self.acceleration[0]),
                        self.scale_accelerometer_to_g(self.acceleration[1]),
                        self.scale_accelerometer_to_g(self.acceleration[2]))
                if self.debug:
                    self.debug_log("Got Accelerometer value Z. Value: %s",
                                   event_value)
            elif event_type == self.pinproc.EventTypeBurstSwitchOpen:
                if self.debug:
                    self.debug_log("Got burst open event value %s",
                                   event_value)
                self._handle_burst(event_value, 0)
            elif event_type == self.pinproc.EventTypeBurstSwitchClosed:
                if self.debug:
                    self.debug_log("Got burst closed event value %s",
                                   event_value)
                self._handle_burst(event_value, 1)
            else:  # pragma: no cover
                self.log.warning(
                    "Received unrecognized event from the P3-ROC. "
                    "Type: %s, Value: %s", event_type, event_value)

    def _handle_burst(self, event_value, state):
        input_num = event_value & 0x3F
        output_num = (event_value >> 6) & 0x1F
        burst_number1 = "burst-{}-{}".format(input_num, output_num)
        self.machine.switch_controller.process_switch_by_num(state=state,
                                                             num=burst_number1,
                                                             platform=self)
        burst_number2 = "burst-{}-{}".format(input_num, output_num + 32)
        self.machine.switch_controller.process_switch_by_num(state=state,
                                                             num=burst_number2,
                                                             platform=self)
Пример #8
0
class P3RocHardwarePlatform(PROCBasePlatform, I2cPlatform,
                            AccelerometerPlatform):
    """Platform class for the P3-ROC hardware controller.

    Args:
        machine: The MachineController instance.

    Attributes:
        machine: The MachineController instance.
    """
    def __init__(self, machine):
        """Initialise and connect P3-Roc."""
        super().__init__(machine)
        self.log = logging.getLogger('P3-ROC')
        self.debug_log("Configuring P3-ROC hardware.")

        # validate config for p3_roc
        self.machine.config_validator.validate_config(
            "p3_roc", self.machine.config['p_roc'])

        if self.machine_type != self.pinproc.MachineTypePDB:
            raise AssertionError("P3-Roc can only handle PDB driver boards")

        self.connect()

        # Because PDBs can be configured in many different ways, we need to
        # traverse the YAML settings to see how many PDBs are being used.
        # Then we can configure the P3-ROC appropriately to use those PDBs.
        # Only then can we relate the YAML coil/light #'s to P3-ROC numbers for
        # the collections.

        self.debug_log("Configuring P3-ROC for PDB driver boards.")
        self.pdbconfig = PDBConfig(self.proc, self.machine.config,
                                   self.pinproc.DriverCount)

        self.acceleration = [0] * 3
        self.accelerometer_device = None  # type: PROCAccelerometer

    def __repr__(self):
        """Return string representation."""
        return '<Platform.P3-ROC>'

    def i2c_write8(self, address, register, value):
        """Write an 8-bit value to the I2C bus of the P3-Roc."""
        self.proc.write_data(7, address << 9 | register, value)

    @asyncio.coroutine
    def i2c_read8(self, address, register):
        """Read an 8-bit value from the I2C bus of the P3-Roc."""
        return self.proc.read_data(7, address << 9 | register) & 0xFF

    @asyncio.coroutine
    def i2c_read_block(self, address, register, count):
        """Read block via I2C."""
        result = []
        position = 0
        while position < count:
            if count - position == 1:
                data = yield from self.i2c_read8(address, register + position)
                result.append(data)
                position += 1
            else:
                data = yield from self.i2c_read16(address, register)
                result.append((data >> 8) & 0xFF)
                result.append(data & 0xFF)
                position += 2
        return result

    @asyncio.coroutine
    def i2c_read16(self, address, register):
        """Read an 16-bit value from the I2C bus of the P3-Roc."""
        return self.proc.read_data(7, address << 9 | 1 << 8 | register)

    @classmethod
    def scale_accelerometer_to_g(cls, raw_value):
        """Convert internal representation to g."""
        # raw value is 0 to 16384 -> 14 bit
        # scale is -2g to 2g (2 complement)
        if raw_value & (1 << 13):
            raw_value -= 1 << 14

        g_value = float(raw_value) / (1 << 12)

        return g_value

    def configure_accelerometer(self, config, callback):
        """Configure the accelerometer on the P3-ROC."""
        config = self.machine.config_validator.validate_config(
            "p3_roc_accelerometer", config)
        if config['number'] != 1:
            raise AssertionError(
                "P3-ROC only has one accelerometer. Use number 1. Found: {}".
                format(config))

        self.accelerometer_device = PROCAccelerometer(callback)
        self._configure_accelerometer()

        return self.accelerometer_device

    def _configure_accelerometer(self):

        # enable polling every 128ms
        enable = 0x0F

        # configure some P3-Roc registers
        self.proc.write_data(6, 0x000, enable)

        # CTRL_REG1 - set to standby
        self.proc.write_data(6, 0x12A, 0)

        # XYZ_DATA_CFG - disable high pass filter, scale 0 to 2g
        self.proc.write_data(6, 0x10E, 0x00)

        # CTRL_REG1 - set device to active and in low noise mode
        # 800HZ output data rate
        self.proc.write_data(6, 0x12A, 0x05)

        # CTRL_REG2 - set no sleep, high resolution mode
        self.proc.write_data(6, 0x12B, 0x02)

        # for auto-polling of accelerometer every 128 ms (8 times a sec). set 0x0F
        # disable polling + IRQ status addr FF_MT_SRC
        self.proc.write_data(6, 0x000, 0x1E0F)
        # flush data to proc
        self.proc.flush()

    def configure_driver(self, config: DriverConfig, number: str,
                         platform_settings: dict):
        """Create a P3-ROC driver.

        Typically drivers are coils or flashers, but for the P3-ROC this is
        also used for matrix-based lights.

        Args:
            config: Dictionary of settings for the driver.

        Returns:
            A reference to the PROCDriver object which is the actual object you
            can use to pulse(), patter(), enable(), etc.
        """
        # todo need to add virtual driver support for driver counts > 256

        # Find the P3-ROC number for each driver. For P3-ROC driver boards, the
        # P3-ROC number is specified via the Ax-By-C format.

        proc_num = self.pdbconfig.get_proc_coil_number(str(number))
        if proc_num == -1:
            raise AssertionError(
                "Driver {} cannot be controlled by the P3-ROC. ".format(
                    str(number)))

        proc_driver_object = PROCDriver(proc_num, config, self, number)

        return proc_driver_object

    def configure_switch(self, number: str, config: SwitchConfig,
                         platform_config: dict):
        """Configure a P3-ROC switch.

        Args:
            config: Dictionary of settings for the switch. In the case
                of the P3-ROC, it uses the following:

        Returns: A configured switch object.
        """
        del platform_config
        proc_num = self.pdbconfig.get_proc_switch_number(str(number))
        return self._configure_switch(config, proc_num)

    @asyncio.coroutine
    def get_hw_switch_states(self):
        """Read in and set the initial switch state.

        The P-ROC uses the following values for hw switch states:
        1 - closed (debounced)
        2 - open (debounced)
        3 - closed (not debounced)
        4 - open (not debounced)
        """
        states = self.proc.switch_get_states()

        for switch, state in enumerate(states):
            # Note: The P3-ROC will return a state of "3" for switches from non-
            # connected SW-16 boards, so that's why we only check for "1" below
            if state == 1:
                states[switch] = 1
            else:
                states[switch] = 0

        return states

    def tick(self):
        """Check the P3-ROC for any events (switch state changes).

        Also tickles the watchdog and flushes any queued commands to the P3-ROC.
        """
        # Get P3-ROC events
        for event in self.proc.get_events():
            event_type = event['type']
            event_value = event['value']
            if event_type == self.pinproc.EventTypeSwitchClosedDebounced:
                self.machine.switch_controller.process_switch_by_num(
                    state=1, num=event_value, platform=self)
            elif event_type == self.pinproc.EventTypeSwitchOpenDebounced:
                self.machine.switch_controller.process_switch_by_num(
                    state=0, num=event_value, platform=self)
            elif event_type == self.pinproc.EventTypeSwitchClosedNondebounced:
                self.machine.switch_controller.process_switch_by_num(
                    state=1, num=event_value, platform=self)
            elif event_type == self.pinproc.EventTypeSwitchOpenNondebounced:
                self.machine.switch_controller.process_switch_by_num(
                    state=0, num=event_value, platform=self)

            # The P3-ROC will always send all three values sequentially.
            # Therefore, we will trigger after the Z value
            elif event_type == self.pinproc.EventTypeAccelerometerX:
                self.acceleration[0] = event_value
                self.debug_log("Got Accelerometer value X. Value: %s",
                               event_value)
            elif event_type == self.pinproc.EventTypeAccelerometerY:
                self.acceleration[1] = event_value
                self.debug_log("Got Accelerometer value Y. Value: %s",
                               event_value)
            elif event_type == self.pinproc.EventTypeAccelerometerZ:
                self.acceleration[2] = event_value

                # trigger here
                if self.accelerometer_device:
                    self.accelerometer_device.update_acceleration(
                        self.scale_accelerometer_to_g(self.acceleration[0]),
                        self.scale_accelerometer_to_g(self.acceleration[1]),
                        self.scale_accelerometer_to_g(self.acceleration[2]))
                    self.debug_log("Got Accelerometer value Z. Value: %s",
                                   event_value)

            else:  # pragma: no cover
                self.log.warning(
                    "Received unrecognized event from the P3-ROC. "
                    "Type: %s, Value: %s", event_type, event_value)

        self.proc.watchdog_tickle()
        self.proc.flush()
Пример #9
0
class PRocHardwarePlatform(PROCBasePlatform, DmdPlatform,
                           SegmentDisplayPlatform):
    """Platform class for the P-ROC hardware controller.

    Args:
        machine: The MachineController instance.

    Attributes:
        machine: The MachineController instance.
    """
    def __init__(self, machine):
        """Initialise P-ROC."""
        super().__init__(machine)
        self.log = logging.getLogger('P-ROC')
        self.debug_log("Configuring P-ROC hardware")

        # validate config for p_roc
        self.config = self.machine.config_validator.validate_config(
            "p_roc", self.machine.config['p_roc'])

        self.dmd = None
        self.alpha_display = None

        self.connect()

        self.aux_port = AuxPort(self)
        self.aux_port.reset()
        self._use_extended_matrix = False
        self._use_first_eight_direct_inputs = False

        # Because PDBs can be configured in many different ways, we need to
        # traverse the YAML settings to see how many PDBs are being used.
        # Then we can configure the P-ROC appropriately to use those PDBs.
        # Only then can we relate the YAML coil/light #'s to P-ROC numbers for
        # the collections.
        if self.machine_type == self.pinproc.MachineTypePDB:
            self.debug_log("Configuring P-ROC for PDBs (P-ROC driver boards)")
            self.pdbconfig = PDBConfig(self.proc, self.machine.config,
                                       self.pinproc.DriverCount)

        else:
            self.debug_log("Configuring P-ROC for OEM driver boards")

    def _get_default_subtype(self):
        """Return default subtype for P-Roc."""
        return "matrix"

    def __repr__(self):
        """Return string representation."""
        return '<Platform.P-ROC>'

    def get_info_string(self):
        """Dump infos about boards."""
        infos = "Firmware Version: {} Firmware Revision: {} Hardware Board ID: {}\n".format(
            self.version, self.revision, self.hardware_version)
        return infos

    def configure_driver(self, config: DriverConfig, number: str,
                         platform_settings: dict):
        """Create a P-ROC driver.

        Typically drivers are coils or flashers, but for the P-ROC this is
        also used for matrix-based lights.

        Args:
            config: Dictionary of settings for the driver.

        Returns:
            A reference to the PROCDriver object which is the actual object you
            can use to pulse(), patter(), enable(), etc.

        """
        # todo need to add Aux Bus support
        # todo need to add virtual driver support for driver counts > 256

        # Find the P-ROC number for each driver. For P-ROC driver boards, the
        # P-ROC number is specified via the Ax-By-C format. For OEM driver
        # boards configured via driver numbers, libpinproc's decode() method
        # can provide the number.

        if self.machine_type == self.pinproc.MachineTypePDB:
            proc_num = self.pdbconfig.get_proc_coil_number(str(number))
            if proc_num == -1:
                raise AssertionError(
                    "Driver {} cannot be controlled by the P-ROC. ".format(
                        str(number)))
        else:
            proc_num = self.pinproc.decode(self.machine_type, str(number))

        return PROCDriver(proc_num, config, self, number)

    def configure_switch(self, number: str, config: SwitchConfig,
                         platform_config: dict):
        """Configure a P-ROC switch.

        Args:
            number: String number of the switch to configure.
            config: SwitchConfig settings.

        Returns: A configured switch object.

        """
        del platform_config
        try:
            if number.startswith("SD") and 0 <= int(number[2:]) <= 7:
                self._use_first_eight_direct_inputs = True
            _, y = number.split('/', 2)
            if int(y) > 7:
                self._use_extended_matrix = True
        except ValueError:
            pass

        if self._use_extended_matrix and self._use_first_eight_direct_inputs:
            raise AssertionError(
                "P-Roc vannot use extended matrix and the first eight direct inputs at the same "
                "time. Either only use SD8 to SD31 or only use matrix X/Y with Y <= 7. Offending "
                "switch: {}".format(number))

        if self.machine_type == self.pinproc.MachineTypePDB:
            proc_num = self.pdbconfig.get_proc_switch_number(str(number))
            if proc_num == -1:
                raise AssertionError(
                    "Switch {} cannot be controlled by the P-ROC. ".format(
                        str(number)))

        else:
            proc_num = self.pinproc.decode(self.machine_type, str(number))
        return self._configure_switch(config, proc_num)

    @asyncio.coroutine
    def get_hw_switch_states(self):
        """Read in and set the initial switch state.

        The P-ROC uses the following values for hw switch states:
        1 - closed (debounced)
        2 - open (debounced)
        3 - closed (not debounced)
        4 - open (not debounced)
        """
        states = self.proc.switch_get_states()

        for switch, state in enumerate(states):
            if state == 3 or state == 1:
                states[switch] = 1
            else:
                states[switch] = 0

        return states

    def configure_dmd(self):
        """Configure a hardware DMD connected to a classic P-ROC."""
        self.dmd = PROCDMD(self.pinproc, self.proc, self.machine)
        return self.dmd

    def configure_segment_display(
            self, number: str) -> "SegmentDisplayPlatformInterface":
        """Configure display."""
        number_int = int(number)
        if 0 < number_int >= 4:
            raise AssertionError(
                "Number must be between 0 and 3 for p_roc segment display.")

        if not self.alpha_display:
            self.alpha_display = AuxAlphanumericDisplay(self, self.aux_port)

        return PRocAlphanumericDisplay(self.alpha_display, number_int)

    def tick(self):
        """Check the P-ROC for any events (switch state changes or notification that a DMD frame was updated).

        Also tickles the watchdog and flushes any queued commands to the P-ROC.
        """
        # Get P-ROC events (switches & DMD frames displayed)
        for event in self.proc.get_events():
            event_type = event['type']
            event_value = event['value']
            if event_type == self.pinproc.EventTypeDMDFrameDisplayed:
                pass
            elif event_type == self.pinproc.EventTypeSwitchClosedDebounced:
                self.machine.switch_controller.process_switch_by_num(
                    state=1, num=event_value, platform=self)
            elif event_type == self.pinproc.EventTypeSwitchOpenDebounced:
                self.machine.switch_controller.process_switch_by_num(
                    state=0, num=event_value, platform=self)
            elif event_type == self.pinproc.EventTypeSwitchClosedNondebounced:
                self.machine.switch_controller.process_switch_by_num(
                    state=1, num=event_value, platform=self)
            elif event_type == self.pinproc.EventTypeSwitchOpenNondebounced:
                self.machine.switch_controller.process_switch_by_num(
                    state=0, num=event_value, platform=self)
            else:
                self.log.warning(
                    "Received unrecognized event from the P-ROC. "
                    "Type: %s, Value: %s", event_type, event_value)

        self.proc.watchdog_tickle()
        self.proc.flush()
Пример #10
0
class HardwarePlatform(PROCBasePlatform, DmdPlatform):
    """Platform class for the P-ROC hardware controller.

    Args:
        machine: The MachineController instance.

    Attributes:
        machine: The MachineController instance.
    """
    def __init__(self, machine):
        """Initialise P-ROC."""
        super(HardwarePlatform, self).__init__(machine)
        self.log = logging.getLogger('P-ROC')
        self.debug_log("Configuring P-ROC hardware")

        # validate config for p_roc
        self.machine.config_validator.validate_config(
            "p_roc", self.machine.config['p_roc'])

        self.dmd = None

        self.connect()

        # Clear out the default program for the aux port since we might need it
        # for a 9th column. Details:
        # http://www.pinballcontrollers.com/forum/index.php?topic=1360
        commands = []
        commands += [self.pinproc.aux_command_disable()]

        for dummy_iterator in range(1, 255):
            commands += [self.pinproc.aux_command_jump(0)]

        self.proc.aux_send_commands(0, commands)
        # End of the clear out the default program for the aux port.

        # Because PDBs can be configured in many different ways, we need to
        # traverse the YAML settings to see how many PDBs are being used.
        # Then we can configure the P-ROC appropriately to use those PDBs.
        # Only then can we relate the YAML coil/light #'s to P-ROC numbers for
        # the collections.
        if self.machine_type == self.pinproc.MachineTypePDB:
            self.debug_log("Configuring P-ROC for PDBs (P-ROC driver boards)")
            self.pdbconfig = PDBConfig(self.proc, self.machine.config,
                                       self.pinproc.DriverCount)

        else:
            self.debug_log("Configuring P-ROC for OEM driver boards")

    def __repr__(self):
        """Return string representation."""
        return '<Platform.P-ROC>'

    def configure_driver(self, config):
        """Create a P-ROC driver.

        Typically drivers are coils or flashers, but for the P-ROC this is
        also used for matrix-based lights.

        Args:
            config: Dictionary of settings for the driver.

        Returns:
            A reference to the PROCDriver object which is the actual object you
            can use to pulse(), patter(), enable(), etc.

        """
        # todo need to add Aux Bus support
        # todo need to add virtual driver support for driver counts > 256

        # Find the P-ROC number for each driver. For P-ROC driver boards, the
        # P-ROC number is specified via the Ax-By-C format. For OEM driver
        # boards configured via driver numbers, libpinproc's decode() method
        # can provide the number.

        if self.machine_type == self.pinproc.MachineTypePDB:
            proc_num = self.pdbconfig.get_proc_coil_number(
                str(config['number']))
            if proc_num == -1:
                raise AssertionError(
                    "Driver {} cannot be controlled by the P-ROC. ".format(
                        str(config['number'])))
        else:
            proc_num = self.pinproc.decode(self.machine_type,
                                           str(config['number']))

        return PROCDriver(proc_num, config, self)

    def configure_gi(self, config):
        """Configure a GI."""
        # GIs are coils in P-Roc
        if self.machine_type == self.pinproc.MachineTypePDB:
            proc_num = self.pdbconfig.get_proc_coil_number(
                str(config['number']))
            if proc_num == -1:
                raise AssertionError(
                    "Gi Driver {} cannot be controlled by the P-ROC. ".format(
                        str(config['number'])))
        else:
            proc_num = self.pinproc.decode(self.machine_type,
                                           str(config['number']))
        proc_driver_object = PROCGiString(proc_num, self.proc, config)

        return proc_driver_object

    def configure_matrixlight(self, config):
        """Configure a matrix light."""
        if self.machine_type == self.pinproc.MachineTypePDB:
            proc_num = self.pdbconfig.get_proc_light_number(
                str(config['number']))
            if proc_num == -1:
                raise AssertionError(
                    "Matrixlight {} cannot be controlled by the P-ROC. ".
                    format(str(config['number'])))

        else:
            proc_num = self.pinproc.decode(self.machine_type,
                                           str(config['number']))

        return PROCMatrixLight(proc_num, self.proc)

    def configure_switch(self, config):
        """Configure a P-ROC switch.

        Args:
            config: Dictionary of settings for the switch. In the case
                of the P-ROC, it uses the following:

        Returns:
            switch : A reference to the switch object that was just created.
            proc_num : Integer of the actual hardware switch number the P-ROC
                uses to refer to this switch. Typically your machine
                configuration files would specify a switch number like `SD12` or
                `7/5`. This `proc_num` is an int between 0 and 255.

        """
        if self.machine_type == self.pinproc.MachineTypePDB:
            proc_num = self.pdbconfig.get_proc_switch_number(
                str(config['number']))
            if proc_num == -1:
                raise AssertionError(
                    "Switch {} cannot be controlled by the P-ROC. ".format(
                        str(config['number'])))

        else:
            proc_num = self.pinproc.decode(self.machine_type,
                                           str(config['number']))
        return self._configure_switch(config, proc_num)

    def get_hw_switch_states(self):
        """Read in and set the initial switch state.

        The P-ROC uses the following values for hw switch states:
        1 - closed (debounced)
        2 - open (debounced)
        3 - closed (not debounced)
        4 - open (not debounced)
        """
        states = self.proc.switch_get_states()

        for switch, state in enumerate(states):
            if state == 3 or state == 1:
                states[switch] = 1
            else:
                states[switch] = 0

        return states

    def configure_dmd(self):
        """Configure a hardware DMD connected to a classic P-ROC."""
        self.dmd = PROCDMD(self.pinproc, self.proc, self.machine)
        return self.dmd

    def tick(self, dt):
        """Check the P-ROC for any events (switch state changes or notification that a DMD frame was updated).

        Also tickles the watchdog and flushes any queued commands to the P-ROC.
        """
        del dt
        # Get P-ROC events (switches & DMD frames displayed)
        for event in self.proc.get_events():
            event_type = event['type']
            event_value = event['value']
            if event_type == self.pinproc.EventTypeDMDFrameDisplayed:
                pass
            elif event_type == self.pinproc.EventTypeSwitchClosedDebounced:
                self.machine.switch_controller.process_switch_by_num(
                    state=1, num=event_value, platform=self)
            elif event_type == self.pinproc.EventTypeSwitchOpenDebounced:
                self.machine.switch_controller.process_switch_by_num(
                    state=0, num=event_value, platform=self)
            elif event_type == self.pinproc.EventTypeSwitchClosedNondebounced:
                self.machine.switch_controller.process_switch_by_num(
                    state=1, num=event_value, platform=self)
            elif event_type == self.pinproc.EventTypeSwitchOpenNondebounced:
                self.machine.switch_controller.process_switch_by_num(
                    state=0, num=event_value, platform=self)
            else:
                self.log.warning(
                    "Received unrecognized event from the P-ROC. "
                    "Type: %s, Value: %s", event_type, event_value)

        self.proc.watchdog_tickle()
        self.proc.flush()