class HardwarePlatform(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(HardwarePlatform, self).__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 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) 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 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.""" if config['number'] != "1": raise AssertionError( "P3-ROC only has one accelerometer. Use number 1") self.accelerometer_device = PROCAccelerometer(callback) self._configure_accelerometer() 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): """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(config['number'])) if proc_num == -1: raise AssertionError( "Driver {} cannot be controlled by the P3-ROC. ".format( str(config['number']))) proc_driver_object = PROCDriver(proc_num, config, self) return proc_driver_object def configure_gi(self, config): """Configure a GI driver on the P3-Roc. GIs are coils in P3-Roc """ proc_num = self.pdbconfig.get_proc_coil_number(str(config['number'])) if proc_num == -1: raise AssertionError( "Gi Driver {} cannot be controlled by the P3-ROC. ".format( 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 in P3-Roc.""" proc_num = self.pdbconfig.get_proc_light_number(str(config['number'])) if proc_num == -1: raise AssertionError( "Matrixlight {} cannot be controlled by the P3-ROC. ".format( str(config['number']))) proc_driver_object = PROCMatrixLight(proc_num, self.proc) return proc_driver_object def configure_switch(self, config): """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: switch : A reference to the switch object that was just created. proc_num : Integer of the actual hardware switch number the P3-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. """ proc_num = self.pdbconfig.get_proc_switch_number(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): # 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, dt): """Check the P3-ROC for any events (switch state changes). Also tickles the watchdog and flushes any queued commands to the P3-ROC. """ del dt # 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()
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()