def test_read_conversion(patch_pigpio_i2c, mock_i2c_hardware, ads1x15, seed): """_________________________________________________________________________________________________________TEST #10 Tests that the proper cfg is generated and written to the config register given kwargs, and tests that the conversion register is properly read & converted to signed int """ random.seed(seed) kwargs = { "MUX": random.choice(ads1x15._CONFIG_VALUES[1]), "PGA": random.choice(ads1x15._CONFIG_VALUES[2]), "MODE": random.choice(ads1x15._CONFIG_VALUES[3]), "DR": random.choice(ads1x15._CONFIG_VALUES[4]) } conversion_bytes = token_bytes(2) expected_val = int.from_bytes(conversion_bytes, 'big', signed=True) * kwargs['PGA'] / 32767 mock = mock_i2c_hardware(i2c_bus=1, i2c_address=ads1x15._DEFAULT_ADDRESS, n_registers=4, reg_values=[conversion_bytes, b'\x85\x83']) pig = PigpioConnection() pig.add_mock_hardware(mock['device'], mock['i2c_address'], mock['i2c_bus']) ads = ads1x15(pig=pig) expected_default_config = ads.config.unpack(0xC3E3) expected_config = ads.config.unpack(ads.config.pack(0xC3E3, **kwargs)) assert ads.print_config() == expected_default_config result = ads.read_conversion(**kwargs) assert ads.print_config() == expected_config assert result == expected_val """__________________________________________________________________________________________________________
def test_analog_sensor_average_read(patch_pigpio_i2c, mock_i2c_hardware, ads1x15, seed): """__________________________________________________________________________________________________________TEST #6 Performs a several updates before averaging AnalogSensor.data to determine if the result matches expectations - Data loaded into the conversion register is gaussian normal about the expected value """ random.seed(seed) pga = random.choice(ads1x15._CONFIG_VALUES[2]) kwargs = { "MUX": random.choice(ads1x15._CONFIG_VALUES[1]), "PGA": pga, "MODE": random.choice(ads1x15._CONFIG_VALUES[3]), "DR": random.choice(ads1x15._CONFIG_VALUES[4]), 'offset_voltage': round(random.uniform(-0.35, 0.35) * pga, 2), 'output_span': round(random.uniform(.75, 1.0) * pga, 2), 'conversion_factor': round(random.choice([-1, 1]) * random.uniform(1, 10), 2) } mock = mock_i2c_hardware( i2c_bus=1, i2c_address=ads1x15._DEFAULT_ADDRESS, n_registers=4, reg_values=[b'\x00\x00', b'\x85\x83'] ) def voltage(bytes_value): val = int.from_bytes(bytes_value, 'big', signed=True) result = val*kwargs['PGA'] / 32767 return result def gaussian_bytes(mu): def _gauss(): val = random.gauss(mu, mu/1000) result = int(round(val)).to_bytes(2, 'big') return result return _gauss n_iter = 1000 target = random.randint(1000, 20000) pig = PigpioConnection() pig.add_mock_hardware(mock['device'], mock['i2c_address'], mock['i2c_bus']) ads = ads1x15(pig=pig) a_sensor = AnalogSensor(ads, **kwargs) a_sensor.maxlen_data = n_iter gauss_cannon = gaussian_bytes(target) bytes_data = [gauss_cannon() for _ in range(n_iter)] avg_voltage = np.mean([voltage(byt) for byt in bytes_data]) expected_value = kwargs['conversion_factor'] * (avg_voltage - kwargs['offset_voltage']) / kwargs['output_span'] for i in range(n_iter): pig.mock_i2c[1][mock['i2c_address']].write_mock_hardware_register(0, bytes_data[i]) a_sensor.update() data = a_sensor.data result_0 = a_sensor.get(average=True) result_1 = data[:, 1].mean() assert len(data) == n_iter assert result_0 - result_1 < 0.1 assert result_0 - expected_value < 0.1 """__________________________________________________________________________________________________________
def test_mock_pigpio_base(patch_pigpio_base): """__________________________________________________________________________________________________________TEST #1 Tests that the base fixture of pigpio mocks is indeed enough to instantiate pigpio.pi() """ pig = PigpioConnection() assert isinstance(pig, pigpio.pi) assert pig.connected pig.stop() assert not pig.connected """__________________________________________________________________________________________________________
def test_analog_sensor_single_read_drop_kwargs(patch_pigpio_i2c, mock_i2c_hardware, ads1x15, seed): """__________________________________________________________________________________________________________TEST #4 Same as the above test, except it will randomly drop kwargs to check that the default attributes are properly filled in and that the read functions normally. - Note: calibration values are stored seperately s.t. the later verification of the result can be calculated even without the store the default calibration for later conversion. """ random.seed(seed) kwargs = { "MUX": random.choice(ads1x15._CONFIG_VALUES[1]), "PGA": random.choice(ads1x15._CONFIG_VALUES[2]), "MODE": random.choice(ads1x15._CONFIG_VALUES[3]), "DR": random.choice(ads1x15._CONFIG_VALUES[4]), 'offset_voltage': 0, 'output_span': 5, 'conversion_factor': 10 } kw_copy = kwargs.copy() for field in kw_copy: if random.getrandbits(1) and field is not "MUX": del kwargs[field] kw_copy[field] = dict(**ads1x15._DEFAULT_VALUES, **AnalogSensor._DEFAULT_CALIBRATION)[field] mock = mock_i2c_hardware( i2c_bus=1, i2c_address=ads1x15._DEFAULT_ADDRESS, n_registers=4, reg_values=[ b'\x00\x00', b'\x85\x83' ] ) pig = PigpioConnection() pig.add_mock_hardware(mock['device'], mock['i2c_address'], mock['i2c_bus']) ads = ads1x15(pig=pig) a_sensor = AnalogSensor(ads, **kwargs) n_iter = 250 conversion_bytes = [token_bytes(2) for _ in range(n_iter)] raw_voltage = [int.from_bytes(cb, 'big', signed=True) * kw_copy['PGA'] / 32767 for cb in conversion_bytes] expected = [ kw_copy['conversion_factor'] * (rv - kw_copy['offset_voltage']) / kw_copy['output_span'] for rv in raw_voltage ] for i in range(n_iter): pig.mock_i2c[1][mock['i2c_address']].write_mock_hardware_register(0, conversion_bytes[i]) result = a_sensor.get() assert round(result, 9) == round(expected[i], 9) """__________________________________________________________________________________________________________
def test_pigpio_connection_exception(patch_pigpio_base, patch_bad_socket): """__________________________________________________________________________________________________________TEST #2 Tests to make sure an exception is thrown if, upon init, a PigpioConnection finds it is not connected. """ with pytest.raises(RuntimeError): PigpioConnection() with pytest.raises(RuntimeError): IODeviceBase()
def test_analog_sensor_single_read(patch_pigpio_i2c, mock_i2c_hardware, ads1x15, seed, pga, mode): """__________________________________________________________________________________________________________TEST #3 Tests the proper functioning of an AnalogSensor with random, but plausible, calibration. - Performs a sequence of observations and verifies that each matches expectations and that the age of each reading properly reflects called updates """ random.seed(seed) kwargs = { "MUX": random.choice(ads1x15._CONFIG_VALUES[1]), "PGA": pga, "MODE": mode, "DR": random.choice(ads1x15._CONFIG_VALUES[4]), 'offset_voltage': round(random.uniform(-0.35, 0.35) * pga, 2), 'output_span': round(random.uniform(.75, 1.0) * pga, 2), 'conversion_factor': round(random.choice([-1, 1]) * random.uniform(1, 10), 2) } mock = mock_i2c_hardware( i2c_bus=1, i2c_address=ads1x15._DEFAULT_ADDRESS, n_registers=4, reg_values=[ b'\x00\x00', b'\x85\x83' ] ) pig = PigpioConnection() pig.add_mock_hardware(mock['device'], mock['i2c_address'], mock['i2c_bus']) ads = ads1x15(pig=pig) a_sensor = AnalogSensor(ads, **kwargs) n_iter = 250 conversion_bytes = [token_bytes(2) for _ in range(n_iter)] raw_voltage = [int.from_bytes(cb, 'big', signed=True) * kwargs['PGA'] / 32767 for cb in conversion_bytes] expected = [ kwargs['conversion_factor'] * (rv - kwargs['offset_voltage']) / kwargs['output_span'] for rv in raw_voltage ] for i in range(n_iter): pig.mock_i2c[1][mock['i2c_address']].write_mock_hardware_register(0, conversion_bytes[i]) result = a_sensor.get() assert round(result, 9) == round(expected[i], 9) """__________________________________________________________________________________________________________
def test_analog_sensor_kwarg_exceptions(patch_pigpio_i2c, mock_i2c_hardware, ads1x15, seed, pga, mode): """__________________________________________________________________________________________________________TEST #5 Tests that an exception is properly raised when either: - a kwarg is passed that it unrecognized - MUX is not passed in kwargs """ random.seed(seed) kwargs = { "MUX": random.choice(ads1x15._CONFIG_VALUES[1]), "PGA": pga, "MODE": mode, "DR": random.choice(ads1x15._CONFIG_VALUES[4]), 'offset_voltage': round(random.uniform(-0.35, 0.35) * pga, 2), 'output_span': round(random.uniform(.75, 1.0) * pga, 2), 'conversion_factor': round(random.choice([-1, 1]) * random.uniform(1, 10), 2) } mock = mock_i2c_hardware( i2c_bus=1, i2c_address=ads1x15._DEFAULT_ADDRESS, n_registers=4, reg_values=[ b'\x00\x00', b'\x85\x83' ] ) pig = PigpioConnection() pig.add_mock_hardware(mock['device'], mock['i2c_address'], mock['i2c_bus']) ads = ads1x15(pig=pig) with pytest.raises(TypeError): bad_kwargs = kwargs.copy() bad_kwargs['bad_key'] = 'bad_value' AnalogSensor(ads, **bad_kwargs) with pytest.raises(TypeError): bad_kwargs = kwargs.copy() del bad_kwargs['MUX'] AnalogSensor(ads, **bad_kwargs) """__________________________________________________________________________________________________________
def test_i2c_device(patch_pigpio_i2c, mock_i2c_hardware, seed): """__________________________________________________________________________________________________________TEST #5 Tests the various basic I2CDevice methods, open, close, read, write, etc. - Note: i2c_device.read_device() returns raw bytes (assumed to have big-endian ordering.) read_register, on the other hand, returns the integer representation of those bytes. write_device and write_register take an integer argument which is the written to the device register as a big-endian two's complement. - The RPi is native little endian, so to simulate/test the byte-swapping that occurs over I2C we generate a random 16-bit integer (`data`), write it to the register, and read it back. - If the int->byte-> int conversions in i2c_device are working correctly, we should expect one of two possible values to be returned (depending on whether read_device() or read_register() is called) - read_device() should return the big-endian two's complement of `data` - read_register() should just return `data` """ results = [] random.seed(seed) mock = mock_i2c_hardware() n_registers = len(mock['values']) data = [random.getrandbits(15) for _ in range(n_registers)] signed = random.getrandbits(1) expected = [data[0].to_bytes(2, 'big', signed=signed) ] if n_registers == 1 else data pig = PigpioConnection() pig.add_mock_hardware(mock['device'], mock['i2c_address'], mock['i2c_bus']) i2c_device = I2CDevice(mock['i2c_address'], mock['i2c_bus'], pig=pig) assert i2c_device.pigpiod_ok if n_registers == 1: i2c_device.write_device(data[0], signed=signed) results.append(i2c_device.read_device()[1]) else: for register in range(n_registers): i2c_device.write_register(register, data[register], signed=signed) results.append(i2c_device.read_register(register, signed=signed)) i2c_device._close() assert not i2c_device._pig.mock_i2c[mock['i2c_bus']] assert results == expected """__________________________________________________________________________________________________________
def test_sfm_single_read(patch_pigpio_i2c, mock_i2c_hardware, seed): """__________________________________________________________________________________________________________TEST #7 Tests the proper functioning of an SFM sensor. similar to single read of analog sensor, except starts with 4 bytes in the register and discards two (just like in real life) """ random.seed(seed) mock = mock_i2c_hardware( i2c_bus=1, i2c_address=SFM3200._DEFAULT_ADDRESS, reg_values=[b'\x00\x00'] ) pig = PigpioConnection() pig.add_mock_hardware(mock['device'], mock['i2c_address'], mock['i2c_bus']) sfm = SFM3200(address=mock['i2c_address'], i2c_bus=mock['i2c_bus'], pig=pig) n_iter = random.randint(100, 1000) conversion_bytes = [token_bytes(4) for _ in range(n_iter)] raw_int = [int.from_bytes(cb[:2], 'big') for cb in conversion_bytes] expected = [(rv - sfm._FLOW_OFFSET) / sfm._FLOW_SCALE_FACTOR for rv in raw_int] for i in range(n_iter): pig.mock_i2c[1][mock['i2c_address']].write_mock_hardware_register(0, conversion_bytes[i]) result = sfm.get() assert round(result, 10) == round(expected[i], 10) """__________________________________________________________________________________________________________
def test_mock_pigpio_i2c(patch_pigpio_i2c, mock_i2c_hardware, seed): """__________________________________________________________________________________________________________TEST #4 Tests the functionality of the mock pigpio i2c device interface. More specifically, tests that: - mock hardware devices are initialized correctly - mock handles, i2c_open(), and i2c_close() can be used to add, interact with, and remove mock hardware devices - correct functioning of i2c_open is implied by the read/write tests - the correct functioning of i2c_close() is ascertained from the assertion that pig.mock_i2c[i2c_bus] is empty - mock i2c read and write functions work as intended: - This is three tests in one. For each register of each mock hardware device: 1) Read the register with i2c_read and store the result in results['init']. We expect it to be the value from the correct register of the matching mock_device (expected['init']) 2) Write two random bytes to the register with i2c_write. Look at the actual contents of the target register and put them in results['write']. We expect them to be the same as the two random bytes we generated. 3) Read the register with i2c_read again and put the results in expected['read']. These should also match the two random bytes from the 'write' test. -> Assert that the results match what we expect - if there is only one register on the device, test read_device() and write_device() instead of read_register() and write_register() -> this matches how such a device would be interacted with in practice. """ random.seed(seed) n_devices = random.randint(1, 20) address_pool = random.sample(range(128), k=n_devices) pig = PigpioConnection() mocks = [] for i in range(n_devices): mock = mock_i2c_hardware(i2c_address=address_pool.pop()) pig.add_mock_hardware(mock['device'], mock['i2c_address'], mock['i2c_bus']) mock['handle'] = pig.i2c_open(mock['i2c_bus'], mock['i2c_address']) mocks.append(mock) for mock_device in mocks: handle = mock_device['handle'] bus = mock_device['i2c_bus'] address = mock_device['i2c_address'] results = {'init': [], 'write': [], 'read': []} expected = {'init': [], 'write': [], 'read': []} for reg in range(len(mock_device['values'])): expected['init'] = mock_device['values'][reg] expected['write'] = token_bytes(2) expected['read'] = expected['write'] if len(mock_device['values']) == 1: results['init'] = pig.i2c_read_device(handle, 2)[1] pig.i2c_write_device(handle, expected['write']) results['write'] = pig.mock_i2c[bus][address].registers[0][-1] results['read'] = pig.i2c_read_device(handle, 2)[1] else: results['init'] = pig.i2c_read_i2c_block_data(handle, reg, count=2)[1] pig.i2c_write_i2c_block_data(handle, reg, expected['write']) results['write'] = pig.mock_i2c[bus][address].registers[reg][ -1] results['read'] = pig.i2c_read_i2c_block_data(handle, reg, count=2)[1] assert results == expected pig.i2c_close(handle) assert not pig.mock_i2c[0] assert not pig.mock_i2c[1] assert not pig.mock_i2c['spi'] """__________________________________________________________________________________________________________