def test_address_select(): bus = MockSMBus(1) device = Device([0x00, 0x01], i2c_dev=bus, registers=(Register('test', 0x00, fields=(BitField('test', 0xFF), )), )) assert device.get_addresses() == [0x00, 0x01] assert device.select_address(0x01) is True with pytest.raises(ValueError): device.select_address(0x02) assert device.next_address() == 0x00 assert device.next_address() == 0x01
class BH1745: """BH1745 colour sensor.""" def __init__(self, i2c_addr=0x38, i2c_dev=None): """Initialise sensor. :param i2c_addr: i2c address of sensor :param i2c_dev: SMBus-compatible instance """ self._i2c_addr = i2c_addr self._i2c_dev = i2c_dev self._is_setup = False # Device definition self._bh1745 = Device(I2C_ADDRESSES, i2c_dev=self._i2c_dev, bit_width=8, registers=( # Part ID should be 0b001011 or 0x0B Register('SYSTEM_CONTROL', 0x40, fields=( BitField('sw_reset', 0b10000000), BitField('int_reset', 0b01000000), BitField('part_id', 0b00111111, read_only=True) )), Register('MODE_CONTROL1', 0x41, fields=( BitField('measurement_time_ms', 0b00000111, adapter=LookupAdapter({ 160: 0b000, 320: 0b001, 640: 0b010, 1280: 0b011, 2560: 0b100, 5120: 0b101 })), )), Register('MODE_CONTROL2', 0x42, fields=( BitField('valid', 0b10000000, read_only=True), BitField('rgbc_en', 0b00010000), BitField('adc_gain_x', 0b00000011, adapter=LookupAdapter({ 1: 0b00, 2: 0b01, 16: 0b10})) )), Register('MODE_CONTROL3', 0x44, fields=( BitField('on', 0b11111111, adapter=LookupAdapter({True: 2, False: 0})), )), Register('COLOUR_DATA', 0x50, fields=( BitField('red', 0xFFFF000000000000, adapter=U16ByteSwapAdapter()), BitField('green', 0x0000FFFF00000000, adapter=U16ByteSwapAdapter()), BitField('blue', 0x00000000FFFF0000, adapter=U16ByteSwapAdapter()), BitField('clear', 0x000000000000FFFF, adapter=U16ByteSwapAdapter()) ), bit_width=64, read_only=True), Register('DINT_DATA', 0x58, fields=( BitField('data', 0xFFFF, adapter=U16ByteSwapAdapter()), ), bit_width=16), Register('INTERRUPT', 0x60, fields=( BitField('status', 0b10000000, read_only=True), BitField('latch', 0b00010000, adapter=LookupAdapter({0: 1, 1: 0})), BitField('source', 0b00001100, read_only=True, adapter=LookupAdapter({ 'red': 0b00, 'green': 0b01, 'blue': 0b10, 'clear': 0b11 })), BitField('enable', 0b00000001) )), # 00: Interrupt status is toggled at each measurement end # 01: Interrupt status is updated at each measurement end # 10: Interrupt status is updated if 4 consecutive threshold judgements are the same # 11: Blah blah ditto above except for 8 consecutive judgements Register('PERSISTENCE', 0x61, fields=( BitField('mode', 0b00000011, adapter=LookupAdapter({ 'toggle': 0b00, 'update': 0b01, 'update_on_4': 0b10, 'update_on_8': 0b11 })), )), # High threshold defaults to 0xFFFF # Low threshold defaults to 0x0000 Register('THRESHOLD', 0x62, fields=( BitField('high', 0xFFFF0000, adapter=U16ByteSwapAdapter()), BitField('low', 0x0000FFFF, adapter=U16ByteSwapAdapter()) ), bit_width=32), # Default MANUFACTURER ID is 0xE0h Register('MANUFACTURER', 0x92, fields=( BitField('id', 0xFF), ), read_only=True, volatile=False) )) self._bh1745.select_address(self._i2c_addr) # TODO : Integrate into i2cdevice so that LookupAdapter fields can always be exported to constants # Iterate through all register fields and export their lookup tables to constants for register in self._bh1745.registers: register = self._bh1745.registers[register] for field in register.fields: field = register.fields[field] if isinstance(field.adapter, LookupAdapter): for key in field.adapter.lookup_table: name = 'BH1745_{register}_{field}_{key}'.format( register=register.name, field=field.name, key=key ).upper() globals()[name] = key """ Approximate compensation for the spectral response performance curves """ self._channel_compensation = (2.2, 1.0, 1.8, 10.0) self._enable_channel_compensation = True # Public API methods def ready(self): """Return true if setup has been successful.""" return self._is_setup def setup(self, i2c_addr=None, timeout=BH1745_RESET_TIMEOUT_SEC): """Set up the bh1745 sensor. :param i2c_addr: Optional i2c_addr to switch to """ if self._is_setup: return True if timeout <= 0: raise ValueError('Device timeout period must be greater than 0') if i2c_addr is not None: self._bh1745.select_address(i2c_addr) try: self._bh1745.get('SYSTEM_CONTROL') except IOError: raise RuntimeError('BH1745 not found: IO error attempting to query device!') if self._bh1745.get('SYSTEM_CONTROL').part_id != 0b001011 or self._bh1745.get('MANUFACTURER').id != 0xE0: raise RuntimeError('BH1745 not found: Manufacturer or Part ID mismatch!') self._is_setup = True self._bh1745.set('SYSTEM_CONTROL', sw_reset=1) t_start = time.time() pending_reset = True while time.time() - t_start < timeout: if not self._bh1745.get('SYSTEM_CONTROL').sw_reset: pending_reset = False break time.sleep(0.01) if pending_reset: raise BH1745TimeoutError('Timeout waiting for BH1745 to reset.') self._bh1745.set('SYSTEM_CONTROL', int_reset=0) self._bh1745.set('MODE_CONTROL1', measurement_time_ms=320) self._bh1745.set('MODE_CONTROL2', adc_gain_x=1, rgbc_en=1) self._bh1745.set('MODE_CONTROL3', on=1) self._bh1745.set('THRESHOLD', low=0xFFFF, high=0x0000) self._bh1745.set('INTERRUPT', latch=1) time.sleep(0.320) def set_measurement_time_ms(self, time_ms): """Set the measurement time in milliseconds. :param time_ms: The time in milliseconds: 160, 320, 640, 1280, 2560, 5120 """ self.setup() self._bh1745.set('MODE_CONTROL1', measurement_time_ms=time_ms) def set_adc_gain_x(self, gain_x): """Set the ADC gain multiplier. :param gain_x: Must be either 1, 2 or 16 """ self.setup() self._bh1745.set('MODE_CONTROL2', adc_gain_x=gain_x) def set_leds(self, state): """Toggle the onboard LEDs. :param state: Either 1 for on, or 0 for off """ self.setup() self._bh1745.set('INTERRUPT', enable=1 if state else 0) def set_channel_compensation(self, r, g, b, c): """Set the channel compensation scale factors. :param r: multiplier for red channel :param g: multiplier for green channel :param b: multiplier for blue channel :param c: multiplier for clear channel If you intend to measure a particular class of objects, say a set of matching wooden blocks with similar reflectivity and paint finish you should calibrate the channel compensation until you see colour values that broadly represent the colour of the objects you're testing. The default values were derived by testing a set of 5 Red, Green, Blue, Yellow and Orange wooden blocks. These scale factors are applied in `get_rgbc_raw` right after the raw values are read from the sensor. """ self._channel_compensation = (r, g, b, c) def enable_white_balance(self, enable): """Enable scale compensation for the channels. :param enable: True to enable, False to disable See: `set_channel_compensation` for details. """ self._enable_channel_compensation = True if enable else False def get_rgbc_raw(self): """Return the raw Red, Green, Blue and Clear readings.""" self.setup() colour_data = self._bh1745.get('COLOUR_DATA') r, g, b, c = colour_data.red, colour_data.green, colour_data.blue, colour_data.clear if self._enable_channel_compensation: cr, cg, cb, cc = self._channel_compensation r, g, b, c = r * cr, g * cg, b * cb, c * cc return (r, g, b, c) def get_rgb_clamped(self): """Return an RGB value scaled against max(r, g, b). This will clamp/saturate one of the colour channels, providing a clearer idea of what primary colour an object is most likely to be. However the resulting colour reading will not be accurate for other purposes. """ r, g, b, c = self.get_rgbc_raw() div = max(r, g, b) if div > 0: r, g, b = [int((x / float(div)) * 255) for x in (r, g, b)] return (r, g, b) return (0, 0, 0) def get_rgb_scaled(self): """Return an RGB value scaled against the clear channel.""" r, g, b, c = self.get_rgbc_raw() if c > 0: r, g, b = [min(255, int((x / float(c)) * 255)) for x in (r, g, b)] return (r, g, b) return (0, 0, 0)
class LSM303D: def __init__(self, i2c_addr=0x1D, i2c_dev=None): self._i2c_addr = i2c_addr self._i2c_dev = i2c_dev self._lsm303d = Device( [0x1D, 0x1E], i2c_dev=self._i2c_dev, bit_width=8, registers=( Register('TEMPERATURE', 0x05 | 0x80, fields=(BitField('temperature', 0xFFFF, adapter=TemperatureAdapter()), ), bit_width=16), # Magnetometer interrupt status Register('MAGNETOMETER_STATUS', 0x07, fields=( BitField('xdata', 0b00000001), BitField('ydata', 0b00000010), BitField('zdata', 0b00000100), BitField('data', 0b00001000), BitField('xoverrun', 0b00010000), BitField('yoverrun', 0b00100000), BitField('zoverrun', 0b01000000), BitField('overrun', 0b10000000), )), Register('MAGNETOMETER', 0x08 | 0x80, fields=( BitField('x', 0xFFFF00000000, adapter=S16ByteSwapAdapter()), BitField('y', 0x0000FFFF0000, adapter=S16ByteSwapAdapter()), BitField('z', 0x00000000FFFF, adapter=S16ByteSwapAdapter()), ), bit_width=8 * 6), Register('WHOAMI', 0x0F, fields=(BitField('id', 0xFF), )), Register( 'MAGNETOMETER_INTERRUPT', 0x12, fields=( BitField('enable', 0b00000001), BitField('4d', 0b00000010), BitField('latch', 0b00000100), BitField( 'polarity', 0b00001000), # 0 = active-low, 1 = active-high BitField('pin_config', 0b00010000), # 0 = push-pull, 1 = open-drain BitField('z_enable', 0b00100000), BitField('y_enable', 0b01000000), BitField('x_enable', 0b10000000), )), Register('MAGNETOMETER_INTERRUPT_SOURCE', 0x13, fields=( BitField('event', 0b00000001), BitField('overflow', 0b00000010), BitField('z_negative', 0b00000100), BitField('y_negative', 0b00001000), BitField('x_negative', 0b00010000), BitField('z_positive', 0b00100000), BitField('y_positive', 0b01000000), BitField('x_positive', 0b10000000), )), Register('MAGNETOMETER_INTERRUPT_THRESHOLD', 0x14 | 0x80, fields=(BitField('threshold', 0xFFFF, adapter=U16ByteSwapAdapter()), ), bit_width=16), Register('MAGNETOMETER_OFFSET', 0x16 | 0x80, fields=( BitField('x', 0xFFFF00000000, adapter=S16ByteSwapAdapter()), BitField('y', 0x0000FFFF0000, adapter=S16ByteSwapAdapter()), BitField('z', 0x00000000FFFF, adapter=S16ByteSwapAdapter()), ), bit_width=8 * 6), Register('HP_ACCELEROMETER_REFERENCE', 0x1c | 0x80, fields=( BitField('x', 0xFF0000), BitField('y', 0x00FF00), BitField('z', 0x0000FF), ), bit_width=8 * 3), Register('CONTROL0', 0x1f, fields=( BitField('int2_high_pass', 0b00000001), BitField('int1_high_pass', 0b00000010), BitField('click_high_pass', 0b00000100), BitField('fifo_threshold', 0b00100000), BitField('fifo_enable', 0b01000000), BitField('reboot_memory', 0b10000000), )), Register('CONTROL1', 0x20, fields=( BitField('accel_x_enable', 0b00000001), BitField('accel_y_enable', 0b00000010), BitField('accel_z_enable', 0b00000100), BitField('block_data_update', 0b00001000), BitField('accel_data_rate_hz', 0b11110000, adapter=LookupAdapter({ 0: 0, 3.125: 0b0001, 6.25: 0b0010, 12.5: 0b0011, 25: 0b0100, 50: 0b0101, 100: 0b0110, 200: 0b0111, 400: 0b1000, 800: 0b1001, 1600: 0b1010 })), )), Register('CONTROL2', 0x21, fields=( BitField('serial_interface_mode', 0b00000001), BitField('accel_self_test', 0b00000010), BitField('accel_full_scale_g', 0b00111000, adapter=LookupAdapter({ 2: 0b000, 4: 0b001, 6: 0b010, 8: 0b011, 16: 0b100 })), BitField('accel_antialias_bw_hz', 0b11000000, adapter=LookupAdapter({ 50: 0b11, 362: 0b10, 194: 0b01, 773: 0b00 })), )), # Known in the datasheet as CTRL3 Register('INTERRUPT1', 0x22, fields=( BitField('enable_fifo_empty', 0b00000001), BitField('enable_accel_dataready', 0b00000010), BitField('enable_accelerometer', 0b00000100), BitField('enable_magnetometer', 0b00001000), BitField('enable_ig2', 0b00010000), BitField('enable_ig1', 0b00100000), BitField('enable_click', 0b01000000), BitField('enable_boot', 0b10000000), )), # Known in the datasheet as CTRL4 Register('INTERRUPT2', 0x23, fields=( BitField('enable_fifo', 0b00000001), BitField('enable_fifo_overrun', 0b00000010), BitField('enable_mag_dataready', 0b00000100), BitField('enable_accel_dataready', 0b00001000), BitField('enable_magnetometer', 0b00010000), BitField('enable_ig2', 0b00100000), BitField('enable_ig1', 0b01000000), BitField('enable_click', 0b10000000), )), Register('CONTROL5', 0x24, fields=( BitField('latch_int1', 0b00000001), BitField('latch_int2', 0b00000010), BitField('mag_data_rate_hz', 0b00011100, adapter=LookupAdapter({ 3.125: 0b000, 6.25: 0b001, 12.5: 0b010, 25: 0b011, 50: 0b100, 100: 0b101, })), BitField('mag_resolution', 0b01100000), BitField('enable_temperature', 0b10000000), )), Register('CONTROL6', 0x25, fields=(BitField('mag_full_scale_gauss', 0b01100000, adapter=LookupAdapter({ 2: 0b00, 4: 0b01, 8: 0b10, 12: 0b11 })), )), Register( 'CONTROL7', 0x26, fields=( BitField('mag_mode', 0b00000011, adapter=LookupAdapter({ 'continuous': 0b00, 'single': 0b01, 'off': 0b10 })), BitField('mag_lowpowermode', 0b00000100), BitField('temperature_only', 0b00010000), BitField('filter_accel', 0b00100000), BitField('high_pass_mode_accel', 0b11000000), # See page 39 of lsm303d.pdf )), # Accelerometer interrupt status register Register('ACCELEROMETER_STATUS', 0x27, fields=(BitField('xdata', 0b00000001), BitField('ydata', 0b00000010), BitField('zdata', 0b00000100), BitField('data', 0b00001000), BitField('xoverrun', 0b00010000), BitField('yoverrun', 0b00100000), BitField('zoverrun', 0b01000000), BitField('overrun', 0b10000000))), # X/Y/Z values from accelerometer Register('ACCELEROMETER', 0x28 | 0x80, fields=( BitField('x', 0xFFFF00000000, adapter=S16ByteSwapAdapter()), BitField('y', 0x0000FFFF0000, adapter=S16ByteSwapAdapter()), BitField('z', 0x00000000FFFF, adapter=S16ByteSwapAdapter()), ), bit_width=8 * 6), # FIFO control register Register('FIFO_CONTROL', 0x2e, fields=( BitField('mode', 0b11100000), BitField('threshold', 0b00011111), )), # FIFO status register Register( 'FIFO_STATUS', 0x2f, fields=( BitField('threshold_exceeded', 1 << 7), BitField('overrun', 1 << 6), BitField('empty', 1 << 5), BitField('unread_levels', 0b00011111 ), # Current number of unread FIFO levels )), # 0x30: Internal interrupt generator 1: configuration register # 0x31: Internal interrupt generator 1: status register # 0x32: Internal interrupt generator 1: threshold register # 0x33: Internal interrupt generator 1: duration register Register( 'IG_CONFIG1', 0x30 | 0x80, fields=( # 0x30 BitField('and_or_combination', 1 << 31), BitField('6d_enable', 1 << 30), BitField('z_high_enable', 1 << 29), BitField('z_low_enable', 1 << 28), BitField('y_high_enable', 1 << 27), BitField('y_low_enable', 1 << 26), BitField('x_high_enable', 1 << 25), BitField('x_low_enble', 1 << 24), # 0x31 BitField('interrupt_status', 1 << 23), BitField('z_high', 1 << 22), BitField('z_low', 1 << 21), BitField('y_high', 1 << 20), BitField('y_low', 1 << 19), BitField('x_high', 1 << 18), BitField('x_low', 1 << 17), BitField('status', 1 << 16), # 0x32 BitField('threshold', 0xff << 8), # 0x33 BitField('duration', 0xff), ), bit_width=32), # 0x34: Internal interrupt generator 2: configuration register # 0x35: Internal interrupt generator 2: status register # 0x36: Internal interrupt generator 2: threshold register # 0x37: Internal interrupt generator 2: duration register Register( 'IG_CONFIG1', 0x30 | 0x80, fields=( # 0x34 BitField('and_or_combination', 1 << 31), BitField('6d_enable', 1 << 30), BitField('z_high_enable', 1 << 29), BitField('z_low_enable', 1 << 28), BitField('y_high_enable', 1 << 27), BitField('y_low_enable', 1 << 26), BitField('x_high_enable', 1 << 25), BitField('x_low_enble', 1 << 24), # 0x35 BitField('interrupt_status', 1 << 23), BitField('z_high', 1 << 22), BitField('z_low', 1 << 21), BitField('y_high', 1 << 20), BitField('y_low', 1 << 19), BitField('x_high', 1 << 18), BitField('x_low', 1 << 17), BitField('status', 1 << 16), # 0x36 BitField('threshold', 0xff << 8), # 0x37 BitField('duration', 0xff), ), bit_width=32), # 0x38: Click: configuration register # 0x39: Click: status register # 0x3A: Click: threshold register # 0x3B: Click: time limit register # 0x3C: Click: time latency register # 0x3D: Click: time window register Register( 'CLICK', 0x38 | 0x80, fields=( # 0x38 # bits 1 << 47 and 1 << 46 are unimplemented BitField('z_doubleclick_enable', 1 << 45), BitField('z_click_enable', 1 << 44), BitField('y_doubleclick_enable', 1 << 43), BitField('y_click_enable', 1 << 42), BitField('x_doubleclick_enable', 1 << 41), BitField('x_click_enable', 1 << 40), # 0x39 # bit 1 << 39 is unimplemented BitField('interrupt_enable', 1 << 38), BitField('doubleclick_enable', 1 << 37), BitField('click_enable', 1 << 36), BitField( 'sign', 1 << 35), # 0 positive detection, 1 negative detection BitField('z', 1 << 34), BitField('y', 1 << 33), BitField('x', 1 << 32), # 0x3A BitField('threshod', 0xFF << 24), # 0x3B BitField('time_limit', 0xFF << 16), # 0x3C BitField('time_latency', 0xFF << 8), # 0x3D BitField('time_window', 0xFF), ), bit_width=8 * 6), # Controls the threshold and duration of returning to sleep mode Register( 'ACT', 0x3e | 0x80, fields=( BitField('threshold', 0xFF00), # 1 LSb = 16mg BitField('duration', 0x00FF) # (duration + 1) * 8/output_data_rate ), bit_width=16))) self._is_setup = False self._accel_full_scale_g = 2 self._mag_full_scale_guass = 2 def set_accel_full_scale_g(self, scale): """Set the full scale range for the accelerometer in g :param scale: One of 2, 4, 6, 8 or 16 g """ self._accel_full_scale_g = scale with self._lsm303d.CONTROL2 as CONTROL2: CONTROL2.set_accel_full_scale_g(self._accel_full_scale_g) CONTROL2.write() def set_mag_full_scale_guass(self, scale): """Set the full scale range for the magnetometer in guass :param scale: One of 2, 4, 8 or 12 guass """ self._mag_full_scale_guass = scale with self._lsm303d.CONTROL6 as CONTROL6: CONTROL6.set_mag_full_scale_gauss(scale) # +-2 CONTROL6.write() def setup(self): if self._is_setup: return self._is_setup = True self._lsm303d.select_address(self._i2c_addr) try: if self._lsm303d.WHOAMI.get_id() != 0x49: raise RuntimeError( "Unable to find lsm303d on 0x{:02x}, WHOAMI returned {:02x}" .format(self._i2c_addr, self._lsm303d.WHOAMI.get_id())) except IOError: raise RuntimeError( "Unable to find lsm303d on 0x{:02x}, IOError".format( self._i2c_addr)) with self._lsm303d.CONTROL1 as CONTROL1: CONTROL1.set_accel_x_enable(1) CONTROL1.set_accel_y_enable(1) CONTROL1.set_accel_z_enable(1) CONTROL1.set_accel_data_rate_hz(50) CONTROL1.write() self.set_accel_full_scale_g(2) with self._lsm303d.INTERRUPT1 as INT1: INT1.set_enable_fifo_empty(0) INT1.set_enable_accel_dataready(0) INT1.set_enable_accelerometer(0) INT1.set_enable_magnetometer(0) INT1.set_enable_ig2(0) INT1.set_enable_ig1(0) INT1.set_enable_click(0) INT1.set_enable_boot(0) INT1.write() with self._lsm303d.INTERRUPT2 as INT2: INT2.set_enable_fifo(0) INT2.set_enable_fifo_overrun(0) INT2.set_enable_mag_dataready(0) INT2.set_enable_accel_dataready(0) INT2.set_enable_magnetometer(0) INT2.set_enable_ig2(0) INT2.set_enable_ig1(0) INT2.set_enable_click(0) INT2.write() with self._lsm303d.CONTROL5 as CONTROL5: CONTROL5.set_mag_data_rate_hz(50) CONTROL5.set_enable_temperature(1) CONTROL5.write() self.set_mag_full_scale_guass(2) with self._lsm303d.CONTROL7 as CONTROL7: CONTROL7.set_mag_mode('continuous') CONTROL7.write() def magnetometer(self): """Return magnetometer x, y and z readings. These readings are given in guass and should be +/- the given mag_full_scale_guass value. """ self.setup() with self._lsm303d.MAGNETOMETER as M: x, y, z = M.get_x(), M.get_y(), M.get_z() x, y, z = [(p / 32676.0) * self._mag_full_scale_guass for p in (x, y, z)] return x, y, z def accelerometer(self): """Return acelerometer x, y and z readings. These readings are given in g annd should be +/- the given accel_full_scale_g value. """ self.setup() with self._lsm303d.ACCELEROMETER as A: x, y, z = A.get_x(), A.get_y(), A.get_z() x, y, z = [(p / 32767.0) * self._accel_full_scale_g for p in (x, y, z)] return x, y, z def temperature(self): """Return the temperature""" self.setup() return self._lsm303d.TEMPERATURE.get_temperature()
class BME280: def __init__(self, i2c_addr=I2C_ADDRESS_GND, i2c_dev=None): self.calibration = BME280Calibration() self._is_setup = False self._i2c_addr = i2c_addr self._i2c_dev = i2c_dev self._bme280 = Device( [I2C_ADDRESS_GND, I2C_ADDRESS_VCC], i2c_dev=self._i2c_dev, bit_width=8, registers=( Register('CHIP_ID', 0xD0, fields=(BitField('id', 0xFF), )), Register('RESET', 0xE0, fields=(BitField('reset', 0xFF), )), Register( 'STATUS', 0xF3, fields=( BitField('measuring', 0b00001000), # 1 when conversion is running BitField( 'im_update', 0b00000001), # 1 when NVM data is being copied )), Register( 'CTRL_MEAS', 0xF4, fields=( BitField( 'osrs_t', 0b11100000, # Temperature oversampling adapter=LookupAdapter({ 1: 0b001, 2: 0b010, 4: 0b011, 8: 0b100, 16: 0b101 })), BitField( 'osrs_p', 0b00011100, # Pressure oversampling adapter=LookupAdapter({ 1: 0b001, 2: 0b010, 4: 0b011, 8: 0b100, 16: 0b101 })), BitField( 'mode', 0b00000011, # Power mode adapter=LookupAdapter({ 'sleep': 0b00, 'forced': 0b10, 'normal': 0b11 })), )), Register( 'CTRL_HUM', 0xF2, fields=( BitField( 'osrs_h', 0b00000111, # Humidity oversampling adapter=LookupAdapter({ 1: 0b001, 2: 0b010, 4: 0b011, 8: 0b100, 16: 0b101 })), )), Register( 'CONFIG', 0xF5, fields=( BitField( 't_sb', 0b11100000, # Temp standby duration in normal mode adapter=LookupAdapter({ 0.5: 0b000, 62.5: 0b001, 125: 0b010, 250: 0b011, 500: 0b100, 1000: 0b101, 10: 0b110, 20: 0b111 })), BitField( 'filter', 0b00011100 ), # Controls the time constant of the IIR filter BitField( 'spi3w_en', 0b0000001, read_only=True ), # Enable 3-wire SPI interface when set to 1. IE: Don't set this bit! )), Register('DATA', 0xF7, fields=(BitField('humidity', 0x000000000000FFFF), BitField('temperature', 0x000000FFFFF00000), BitField('pressure', 0xFFFFF00000000000)), bit_width=8 * 8), Register( 'CALIBRATION', 0x88, fields=( BitField('dig_t1', 0xFFFF << 16 * 12, adapter=U16Adapter()), # 0x88 0x89 BitField('dig_t2', 0xFFFF << 16 * 11, adapter=S16Adapter()), # 0x8A 0x8B BitField('dig_t3', 0xFFFF << 16 * 10, adapter=S16Adapter()), # 0x8C 0x8D BitField('dig_p1', 0xFFFF << 16 * 9, adapter=U16Adapter()), # 0x8E 0x8F BitField('dig_p2', 0xFFFF << 16 * 8, adapter=S16Adapter()), # 0x90 0x91 BitField('dig_p3', 0xFFFF << 16 * 7, adapter=S16Adapter()), # 0x92 0x93 BitField('dig_p4', 0xFFFF << 16 * 6, adapter=S16Adapter()), # 0x94 0x95 BitField('dig_p5', 0xFFFF << 16 * 5, adapter=S16Adapter()), # 0x96 0x97 BitField('dig_p6', 0xFFFF << 16 * 4, adapter=S16Adapter()), # 0x98 0x99 BitField('dig_p7', 0xFFFF << 16 * 3, adapter=S16Adapter()), # 0x9A 0x9B BitField('dig_p8', 0xFFFF << 16 * 2, adapter=S16Adapter()), # 0x9C 0x9D BitField('dig_p9', 0xFFFF << 16 * 1, adapter=S16Adapter()), # 0x9E 0x9F BitField('dig_h1', 0x00FF), # 0xA1 uint8 ), bit_width=26 * 8), Register( 'CALIBRATION2', 0xE1, fields=( BitField('dig_h2', 0xFFFF0000000000, adapter=S16Adapter()), # 0xE1 0xE2 BitField('dig_h3', 0x0000FF00000000), # 0xE3 uint8 BitField('dig_h4', 0x000000FFFF0000, adapter=H4Adapter()), # 0xE4 0xE5[3:0] BitField('dig_h5', 0x00000000FFFF00, adapter=H5Adapter()), # 0xE5[7:4] 0xE6 BitField('dig_h6', 0x000000000000FF, adapter=S8Adapter()) # 0xE7 int8 ), bit_width=7 * 8))) def setup(self, mode='normal', temperature_oversampling=16, pressure_oversampling=16, humidity_oversampling=16, temperature_standby=500): if self._is_setup: return self._is_setup = True self._bme280.select_address(self._i2c_addr) self._mode = mode if mode == "forced": mode = "sleep" #try: # chip = self._bme280.get('CHIP_ID') # if chip.id != CHIP_ID: # raise RuntimeError("Unable to find bme280 on 0x{:02x}, CHIP_ID returned {:02x}".format(self._i2c_addr, chip.id)) #except IOError: # raise RuntimeError("Unable to find bme280 on 0x{:02x}, IOError".format(self._i2c_addr)) self._bme280.set('RESET', reset=0xB6) time.sleep(0.1) self._bme280.set('CTRL_HUM', osrs_h=humidity_oversampling) self._bme280.set('CTRL_MEAS', mode=mode, osrs_t=temperature_oversampling, osrs_p=pressure_oversampling) self._bme280.set('CONFIG', t_sb=temperature_standby, filter=2) self.calibration.set_from_namedtuple(self._bme280.get('CALIBRATION')) self.calibration.set_from_namedtuple(self._bme280.get('CALIBRATION2')) def update_sensor(self): self.setup() if self._mode == "forced": self._bme280.set('CTRL_MEAS', mode="forced") while self._bme280.get('STATUS').measuring: time.sleep(0.001) raw = self._bme280.get('DATA') self.temperature = self.calibration.compensate_temperature( raw.temperature) self.pressure = self.calibration.compensate_pressure( raw.pressure) / 100.0 self.humidity = self.calibration.compensate_humidity(raw.humidity) def get_temperature(self): self.update_sensor() return self.temperature def get_pressure(self): self.update_sensor() return self.pressure def get_humidity(self): self.update_sensor() return self.humidity def get_altitude(self, qnh=1017): self.update_sensor() self.altitude = 44330.0 * (1.0 - pow(self.pressure / qnh, (1.0 / 5.255))) return self.altitude def all(self, qnh=1017): self.update_sensor() self.altitude = 44330.0 * (1.0 - pow(self.pressure / qnh, (1.0 / 5.255))) return self.temperature, self.pressure, self.humidity, self.altitude
class BME280_new: def __init__(self, i2c_addr=I2C_ADDRESS_GND, i2c_dev=None): self.calibration = BME280Calibration() self._is_setup = False self._i2c_addr = i2c_addr self._i2c_dev = i2c_dev self._bme280 = Device( [I2C_ADDRESS_GND, I2C_ADDRESS_VCC], i2c_dev=self._i2c_dev, bit_width=8, registers=( Register('CHIP_ID', 0xD0, fields=(BitField('id', 0xFF), )), Register('RESET', 0xE0, fields=(BitField('reset', 0xFF), )), Register( 'STATUS', 0xF3, fields=( BitField('measuring', 0b00001000), # 1 when conversion is running BitField( 'im_update', 0b00000001), # 1 when NVM data is being copied )), Register( 'CTRL_MEAS', 0xF4, fields=( BitField( 'osrs_t', 0b11100000, # Temperature oversampling adapter=LookupAdapter({ 1: 0b001, 2: 0b010, 4: 0b011, 8: 0b100, 16: 0b101 })), BitField( 'osrs_p', 0b00011100, # Pressure oversampling adapter=LookupAdapter({ 1: 0b001, 2: 0b010, 4: 0b011, 8: 0b100, 16: 0b101 })), BitField( 'mode', 0b00000011, # Power mode adapter=LookupAdapter({ 'sleep': 0b00, 'forced': 0b10, 'normal': 0b11 })), )), Register( 'CTRL_HUM', 0xF2, fields=( BitField( 'osrs_h', 0b00000111, # Humidity oversampling adapter=LookupAdapter({ 1: 0b001, 2: 0b010, 4: 0b011, 8: 0b100, 16: 0b101 })), )), Register( 'CONFIG', 0xF5, fields=( BitField( 't_sb', 0b11100000, # Temp standby duration in normal mode adapter=LookupAdapter({ 0.5: 0b000, 62.5: 0b001, 125: 0b010, 250: 0b011, 500: 0b100, 1000: 0b101, 10: 0b110, 20: 0b111 })), BitField( 'filter', 0b00011100 ), # Controls the time constant of the IIR filter BitField( 'spi3w_en', 0b0000001, read_only=True ), # Enable 3-wire SPI interface when set to 1. IE: Don't set this bit! )), Register('DATA', 0xF7, fields=(BitField('humidity', 0x000000000000FFFF), BitField('temperature', 0x000000FFFFF00000), BitField('pressure', 0xFFFFF00000000000)), bit_width=8 * 8), Register( 'CALIBRATION', 0x88, fields=( BitField('dig_t1', 0xFFFF << 16 * 12, adapter=U16Adapter()), # 0x88 0x89 BitField('dig_t2', 0xFFFF << 16 * 11, adapter=S16Adapter()), # 0x8A 0x8B BitField('dig_t3', 0xFFFF << 16 * 10, adapter=S16Adapter()), # 0x8C 0x8D BitField('dig_p1', 0xFFFF << 16 * 9, adapter=U16Adapter()), # 0x8E 0x8F BitField('dig_p2', 0xFFFF << 16 * 8, adapter=S16Adapter()), # 0x90 0x91 BitField('dig_p3', 0xFFFF << 16 * 7, adapter=S16Adapter()), # 0x92 0x93 BitField('dig_p4', 0xFFFF << 16 * 6, adapter=S16Adapter()), # 0x94 0x95 BitField('dig_p5', 0xFFFF << 16 * 5, adapter=S16Adapter()), # 0x96 0x97 BitField('dig_p6', 0xFFFF << 16 * 4, adapter=S16Adapter()), # 0x98 0x99 BitField('dig_p7', 0xFFFF << 16 * 3, adapter=S16Adapter()), # 0x9A 0x9B BitField('dig_p8', 0xFFFF << 16 * 2, adapter=S16Adapter()), # 0x9C 0x9D BitField('dig_p9', 0xFFFF << 16 * 1, adapter=S16Adapter()), # 0x9E 0x9F BitField('dig_h1', 0x00FF), # 0xA1 uint8 ), bit_width=26 * 8), Register( 'CALIBRATION2', 0xE1, fields=( BitField('dig_h2', 0xFFFF0000000000, adapter=S16Adapter()), # 0xE1 0xE2 BitField('dig_h3', 0x0000FF00000000), # 0xE3 uint8 BitField('dig_h4', 0x000000FFFF0000, adapter=H4Adapter()), # 0xE4 0xE5[3:0] BitField('dig_h5', 0x00000000FFFF00, adapter=H5Adapter()), # 0xE5[7:4] 0xE6 BitField('dig_h6', 0x000000000000FF, adapter=S8Adapter()) # 0xE7 int8 ), bit_width=7 * 8))) def setup(self, mode='normal', temperature_oversampling=16, pressure_oversampling=16, humidity_oversampling=16, temperature_standby=500): if self._is_setup: return self._is_setup = True self._bme280.select_address(self._i2c_addr) self._mode = mode if mode == "forced": mode = "sleep" try: if self._bme280.CHIP_ID.get_id() != CHIP_ID: raise RuntimeError( "Unable to find bme280 on 0x{:02x}, CHIP_ID returned {:02x}" .format(self._i2c_addr, self._bme280.CHIP_ID.get_id())) except IOError: raise RuntimeError( "Unable to find bme280 on 0x{:02x}, IOError".format( self._i2c_addr)) self._bme280.RESET.set_reset(0xB6) time.sleep(0.1) self._bme280.CTRL_HUM.set_osrs_h(humidity_oversampling) with self._bme280.CTRL_MEAS as CTRL_MEAS: CTRL_MEAS.set_mode(mode) CTRL_MEAS.set_osrs_t(temperature_oversampling) CTRL_MEAS.set_osrs_p(pressure_oversampling) CTRL_MEAS.write() with self._bme280.CONFIG as CONFIG: CONFIG.set_t_sb(temperature_standby) CONFIG.set_filter(2) CONFIG.write() with self._bme280.CALIBRATION as CALIBRATION: self.calibration.dig_t1 = CALIBRATION.get_dig_t1() self.calibration.dig_t2 = CALIBRATION.get_dig_t2() self.calibration.dig_t3 = CALIBRATION.get_dig_t3() self.calibration.dig_p1 = CALIBRATION.get_dig_p1() self.calibration.dig_p2 = CALIBRATION.get_dig_p2() self.calibration.dig_p3 = CALIBRATION.get_dig_p3() self.calibration.dig_p4 = CALIBRATION.get_dig_p4() self.calibration.dig_p5 = CALIBRATION.get_dig_p5() self.calibration.dig_p6 = CALIBRATION.get_dig_p6() self.calibration.dig_p7 = CALIBRATION.get_dig_p7() self.calibration.dig_p8 = CALIBRATION.get_dig_p8() self.calibration.dig_p9 = CALIBRATION.get_dig_p9() self.calibration.dig_h1 = CALIBRATION.get_dig_h1() with self._bme280.CALIBRATION2 as CALIBRATION: self.calibration.dig_h2 = CALIBRATION.get_dig_h2() self.calibration.dig_h3 = CALIBRATION.get_dig_h3() self.calibration.dig_h4 = CALIBRATION.get_dig_h4() self.calibration.dig_h5 = CALIBRATION.get_dig_h5() self.calibration.dig_h6 = CALIBRATION.get_dig_h6() def update_sensor(self): self.setup() if self._mode == "forced": # Trigger a reading in forced mode and wait for result self._bme280.CTRL_MEAS.set_mode("forced") while self._bme280.STATUS.get_measuring(): time.sleep(0.001) with self._bme280.DATA as DATA: raw_temperature = DATA.get_temperature() raw_pressure = DATA.get_pressure() raw_humidity = DATA.get_humidity() self.temperature = self.calibration.compensate_temperature( raw_temperature) self.pressure = self.calibration.compensate_pressure( raw_pressure) / 100.0 self.humidity = self.calibration.compensate_humidity(raw_humidity) return (self.temperature, self.pressure, self.humidity ) # TheHWcave: added return values def get_temperature(self): self.update_sensor() return self.temperature def get_pressure(self): self.update_sensor() return self.pressure def get_humidity(self): self.update_sensor() return self.humidity def get_altitude(self, qnh=1013.25): self.update_sensor() pressure = self.get_pressure() altitude = 44330.0 * (1.0 - pow(pressure / qnh, (1.0 / 5.255))) return altitude
class BMP280: def __init__(self, i2c_addr=I2C_ADDRESS_GND, i2c_dev=None): self.calibration = BMP280Calibration() self._is_setup = False self._i2c_addr = i2c_addr self._i2c_dev = i2c_dev self._bmp280 = Device([I2C_ADDRESS_GND, I2C_ADDRESS_VCC], i2c_dev=self._i2c_dev, bit_width=8, registers=( Register('CHIP_ID', 0xD0, fields=( BitField('id', 0xFF), )), Register('RESET', 0xE0, fields=( BitField('reset', 0xFF), )), Register('STATUS', 0xF3, fields=( BitField('measuring', 0b00001000), # 1 when conversion is running BitField('im_update', 0b00000001), # 1 when NVM data is being copied )), Register('CTRL_MEAS', 0xF4, fields=( BitField('osrs_t', 0b11100000, # Temperature oversampling adapter=LookupAdapter({ 1: 0b001, 2: 0b010, 4: 0b011, 8: 0b100, 16: 0b101 })), BitField('osrs_p', 0b00011100, # Pressure oversampling adapter=LookupAdapter({ 1: 0b001, 2: 0b010, 4: 0b011, 8: 0b100, 16: 0b101})), BitField('mode', 0b00000011, # Power mode adapter=LookupAdapter({ 'sleep': 0b00, 'forced': 0b10, 'normal': 0b11})), )), Register('CONFIG', 0xF5, fields=( BitField('t_sb', 0b11100000, # Temp standby duration in normal mode adapter=LookupAdapter({ 0.5: 0b000, 62.5: 0b001, 125: 0b010, 250: 0b011, 500: 0b100, 1000: 0b101, 2000: 0b110, 4000: 0b111})), BitField('filter', 0b00011100), # Controls the time constant of the IIR filter BitField('spi3w_en', 0b0000001, read_only=True), # Enable 3-wire SPI interface when set to 1. IE: Don't set this bit! )), Register('DATA', 0xF7, fields=( BitField('temperature', 0x000000FFFFF0), BitField('pressure', 0xFFFFF0000000), ), bit_width=48), Register('CALIBRATION', 0x88, fields=( BitField('dig_t1', 0xFFFF << 16 * 11, adapter=U16Adapter()), # 0x88 0x89 BitField('dig_t2', 0xFFFF << 16 * 10, adapter=S16Adapter()), # 0x8A 0x8B BitField('dig_t3', 0xFFFF << 16 * 9, adapter=S16Adapter()), # 0x8C 0x8D BitField('dig_p1', 0xFFFF << 16 * 8, adapter=U16Adapter()), # 0x8E 0x8F BitField('dig_p2', 0xFFFF << 16 * 7, adapter=S16Adapter()), # 0x90 0x91 BitField('dig_p3', 0xFFFF << 16 * 6, adapter=S16Adapter()), # 0x92 0x93 BitField('dig_p4', 0xFFFF << 16 * 5, adapter=S16Adapter()), # 0x94 0x95 BitField('dig_p5', 0xFFFF << 16 * 4, adapter=S16Adapter()), # 0x96 0x97 BitField('dig_p6', 0xFFFF << 16 * 3, adapter=S16Adapter()), # 0x98 0x99 BitField('dig_p7', 0xFFFF << 16 * 2, adapter=S16Adapter()), # 0x9A 0x9B BitField('dig_p8', 0xFFFF << 16 * 1, adapter=S16Adapter()), # 0x9C 0x9D BitField('dig_p9', 0xFFFF << 16 * 0, adapter=S16Adapter()), # 0x9E 0x9F ), bit_width=192) )) def setup(self, mode='normal', temperature_oversampling=16, pressure_oversampling=16, temperature_standby=500): if self._is_setup: return self._is_setup = True self._bmp280.select_address(self._i2c_addr) self._mode = mode if mode == "forced": mode = "sleep" try: chip = self._bmp280.get('CHIP_ID') if chip.id != CHIP_ID: raise RuntimeError("Unable to find bmp280 on 0x{:02x}, CHIP_ID returned {:02x}".format(self._i2c_addr, chip.id)) except IOError: raise RuntimeError("Unable to find bmp280 on 0x{:02x}, IOError".format(self._i2c_addr)) self._bmp280.set('CTRL_MEAS', mode=mode, osrs_t=temperature_oversampling, osrs_p=pressure_oversampling) self._bmp280.set('CONFIG', t_sb=temperature_standby, filter=2) self.calibration.set_from_namedtuple(self._bmp280.get('CALIBRATION')) def update_sensor(self): self.setup() if self._mode == "forced": # Trigger a reading in forced mode and wait for result self._bmp280.set("CTRL_MEAS", mode="forced") while self._bmp280.get("STATUS").measuring: time.sleep(0.001) raw = self._bmp280.get('DATA') self.temperature = self.calibration.compensate_temperature(raw.temperature) self.pressure = self.calibration.compensate_pressure(raw.pressure) / 100.0 def get_temperature(self): self.update_sensor() return self.temperature def get_pressure(self): self.update_sensor() return self.pressure def get_altitude(self, qnh=1013.25): self.update_sensor() pressure = self.get_pressure() temperature = self.get_temperature() altitude = ((pow((qnh / pressure), (1.0 / 5.257)) - 1) * (temperature + 273.15)) / 0.0065 return altitude
class BMP280: def __init__(self, i2c_addr=I2C_ADDRESS_GND, i2c_dev=None): self.calibration = BMP280Calibration() self._is_setup = False self._i2c_addr = i2c_addr self._i2c_dev = i2c_dev self._bmp280 = Device( [I2C_ADDRESS_GND, I2C_ADDRESS_VCC], i2c_dev=self._i2c_dev, bit_width=8, registers=( Register('CHIP_ID', 0xD0, fields=(BitField('id', 0xFF), )), Register('RESET', 0xE0, fields=(BitField('reset', 0xFF), )), Register( 'STATUS', 0xF3, fields=( BitField('measuring', 0b00001000), # 1 when conversion is running BitField( 'im_update', 0b00000001), # 1 when NVM data is being copied )), Register( 'CTRL_MEAS', 0xF4, fields=( BitField( 'osrs_t', 0b11100000, # Temperature oversampling adapter=LookupAdapter({ 1: 0b001, 2: 0b010, 4: 0b011, 8: 0b100, 16: 0b101 })), BitField( 'osrs_p', 0b00011100, # Pressure oversampling adapter=LookupAdapter({ 1: 0b001, 2: 0b010, 4: 0b011, 8: 0b100, 16: 0b101 })), BitField( 'mode', 0b00000011, # Power mode adapter=LookupAdapter({ 'sleep': 0b00, 'forced': 0b10, 'normal': 0b11 })), )), Register( 'CONFIG', 0xF5, fields=( BitField( 't_sb', 0b11100000, # Temp standby duration in normal mode adapter=LookupAdapter({ 0.5: 0b000, 62.5: 0b001, 125: 0b010, 250: 0b011, 500: 0b100, 1000: 0b101, 2000: 0b110, 4000: 0b111 })), BitField( 'filter', 0b00011100 ), # Controls the time constant of the IIR filter BitField( 'spi3w_en', 0b0000001, read_only=True ), # Enable 3-wire SPI interface when set to 1. IE: Don't set this bit! )), Register('DATA', 0xF7, fields=( BitField('temperature', 0x000000FFFFF0), BitField('pressure', 0xFFFFF0000000), ), bit_width=48), Register( 'CALIBRATION', 0x88, fields=( BitField('dig_t1', 0xFFFF << 16 * 11, adapter=U16Adapter()), # 0x88 0x89 BitField('dig_t2', 0xFFFF << 16 * 10, adapter=S16Adapter()), # 0x8A 0x8B BitField('dig_t3', 0xFFFF << 16 * 9, adapter=S16Adapter()), # 0x8C 0x8D BitField('dig_p1', 0xFFFF << 16 * 8, adapter=U16Adapter()), # 0x8E 0x8F BitField('dig_p2', 0xFFFF << 16 * 7, adapter=S16Adapter()), # 0x90 0x91 BitField('dig_p3', 0xFFFF << 16 * 6, adapter=S16Adapter()), # 0x92 0x93 BitField('dig_p4', 0xFFFF << 16 * 5, adapter=S16Adapter()), # 0x94 0x95 BitField('dig_p5', 0xFFFF << 16 * 4, adapter=S16Adapter()), # 0x96 0x97 BitField('dig_p6', 0xFFFF << 16 * 3, adapter=S16Adapter()), # 0x98 0x99 BitField('dig_p7', 0xFFFF << 16 * 2, adapter=S16Adapter()), # 0x9A 0x9B BitField('dig_p8', 0xFFFF << 16 * 1, adapter=S16Adapter()), # 0x9C 0x9D BitField('dig_p9', 0xFFFF << 16 * 0, adapter=S16Adapter()), # 0x9E 0x9F ), bit_width=192))) def setup(self): if self._is_setup: return self._is_setup = True self._bmp280.select_address(self._i2c_addr) try: if self._bmp280.CHIP_ID.get_id() != CHIP_ID: raise RuntimeError( "Unable to find bmp280 on 0x{:02x}, CHIP_ID returned {:02x}" .format(self._i2c_addr, self._bmp280.CHIP_ID.get_id())) except IOError: raise RuntimeError( "Unable to find bmp280 on 0x{:02x}, IOError".format( self._i2c_addr)) with self._bmp280.CTRL_MEAS as CTRL_MEAS: CTRL_MEAS.set_mode('normal') CTRL_MEAS.set_osrs_t(16) CTRL_MEAS.set_osrs_p(16) CTRL_MEAS.write() with self._bmp280.CONFIG as CONFIG: CONFIG.set_t_sb(500) CONFIG.set_filter(2) CONFIG.write() with self._bmp280.CALIBRATION as CALIBRATION: self.calibration.dig_t1 = CALIBRATION.get_dig_t1() self.calibration.dig_t2 = CALIBRATION.get_dig_t2() self.calibration.dig_t3 = CALIBRATION.get_dig_t3() self.calibration.dig_p1 = CALIBRATION.get_dig_p1() self.calibration.dig_p2 = CALIBRATION.get_dig_p2() self.calibration.dig_p3 = CALIBRATION.get_dig_p3() self.calibration.dig_p4 = CALIBRATION.get_dig_p4() self.calibration.dig_p5 = CALIBRATION.get_dig_p5() self.calibration.dig_p6 = CALIBRATION.get_dig_p6() self.calibration.dig_p7 = CALIBRATION.get_dig_p7() self.calibration.dig_p8 = CALIBRATION.get_dig_p8() self.calibration.dig_p9 = CALIBRATION.get_dig_p9() def update_sensor(self): self.setup() raw_temperature = self._bmp280.DATA.get_temperature() raw_pressure = self._bmp280.DATA.get_pressure() self.temperature = self.calibration.compensate_temperature( raw_temperature) self.pressure = self.calibration.compensate_pressure(raw_pressure) def get_temperature(self): self.update_sensor() return self.temperature def get_pressure(self): self.update_sensor() return self.pressure
class MCP9600: def __init__(self, i2c_addr=I2C_ADDRESS_DEFAULT, i2c_dev=None): self._is_setup = False self._i2c_addr = i2c_addr self._i2c_dev = i2c_dev self._mcp9600 = Device(I2C_ADDRESSES, i2c_dev=self._i2c_dev, bit_width=8, registers=( Register('HOT_JUNCTION', 0x00, fields=( BitField('temperature', 0xFFFF, adapter=TemperatureAdapter()), ), bit_width=16), Register('DELTA', 0x01, fields=( BitField('value', 0xFFFF, adapter=TemperatureAdapter()), ), bit_width=16), Register('COLD_JUNCTION', 0x02, fields=( BitField('temperature', 0x1FFF, adapter=TemperatureAdapter()), ), bit_width=16), Register('RAW_DATA', 0x03, fields=( BitField('adc', 0xFFFFFF), ), bit_width=24), Register('STATUS', 0x04, fields=( BitField('burst_complete', 0b10000000), BitField('updated', 0b01000000), BitField('input_range', 0b00010000), BitField('alert_4', 0b00001000), BitField('alert_3', 0b00000100), BitField('alert_2', 0b00000010), BitField('alert_1', 0b00000001) )), Register('THERMOCOUPLE_CONFIG', 0x05, fields=( BitField('type_select', 0b01110000, adapter=LookupAdapter({ 'K': 0b000, 'J': 0b001, 'T': 0b010, 'N': 0b011, 'S': 0b100, 'E': 0b101, 'B': 0b110, 'R': 0b111 })), BitField('filter_coefficients', 0b00000111) )), Register('DEVICE_CONFIG', 0x06, fields=( BitField('cold_junction_resolution', 0b10000000, adapter=LookupAdapter({ 0.0625: 0b0, 0.25: 0b1 })), BitField('adc_resolution', 0b01100000, adapter=LookupAdapter({ 18: 0b00, 16: 0b01, 14: 0b10, 12: 0b11 })), BitField('burst_mode_samples', 0b00011100, adapter=LookupAdapter({ 1: 0b000, 2: 0b001, 4: 0b010, 8: 0b011, 16: 0b100, 32: 0b101, 64: 0b110, 128: 0b111 })), BitField('shutdown_modes', 0b00000011, adapter=LookupAdapter({ 'Normal': 0b00, 'Shutdown': 0b01, 'Burst': 0b10 })) )), Register('ALERT1_CONFIG', 0x08, fields=( BitField('clear_interrupt', 0b10000000), BitField('monitor_junction', 0b00010000), # 1 Cold Junction, 0 Thermocouple BitField('rise_fall', 0b00001000), # 1 rising, 0 cooling BitField('state', 0b00000100), # 1 active high, 0 active low BitField('mode', 0b00000010), # 1 interrupt mode, 0 comparator mode BitField('enable', 0b00000001) # 1 enable, 0 disable )), Register('ALERT2_CONFIG', 0x09, fields=( BitField('clear_interrupt', 0b10000000), BitField('monitor_junction', 0b00010000), # 1 Cold Junction, 0 Thermocouple BitField('rise_fall', 0b00001000), # 1 rising, 0 cooling BitField('state', 0b00000100), # 1 active high, 0 active low BitField('mode', 0b00000010), # 1 interrupt mode, 0 comparator mode BitField('enable', 0b00000001) # 1 enable, 0 disable )), Register('ALERT3_CONFIG', 0x0A, fields=( BitField('clear_interrupt', 0b10000000), BitField('monitor_junction', 0b00010000), # 1 Cold Junction, 0 Thermocouple BitField('rise_fall', 0b00001000), # 1 rising, 0 cooling BitField('state', 0b00000100), # 1 active high, 0 active low BitField('mode', 0b00000010), # 1 interrupt mode, 0 comparator mode BitField('enable', 0b00000001) # 1 enable, 0 disable )), Register('ALERT4_CONFIG', 0x0B, fields=( BitField('clear_interrupt', 0b10000000), BitField('monitor_junction', 0b00010000), # 1 Cold Junction, 0 Thermocouple BitField('rise_fall', 0b00001000), # 1 rising, 0 cooling BitField('state', 0b00000100), # 1 active high, 0 active low BitField('mode', 0b00000010), # 1 interrupt mode, 0 comparator mode BitField('enable', 0b00000001) # 1 enable, 0 disable )), Register('ALERT1_HYSTERESIS', 0x0C, fields=( BitField('value', 0xFF), )), Register('ALERT2_HYSTERESIS', 0x0D, fields=( BitField('value', 0xFF), )), Register('ALERT3_HYSTERESIS', 0x0E, fields=( BitField('value', 0xFF), )), Register('ALERT4_HYSTERESIS', 0x0F, fields=( BitField('value', 0xFF), )), Register('ALERT1_LIMIT', 0x10, fields=( BitField('value', 0xFFFF, adapter=AlertLimitAdapter()), ), bit_width=16), Register('ALERT2_LIMIT', 0x11, fields=( BitField('value', 0xFFFF, adapter=AlertLimitAdapter()), ), bit_width=16), Register('ALERT3_LIMIT', 0x12, fields=( BitField('value', 0xFFFF, adapter=AlertLimitAdapter()), ), bit_width=16), Register('ALERT4_LIMIT', 0x13, fields=( BitField('value', 0xFFFF, adapter=AlertLimitAdapter()), ), bit_width=16), Register('CHIP_ID', 0x20, fields=( BitField('id', 0xFF00), BitField('revision', 0x00FF, adapter=RevisionAdapter()) ), bit_width=16) )) self.alert_registers = [ 'ALERT1_CONFIG', 'ALERT2_CONFIG', 'ALERT3_CONFIG', 'ALERT4_CONFIG' ] self.alert_limits = [ 'ALERT1_LIMIT', 'ALERT2_LIMIT', 'ALERT3_LIMIT', 'ALERT4_LIMIT' ] self.alert_hysteresis = [ 'ALERT1_HYSTERESIS', 'ALERT2_HYSTERESIS', 'ALERT3_HYSTERESIS', 'ALERT4_HYSTERESIS' ] self._mcp9600.select_address(self._i2c_addr) try: chip = self._mcp9600.get('CHIP_ID') if chip.id != CHIP_ID: raise RuntimeError("Unable to find mcp9600 on 0x{:02x}, CHIP_ID returned {:02x}".format(self._i2c_addr, chip.id)) except IOError: raise RuntimeError("Unable to find mcp9600 on 0x{:02x}, IOError".format(self._i2c_addr)) def setup(self): pass def set_thermocouple_type(self, thermocouple_type): """Set the type of thermocouple connected to the MCP9600. :param thermocouple_type: One of 'K', 'J', 'T', 'N', 'S', 'E', 'B' or 'R' """ self._mcp9600.set('THERMOCOUPLE_CONFIG', type_select=thermocouple_type) def get_thermocouple_type(self): """Get the type of thermocouple connected to the MCP9600. Returns one of 'K', 'J', 'T', 'N', 'S', 'E', 'B' or 'R' """ return self._mcp9600.get('THERMOCOUPLE_CONFIG').type_select def get_hot_junction_temperature(self): """Return the temperature measured by the attached thermocouple.""" return self._mcp9600.get('HOT_JUNCTION').temperature def get_cold_junction_temperature(self): """Return the temperature measured by the onboard sensor.""" return self._mcp9600.get('COLD_JUNCTION').temperature def get_temperature_delta(self): """Return the difference between hot and cold junction temperatures.""" return self._mcp9600.get('DELTA').value def check_alerts(self): """Check status flags of all alert registers.""" status = self._mcp9600.get('STATUS') return status.alert_1, status.alert_2, status.alert_3, status.alert_4 def clear_alert(self, index): """Clear the interrupt flag on an alert slot. :param index: Index of alert to clear, from 1 to 4 """ self._mcp9600.set(self.alert_registers[index - 1], clear_interrupt=1) def get_alert_hysteresis(self, index): alert_hysteresis = self.alert_hysteresis[index - 1] return self._mcp9600.get(alert_hysteresis).value def get_alert_limit(self, index): alert_limit = self.alert_limits[index - 1] return self._mcp9600.get(alert_limit).value def configure_alert(self, index, limit=None, hysteresis=None, clear_interrupt=True, monitor_junction=0, rise_fall=1, state=1, mode=1, enable=False): """Set up one of the 4 alert slots. :param index: Index of alert to set, from 1 to 4 :param limit: Temperature limit :param hysteresis: Temperature hysteresis :param clear_interrupt: Whether to clear the interrupt flag :param monitor_junction: Which junction to monitor: 0 = HOT, 1 = COLD :param rise_fall: Monitor for 1=rising or 0=falling temperature :param state: Active 1=high or 0=low :param mode: 1=Interrupt mode, must clear interrupt to de-assert, 0=Comparator mode :param enable: True=Enabled, False=Disabled """ alert_register = self.alert_registers[index - 1] if limit is not None: alert_limit = self.alert_limits[index - 1] self._mcp9600.set(alert_limit, value=limit) if hysteresis is not None: alert_hysteresis = self.alert_hysteresis[index - 1] self._mcp9600.set(alert_hysteresis, value=hysteresis) self._mcp9600.set(alert_register, clear_interrupt=1 if clear_interrupt else 0, monitor_junction=monitor_junction, rise_fall=rise_fall, state=state, mode=mode, enable=1 if enable else 0)
class DRV8830: def __init__(self, i2c_addr=I2C_ADDR1, i2c_dev=None): self._i2c_addr = i2c_addr self._i2c_dev = i2c_dev self._drv8830 = Device( [I2C_ADDR1, I2C_ADDR2, I2C_ADDR3, I2C_ADDR4], i2c_dev=self._i2c_dev, registers=( Register( 'CONTROL', 0x00, fields=( BitField('voltage', 0b11111100, adapter=VoltageAdapter()), # vset BitField('out2', 0b00000010), # in2 BitField('out1', 0b00000001), # in1 BitField( 'direction', 0b00000011, adapter=LookupAdapter({ # both in2 and in1 :D 'coast': 0b00, 'reverse': 0b01, 'forward': 0b10, 'brake': 0b11 })))), Register( 'FAULT', 0x01, fields=( BitField( 'clear', 0b10000000 ), # Clears fault status bits when written to 1 BitField('current_limit', 0b00010000 ), # Fault caused by external current limit BitField( 'over_temperature', 0b00001000 ), # Fault caused by over-temperature condition BitField('under_voltage', 0b00000100 ), # Fault caused by undervoltage lockout BitField( 'over_current', 0b00000010), # Fault caused by overcurrent event BitField('fault', 0b00000001) # Fault condition exists )))) self._drv8830.select_address(self._i2c_addr) def select_i2c_address(self, i2c_addr): self._i2c_addr = i2c_addr self._drv8830.select_address(self._i2c_addr) def set_outputs(self, out1, out2): """Set the individual driver outputs. Possible values are 1 (on) and 0 (off) with the following valid permutations: * 1 1 - brake * 0 0 - coast * 1 0 - forward * 0 1 - reverse """ self._drv8830.set('CONTROL', out1=out1, out2=out2) def brake(self): """Set the driver outputs to braking mode.""" self.set_direction('brake') def coast(self): """Set the driver outputs to coasting mode.""" self.set_direction('coast') def forward(self): """Set the driver outputs to forward.""" self.set_direction('forward') def reverse(self): """Set the driver outputs to reverse.""" self.set_direction('reverse') def set_direction(self, direction): """Set the motor driver direction. Basically does the same thing as set_outputs, but takes a string name for direction, one of: coast, reverse, forward or brake. :param direction: string name of direction: coast, reverse, forward or brake """ self._drv8830.set('CONTROL', direction=direction) def set_voltage(self, voltage): """Set the motor driver voltage. Roughly corresponds to motor speed depending upon the characteristics of your motor. :param voltage: from 0.48v to 5.06v """ self._drv8830.set('CONTROL', voltage=voltage) def get_voltage(self): """Return the currently set motor voltage.""" return self._drv8830.get('CONTROL').voltage def get_fault(self): """Get motor driver fault information. Returns a namedtuple of the fault flags: current_limit - external current limit exceeded (ilimit resistor), must clear fault or disable motor to reactivate over_temperature - driver has overheated, device resumes once temperature has dropped under_voltage - driver is below operating voltage (brownout), resumes once voltage has stabalised over_current - over-current protection activated, device disabled, must clear fault to reactivate fault - one or more fault flags is set """ return self._drv8830.get('FAULT') def clear_fault(self): self._drv8830.set('FAULT', clear=True)
class MAX30105: def __init__(self, i2c_addr=I2C_ADDRESS, i2c_dev=None): self._is_setup = False self._i2c_addr = i2c_addr self._i2c_dev = i2c_dev self._active_leds = 0 self._max30105 = Device( I2C_ADDRESS, i2c_dev=self._i2c_dev, bit_width=8, registers=( Register('INT_STATUS_1', 0x00, fields=(BitField('a_full', bit(7)), BitField('data_ready', bit(6)), BitField('alc_overflow', bit(5)), BitField('prox_int', bit(4)), BitField('pwr_ready', bit(0)))), Register('INT_STATUS_2', 0x01, fields=(BitField('die_temp_ready', bit(1)), )), Register('INT_ENABLE_1', 0x02, fields=( BitField('a_full_en', bit(7)), BitField('data_ready_en', bit(6)), BitField('alc_overflow_en', bit(5)), BitField('prox_int_en', bit(4)), )), Register('INT_ENABLE_2', 0x03, fields=(BitField('die_temp_ready_en', bit(1)), )), # Points to MAX30105 write location in FIFO Register('FIFO_WRITE', 0x04, fields=(BitField('pointer', 0b00011111), )), # Counts the number of samples lost up to 0xf Register('FIFO_OVERFLOW', 0x05, fields=(BitField('counter', 0b00011111), )), # Points to read location in FIFO Register('FIFO_READ', 0x06, fields=(BitField('pointer', 0b00011111), )), Register('FIFO_CONFIG', 0x08, fields=(BitField('sample_average', 0b11100000, adapter=LookupAdapter({ 1: 0b000, 2: 0b001, 4: 0b010, 8: 0b011, 16: 0b100, 32: 0b101 })), BitField('fifo_rollover_en', 0b00010000), BitField('fifo_almost_full', 0b00001111))), Register('MODE_CONFIG', 0x09, fields=(BitField('shutdown', 0b10000000), BitField('reset', 0b01000000), BitField('mode', 0b00000111, adapter=LookupAdapter({ 'none': 0b00, 'red_only': 0b010, 'red_ir': 0b011, 'green_red_ir': 0b111 })))), Register( 'SPO2_CONFIG', 0x0A, fields=( BitField('adc_range_nA', 0b01100000, adapter=LookupAdapter({ 2048: 0b00, 4096: 0b01, 8192: 0b10, 16384: 0b11 })), BitField('sample_rate_sps', 0b00011100, adapter=LookupAdapter({ 50: 0b000, 100: 0b001, 200: 0b010, 400: 0b011, 800: 0b100, 1000: 0b101, 1600: 0b110, 3200: 0b111 })), BitField( 'led_pw_us', 0b00000011, adapter=LookupAdapter({ 69: 0b00, # 68.95us 118: 0b01, # 117.78us 215: 0b10, # 215.44us 411: 0b11 # 410.75us })))), Register('LED_PULSE_AMPLITUDE', 0x0C, fields=(BitField('led1_mA', 0xff0000, adapter=PulseAmplitudeAdapter()), BitField('led2_mA', 0x00ff00, adapter=PulseAmplitudeAdapter()), BitField('led3_mA', 0x0000ff, adapter=PulseAmplitudeAdapter())), bit_width=24), Register('LED_PROX_PULSE_AMPLITUDE', 0x10, fields=(BitField('pilot_mA', 0xff, adapter=PulseAmplitudeAdapter()), )), # The below represent 4 timeslots Register('LED_MODE_CONTROL', 0x11, fields=(BitField('slot2', 0x7000, adapter=LEDModeAdapter()), BitField('slot1', 0x0700, adapter=LEDModeAdapter()), BitField('slot4', 0x0070, adapter=LEDModeAdapter()), BitField('slot3', 0x0007, adapter=LEDModeAdapter())), bit_width=16), Register('DIE_TEMP', 0x1f, fields=(BitField('temperature', 0xffff, adapter=TemperatureAdapter()), ), bit_width=16), Register('DIE_TEMP_CONFIG', 0x21, fields=(BitField('temp_en', bit(0)), )), Register('PROX_INT_THRESHOLD', 0x30, fields=(BitField('threshold', 0xff), )), Register('PART_ID', 0xfe, fields=(BitField('revision', 0xff00), BitField('part', 0x00ff)), bit_width=16))) def setup(self, led_power=6.4, sample_average=4, leds_enable=3, sample_rate=400, pulse_width=215, adc_range=16384, timeout=5.0): """Set up the sensor.""" if self._is_setup: return self._is_setup = True self._active_leds = leds_enable self._max30105.select_address(self._i2c_addr) self.soft_reset(timeout=timeout) self._max30105.set('FIFO_CONFIG', sample_average=sample_average, fifo_rollover_en=True) self._max30105.set('SPO2_CONFIG', sample_rate_sps=sample_rate, adc_range_nA=adc_range, led_pw_us=pulse_width) self._max30105.set('LED_PULSE_AMPLITUDE', led1_mA=led_power, led2_mA=led_power, led3_mA=led_power) self._max30105.set('LED_PROX_PULSE_AMPLITUDE', pilot_mA=led_power) # Set the LED mode based on the number of LEDs we want enabled self._max30105.set('MODE_CONFIG', mode=['red_only', 'red_ir', 'green_red_ir'][leds_enable - 1]) # Set up the LEDs requested in sequential slots self._max30105.set('LED_MODE_CONTROL', slot1='red', slot2='ir' if leds_enable >= 2 else 'off', slot3='green' if leds_enable >= 3 else 'off') self.clear_fifo() def soft_reset(self, timeout=5.0): """Reset device.""" self._max30105.set('MODE_CONFIG', reset=True) t_start = time.time() while self._max30105.get( 'MODE_CONFIG').reset and time.time() - t_start < timeout: time.sleep(0.001) if self._max30105.get('MODE_CONFIG').reset: raise RuntimeError("Timeout: Failed to soft reset MAX30105.") def clear_fifo(self): """Clear samples FIFO.""" self._max30105.set('FIFO_READ', pointer=0) self._max30105.set('FIFO_WRITE', pointer=0) self._max30105.set('FIFO_OVERFLOW', counter=0) def get_samples(self): """Return contents of sample FIFO.""" ptr_r = self._max30105.get('FIFO_READ').pointer ptr_w = self._max30105.get('FIFO_WRITE').pointer if ptr_r == ptr_w: return None sample_count = ptr_w - ptr_r if sample_count < 0: sample_count = 32 byte_count = sample_count * 3 * self._active_leds data = [] while byte_count > 0: data += self._max30105._i2c.read_i2c_block_data( self._i2c_addr, 0x07, min(byte_count, 32)) byte_count -= 32 self.clear_fifo() result = [] for x in range(0, len(data), 3): result.append((data[x] << 16) | (data[x + 1] << 8) | data[x + 2]) return result def get_chip_id(self): """Return the revision and part IDs.""" self.setup() part_id = self._max30105.get('PART_ID') return part_id.revision, part_id.part def get_temperature(self, timeout=5.0): """Return the die temperature.""" self.setup() self._max30105.set('INT_ENABLE_2', die_temp_ready_en=True) self._max30105.set('DIE_TEMP_CONFIG', temp_en=True) t_start = time.time() while not self._max30105.get('INT_STATUS_2').die_temp_ready: time.sleep(0.01) if time.time() - t_start > timeout: raise RuntimeError( 'Timeout: Waiting for INT_STATUS_2, die_temp_ready.') return self._max30105.get('DIE_TEMP').temperature def set_mode(self, mode): """Set the sensor mode. :param mode: Mode, either red_only, red_ir or green_red_ir """ self._max30105.set('MODE_CONFIG', mode=mode) def set_slot_mode(self, slot, mode): """Set the mode of a single slot. :param slot: Slot to set, either 1, 2, 3 or 4 :param mode: Mode, either off, red, ir, green, pilot_red, pilot_ir or pilot_green """ if slot == 1: self._max30105.set('LED_MODE_CONTROL', slot1=mode) elif slot == 2: self._max30105.set('LED_MODE_CONTROL', slot2=mode) elif slot == 3: self._max30105.set('LED_MODE_CONTROL', slot3=mode) elif slot == 4: self._max30105.set('LED_MODE_CONTROL', slot4=mode) else: raise ValueError("Invalid LED slot: {}".format(slot)) def set_led_pulse_amplitude(self, led, amplitude): """Set the LED pulse amplitude in milliamps. :param led: LED to set, either 1, 2 or 3 :param amplitude: LED amplitude in milliamps """ if led == 1: self._max30105.set('LED_PULSE_AMPLITUDE', led1_mA=amplitude) elif led == 2: self._max30105.set('LED_PULSE_AMPLITUDE', led2_mA=amplitude) elif led == 3: self._max30105.set('LED_PULSE_AMPLITUDE', led3_mA=amplitude) else: raise ValueError("Invalid LED: {}".format(led)) def set_fifo_almost_full_count(self, count): """Set number of FIFO slots remaining for Almost Full trigger. :param count: Count of remaining samples, from 0 to 15 """ self._max30105.set('FIFO_CONFIG', fifo_almost_full=count) def set_fifo_almost_full_enable(self, value): """Enable the FIFO-almost-full flag.""" self._max30105.set('INT_ENABLE_1', a_full_en=value) def set_data_ready_enable(self, value): """Enable the data-ready flag.""" self._max30105.set('INT_ENABLE_1', data_ready_en=value) def set_ambient_light_compensation_overflow_enable(self, value): """Enable the ambient light compensation overflow flag.""" self._max30105.set('INT_ENABLE_1', alc_overflow_en=value) def set_proximity_enable(self, value): """Enable the proximity interrupt flag.""" self._max30105.set('INT_ENABLE_1', prox_int_en=value) def set_proximity_threshold(self, value): """Set the threshold of the proximity sensor. Sets the infra-red ADC count that will trigger the start of particle-sensing mode. :param value: threshold value from 0 to 255 """ self._max30105.set('PROX_INT_THRESHOLD', threshold=value) def get_fifo_almost_full_status(self): """Get the FIFO-almost-full flag. This interrupt is set when the FIFO write pointer has N free spaces remaining, as defined in `set_fifo_almost_full_count`. The flag is cleared upon read. """ return self._max30105.get('INT_STATUS_1').a_full def get_data_ready_status(self): """Get the data-ready flag. In particle-sensing mode this interrupt triggeres when a new sample has been placed into the FIFO. This flag is cleared upon read, or upon `get_samples()` """ return self._max30105.get('INT_STATUS_1').data_ready def get_ambient_light_compensation_overflow_status(self): """Get the ambient light compensation overflow status flag. Returns True if the ALC has reached its limit, and ambient light is affecting the output of the ADC. This flag is cleared upon read. """ return self._max30105.get('INT_STATUS_1').alc_overflow def get_proximity_triggered_threshold_status(self): """Get the proximity triggered threshold status flag. Returns True if the proximity threshold has been reached and particle-sensing mode has begun. This flag is cleared upon read. """ return self._max30105.get('INT_STATUS_1').prox_int def get_power_ready_status(self): """Get the power ready status flag. Returns True if the sensor has successfully powered up and is ready to collect data. """ return self._max30105.get('INT_STATUS_1').pwr_ready def get_die_temp_ready_status(self): """Get the die temperature ready flag. Returns True if the die temperature value is ready to be read. This flag is cleared upon read, or upon `get_temperature`. """ return self._max30105.get('INT_STATUS_2').die_temp_ready
class ADS1015: def __init__(self, i2c_addr=I2C_ADDRESS_DEFAULT, alert_pin=None, i2c_dev=None): self._lock = Lock() self._is_setup = False self._i2c_addr = i2c_addr self._i2c_dev = i2c_dev self._alert_pin = alert_pin self._deprecated_channels = { 'in0/ref': 'in0/in3', 'in1/ref': 'in1/in3', 'in2/ref': 'in2/in3', 'ref/gnd': 'in3/gnd' } self._is_ads1115 = False self._ads1115 = Device( I2C_ADDRESSES, i2c_dev=self._i2c_dev, bit_width=8, registers=( Register('CONFIG', 0x01, fields=(BitField('data_rate_sps', 0b0000000011100000, adapter=LookupAdapter({ 8: 0b000, 16: 0b001, 32: 0b010, 64: 0b011, 128: 0b100, 475: 0b101, 860: 0b110 })), )), Register('CONV', 0x00, fields=(BitField('value', 0xFFFF, adapter=Conv16Adapter()), ), bit_width=16), )) self._ads1115.select_address(self._i2c_addr) self._ads1015 = Device( I2C_ADDRESSES, i2c_dev=self._i2c_dev, bit_width=8, registers=( Register( 'CONFIG', 0x01, fields=( BitField('operational_status', 0b1000000000000000, adapter=LookupAdapter({ 'active': 0, 'inactive_start': 1 })), BitField( 'multiplexer', 0b0111000000000000, adapter=LookupAdapter({ 'in0/in1': 0b000, # Differential reading between in0 and in1, voltages must not be negative and must not exceed supply voltage 'in0/in3': 0b001, # Differential reading between in0 and in3. pimoroni breakout onboard reference connected to in3 'in1/in3': 0b010, # Differential reading between in1 and in3. pimoroni breakout onboard reference connected to in3 'in2/in3': 0b011, # Differential reading between in2 and in3. pimoroni breakout onboard reference connected to in3 'in0/gnd': 0b100, # Single-ended reading between in0 and GND 'in1/gnd': 0b101, # Single-ended reading between in1 and GND 'in2/gnd': 0b110, # Single-ended reading between in2 and GND 'in3/gnd': 0b111 # Single-ended reading between in3 and GND. Should always read 1.25v (or reference voltage) on pimoroni breakout })), BitField('programmable_gain', 0b0000111000000000, adapter=LookupAdapter({ 6.144: 0b000, 4.096: 0b001, 2.048: 0b010, 1.024: 0b011, 0.512: 0b100, 0.256: 0b101 })), BitField('mode', 0b0000000100000000, adapter=LookupAdapter({ 'continuous': 0, 'single': 1 })), BitField('data_rate_sps', 0b0000000011100000, adapter=LookupAdapter({ 128: 0b000, 250: 0b001, 490: 0b010, 920: 0b011, 1600: 0b100, 2400: 0b101, 3300: 0b110 })), BitField( 'comparator_mode', 0b0000000000010000, adapter=LookupAdapter({ 'traditional': 0b0, # Traditional comparator with hystersis 'window': 0b01 })), BitField('comparator_polarity', 0b0000000000001000, adapter=LookupAdapter({ 'active_low': 0b0, 'active_high': 0b1 })), BitField('comparator_latching', 0b0000000000000100), BitField('comparator_queue', 0b0000000000000011, adapter=LookupAdapter({ 'one': 0b00, 'two': 0b01, 'four': 0b10, 'disabled': 0b11 }))), bit_width=16), Register('CONV', 0x00, fields=(BitField('value', 0xFFF0, adapter=ConvAdapter()), ), bit_width=16), Register('THRESHOLD', 0x02, fields=(BitField('low', 0xFFFF, adapter=S16Adapter()), BitField('high', 0xFFFF, adapter=S16Adapter())), bit_width=32))) self._ads1015.select_address(self._i2c_addr) @synchronized def detect_chip_type(self, timeout=10.0): """Attempt to auto-detect if an ADS1015 or ADS1115 is connected.""" # 250sps is 16sps on ADS1115 sps = 128 samples = 10 self._ads1015.set('CONFIG', mode='single') self._ads1115.set('CONFIG', data_rate_sps=sps) runtimes = [] for x in range(samples): self.start_conversion() t_start = time.time() while self._ads1015.get('CONFIG').operational_status == 'active': if (time.time() - t_start) > timeout: raise ADS1015TimeoutError( "Timed out waiting for conversion.") time.sleep(0.001) runtimes.append(time.time() - t_start) time.sleep(0.001) runtime = sum(runtimes) / float(samples) # If it takes roughly the same time or longer per sample, # then it's an ADS1115. An ADS1015 would be roughly 16x faster. self._is_ads1115 = runtime >= (1.0 / sps) return [DEVICE_ADS1015, DEVICE_ADS1115][self._is_ads1115] def start_conversion(self): """Start a conversion.""" self.set_status('inactive_start') def conversion_ready(self): """Check if conversion is ready.""" return self.get_status() != 'active' def set_status(self, value): """Set the operational status. :param value: Set to true to trigger a conversion, false will have no effect. """ self._ads1015.set('CONFIG', operational_status=value) def get_status(self): """Get the operational status. Result will be true if the ADC is actively performing a conversion and false if it has completed. """ return self._ads1015.get('CONFIG').operational_status def set_multiplexer(self, value): """Set the analog multiplexer. Sets up the analog input in single or differential modes. If b is specified as gnd the ADC will be in single-ended mode, referenced against Ground. If b is specified as in1 or ref the ADC will be in differential mode. a should be one of in0, in1, in2, or in3 This method has no function on the ADS1013 or ADS1014 'in0/in1' - Differential reading between in0 and in1, voltages must not be negative and must not exceed supply voltage 'in0/in3' - Differential reading between in0 and in3 'in1/in3' - Differential reading between in1 and in3 'in2/in3' - Differential reading between in2 and in3 'in0/gnd' - Single-ended reading between in0 and GND 'in1/gnd' - Single-ended reading between in1 and GND 'in2/gnd' - Single-ended reading between in2 and GND 'in3/gnd' - Should always read 1.25v (or reference voltage) :param value: Takes the form a/b """ if value in self._deprecated_channels.keys(): value = self._deprecated_channels[value] self._ads1015.set('CONFIG', multiplexer=value) def get_multiplexer(self): """Return the current analog multiplexer state.""" return self._ads1015.get('CONFIG').multiplexer def set_mode(self, value): """Set the analog mode. In single-mode you must trigger a conversion manually by writing the status bit. :param value: One of 'continuous' or 'single' """ self._ads1015.set('CONFIG', mode=value) def get_mode(self): """Get the analog mode.""" return self._ads1015.get('CONFIG').mode def set_programmable_gain(self, value=2.048): """Set the analog gain. This has no function on the ADS1013. Sets up the full-scale range and resolution of the ADC in volts. The range is always differential, so a value of 6.144v would give a range of +-6.144. A single-ended reading will therefore always have only 11-bits of resolution, since the 12th bit is the (unused) sign bit. :param value: the range in volts - one of 6.144, 4.096, 2.048 (default), 1.024, 0.512 or 0.256 """ self._ads1015.set('CONFIG', programmable_gain=value) def get_programmable_gain(self): """Return the curren gain setting.""" return self._ads1015.get('CONFIG').programmable_gain def set_sample_rate(self, value=1600): """Set the analog sample rate. :param value: The sample rate in samples-per-second Valid values for ADS1015 are 128, 250, 490, 920, 1600 (default), 2400 or 3300 Valid values for ADS1115 are 8, 16, 32, 64, 128 (default), 250, 475, 860 """ if self._is_ads1115: self._ads1115.set('CONFIG', data_rate_sps=value) else: self._ads1015.set('CONFIG', data_rate_sps=value) def get_sample_rate(self): """Return the current sample-rate setting.""" self._ads1015.get('CONFIG').data_rate_sps def set_comparator_mode(self, value): """Set the analog comparator mode. In traditional mode the comparator asserts the alert/ready pin when the conversion data exceeds the high threshold and de-asserts when it falls below the low threshold. In window mode the comparator asserts the alert/ready pin when the conversion data exceeds the high threshold or falls below the low threshold. :param value: Either 'traditional' or 'window' """ self._ads1015.set('CONFIG', comparator_mode=value) def get_comparator_mode(self): """Return the current comparator mode.""" self._ads1015.get('CONFIG').comparator_mode def set_comparator_latching(self, value): self._ads1015.set('CONFIG', comparator_latching=value) def get_comparator_latching(self): self._ads1015.get('CONFIG').comparator_latching def set_comparator_queue(self, value): self._ads1015.set('CONFIG', comparator_queue=value) def get_comparator_queue(self): return self._ads1015.get('CONFIG').comparator_queue def wait_for_conversion(self, timeout=10): """Wait for ADC conversion to finish. Timeout exception is alised as ads1015.ADS1015TimeoutError for convinience. :param timeout: conversion timeout in seconds :raises TimeoutError in Python 3.x :raises socket.timeout in Python 2.x """ t_start = time.time() while not self.conversion_ready(): time.sleep(0.001) if (time.time() - t_start) > timeout: raise ADS1015TimeoutError("Timed out waiting for conversion.") def get_reference_voltage(self): """Read the reference voltage that is included on the pimoroni PM422 breakout.""" return self.get_voltage(channel='in3/gnd') @synchronized def get_voltage(self, channel=None): """Read the raw voltage of a channel.""" if channel is not None: self.set_multiplexer(channel) self.start_conversion() self.wait_for_conversion() value = self.get_conversion_value() gain = self.get_programmable_gain() gain *= 1000.0 # Convert gain from V to mV if self._is_ads1115: value /= 32768.0 # Divide by total register size else: value /= 2048.0 value *= float(gain) # Multiply by current gain value to get mV value /= 1000.0 # mV to V return value def get_compensated_voltage(self, channel=None, vdiv_a=8060000, vdiv_b=402000, reference_voltage=1.241): """Read and compensate the voltage of a channel.""" pin_v = self.get_voltage(channel=channel) input_v = pin_v * (float(vdiv_a + vdiv_b) / float(vdiv_b)) input_v += reference_voltage return round(input_v, 3) def get_conversion_value(self): if self._is_ads1115: return self._ads1115.get('CONV').value else: return self._ads1015.get('CONV').value def set_low_threshold(self, value): self._ads1015.set('THRESHOLD', low=value) def get_low_threshold(self): self._ads1015.get('THRESHOLD').low def set_high_threshold(self, value): self._ads1015.set('THRESHOLD', high=value) def get_high_threshold(self): self._ads1015.get('THRESHOLD').high