def _remove_switch_coil_mapping(self, switch_num, driver: "OPPSolenoid"): """Remove mapping between switch and coil.""" if self.min_version < 0x00020000: return _, _, coil_num = driver.number.split('-') msg = bytearray() msg.append(driver.sol_card.addr) msg.extend(OppRs232Intf.SET_SOL_INP_CMD) msg.append(int(switch_num)) msg.append(int(coil_num) + ord(OppRs232Intf.CFG_SOL_INP_REMOVE)) msg.extend(OppRs232Intf.calc_crc8_whole_msg(msg)) msg.extend(OppRs232Intf.EOM_CMD) final_cmd = bytes(msg) self.log.debug("Unmapping input %s and coil %s", switch_num, coil_num) self.send_to_processor(driver.sol_card.chain_serial, final_cmd)
def vers_resp(self, chain_serial, msg): """Process version response. Args: chain_serial: Serial of the chain which received the message. msg: Message to parse. """ # Multiple get version responses can be received at once self.log.debug("Received Version Response:%s", "".join(" 0x%02x" % b for b in msg)) end = False curr_index = 0 while not end: # Verify the CRC8 is correct crc8 = OppRs232Intf.calc_crc8_part_msg(msg, curr_index, 6) if msg[curr_index + 6] != ord(crc8): self.badCRC += 1 hex_string = "".join(" 0x%02x" % b for b in msg) self.log.warning("Msg contains bad CRC:%s.", hex_string) end = True else: version = (msg[curr_index + 2] << 24) | \ (msg[curr_index + 3] << 16) | \ (msg[curr_index + 4] << 8) | \ msg[curr_index + 5] self.log.debug("Firmware version: %d.%d.%d.%d", msg[curr_index + 2], msg[curr_index + 3], msg[curr_index + 4], msg[curr_index + 5]) if version < self.minVersion: self.minVersion = version if version == BAD_FW_VERSION: raise AssertionError( "Original firmware sent only to Brian before adding " "real version numbers. The firmware must be updated before " "MPF will work.") if not end: if msg[curr_index + 7] == ord(OppRs232Intf.EOM_CMD): end = True elif msg[curr_index + 8] == ord(OppRs232Intf.GET_GET_VERS_CMD): curr_index += 7 else: hex_string = "".join(" 0x%02x" % b for b in msg) self.log.warning("Malformed GET_VERS_CMD response:%s.", hex_string) end = True self.opp_connection[chain_serial].lost_synch()
def reconfigure_driver(self, driver, use_hold: bool): """Reconfigure a driver. Args: driver: Driver object. use_hold: Whether this driver stays enabled after a trigger or not. """ # If hold is 0, set the auto clear bit if not use_hold: cmd = ord(OppRs232Intf.CFG_SOL_AUTO_CLR) driver.hw_driver.can_be_pulsed = True hold = 0 else: cmd = 0 driver.hw_driver.can_be_pulsed = False hold = self.get_hold_value(driver) if not hold: raise AssertionError("Hold may not be 0") if hold >= 16: hold = 15 if self.minVersion >= 0x00020000: # set flag for full power cmd += ord(OppRs232Intf.CFG_SOL_ON_OFF) # TODO: implement separate hold power (0-f) and minimum off time (0-7) minimum_off = self.get_minimum_off_time(driver) if driver.hw_driver.use_switch: cmd += ord(OppRs232Intf.CFG_SOL_USE_SWITCH) _, solenoid = driver.config['number'].split('-') pulse_len = self._get_pulse_ms_value(driver) msg = bytearray() msg.append(driver.hw_driver.solCard.addr) msg.extend(OppRs232Intf.CFG_IND_SOL_CMD) msg.append(int(solenoid)) msg.append(cmd) msg.append(pulse_len) msg.append(hold + (minimum_off << 4)) msg.extend(OppRs232Intf.calc_crc8_whole_msg(msg)) msg.extend(OppRs232Intf.EOM_CMD) final_cmd = bytes(msg) self.log.debug("Writing individual config: %s", "".join(" 0x%02x" % b for b in final_cmd)) self.send_to_processor(driver.hw_driver.solCard.chain_serial, final_cmd)
def _parse_gen2_board(self, chain_serial, msg, read_input_msg): has_neo = False wing_index = 0 sol_mask = 0 inp_mask = 0 incand_mask = 0 while wing_index < OppRs232Intf.NUM_G2_WING_PER_BRD: if msg[2 + wing_index] == ord(OppRs232Intf.WING_SOL): sol_mask |= (0x0f << (4 * wing_index)) inp_mask |= (0x0f << (8 * wing_index)) elif msg[2 + wing_index] == ord(OppRs232Intf.WING_INP): inp_mask |= (0xff << (8 * wing_index)) elif msg[2 + wing_index] == ord(OppRs232Intf.WING_INCAND): incand_mask |= (0xff << (8 * wing_index)) elif msg[2 + wing_index] == ord(OppRs232Intf.WING_NEO): has_neo = True wing_index += 1 if incand_mask != 0: self.opp_incands.append( OPPIncandCard(chain_serial, msg[0], incand_mask, self.incandDict)) if sol_mask != 0: self.opp_solenoid.append( OPPSolenoidCard(chain_serial, msg[0], sol_mask, self.solDict, self)) if inp_mask != 0: # Create the input object, and add to the command to read all inputs self.opp_inputs.append( OPPInputCard(chain_serial, msg[0], inp_mask, self.inpDict, self.inpAddrDict)) # Add command to read all inputs to read input message inp_msg = bytearray() inp_msg.append(msg[0]) inp_msg.extend(OppRs232Intf.READ_GEN2_INP_CMD) inp_msg.append(0) inp_msg.append(0) inp_msg.append(0) inp_msg.append(0) inp_msg.extend(OppRs232Intf.calc_crc8_whole_msg(inp_msg)) read_input_msg.extend(inp_msg) if has_neo: self.opp_neopixels.append( OPPNeopixelCard(chain_serial, msg[0], self.neoCardDict, self))
def _kick_coil(self, sol_int, on): mask = 1 << sol_int msg = bytearray() msg.append(self.solCard.addr) msg.extend(OppRs232Intf.KICK_SOL_CMD) if on: msg.append((mask >> 8) & 0xff) msg.append(mask & 0xff) else: msg.append(0) msg.append(0) msg.append((mask >> 8) & 0xff) msg.append(mask & 0xff) msg.extend(OppRs232Intf.calc_crc8_whole_msg(msg)) cmd = bytes(msg) self.log.debug("Triggering solenoid driver: %s", "".join(" 0x%02x" % b for b in cmd)) self.solCard.platform.send_to_processor(self.solCard.chain_serial, cmd)
def send_vers_cmd(self): """Send get firmware version message.""" whole_msg = bytearray() for card_addr in self.platform.gen2_addr_arr[self.chain_serial]: msg = bytearray() msg.append(card_addr) msg.extend(OppRs232Intf.GET_VERS_CMD) msg.append(0) msg.append(0) msg.append(0) msg.append(0) msg.extend(OppRs232Intf.calc_crc8_whole_msg(msg)) whole_msg.extend(msg) whole_msg.extend(OppRs232Intf.EOM_CMD) cmd = bytes(whole_msg) self.log.debug("Sending get version command: %s", "".join(" 0x%02x" % b for b in cmd)) self.send(cmd)
def send_get_gen2_cfg_cmd(self): """Send get gen2 configuration message to find populated wing boards.""" whole_msg = bytearray() for card_addr in self.platform.gen2_addr_arr[self.chain_serial]: msg = bytearray() msg.append(card_addr) msg.extend(OppRs232Intf.GET_GEN2_CFG) msg.append(0) msg.append(0) msg.append(0) msg.append(0) msg.extend(OppRs232Intf.calc_crc8_whole_msg(msg)) whole_msg.extend(msg) whole_msg.extend(OppRs232Intf.EOM_CMD) cmd = bytes(whole_msg) self.log.debug("Sending get Gen2 Cfg command: %s", "".join(" 0x%02x" % b for b in cmd)) self.send(cmd)
def get_gen2_cfg_resp(self, chain_serial, msg): """Process cfg response. Args: chain_serial: Serial of the chain which received the message. msg: Message to parse. """ # Multiple get gen2 cfg responses can be received at once self.log.debug("Received Gen2 Cfg Response:%s", "".join(" 0x%02x" % b for b in msg)) curr_index = 0 read_input_msg = bytearray() while True: # check that message is long enough, must include crc8 if len(msg) < curr_index + 7: self.log.warning("Msg is too short: %s.", "".join(" 0x%02x" % b for b in msg)) self.opp_connection[chain_serial].lost_synch() break # Verify the CRC8 is correct crc8 = OppRs232Intf.calc_crc8_part_msg(msg, curr_index, 6) if msg[curr_index + 6] != ord(crc8): self.badCRC += 1 self.log.warning("Msg contains bad CRC:%s.", "".join(" 0x%02x" % b for b in msg)) break self._parse_gen2_board(chain_serial, msg[curr_index:curr_index + 6], read_input_msg) if (len(msg) > curr_index + 7) and (msg[curr_index + 7] == ord( OppRs232Intf.EOM_CMD)): break elif (len(msg) > curr_index + 8) and (msg[curr_index + 8] == ord( OppRs232Intf.GET_GEN2_CFG)): curr_index += 7 else: self.log.warning("Malformed GET_GEN2_CFG response:%s.", "".join(" 0x%02x" % b for b in msg)) self.opp_connection[chain_serial].lost_synch() break read_input_msg.extend(OppRs232Intf.EOM_CMD) self.read_input_msg[chain_serial] = bytes(read_input_msg)
def read_gen2_inp_resp(self, chain_serial, msg): """Read switch changes. Args: chain_serial: Serial of the chain which received the message. msg: Message to parse. """ # Single read gen2 input response. Receive function breaks them down # Verify the CRC8 is correct if len(msg) < 6: self.log.warning("Msg too shortC: %s.", "".join(" 0x%02x" % b for b in msg)) self.opp_connection[chain_serial].lost_synch() return crc8 = OppRs232Intf.calc_crc8_part_msg(msg, 0, 6) if msg[6] != ord(crc8): self.badCRC += 1 self.log.warning("Msg contains bad CRC:%s.", "".join(" 0x%02x" % b for b in msg)) else: opp_inp = self.inpAddrDict[chain_serial + '-' + str(msg[0])] new_state = (msg[2] << 24) | \ (msg[3] << 16) | \ (msg[4] << 8) | \ msg[5] # Update the state which holds inputs that are active changes = opp_inp.oldState ^ new_state if changes != 0: curr_bit = 1 for index in range(0, 32): if (curr_bit & changes) != 0: if (curr_bit & new_state) == 0: self.machine.switch_controller.process_switch_by_num( state=1, num=opp_inp.chain_serial + '-' + opp_inp.cardNum + '-' + str(index), platform=self) else: self.machine.switch_controller.process_switch_by_num( state=0, num=opp_inp.chain_serial + '-' + opp_inp.cardNum + '-' + str(index), platform=self) curr_bit <<= 1 opp_inp.oldState = new_state
def read_matrix_inp_resp_initial(self, chain_serial, msg): """Read initial matrix switch states. Args: chain_serial: Serial of the chain which received the message. msg: Message to parse. """ # Verify the CRC8 is correct if len(msg) < 11: raise AssertionError("Received too short initial input response: " + "".join(" 0x%02x" % b for b in msg)) crc8 = OppRs232Intf.calc_crc8_part_msg(msg, 0, 10) if msg[10] != ord(crc8): self.badCRC += 1 self.log.warning("Msg contains bad CRC:%s.", "".join(" 0x%02x" % b for b in msg)) else: if chain_serial + '-' + str(msg[0]) not in self.matrixInpAddrDict: self.log.warning("Got input response for invalid matrix card at initial request: %s. Msg: %s.", msg[0], "".join(" 0x%02x" % b for b in msg)) return opp_inp = self.matrixInpAddrDict[chain_serial + '-' + str(msg[0])] opp_inp.oldState = ((msg[2] << 56) | (msg[3] << 48) | (msg[4] << 40) | (msg[5] << 32) | (msg[6] << 24) | (msg[7] << 16) | (msg[8] << 8) | msg[9])
def read_gen2_inp_resp_initial(self, chain_serial, msg): """Read initial switch states. Args: chain_serial: Serial of the chain which received the message. msg: Message to parse. """ # Verify the CRC8 is correct if len(msg) < 6: raise AssertionError("Received too short initial input response: " + "".join(" 0x%02x" % b for b in msg)) crc8 = OppRs232Intf.calc_crc8_part_msg(msg, 0, 6) if msg[6] != ord(crc8): self.badCRC += 1 self.log.warning("Msg contains bad CRC:%s.", "".join(" 0x%02x" % b for b in msg)) else: opp_inp = self.inpAddrDict[chain_serial + '-' + str(msg[0])] new_state = (msg[2] << 24) | \ (msg[3] << 16) | \ (msg[4] << 8) | \ msg[5] opp_inp.oldState = new_state
def update_incand(self): """Update all the incandescents connected to OPP hardware. This is done once per game loop if changes have been made. It is currently assumed that the oversampling will guarantee proper communication with the boards. If this does not end up being the case, this will be changed to update all the incandescents each loop. Note: This could be made much more efficient by supporting a command that simply sets the state of all 32 of the LEDs as either on or off. """ for incand in self.opp_incands: whole_msg = bytearray() # Check if any changes have been made if (incand.oldState ^ incand.newState) != 0: # Update card incand.oldState = incand.newState msg = bytearray() msg.append(incand.addr) msg.extend(OppRs232Intf.INCAND_CMD) msg.extend(OppRs232Intf.INCAND_SET_ON_OFF) msg.append((incand.newState >> 24) & 0xff) msg.append((incand.newState >> 16) & 0xff) msg.append((incand.newState >> 8) & 0xff) msg.append(incand.newState & 0xff) msg.extend(OppRs232Intf.calc_crc8_whole_msg(msg)) whole_msg.extend(msg) if len(whole_msg) != 0: whole_msg.extend(OppRs232Intf.EOM_CMD) send_cmd = bytes(whole_msg) self.send_to_processor(incand.chain_serial, send_cmd) self.log.debug("Update incand cmd:%s", "".join(" 0x%02x" % b for b in send_cmd))
def reconfigure_driver(self, pulse_settings: PulseSettings, hold_settings: Optional[HoldSettings], recycle: bool = False): """Reconfigure a driver.""" new_config_state = (pulse_settings, hold_settings, recycle, bool(self.switch_rule)) # if config would not change do nothing if new_config_state == self._config_state: return self._config_state = new_config_state # If hold is 0, set the auto clear bit if not hold_settings or not hold_settings.power: cmd = ord(OppRs232Intf.CFG_SOL_AUTO_CLR) hold = 0 else: cmd = 0 hold = int(hold_settings.power * 16) if hold >= 16: if self.solCard.platform.minVersion >= 0x00020000: # set flag for full power cmd += ord(OppRs232Intf.CFG_SOL_ON_OFF) hold = 0 else: hold = 15 minimum_off = self.get_minimum_off_time(recycle) _, _, solenoid = self.number.split('-') # Before version 0.2.0.0 set solenoid input wasn't available. # CFG_SOL_USE_SWITCH was used to enable/disable a solenoid. This # will work as long as switches are added using _add_switch_coil_mapping if self.switch_rule: if self.solCard.platform.minVersion < 0x00020000: cmd += ord(OppRs232Intf.CFG_SOL_USE_SWITCH) elif str(((int(solenoid) & 0x0c) << 1) | (int(solenoid) & 0x03)) in\ [switch.split('-')[2] for switch in self.switches]: # If driver is using matching switch set CFG_SOL_USE_SWITCH # in case config happens after set switch command cmd += ord(OppRs232Intf.CFG_SOL_USE_SWITCH) pulse_len = pulse_settings.duration msg = bytearray() msg.append(self.solCard.addr) msg.extend(OppRs232Intf.CFG_IND_SOL_CMD) msg.append(int(solenoid)) msg.append(cmd) msg.append(pulse_len) msg.append(hold + (minimum_off << 4)) msg.extend(OppRs232Intf.calc_crc8_whole_msg(msg)) msg.extend(OppRs232Intf.EOM_CMD) final_cmd = bytes(msg) self.log.debug("Writing individual config: %s", "".join(" 0x%02x" % b for b in final_cmd)) self.solCard.platform.send_to_processor(self.solCard.chain_serial, final_cmd)
def _crc_message(self, msg, term=True): crc_msg = msg + OppRs232Intf.calc_crc8_part_msg(msg, 0, len(msg)) if term: crc_msg += b'\xff' return crc_msg