def test_high_bound(self): """Tests Maximum values""" self.assertEqual([255], can_pack([(255, 1)])) hex_val = int("ffffffffffffffff", 16) # 8-byte val is arbitrary, can_pack has no limit on output length self.assertEqual([255, 255, 255, 255, 255, 255, 255, 255], can_pack([(hex_val, 8)]))
def test_high_bound(self, mock_next_message, mock_send_message): '''Tests maximum values for port and pin''' gpio_pin_data = can_util.can_pack([(BABYDRIVER_CAN_MESSAGE_ID, 1), (1, 1)]) gpio_pin_msg = can_util.Message( message_id=BABYDRIVER_CAN_MESSAGE_ID, device_id=BABYDRIVER_DEVICE_ID, data=gpio_pin_data, ) status_data = can_util.can_pack([(BABYDRIVER_CAN_MESSAGE_ID, 1), (0, 1)]) status_msg = can_util.Message( message_id=BABYDRIVER_CAN_MESSAGE_ID, device_id=BABYDRIVER_DEVICE_ID, data=status_data, ) mock_next_message.side_effect = [gpio_pin_msg, status_msg] self.assertTrue(gpio_get(GpioPort.F, 15)) mock_next_message.side_effect = [gpio_pin_msg, status_msg] self.assertTrue(gpio_get('F', 15)) mock_next_message.side_effect = [gpio_pin_msg, status_msg] self.assertTrue(gpio_get('f', 15)) gpio_pin_data = can_util.can_pack([(BABYDRIVER_CAN_MESSAGE_ID, 1), (0, 1)]) gpio_pin_msg = can_util.Message( message_id=BABYDRIVER_CAN_MESSAGE_ID, device_id=BABYDRIVER_DEVICE_ID, data=gpio_pin_data, ) mock_next_message.side_effect = [gpio_pin_msg, status_msg] self.assertFalse(gpio_get(GpioPort.F, 15)) mock_next_message.side_effect = [gpio_pin_msg, status_msg] self.assertFalse(gpio_get('F', 15)) mock_next_message.side_effect = [gpio_pin_msg, status_msg] self.assertFalse(gpio_get('f', 15))
def test_fail_conditions(self, mock_next_message, mock_send_message): '''Tests fail conditions''' gpio_pin_data = can_util.can_pack([(BABYDRIVER_CAN_MESSAGE_ID, 1), (1, 1)]) gpio_pin_msg = can_util.Message( message_id=BABYDRIVER_CAN_MESSAGE_ID, device_id=BABYDRIVER_DEVICE_ID, data=gpio_pin_data, ) status_data = can_util.can_pack([(BABYDRIVER_CAN_MESSAGE_ID, 1), (1, 1)]) status_msg = can_util.Message( message_id=BABYDRIVER_CAN_MESSAGE_ID, device_id=BABYDRIVER_DEVICE_ID, data=status_data, ) # This should fail because of a non zero status message mock_next_message.side_effect = [gpio_pin_msg, status_msg] self.assertRaises(Exception, gpio_get, GpioPort.A, 0) status_data = can_util.can_pack([(BABYDRIVER_CAN_MESSAGE_ID, 1), (0, 1)]) status_msg = can_util.Message( message_id=BABYDRIVER_CAN_MESSAGE_ID, device_id=BABYDRIVER_DEVICE_ID, data=status_data, ) # invalid port fail mock_next_message.side_effect = [gpio_pin_msg, status_msg] self.assertRaises(ValueError, gpio_get, -1, 0) mock_next_message.side_effect = [gpio_pin_msg, status_msg] self.assertRaises(ValueError, gpio_get, 6, 0) mock_next_message.side_effect = [gpio_pin_msg, status_msg] self.assertRaises(AttributeError, gpio_get, 'G', 0) # invalid pin fail mock_next_message.side_effect = [gpio_pin_msg, status_msg] self.assertRaises(ValueError, gpio_get, GpioPort.A, -1) mock_next_message.side_effect = [gpio_pin_msg, status_msg] self.assertRaises(ValueError, gpio_get, GpioPort.A, 16)
def adc_read(port, pin, raw=False): """ Reads a raw or converted ADC value. Args: port: The port of the GPIO pin to read from. pin: The pin number of the GPIO pin to read from. raw: If raw is True, a raw read should be performed; otherwise a converted read. Defaults to converted. Returns: The ADC reading as a 16-bit integer. Raises: ValueError: if the range of the input args is invalid. Exception: if we receive a nonzero status code. """ # If port is entered as a str, convert to int if isinstance(port, str): port = getattr(GpioPort, port.capitalize()) # Check the ranges of input args if port < 0 or port >= GpioPort.NUM_GPIO_PORTS: raise ValueError("ERROR: invalid GPIO port") if pin < 0 or pin >= NUM_PINS_PER_PORT: raise ValueError("ERROR: invalid GPIO pin number") if raw not in (True, False): raise ValueError("ERROR: raw must be a bool value") # Send CAN message data = [ (port, 1), (pin, 1), (raw, 1), ] data = can_util.can_pack(data) can_util.send_message(babydriver_id=BabydriverMessageId.ADC_READ_COMMAND, data=data) # Wait to receive the first CAN message with data data_msg = can_util.next_message(babydriver_id=BabydriverMessageId.ADC_READ_DATA) # Extract the low and high bytes of the ADC conversion result_low = data_msg.data[1] result_high = data_msg.data[2] # Wait to receive the second CAN message with status status_msg = can_util.next_message(babydriver_id=BabydriverMessageId.STATUS) status = status_msg.data[1] # Check if status is not ok if status != OK_STATUS: raise Exception("ERROR: received a nonzero STATUS_CODE: {}".format(status)) return (result_high << 8) | result_low
def i2c_read(port, address, rx_len, reg=None): """ Reads a number of bytes over I2C Args: port: I2C port (1 or 2) to read from address: I2C address to read from rx_len: expected response length reg: register to read from, or None if it is a normal read (non-register read) Raises: ValueError: if parameters passed into i2c_read are invalid Exception: if a non-zero status code was received """ is_reg = 0 if reg is None: reg = 0 else: is_reg = 1 if port not in (1, 2): raise ValueError("Expected port of 1 or 2 ") if address < 0 or address > 255: raise ValueError("Expected an uint8 address between 0 and 255") if rx_len < 0 or rx_len > 255: raise ValueError( "Expected an uint8 response length value between 0 and 255") if reg < 0 or reg > 255: raise ValueError("Expected an uint8 register between 0 and 255") # shift port to 0 or 1, and send can message data_pack = can_util.can_pack([(port - 1, 1), (address, 1), (rx_len, 1), (is_reg, 1), (reg, 1)]) can_util.send_message(babydriver_id=BabydriverMessageId.I2C_READ_COMMAND, data=data_pack) read_data = [] num_msgs = ceil(rx_len / 7) # Loop to receive the ceil(rx_len/7) CAN messages, with only the # last 7 bytes of each msg being data and storing only rx_len bytes # of data to the read_data list for message in range(num_msgs): read_msg = can_util.next_message( babydriver_id=BabydriverMessageId.I2C_READ_DATA) if message == (num_msgs - 1) and (rx_len % 7) != 0: read_data.extend(read_msg.data[1:rx_len % 7 + 1]) else: read_data.extend(read_msg.data[1:8]) # Wait to receive the status CAN message status_msg = can_util.next_message( babydriver_id=BabydriverMessageId.STATUS) status = status_msg.data[1] # Check if status is not STATUS_CODE_OK (0) if status != 0: raise Exception("Received STATUS_CODE {}".format(status)) return read_data
def test_multiple_inputs(self): """Tests with multiple value lists""" self.assertEqual([0, 0], can_pack([(0, 1), (0, 1)])) zero_list = [(0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1), (0, 1)] self.assertEqual([0, 0, 0, 0, 0, 0, 0, 0], can_pack(zero_list)) self.assertEqual([255, 255], can_pack([(255, 1), (255, 1)])) max_list = [(255, 1), (255, 1), (255, 1), (255, 1), (255, 1), (255, 1), (255, 1), (255, 1)] self.assertEqual([255, 255, 255, 255, 255, 255, 255, 255], can_pack(max_list)) # test some arbitrary values - switch to little endian properly self.assertEqual([239, 205, 171, 0], can_pack([(int('ABCDEF', 16), 4)])) self.assertEqual([1, 2, 3], can_pack([(1, 1), (2, 1), (3, 1)])) self.assertEqual([27, 182, 100, 0], can_pack([(27, 1), (182, 1), (100, 2)])) arbitrary_list = [(365, 2), (99, 1), (12093847, 3)] self.assertEqual([109, 1, 99, 151, 137, 184], can_pack(arbitrary_list)) outlist = [239, 205, 171, 0, 3, 2, 1] self.assertEqual( outlist, can_pack([(int('ABCDEF', 16), 4), (int('010203', 16), 3)]))
def i2c_write(port, address, tx_bytes, reg=None): """ Writes over I2C to the given port/address Args: port: port of the I2C to write to (1 or 2) address: I2C address to write to (0-255) tx_bytes: list of bytes to write over I2C (a list of ints 0-255) reg: the register to write to, or None if no register is required Raises: Exception: if a non-zero status code was received """ if port not in (1, 2): raise ValueError("Expected port of 1 (I2C_PORT_1) or 2 (I2C_PORT_2)") if address < 0 or address > 255: raise ValueError("Expected address between 0 and 255") if not all(0 <= byte < 256 for byte in tx_bytes): raise ValueError("Expected list of bytes between 0 and 255") is_reg = 0 if reg is None: reg = 0 else: is_reg = 1 if reg < 0 or reg > 255: raise ValueError("Expected register to write to between 0 and 255") can_pack = can_util.can_pack([(port - 1, 1), (address, 1), (len(tx_bytes), 1), (is_reg, 1), (reg, 1)]) can_util.send_message( babydriver_id=BabydriverMessageId.I2C_WRITE_COMMAND, data=can_pack, ) # Sends CAN message for i in range(0, len(tx_bytes), 7): can_util.send_message( babydriver_id=BabydriverMessageId.I2C_WRITE_DATA, data=tx_bytes[i:i + 7], ) status_msg = can_util.next_message( babydriver_id=BabydriverMessageId.STATUS) # Raises Exception if status is non-OK if status_msg.data[1] != 0: raise Exception("Received STATUS_CODE {}".format(status_msg.data[1]))
def unregister_gpio_interrupt(port, pin): """ Unregisters a gpio interrupt on a pin Args: port: Port of the GPIO pin to register an interrupt in pin: Pin number of the GPIO pin to register an interrupt in Raises: Value error: if the parameters passed into register_gpio_interrupt are incorrect Attribute error: if the port parameter is invalid (refer gpio_port.py for acceptable port parameters) Exception: if a non-zero status code is received when attempting to uregister an interrupt Key error: if no interrupt is registered in the given port and pin """ if isinstance(port, str): port = getattr(GpioPort, port.capitalize()) if port < 0 or port >= GpioPort.NUM_GPIO_PORTS: raise ValueError("invalid GPIO port (Range: 'A' - 'F')") if pin < 0 or pin >= NUM_PINS_PER_PORT: raise ValueError("Invalid GPIO pin number (Range: 0 - 15)") msg_data_unregister_gpio_interrupt = [(port, 1), (pin, 1)] can_util.send_message( babydriver_id=message_defs.BabydriverMessageId.GPIO_IT_UNREGISTER_COMMAND, data=can_util.can_pack(msg_data_unregister_gpio_interrupt) ) status_message = can_util.next_message(babydriver_id=message_defs.BabydriverMessageId.STATUS) received_status = status_message.data[1] # Check if status is invalid (0 refers to STATUS_CODE_OK) if received_status != 0: raise Exception("Received a nonzero STATUS_CODE: {}".format(received_status)) # Clearing callback related to the interrupt that was unregistered if (port, pin) not in callback_dict: raise KeyError("No interrupt registered on given port and pin") del callback_dict[(port, pin)]
def gpio_get(port, pin): ''' Returns the state of the GPIO pin at the given port and pin number as a bool The port can be entered as either a string or int value (e.g. 'A' or 0). The pin is an int value ''' if isinstance(port, str): port = getattr(GpioPort, port.capitalize()) if port < 0 or port >= GpioPort.NUM_GPIO_PORTS: raise ValueError("ERROR: invalid GPIO port") if pin < 0 or pin >= GPIO_PINS_PER_PORT: raise ValueError("ERROR: invalid GPIO pin number") data = can_util.can_pack([(port, 1), (pin, 1)]) can_util.send_message( babydriver_id=BabydriverMessageId.GPIO_GET_COMMAND, data=data, ) gpio_data_msg = can_util.next_message( babydriver_id=BabydriverMessageId.GPIO_GET_DATA, ) status_msg = can_util.next_message( babydriver_id=BabydriverMessageId.STATUS, ) status = status_msg.data[1] if status != 0: raise Exception("ERROR: Non-OK status returned: {}".format(status)) raw_state = gpio_data_msg.data[1] gpio_pin_state = bool(raw_state) return gpio_pin_state
def gpio_set(port, pin, state): """ Sets a GPIO pin Args: port: port of the GPIO pin to set (can be entered as either a string or integer) pin: pin number of the GPIO pin to set state: 0 if setting low, 1 if setting high Raises: AttributeError: if port parameter is entered as a string and is invalid ValueError: if parameters passed into gpio_set are invalid Exception: if a non-zero status code was received """ # If given port as a string, it is converted to a string if isinstance(port, str): port = getattr(GpioPort, port.capitalize()) # Checks whether valid parameters were passed into gpio_set if port < 0 or port >= GpioPort.NUM_GPIO_PORTS: raise ValueError("Expected port between A and {}".format( chr(GpioPort.NUM_GPIO_PORTS + ord('A') - 1) )) if pin < 0 or pin >= NUM_PINS_PER_PORT: raise ValueError("Expected pin between 0 and {}".format(NUM_PINS_PER_PORT - 1)) if state not in (0, 1): raise ValueError("Expected state of 0 (low) or 1 (high)") can_pack_args = [(port, 1), (pin, 1), (state, 1)] can_util.send_message( babydriver_id=BabydriverMessageId.GPIO_SET, data=can_util.can_pack(can_pack_args) ) gpio_set_status = can_util.next_message(babydriver_id=BabydriverMessageId.STATUS) # Checks for valid status code if gpio_set_status.data[1] != 0: raise Exception("Received STATUS_CODE {}".format(gpio_set_status.data[1]))
def test_low_bound(self): """Tests Minimum values""" self.assertEqual([0], can_pack([(0, 1)])) self.assertEqual([0, 0, 0, 0, 0, 0, 0, 0], can_pack([(0, 8)])) self.assertEqual([1], can_pack([(1, 1)])) self.assertEqual([1, 0, 0, 0, 0, 0, 0, 0], can_pack([(1, 8)]))
def register_gpio_interrupt(port, pin, edge=InterruptEdge.RISING_AND_FALLING, callback=None): """ Registers a gpio interrupt on a pin Args: port: port of the GPIO pin to register an interrupt in pin: pin number of the GPIO pin to register an interrupt in edge: callback function is called when this interrupt edge occurs. Can be entered as a string or a number (RISING (0), FALLING (1) or RISING_AND_FALLING (2)) callback: if callback is None, a default callback function will be called that prints "Interrupt on P<port><pin>: <edge>" in this format. The user-defined callback function should follow this format: function_name(info) where the only parameter is info, a named tuple which holds the port (info.port),pin (info.pin) and edge (info.edge) of the GPIO interrupt that occured. Raises: Value error: if the parameters passed into register_gpio_interrupt are incorrect Attribute error: if the port parameter or interrupt edge is invalid (refer gpio_port.py for acceptable port parameters and InterruptEdge for appropriate edge parameters) Exception: if a non-zero status code is received when attempting to register an interrupt Type error: if the callback function (called when interrupt occurs) is of incorrect format Note: There is a hard STM32 limit that only one GPIO interrupt can be registered at a time per pin number. For example, PA2 and PB2 would share the same GPIO pin. """ if isinstance(port, str): port = getattr(GpioPort, port.capitalize()) if port < 0 or port >= GpioPort.NUM_GPIO_PORTS: raise ValueError("invalid GPIO port (Range: 'A' - 'F')") if pin < 0 or pin >= NUM_PINS_PER_PORT: raise ValueError("Invalid GPIO pin number (Range: 0 - 15)") if isinstance(edge, str): if edge.upper() not in InterruptEdge.__members__: raise AttributeError("Enter 'RISING', 'FALLING' or 'RISING_AND_FALLING' for " "interrupt edge") edge = InterruptEdge[edge.upper()] if edge < 0 or edge >= InterruptEdge.NUM_INTERRUPT_EDGES: raise ValueError("invalid interrupt edge (enter 0 (Rising), 1 (Falling) " "or 2 (Rising_and_falling") if callback is not None and not callable(callback): raise ValueError("invalid callback function") msg_data_register_gpio_interrupt = [(port, 1), (pin, 1), (edge, 1)] can_util.send_message( babydriver_id=message_defs.BabydriverMessageId.GPIO_IT_REGISTER_COMMAND, data=can_util.can_pack(msg_data_register_gpio_interrupt) ) status_message = can_util.next_message(babydriver_id=message_defs.BabydriverMessageId.STATUS) received_status = status_message.data[1] # Check if status is invalid (0 refers to STATUS_CODE_OK) if received_status != 0: raise Exception("received a nonzero STATUS_CODE: {}".format(received_status)) # Adding callback to dictionary to be called upon when interrupt occurs if callback is None: callback_dict[(port, pin)] = default_callback else: callback_dict[(port, pin)] = callback
def spi_exchange(tx_bytes, rx_len, spi_port=1, spi_mode=0, baudrate=6000000, cs=None): """ Sends data through SPI The tx_bytes is an int array The rx_len is a non-negative int. The spi_port is a non-negative int. The spi_mode is a non-negative int. The baudrate is a non-negative int. The CS pin, if not None, is a tuple (port, pin), where pin is an int in [0, NUM_PINS_PER_PORT) and port is either an int in [0, NUM_GPIO_PORTS), or a string 'a' through 'f', case insensitive Args: tx_bytes: The bytes to TX. rx_len: Number of bytes to RX spi_port: port of the pin to perform SPI exchange on (1 or 2) spi_mode: SPI mode to use (0, 1, 2 or 3) baudrate: baudrate to use (defaults to 6000000) cs: chip select port and pin to use (defaults to (1, 1)) Raises: AttributeError: if cs_port parameter is entered as a string and is invalid ValueError: if spi_port, spi_mode, rx_len, cs_port, cs_pin don't meet their requirements Exception: if a non-zero status code was received. """ if spi_port not in (1, 2): raise ValueError("ERROR: Expected SPI port 1 or 2") if spi_mode not in (0, 1, 2, 3): raise ValueError("ERROR: Expected mode between 0 and 3") # pylint: disable=superfluous-parens if not (0 <= len(tx_bytes) <= 255): raise ValueError( "ERROR: numbers of tx_bytes must be between 0 and 255") # pylint: disable=superfluous-parens if not (0 <= rx_len <= 255): raise ValueError( "ERROR: rx_len must be a non-negative integer between 0 and 255") if baudrate < 0: raise ValueError("ERROR: baudrate must be a non-negative integer") cs_port = 0 if cs is None else cs[0] cs_pin = 0 if cs is None else cs[1] use_cs = 0 if cs is None else 1 if isinstance(cs_port, str): cs_port = getattr(GpioPort, cs_port.capitalize()) if cs_port < 0 or cs_port >= GpioPort.NUM_GPIO_PORTS: raise ValueError("ERROR: Expected CS port between A and {}".format( chr(GpioPort.NUM_GPIO_PORTS + ord('A') - 1))) if cs_pin < 0 or cs_pin >= GPIO_PINS_PER_PORT: raise ValueError("ERROR: Expected CS pin between 0 and {}".format( GPIO_PINS_PER_PORT - 1)) data1 = [ (spi_port - 1, 1), # 0 for SPI_PORT 1, 1 for SPI_PORT_2 (spi_mode, 1), (len(tx_bytes), 1), # tx_len (rx_len, 1), (cs_port, 1), (cs_pin, 1), (use_cs, 1), ] data2 = [(baudrate, 4)] can_util.send_message( babydriver_id=BabydriverMessageId.SPI_EXCHANGE_METADATA_1, data=can_util.can_pack(data1)) can_util.send_message( babydriver_id=BabydriverMessageId.SPI_EXCHANGE_METADATA_2, data=can_util.can_pack(data2)) # collect bytes into groups of 7 chunks = [tx_bytes[x:x + 7] for x in range(0, len(tx_bytes), 7)] for data in chunks: # pad with 0s if length isn't 7 if len(data) < 7: data += [0] * (7 - len(data)) tmp = [(d, 1) for d in data] can_util.send_message( babydriver_id=BabydriverMessageId.SPI_EXCHANGE_TX_DATA, data=can_util.can_pack(tmp)) # Receive bytes rx_bytes_left = rx_len rx_data = [] while rx_bytes_left > 0: rx_msg = can_util.next_message( babydriver_id=BabydriverMessageId.SPI_EXCHANGE_RX_DATA) msg_data = rx_msg.data # list constructor to handle bytearrays rx_data += list(msg_data[1:min(rx_bytes_left + 1, 8)]) rx_bytes_left -= 7 status = can_util.next_message(babydriver_id=BabydriverMessageId.STATUS) if status.data[1] != 0: raise Exception("ERROR: Received non-zero status code: {}".format( status.data[1])) return rx_data