Esempio n. 1
0
    def __init__(self, bus_n, bus_addr=0x18, output='csv'):

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)

        self.state_mapper = {0: "closed", 1: "open"}

        # information about this device
        self.device = DeviceData("Qwiic Relay")
        self.device.description = ("Sparkfun Single Pole Double Throw Relay")
        self.device.urls = "https://learn.sparkfun.com/tutorials/qwiic-single-relay-hookup-guide/all"
        self.device.active = None
        self.device.error = None
        self.device.bus = repr(self.bus)
        self.device.manufacturer = "Sparkfun"
        self.device.version_hw = "1"
        self.device.version_sw = "1"

        self.application = "test"

        del self.device.accuracy
        del self.device.precision
        del self.device.dtype

        # data recording method

        self.writer_output = output
        self.csv_writer = CSVWriter("Qwiic Relay", time_format='std_time_ms')
        self.csv_writer.device = self.device.__dict__
        self.csv_writer.header = ["sample_id", "state"]

        self.json_writer = JSONWriter("Qwiic Relay", time_format='std_time_ms')
        self.json_writer.device = self.device.__dict__
        self.json_writer.header = ["sample_id", "state"]
Esempio n. 2
0
    def __init__(self, bus_n, bus_addr=0x18, output='csv', name='Qwiic_1xRelay'):

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)

        self.state_mapper = {0: "closed", 1: "open"}
        
        # information about this device
        self.metadata = Meta(name=name)
        self.metadata.description = 'Sparkfun Single Pole Double Throw Relay'
        self.metadata.urls = 'https://learn.sparkfun.com/tutorials/qwiic-single-relay-hookup-guide/all'
        self.metadata.manufacturer = 'Sparkfun'
        
        self.metadata.header    = ["description", "state"]
        self.metadata.dtype     = ['str', 'str']
        self.metadata.units     = None
        self.metadata.accuracy  = None 
        self.metadata.precision = None
        
        self.metadata.bus_n = bus_n
        self.metadata.bus_addr = hex(bus_addr)

        # data recording method
        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata, time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata, time_format='std_time_ms')
Esempio n. 3
0
    def __init__(self, bus_n, bus_addr=0x0F, output='csv'):

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)

        # PWM clock frequency
        self.frequency = 31372

        # speed can be -255 to 255
        self.motor_1_speed = 0
        self.motor_2_speed = 0

        # direction of DC motor
        self.direction = ""

        # driver mode, 'dc' or 'step'
        self.mode = "dc"

        # phase of the motor being used, only applies in stepper mode
        self.phase = None

        # step code initialize
        self.step_codes_cw = None
        self.step_codes_ccw = None

        # motor phase steps, default to 2 phase
        self.set_phase(phase=2)

        # number of steps commanded
        self.steps = 0

        # running total steps, for positioning microsteps
        self.step_count = 0

        # information about this device
        self.device = DeviceData('Grove Motor Driver')
        self.device.description = ('I2C DC and stepper motor controller')
        self.device.urls = 'http://wiki.seeedstudio.com/Grove-I2C_Motor_Driver_V1.3/'
        self.device.active = None
        self.device.error = None
        self.device.bus = repr(self.bus)
        self.device.manufacturer = 'Seeed Studio'
        self.device.version_hw = '1.3'
        self.device.version_sw = '1.0'
        self.device.accuracy = None
        self.device.precision = '1 microstep'
        self.device.calibration_date = None

        # data recording method
        self.writer_output = output
        self.csv_writer = CSVWriter(self.device.name, time_format='std_time_ms')
        self.csv_writer.device = self.device.values()
        self.csv_writer.header = ['description', 'freq',
                                  'm1_speed', 'm2_speed', 'm_direction',
                                  'mode', 'phase', 'steps']

        self.json_writer = JSONWriter(self.device.name, time_format='std_time_ms')
        self.json_writer.device = self.device.values()
        self.json_writer.header = self.csv_writer.header
Esempio n. 4
0
    def __init__(self, output='json'):

        # TODO: make safe for MicroPython, leave here for now in Conda
        from collections import deque
        from math import sin, pi

        from meerkat.data import CSVWriter, JSONWriter

        # data bus placeholder
        self.bus = None
        self.bus_addr = None

        # what kind of data output to file
        self.output = output

        # types of verbose printing
        self.verbose = False
        self.verbose_data = False

        # thread safe deque for sharing and plotting
        self.q_maxlen = 300
        self.q = deque(maxlen=self.q_maxlen)
        self.q_prefill_zeros = False

        # realtime/stream options
        self.go = False
        self.unlimited = False
        self.max_samples = 1000

        # information about this device
        self.device = DeviceData('Software Test')
        self.device.description = 'Dummy data for software testing'
        self.device.urls = None
        self.device.manufacturer = None
        self.device.version_hw = None
        self.device.version_sw = None
        self.device.accuracy = None
        self.device.precision = None
        self.device.bus = None
        self.device.state = 'Test Not Running'
        self.device.active = False
        self.device.error = None
        self.device.dtype = None
        self.device.calibration_date = None

        # data writer
        if self.output == 'csv':
            self.writer = CSVWriter('Software Test')
            #self.writer.device = self.device.values()
        elif self.output == 'json':
            self.writer = JSONWriter('Software Test')

        self.writer.header = ['index', 'degrees', 'amplitude']
        self.writer.device = self.device.values()

        # example data of one 360 degree, -1 to 1 sine wave
        self._deg = [n for n in range(360)]
        self._amp = [sin(d * (pi / 180.0)) for d in self._deg]
        self._test_data = list(zip(self._deg, self._amp))
Esempio n. 5
0
    def __init__(self, bus_n, bus_addr, output='csv'):
        """Initialize worker device on i2c bus.

        Parameters
        ----------
        bus_n : int, i2c bus number on Controller
        bus_addr : int, i2c bus number of this Worker device
        """

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)


        # time to wait for conversions to finish
        self.short_delay   = 0.3  # seconds for regular commands
        self.long_delay    = 1.5  # seconds for readings
        self.cal_delay     = 3.0  # seconds for calibrations

        # information about this device
        self.device = DeviceData('Atlas_Base')
        self.device.description = ('')
        self.device.urls = 'www.atlas-scientific.com'
        self.device.active = None
        self.device.error = None
        self.device.bus = repr(self.bus)
        self.device.manufacturer = 'Atlas Scientific'
        self.device.version_hw = '1.0'
        self.device.version_sw = '1.0'
        self.device.accuracy = None
        self.device.precision = 'Varies'
        self.device.calibration_date = None

        """
        # data recording method
        if output == 'csv':
            self.writer = CSVWriter('Atlas_Base', time_format='std_time_ms')

        elif output == 'json':
            self.writer = JSONWriter('Atlas_Base', time_format='std_time_ms')
        else:
            pass  # holder for another writer or change in default
        self.writer.header = ['description', 'sample_n', 'not_set']
        self.writer.device = self.device.values()
        """

        # data recording information
        self.sample_id = None

        # data recording method
        self.writer_output = output
        self.csv_writer = CSVWriter("Atlas_Base", time_format='std_time_ms')
        self.csv_writer.device = self.device.__dict__
        self.csv_writer.header = ['description', 'sample_n', 'not_set']

        self.json_writer = JSONWriter("Atlas_Base", time_format='std_time_ms')
        self.json_writer.device = self.device.__dict__
        self.json_writer.header = self.csv_writer.header
Esempio n. 6
0
    def __init__(self, bus_n, bus_addr=0x18, output='csv', name='mcp9808'):
        """Initialize worker device on i2c bus.

        Parameters
        ----------
        bus_n : int, i2c bus number on Controller
        bus_addr : int, i2c bus number of this Worker device
        """

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)

        # register values and defaults
        # TODO: do the constant values need to be specified?
        self.reg_map = {
            'config': REG_CONFIG,
            'upper_temp': REG_UPPER_TEMP,
            'lower_temp': REG_LOWER_TEMP,
            'crit_temp': REG_CRIT_TEMP,
            'ambient': REG_AMBIENT_TEMP,
            'manufacturer': REG_MANUF_ID,
            'device_id': REG_DEVICE_ID
        }

        self.alert_critial = None
        self.alert_upper = None
        self.alert_lower = None

        self.manufacturer_id = None
        self.device_id = None
        self.revision = None

        # information about this device
        self.metadata = Meta(name=name)
        self.metadata.description = 'Microchip Tech digital temperature sensor'
        self.metadata.urls = 'https://www.microchip.com/datasheet/MCP9808'
        self.metadata.manufacturer = 'Adafruit Industries & Microchip Tech'

        self.metadata.header = ['description', 'sample_n', 'temp_C']
        self.metadata.dtype = ['str', 'int', 'float']
        self.metadata.units = [None, 'count', 'degrees Celcius']
        self.metadata.accuracy = [None, 1, '+/- 0.25 typical']
        self.metadata.precision = [None, 1, '0.0625 max']

        self.metadata.bus_n = bus_n
        self.metadata.bus_addr = hex(bus_addr)

        # data recording method
        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata,
                                    time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata,
                                      time_format='std_time_ms')
Esempio n. 7
0
    def __init__(self, bus_n, bus_addr=0x0F, output='csv', name='Grove Motor Driver'):

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)

        # PWM clock frequency
        self.frequency = 31372

        # speed can be -255 to 255
        self.motor_1_speed = 0
        self.motor_2_speed = 0

        # direction of DC motor
        self.direction = ""

        # driver mode, 'dc' or 'step'
        self.mode = "dc"

        # phase of the motor being used, only applies in stepper mode
        self.phase = None

        # step code initialize
        self.step_codes_cw = None
        self.step_codes_ccw = None

        # motor phase steps, default to 2 phase
        self.set_phase(phase=2)

        # number of steps commanded
        self.steps = 0

        # running total steps, for positioning microsteps
        self.step_count = 0

        # information about this device
        self.metadata = Meta(name=name)
        self.metadata.description = ('I2C DC and stepper motor controller')
        self.metadata.urls = 'http://wiki.seeedstudio.com/Grove-I2C_Motor_Driver_V1.3/'
        self.metadata.manufacturer = 'Seeed Studio'
        
        self.metadata.header    = ['description', 'freq',
                                   'm1_speed', 'm2_speed', 'm_direction',
                                   'mode', 'phase', 'steps']
        self.metadata.dtype     = ['str', 'int', 'int', 'int', 'str', 'str', 'int', 'int']
        self.metadata.accuracy  = None
        self.metadata.precision = None
        
        self.metadata.bus_n = bus_n
        self.metadata.bus_addr = hex(bus_addr)
        
        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata, time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata, time_format='std_time_ms')
Esempio n. 8
0
    def __init__(self, bus_n, bus_addr=0x68, output='csv'):

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)

        # Wake up the MPU-6050 since it starts in sleep mode
        # by toggling bit6 from 1 to 0, see pg 40 of RM-MPU-6000A-00 v4.2
        self.bus.write_register_8bit(self.PWR_MGMT_1, 0x00)

        # information about this device
        self.device = DeviceData('MPU-6050')
        self.device.description = ('TDK InvenSense Gyro & Accelerometer')
        self.device.urls = 'https://www.invensense.com/products/motion-tracking/6-axis/mpu-6050/'
        self.device.active = None
        self.device.error = None
        self.device.bus = repr(self.bus)
        self.device.manufacturer = 'TDK'
        self.device.version_hw = '0.1'
        self.device.version_sw = '0.1'
        self.device.gyro_accuracy = '+/-3%, +/-2% cross axis'
        self.device.gyro_precision = '16bit'
        self.device.gyro_noise = '0.05 deg/s-rms'
        self.device.accel_accuracy = '+/-0.5%, +/-2 cross axis'
        self.device.accel_precision = '16bit'
        self.device.accel_noise = 'PSD 400 ug / Hz**1/2'
        self.device.calibration_date = None
        '''
        # data recording method
        if output == 'csv':
            self.writer = CSVWriter('MPU-6050', time_format='std_time_ms')
            self.writer.header = ['description', 'sample_n', 'arange', 'grange',
                                  'ax', 'ay', 'az', 'gx', 'gy', 'gz', 'temp_C']
        elif output == 'json':
            self.writer = JSONWriter('MPU-6050', time_format='std_time_ms')
        else:
            pass  # holder for another writer or change in default
        self.writer.device = self.device.values()
        '''

        # data recording information
        self.sample_id = None

        # data recording method
        self.writer_output = output
        self.csv_writer = CSVWriter("MPU-6050", time_format='std_time_ms')
        self.csv_writer.device = self.device.__dict__
        self.csv_writer.header = [
            'description', 'sample_n', 'ax', 'ay', 'az', 'gx', 'gy', 'gz'
        ]

        self.json_writer = JSONWriter("MCP9808", time_format='std_time_ms')
        self.json_writer.device = self.device.__dict__
        self.json_writer.header = self.csv_writer.header
Esempio n. 9
0
    def __init__(self, bus_n, bus_addr=0x62, output='csv', sensor_id='SCD4x'):
        """Initialize worker device on i2c bus.

        Parameters
        ----------
        bus_n : int, i2c bus number on Controller
        bus_addr : int, i2c bus number of this Worker device
        output : str, writer output format, either 'csv' or 'json'
        sensor_id : str, sensor id, 'BME680' by default
        """

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)

        # information about this device
        self.metadata = Meta(name='SCD4x')
        self.metadata.description = 'SCD4x CO2 gas Sensor'
        self.metadata.urls = 'https://www.sensirion.com/en/environmental-sensors/carbon-dioxide-sensors/carbon-dioxide-sensor-scd4x/'
        self.metadata.manufacturer = 'Sensirion'

        self.metadata.header = [
            'system_id', 'sensor_id', 'description', 'sample_n', 'co2', 'tC',
            'rh'
        ]
        self.metadata.dtype = [
            'str', 'str', 'str', 'int', 'int', 'float', 'int'
        ]
        self.metadata.units = [
            'NA', 'NA', 'NA', 'count', 'ppm', 'degrees Celsius', 'percent'
        ]
        self.metadata.accuracy = [
            'NA', 'NA', 'NA', '1', '+/- 40 + 5%', '+/- 1.5', '+/- 0.4'
        ]
        self.metadata.precision = [
            'NA', 'NA', 'NA', 'NA', '+/- 10', '+/- 0.1', '+/- 0.4'
        ]
        self.metadata.range = [
            'NA', 'NA', 'NA', 'NA', '0-40000', '-10-60', '0-100'
        ]

        self.metadata.bus_n = bus_n
        self.metadata.bus_addr = hex(bus_addr)

        # data recording information
        self.system_id = None
        self.sensor_id = sensor_id
        self.dt = None

        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata,
                                    time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata,
                                      time_format='std_time_ms')
Esempio n. 10
0
    def __init__(self, bus_n, bus_addr=0x68, output='csv', name='MPU6050'):

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)

        # Wake up the MPU-6050 since it starts in sleep mode
        # by toggling bit6 from 1 to 0, see pg 40 of RM-MPU-6000A-00 v4.2
        self.bus.write_register_8bit(self.PWR_MGMT_1, 0x00)

        # information about this device
        self.metadata = Meta(name=name)
        self.metadata.description = 'TDK InvenSense Gyro & Accelerometer'
        self.metadata.urls = 'https://www.invensense.com/products/motion-tracking/6-axis/mpu-6050/'
        self.metadata.manufacturer = 'Adafruit Industries & TDK'

        # note: accuracy in datasheet is relative to scale factor - LSB/(deg/s) +/-3%
        # is there a better way to describe this? +/-3% below implies relative to deg/s output...
        self.metadata.header = [
            'description', 'sample_n', 'ax', 'ay', 'az', 'gx', 'gy', 'gz'
        ]
        self.metadata.dtype = [
            'str', 'int', 'float', 'float', 'float', 'float', 'float', 'float'
        ]
        self.metadata.units = [
            None, 'count', 'g', 'g', 'g', 'deg/s', 'deg/s', 'deg/s'
        ]
        self.metadata.accuracy = [
            None, 1, '+/-3%', '+/-3%', '+/-3%', '+/-3%', '+/-3%', '+/-3%'
        ]
        self.metadata.accuracy_precision_note = 'See datasheet for scale factor dependent accuracy & LSB precision'
        self.metadata.precision = None

        # specific specifications
        self.metadata.gyro_accuracy = '+/-3%, +/-2% cross axis'
        self.metadata.gyro_precision = '16bit'
        self.metadata.gyro_noise = '0.05 deg/s-rms'
        self.metadata.accel_accuracy = '+/-0.5%, +/-2 cross axis'
        self.metadata.accel_precision = '16bit'
        self.metadata.accel_noise = 'PSD 400 ug / Hz**1/2'

        self.metadata.bus_n = bus_n
        self.metadata.bus_addr = hex(bus_addr)

        # data recording classes
        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata,
                                    time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata,
                                      time_format='std_time_ms')
Esempio n. 11
0
    def __init__(self, bus_n, bus_addr=0x00, output='csv'):
        """Initialize worker device on i2c bus.

        Parameters
        ----------
        bus_n : int, i2c bus number on Controller
        bus_addr : int, i2c bus number of this Worker device
        """

        # i2c bus
        self.bus = base.I2C(bus_n=bus_n, bus_addr=bus_addr)

        # print debug statements
        self.verbose = False

        # information about this device
        self.device = base.DeviceData('ExampleDevice')
        self.device.description = ('Just an example, replace')
        self.device.urls = 'www.example.com'
        self.device.active = None
        self.device.error = None
        self.device.bus = repr(self.bus)
        self.device.manufacturer = 'None'
        self.device.version_hw = '1.0'
        self.device.version_sw = '1.0'
        self.device.accuracy = None
        self.device.precision = 'replace'
        self.device.calibration_date = None

        # add device specific attributes
        self.device.other_attributes = None

        # data recording method
        if output == 'csv':
            self.writer = CSVWriter('ExampleDevice', time_format='std_time_ms')
        elif output == 'json':
            self.writer = JSONWriter('ExampleDevice',
                                     time_format='std_time_ms')
        else:
            pass  # holder for another writer or change in default
        # set writer header for device's output format, ADS1115 shown example
        self.writer.header = ['description', 'sample_n', 'voltage', 'current']
        self.writer.device = self.device.values()

        # data recording information
        self.sample_id = None

        # intialized configuration values
        self.get_config()
Esempio n. 12
0
    def __init__(self,
                 bus_n,
                 bus_addr=0x69,
                 output_format='float',
                 output='csv',
                 sensor_id='SPS30'):
        """Initialize worker device on i2c bus.

        Parameters
        ----------
        bus_n : int, i2c bus number on Controller
        bus_addr : int, i2c bus number of this Worker device
        output_format : str, what format measurements will be returned in
            either 'float' or 'int'
        output : str, writer output format, either 'csv' or 'json'
        sensor_id : str, sensor id, 'BME680' by default
        """

        self.output_format = None

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)

        # information about this device
        self.metadata = Meta(name='SPS30')
        self.metadata.description = 'SPS30 Particulate Matter Sensor'
        self.metadata.urls = 'https://www.sensirion.com/en/environmental-sensors/particulate-matter-sensors-pm25'
        self.metadata.manufacturer = 'Sensirion'
        self.metadata.bus_n = bus_n
        self.metadata.bus_addr = hex(bus_addr)
        self.metadata.speed_warning = 0
        self.metadata.laser_error = 0
        self.metadata.fan_error = 0

        self.set_format(output_format)

        # data recording information
        self.system_id = None
        self.sensor_id = sensor_id
        self.blocking_settle_dt = 15  # seconds
        self.blocking_timeout = 30  # seconds

        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata,
                                    time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata,
                                      time_format='std_time_ms')
Esempio n. 13
0
    def __init__(self, bus_n, bus_addr=0x10, output='csv', name='pa1010d'):
        """PA1010D GPS module using MTK3333 chipset
        
        Supported NMEA sentences: 'GGA', 'GSA', 'GSV', 'RMC', 'VTG'
        
        Parameters
        ----------
        bus_n : int, i2c bus number on Controller
        bus_addr : int, i2c bus number of this Worker device
        nmea_sentence : str, NMEA sentence type to save for CSV.
            JSON will save
        """

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)

        # maximum data in buffer size
        self._bytes_per_burst = 255
        
        # standard metadata information about this device
        self.metadata = Meta(name=name)
        
        self.metadata.description      = 'Adafruit PA1010D GPS/GNSS module'
        self.metadata.urls             = 'https://www.cdtop-tech.com/products/pa1010d'
        self.metadata.manufacturer     = 'CDTop Technology'
        self.metadata.state            = None
        
        self.metadata.header    = ['description', 'sample_n', 'nmea_sentence']
        self.metadata.dtype     = ['str', 'int', 'str']
        self.metadata.units     = None
        self.metadata.accuracy  = None
        self.metadata.precision = '<3.0 meters'
        
        self.metadata.bus_n            = bus_n
        self.metadata.bus_addr         = hex(bus_addr)
        
        # custom metadata attributes
        self.metadata.supported_nmea_sentences = ['GGA', 'GSA', 'GSV', 'RMC', 'VTG']
        
        # data recording method
        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata,
                                    time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata,
                                      time_format='std_time_ms')
Esempio n. 14
0
    def __init__(self, bus_n, bus_addr=0x68, output='csv', name='DS3231'):
        """Initialize worker device on i2c bus.

        Parameters
        ----------
        bus_n : int, i2c bus number on Controller
        bus_addr : int, i2c bus number of this Worker device
        output : str, output data format, either 'csv' (default) or 'json'
        """

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)

        # information about this device
        self.metadata = Meta(name=name)
        self.metadata.description = 'Adafruit DS3221 Precision RTC'
        self.metadata.urls = 'https://datasheets.maximintegrated.com/en/ds/DS3231.pdf'
        self.metadata.manufacturer = 'Adafruit Industries'

        self.metadata.header = [
            "description", "sample_n", "rtc_time", "temp_C"
        ]
        self.metadata.dtype = ['str', 'int', 'str', 'float']
        self.metadata.units = [None, 'count', 'datetime', 'degrees Celcius']
        self.metadata.accuracy = [None, 1, '+/- 3.5 ppm', '+/- 3.0']
        self.metadata.precision = [None, 1, '1 second', 0.25]

        self.metadata.bus_n = bus_n
        self.metadata.bus_addr = hex(bus_addr)

        # python strftime specification for RTC output precision
        self.metadata.rtc_time_format = '%Y-%m-%d %H:%M:%S'

        # data recording method
        # note: using millisecond accuracy on driver timestamp, even though
        # RTC is only 1 second resolution
        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata,
                                    time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata,
                                      time_format='std_time_ms')
Esempio n. 15
0
    def __init__(self, bus_n, bus_addr, output='csv', name='atlas_base'):
        """Initialize worker device on i2c bus.

        Parameters
        ----------
        bus_n : int, i2c bus number on Controller
        bus_addr : int, i2c bus number of this Worker device
        """

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)


        # time to wait for conversions to finish
        self.short_delay   = 0.3  # seconds for regular commands
        self.long_delay    = 1.5  # seconds for readings
        self.cal_delay     = 3.0  # seconds for calibrations

        # data recording information
        self.sample_id = None
        
        # information about this device
        self.metadata = Meta(name=name)
        self.metadata.description = 'Base class for Atlas Scientific Devices'
        self.metadata.urls = 'www.atlas-scientific.com'
        self.metadata.manufacturer = 'Atlas Scientific'
        
        self.metadata.header    = None
        self.metadata.dtype     = None
        self.metadata.units     = None
        self.metadata.accuracy  = None 
        self.metadata.precision = None
        
        self.metadata.bus_n = bus_n
        self.metadata.bus_addr = hex(bus_addr)

        # data recording method
        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata, time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata, time_format='std_time_ms')
Esempio n. 16
0
    def __init__(self, bus_n, bus_addr=0x20, output='csv'):
        """Initialize worker device on i2c bus.

        Parameters
        ----------
        bus_n : int, i2c bus number on Controller
        bus_addr : int, i2c bus number of this Worker device
        output : str, output data format, either 'csv' (default) or 'json'
        """

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)

        # set direction of I/O to output for all pins
        self.bus.write_register_8bit(reg_addr=0x00, data=0)

        self.reg_olat = None

        # information about this device
        self.metadata = Meta('MCP23008')
        self.metadata.description = '8 channel I2C relay board by Peter Jakab'
        self.metadata.urls = 'https://jap.hu/electronic/relay_module_i2c.html'
        self.metadata.manufacturer = 'Peter Jakab'

        self.metadata.header = ['description', 'sample_n', 'relay_state']
        self.metadata.dtype = ['str', 'int', 'str']
        self.metadata.accuracy = None
        self.metadata.precision = None

        self.metadata.bus_n = bus_n
        self.metadata.bus_addr = hex(bus_addr)

        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata,
                                    time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata,
                                      time_format='std_time_ms')
Esempio n. 17
0
class SPS30():
    def __init__(self,
                 bus_n,
                 bus_addr=0x69,
                 output_format='float',
                 output='csv',
                 sensor_id='SPS30'):
        """Initialize worker device on i2c bus.

        Parameters
        ----------
        bus_n : int, i2c bus number on Controller
        bus_addr : int, i2c bus number of this Worker device
        output_format : str, what format measurements will be returned in
            either 'float' or 'int'
        output : str, writer output format, either 'csv' or 'json'
        sensor_id : str, sensor id, 'BME680' by default
        """

        self.output_format = None

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)

        # information about this device
        self.metadata = Meta(name='SPS30')
        self.metadata.description = 'SPS30 Particulate Matter Sensor'
        self.metadata.urls = 'https://www.sensirion.com/en/environmental-sensors/particulate-matter-sensors-pm25'
        self.metadata.manufacturer = 'Sensirion'
        self.metadata.bus_n = bus_n
        self.metadata.bus_addr = hex(bus_addr)
        self.metadata.speed_warning = 0
        self.metadata.laser_error = 0
        self.metadata.fan_error = 0

        self.set_format(output_format)

        # data recording information
        self.system_id = None
        self.sensor_id = sensor_id
        self.blocking_settle_dt = 15  # seconds
        self.blocking_timeout = 30  # seconds

        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata,
                                    time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata,
                                      time_format='std_time_ms')

    def set_format(self, output_format):
        """Set output format to either float or integer.

        Parameters
        ----------
        output_format : str, either 'int' or 'float' where
            'int' = Big-endian unsigned 16-bit integer values
            'float' = Big-endian IEEE754 float values
        """
        self.output_format = output_format

        # Note: precision reported is for the lower of the two concentration ranges listed in the datasheet
        self.metadata.header = [
            'system_id', 'sensor_id', 'description', 'sample_n', 'mc_pm_1_0',
            'mc_pm_2_5', 'mc_pm_4_0', 'mc_pm_10', 'nc_pm_0_5', 'nc_pm_1_0',
            'nc_pm_2_5', 'nc_pm_4_0', 'nc_pm_10', 'typical_partical_size'
        ]
        self.metadata.range = [
            'NA', 'NA', 'NA', 'NA', '0.3-1.0', '0.3-2.5', '0.3-4.0', '0.3-10',
            '0.3-0.5', '0.3-1.0', '0.3-2.5', '0.3-4.0', '0.3-10.0', '0-3000'
        ]
        self.metadata.precision = [
            'NA', 'NA', 'NA', '1', '+/-10', '+/-10', '+/-25', '+/-25',
            '+/-100', '+/-100', '+/-100', '+/-250', '+/-250', '1'
        ]

        self.metadata.mass_concentration_range = '0-1000µg/m3'
        self.metadata.number_concentration_range = '0-3000 #/cm3'

        # Note: accuracy is not specified from the manufacturer, calibration required to report accuracy
        self.metadata.accuracy = ['NA', 'NA', 'NA', '1'] + ['NA'] * 10

        _units = ['NA', 'NA', 'NA', 'count'] + ['µg/m3'] * 4 + ['#/cm3'] * 5
        if output_format == 'int':
            self.metadata.dtype = ['str', 'str', 'str', 'int'] + ['int'] * 10
            self.metadata.units = _units + ['nm']

        if output_format == 'float':
            self.metadata.dtype = ['str', 'str', 'str', 'int'] + ['float'] * 10
            self.metadata.units = _units + ['µm']

    def start_measurement(self):
        """Start measurement and set the output format. See ch 6.3.1

        Parameters
        ----------
        output_format : str, either 'int' or 'float' where
            'int' = Big-endian unsigned 16-bit integer values
            'float' = Big-endian IEEE754 float values
        """
        command = {
            'int': [0x05, 0x00, 0xF6],
            'float': [0x03, 0x00, 0xAC]
        }[self.output_format]
        self.bus.write_n_bytes([0x00, 0x10] + command)

    def stop_measurement(self):
        """Stop measurement. See ch 6.3.2"""
        self.bus.write_n_bytes([0x01, 0x04])

    def data_ready(self):
        """Read Data-Ready Flag. See ch 6.3.3

        Returns
        -------
        bool, True if data is ready, otherwise False
        """
        self.bus.write_n_bytes([0x02, 0x02])
        time.sleep(0.1)
        d = self.bus.read_n_bytes(3)
        d = CRC_check(d)
        if d[1] == 0x00:
            return False
        elif d[1] == 0x01:
            return True

    def measured_values_blocking(self, verbose=False):
        """Block and poll until new data is available

        Parameters
        ----------
        dt : int, seconds to pause between polling requests
        timeout : int, maximum seconds to poll
        # continuous : bool, if True do not stop measurement to read latest data
        verbose : bool, print debug statements
        """
        self.start_measurement()
        time.sleep(self.blocking_settle_dt)

        t0 = time.time()
        while (time.time() - t0 < self.blocking_timeout):
            if self.data_ready():
                self.stop_measurement()
                return self.measured_values()
            if verbose:
                print('waiting...')
        return False

    def measured_values(self, return_bytes=False):
        """Read measured values. See ch 6.3.4 for I2C method
        and ch 4.3 for format

        Parameters
        ----------
        bytes : bool, return bytes without CRC check or unpacking
        """
        byte_number = {'int': 30, 'float': 60}[self.output_format]
        self.bus.write_n_bytes([0x03, 0x00])

        # sleep long enough for the measurement and/or settling
        time.sleep(self.blocking_settle_dt + 0.1)

        d = self.bus.read_n_bytes(byte_number)
        if return_bytes:
            return d
        d = CRC_check(d)
        if self.output_format == 'float':
            d_f = []
            for n in range(0, len(d), 4):
                d_f.append(struct.unpack('>f', bytes(d[n:n + 4]))[0])
            d_f = [round(n, 2) for n in d_f]
            return d_f
        elif self.output_format == 'int':
            d_i = []
            for n in range(0, len(d), 2):
                d_i.append(struct.unpack('>h', bytes(d[n:n + 2]))[0])
            return d_i

    '''
    Features disabled due to I2C bus errors sending wake command

    def sleep(self):
        """Put sesnor to sleep. See ch 6.3.5
        Note: can only be executed while in Idle Mode"""
        self.bus.write_n_bytes([0x10, 0x01])

    def wake(self):
        """Power sensor up from sleep state. See ch 6.3.6"""
        self.bus.write_n_bytes([0x11, 0x03])
        self.bus.write_n_bytes([0x11, 0x03])
    '''

    def start_fan_cleaning(self):
        """Start fan cleaning cycle. See ch 6.3.7
        Can only be executed in Measurement Mode"""
        self.bus.write_n_bytes([0x56, 0x07])

    def read_cleaning_interval(self):
        """Read the fan cleaning cycle. See ch 6.3.8"""
        self.bus.write_n_bytes([0x80, 0x04])
        time.sleep(0.1)
        d = self.bus.read_n_bytes(6)
        d = CRC_check(d)
        return int((d[0] << 16) | d[1])

    def write_cleaning_interval(self, interval):
        """Write the cleaning interval. See ch 6.3.8 and 4.2"""
        i_msb = interval >> 16
        i_lsb = interval & (2**16 - 1)
        self.bus.write_n_bytes([0x80, 0x04, i_msb, i_lsb])

    def product_type(self):
        """Read product type. See ch 6.3.9"""
        self.bus.write_n_bytes([0XD0, 0X02])
        time.sleep(0.1)
        d = self.bus.read_n_bytes(12)
        d = CRC_check(d)
        d = [chr(x) for x in d if ascii_check(x)]
        return ''.join(d)

    def serial(self):
        """Read device serial number. See ch 6.3.9"""
        self.bus.write_n_bytes([0xD0, 0x33])
        time.sleep(0.1)
        d = self.bus.read_n_bytes(48)
        d = CRC_check(d)
        d = [chr(x) for x in d if ascii_check(x)]
        return ''.join(d)

    def version(self):
        """Get firmware version in major.minor notation. See ch 6.3.10"""
        self.bus.write_n_bytes([0xD1, 0x00])
        d = CRC_check(d)
        d = [chr(x) for x in d if ascii_check(x)]
        return '.'.join(d)

    def status(self):
        """Get status device status. See ch 4.4"""
        self.bus.write_n_bytes([0xD2, 0x06])
        time.sleep(0.1)
        d = self.bus.read_n_bytes(6)
        d = CRC_check(d)
        d = (d[0] << 8) + d[1]
        self.metadata.speed_warning = d >> 21 & 1
        self.metadata.laser_error = d >> 5 & 1
        self.metadata.fan_error = d >> 4 & 1
        return (self.metadata.speed_warning, self.metadata.laser_error,
                self.metadata.fan_error)

    def status_clear(self):
        """Clear device status. See ch 6.3.12"""
        self.bus.write_n_bytes([0xD2, 0x10])

    def reset(self):
        """Reset device. See ch 6.3.13"""
        self.bus.write_n_bytes([0xD3, 0x04])

    def publish(self, description='NA', n=1, delay=None, blocking=True):
        """Get measured air partical data and output in JSON,
        plus metadata at intervals set by self.metadata_interval

        Parameters
        ----------
        description : str, description of data collected
        n : int, number of samples to record in this burst
        delay : float, seconds to delay between samples if n > 1. This is
            in addition to any blocking settle time set.
        blocking : bool, if True wait until data is ready. If False,
            self.start_measurement and self.stop_measurement must be
            called externally to this method.

        Returns
        -------
        str, formatted in JSON with keys:
            description : str
            n : sample number in this burst
            and values as described in self.metadata.header
        """

        if blocking:
            get = self.measured_values_blocking
        else:
            get = self.measured_values

        data_list = []
        for m in range(n):
            _data = self.json_writer.publish(
                [self.system_id, self.sensor_id, description, m] + get())
            data_list.append(_data)
            if n == 1:
                return data_list[0]
            if delay is not None:
                time.sleep(delay)
        return data_list

    def write(self, description='NA', n=1, delay=None, blocking=True):
        """Get measured air partical data and save to file,
        formatted as either CSV with extension .csv or
        JSON and extension .jsontxt.

        Parameters
        ----------
        description : str, description of data collected
        n : int, number of samples to record in this burst
        delay : float, seconds to delay between samples if n > 1. This is
            in addition to any blocking settle time set.
        blocking : bool, if True wait until data is ready. If False,
            self.start_measurement and self.stop_measurement must be
            called externally to this method.

        Returns
        -------
        None, writes to disk the following data:
            description : str
            n : sample number in this burst
            and values as described in self.metadata.header
        """

        if blocking:
            get = self.measured_values_blocking
        else:
            get = self.measured_values

        wr = {
            "csv": self.csv_writer,
            "json": self.json_writer
        }[self.writer_output]
        for m in range(n):
            wr.write([self.system_id, self.sensor_id, description, m] + get())
            if delay is not None:
                time.sleep(delay)
Esempio n. 18
0
    def __init__(self, bus_n, bus_addr=0x18, output='csv'):
        """Initialize worker device on i2c bus.

        Parameters
        ----------
        bus_n : int, i2c bus number on Controller
        bus_addr : int, i2c bus number of this Worker device
        """

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)

        # register values and defaults
        # TODO: do the constant values need to be specified?
        self.reg_map = {
            'config': REG_CONFIG,
            'upper_temp': REG_UPPER_TEMP,
            'lower_temp': REG_LOWER_TEMP,
            'crit_temp': REG_CRIT_TEMP,
            'ambient': REG_AMBIENT_TEMP,
            'manufacturer': REG_MANUF_ID,
            'device_id': REG_DEVICE_ID
        }

        self.alert_critial = None
        self.alert_upper = None
        self.alert_lower = None

        self.manufacturer_id = None
        self.device_id = None
        self.revision = None

        # information about this device
        self.device = DeviceData('MCP9808')
        self.device.description = (
            '+/-0.5 degrees Celcius ' +
            'maximum accuracy digital temperature sensor')
        self.device.urls = 'https://www.microchip.com/datasheet/MCP9808'
        self.device.active = None
        self.device.error = None
        self.device.bus = repr(self.bus)
        self.device.manufacturer = 'Microchip'
        self.device.version_hw = '0.1'
        self.device.version_sw = '0.1'
        self.device.accuracy = '+/-0.25 (typical) C'
        self.device.precision = '0.0625 C maximum'
        self.device.units = 'Degrees Celcius'
        self.device.calibration_date = None

        # data recording information
        self.sample_id = None

        # data recording method
        self.writer_output = output
        self.csv_writer = CSVWriter("MCP9808", time_format='std_time_ms')
        self.csv_writer.device = self.device.__dict__
        self.csv_writer.header = ['description', 'sample_n', 'temperature']

        self.json_writer = JSONWriter("MCP9808", time_format='std_time_ms')
        self.json_writer.device = self.device.__dict__
        self.json_writer.header = ['description', 'sample_n', 'temperature']
Esempio n. 19
0
class GroveMotor:

    def __init__(self, bus_n, bus_addr=0x0F, output='csv', name='Grove Motor Driver'):

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)

        # PWM clock frequency
        self.frequency = 31372

        # speed can be -255 to 255
        self.motor_1_speed = 0
        self.motor_2_speed = 0

        # direction of DC motor
        self.direction = ""

        # driver mode, 'dc' or 'step'
        self.mode = "dc"

        # phase of the motor being used, only applies in stepper mode
        self.phase = None

        # step code initialize
        self.step_codes_cw = None
        self.step_codes_ccw = None

        # motor phase steps, default to 2 phase
        self.set_phase(phase=2)

        # number of steps commanded
        self.steps = 0

        # running total steps, for positioning microsteps
        self.step_count = 0

        # information about this device
        self.metadata = Meta(name=name)
        self.metadata.description = ('I2C DC and stepper motor controller')
        self.metadata.urls = 'http://wiki.seeedstudio.com/Grove-I2C_Motor_Driver_V1.3/'
        self.metadata.manufacturer = 'Seeed Studio'
        
        self.metadata.header    = ['description', 'freq',
                                   'm1_speed', 'm2_speed', 'm_direction',
                                   'mode', 'phase', 'steps']
        self.metadata.dtype     = ['str', 'int', 'int', 'int', 'str', 'str', 'int', 'int']
        self.metadata.accuracy  = None
        self.metadata.precision = None
        
        self.metadata.bus_n = bus_n
        self.metadata.bus_addr = hex(bus_addr)
        
        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata, time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata, time_format='std_time_ms')
        
    def get_info(self):
        pid = self.bus.read_register_16bit(0x00)
        return pid

    def set_phase(self, phase=2):
        """Set the phase of the stepper motor being used.
        Defaults to a 2 phase.

        Parameters
        ----------
        phase : int, either 2 or 4 for the phase of the motor design
        """

        if phase == 4:
            # 4 phase motor
            self.step_codes_cw  = [0b0001, 0b0011, 0b0010, 0b0110,
                                   0b0100, 0b1100, 0b1000, 0b1001]
            self.step_codes_ccw = [0b1000, 0b1100, 0b0100, 0b0110,
                                   0b0010, 0b0011, 0b0001, 0b1001]
            self.phase = 4
        else:
            # default to 2 phase motor
            self.step_codes_cw  = [0b0001, 0b0101, 0b0100, 0b0110,
                                   0b0010, 0b1010, 0b1000, 0b1001]
            self.step_codes_ccw = [0b1000, 0b1010, 0b0010, 0b0110,
                                   0b0100, 0b0101, 0b0001, 0b1001]
            self.phase = 2

    def set_frequency(self, f_Hz=31372):
        """Set the frequency of the PWM cycle where
            cycle length = 510
            system_clock = 16MHz

        Available frequencies in Hz: 31372, 3921, 490, 122, 30

        Parameters
        ----------
        f_Hz : int, frequencey in Hertz (Hz).  Default is 31372
        """
        freq_mapper = {31372: 0x01, 3921: 0x02, 490: 0x03, 122: 0x04, 30: 0x05}
        self.frequency = freq_mapper[f_Hz]
        self.bus.write_n_bytes([0x84, self.frequency, 0x01])

    def set_speed(self, motor_id, motor_speed):
        """Set the speed (duty cycle) of motor

        Parameters
        ----------
        motor_id : int, either 1 or 2 where 1 = J1 and 2 = J5 on the board
        motor_speed : int, between -255 and 255 where > 0 is clockwise
        """
        assert (motor_speed > -256) & (motor_speed < 256)
        if motor_id == 1:
            self.motor_1_speed = motor_speed
        if motor_id == 2:
            self.motor_2_speed = motor_speed

        self.bus.write_n_bytes([0x82, self.motor_1_speed, self.motor_2_speed])

    def stop(self, motor_id=None):
        """Stop one or both motors.  Alias for set_speed(motor_id, motor_speed=0).

        Parameters
        ----------
        motor_id : int, (optional) 1 or 2 where 1 = J1 and 2 = J5 on the board.
            If not set, defaults to stopping both.
        """

        if (motor_id == 1) or (motor_id == 2):
            self.set_speed(motor_id=motor_id, motor_speed=0)
        else:
            self.set_speed(motor_id=1, motor_speed=0)
            self.set_speed(motor_id=2, motor_speed=0)

    def set_direction(self, code='cw'):
        """Set the direction of one or both motors in dc mode.

        Accepted string command codes are:
            'cw' : clockwise
            'ccw' : counter clockwise
            'm1m2cw' : motor 1 and motor 2 clockwise
            'm1m2cc' : motor 1 and motor 2 counter clockwise
            'm1cw_m2ccw' : motor 1 clockwise and motor 2 counter clockwise
            'm1ccw_m2cw' : motor 1 counter clockwise and motor 2 clockwise

        Parameters
        ----------
        code : str, command code, default is 'cw'
        """
        direction_mapper = {'cw': 0x0a, 'ccw': 0x05,
                            'm1m2cw': 0x0a, 'm1m2cc': 0x05,
                            'm1cw_m2ccw': 0x06, 'm1ccw_m2cw': 0x09}
        self.direction = direction_mapper[code]
        self.bus.write_n_bytes([0xaa, self.direction, 0x01])

    def set_mode(self, mode_type="dc"):
        if (mode_type == "full_step") or (mode_type == "micro_step"):
            self.set_speed(motor_id=1, motor_speed=255)
            self.set_speed(motor_id=2, motor_speed=255)
            self.mode = mode_type
        else:
            self.mode = "dc"

    def step_full(self, steps, delay=0.0001, verbose=False):
        """Stepper motor motion in full steps
        (four steps per step commanded)

        Parameters
        ----------
        steps : int, number of motor steps where
            positive numbers are clockwise
            negative numbers are counter clockwise
        delay : float, seconds to delay between step commands,
            default is 0.0001 seconds which smooths operation on the pi
        verbose : bool, print debug statements
        """

        self.steps = steps

        if steps > 0:
            step_codes = self.step_codes_cw
            self.direction = "step_cw"
        if steps < 0:
            step_codes = self.step_codes_ccw
            self.direction = "step_ccw"
        if steps == 0:
            self.stop()
            return

        self.set_mode(mode_type="full_step")  # set speed to 255, i.e. full current

        for _ in range(abs(steps)):
            for sc in step_codes:
                if verbose: print("{0:04b}".format(sc))
                self.bus.write_n_bytes([0xaa, sc, 0x01])
                time.sleep(delay)

        self.stop()  # set speed to 0, i.e. current off

    def step_micro(self, steps, delay=0.0001, verbose=False):
        """Stepper motor motion in micro steps
        (one step per step commanded)

        Parameters
        ----------
        steps : int, number of motor steps where
            positive numbers are clockwise
            negative numbers are counter clockwise
        delay : float, seconds to delay between step commands,
            default is 0.0001 seconds which smooths operation on the pi
        verbose : bool, print debug statements
        """

        self.steps = steps

        if steps > 0:
            step_codes = self.step_codes_cw
            self.direction = "step_cw"
        if steps < 0:
            step_codes = self.step_codes_ccw
            self.direction = "step_ccw"
        if steps == 0:
            self.stop()
            return

        self.set_mode(mode_type="micro_step")  # set speed to 255, i.e. full current

        for _ in range(abs(steps)):
            if verbose: print("n >>", _)
            ix = self.step_count * 2
            if verbose: print("ix >> ", ix)
            for sc in step_codes[ix:ix + 2]:
                if verbose: print("bin >> {0:04b}".format(sc))
                self.bus.write_n_bytes([0xaa, sc, 0x01])
                time.sleep(delay)
            self.step_count = (self.step_count + 1) % 4

        self.stop()  # set speed to 0, i.e. current off

    def get(self, description='no_description'):
        """Get formatted output.

        Parameters
        ----------
        description : char, description of data sample collected

        Returns
        -------
        data : list, data that will be saved to disk with self.write containing:
            description : str
        """
        return [description, self.frequency,
                self.motor_1_speed, self.motor_2_speed,
                self.direction, self.mode,
                self.phase, self.steps]

    def publish(self, description='no_description'):
        """Format output and save to file, formatted as either .csv or .json.

        Parameters
        ----------
        description : char, description of data sample collected

        Returns
        -------
        None, writes to disk the following data:
            description : str, description of sample
        """
        return self.json_writer.publish(self.get(description=description))

    def write(self, description='no_description'):
        """Format output and save to file, formatted as either .csv or .json.

        Parameters
        ----------
        description : char, description of data sample collected

        Returns
        -------
        None, writes to disk the following data:
            description : str, description of sample
        """
        wr = {"csv": self.csv_writer,
              "json": self.json_writer}[self.writer_output]
        wr.write(self.get(description=description))
Esempio n. 20
0
    def __init__(self, bus_n, bus_addr=0x00, output='json', name='software_test'):

        # data bus placeholder
        self.bus = 'fake I2C bus object on bus {} and address {}'.format(bus_n, bus_addr)

        # types of verbose printing
        self.verbose = False
        self.verbose_data = False

        # thread safe deque for sharing and plotting
        self.q_maxlen = 300
        self.q = deque(maxlen=self.q_maxlen)
        self.q_prefill_zeros = False

        # realtime/stream options
        self.go = False
        self.unlimited = False
        self.max_samples = 1000

        ## Metadata information about this device
        self.metadata = Meta(name=name)

        # device/source specific descriptions
        self.metadata.description  = 'dummy_data'
        
        # URL(s) for data source reference
        self.metadata.urls         = 'www.example.com'
        
        # manufacturer of device/source of data
        self.metadata.manufacturer = 'Nikola Tesla Company'
        
        ## data output descriptions
        # names of each kind of data value being recorded
        self.metadata.header       = ['description', 'sample_n', 'degree', 'amplitude']
        
        # data types (int, float, etc) for each data value
        self.metadata.dtype        = ['str', 'int', 'float', 'float']
        
        # measured units of data values
        self.metadata.units        = [None, 'count', 'degrees', 'real numbers']
        
        # accuracy in units of data values
        self.metadata.accuracy     = [None, 1, 0.2, 0.2] 
        
        # precision in units of data values
        self.metadata.precision    = [None, 1, 0.1, 0.1]
        
        # I2C bus the device is on
        self.metadata.bus_n = bus_n
        
        # I2C bus address the device is on
        self.metadata.bus_addr = bus_addr

        ## Data writers for this device
        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata, time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata, time_format='std_time_ms')
        
        # synthetic data
        self._deg = list(range(360))
        self._amp = [sin(d * (pi/180.0)) for d in self._deg]
        self._test_data = list(zip(self._deg, self._amp))
Esempio n. 21
0
class Single:

    def __init__(self, bus_n, bus_addr=0x18, output='csv', name='Qwiic_1xRelay'):

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)

        self.state_mapper = {0: "closed", 1: "open"}
        
        # information about this device
        self.metadata = Meta(name=name)
        self.metadata.description = 'Sparkfun Single Pole Double Throw Relay'
        self.metadata.urls = 'https://learn.sparkfun.com/tutorials/qwiic-single-relay-hookup-guide/all'
        self.metadata.manufacturer = 'Sparkfun'
        
        self.metadata.header    = ["description", "state"]
        self.metadata.dtype     = ['str', 'str']
        self.metadata.units     = None
        self.metadata.accuracy  = None 
        self.metadata.precision = None
        
        self.metadata.bus_n = bus_n
        self.metadata.bus_addr = hex(bus_addr)

        # data recording method
        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata, time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata, time_format='std_time_ms')
        
    def get_version(self):
        """Get the firmware version of the relay

        Returns
        -------
        int, version number of firmware
        """
        return self.bus.read_register_8bit(0x04)

    def get_status(self):
        """Get the status of the relay

        Returns
        -------
        int, where 0 == relay is open / not connected
                   1 == relay is closed / connected
        """

        return self.bus.read_register_8bit(0x05)

    def off(self):
        """Turn the relay off.  State will report 0."""
        self.bus.write_byte(0x00)

    def on(self):
        """Turn the relay on.  State will report 1."""
        self.bus.write_byte(0x01)

    def toggle(self, verbose=False):
        """Toggle state of relay
        if open, close
        if closed, open

        Parameters
        ----------
        verbose : bool, print debug statements
        """

        state = self.get_status()
        if verbose:
            print("Relay found {}.".format(self.state_mapper[state]))

        if state == 0:
            self.on()
        elif state == 1:
            self.off()

        if verbose:
            print("Relay toggled.")

    def change_address(self, new_address, verbose=False):
        """Change the I2C address of the relay.  This is a persistant change in
        EPROM memory.

        Parameters
        ----------
        new_address : int, new I2C address between 0x07 and 0x78
        verbose : bool, print debug statements
        """

        if ((new_address < 0x07) or (new_address > 0x78)):
            if verbose:
                print("Address outside allowed range")
            return False

        self.bus.write_register_8bit(0x03, new_address)
        if verbose:
            print("Relay I2C address changed to 0x{:02x}".format(new_address))

    def get(self, description='NA'):
        """Get output of relay state in list format.

        Parameters
        ----------
        description : char, description of data sample collected

        Returns
        -------
        data : list, data containing:
            description: str, description of sample under test
            state : int, where 0 == relay is open / not connected
                               1 == relay is closed / connected
        """
        return [description, self.get_status()]

    def publish(self, description='NA'):
        """Output relay status data in JSON.

        Parameters
        ----------
        description : char, description of data sample collected

        Returns
        -------
        str, formatted in JSON with keys:
            description : str, description of sample
            state : int, where 0 == relay is open / not connected
                               1 == relay is closed / connected
        """
        return self.json_writer.publish(self.get(description=description))

    def write(self, description='NA', delay=None):
        """Format output and save to file, formatted as either
        .csv or .json.

        Parameters
        ----------
        description : char, description of data sample collected

        Returns
        -------
        None, writes to disk the following data:
            description : str, description of sample
            state : int, where 0 == relay is open / not connected
                               1 == relay is closed / connected
        """
        wr = {"csv": self.csv_writer,
              "json": self.json_writer}[self.writer_output]
        wr.write(self.get(description=description))
Esempio n. 22
0
class INA219:
    def __init__(self, bus_n, bus_addr=0x40, output='csv', name='ina219'):
        """Initialize worker device on i2c bus.

        Parameters
        ----------
        bus_n : int, i2c bus number on Controller
        bus_addr : int, i2c bus number of this Worker device
        """

        # i2c bus
        self.bus = base.I2C(bus_n=bus_n, bus_addr=bus_addr)

        self.reg_config = None
        self.reg_shunt_voltage = None
        self.reg_bus_voltage = None
        self.reg_power = None
        self.reg_calibration = None

        self.reg_map = {
            'config': 0x00,
            'shunt_voltage': 0x01,
            'bus_voltage': 0x02,
            'power': 0x03,
            'current': 0x04,
            'calibration': 0x05
        }

        # bus voltage range
        self.bv_reg_to_bv = {0: 16, 1: 32}

        # programable gain amplifier
        self.pga_reg_to_gain = {0: 1, 1: 2, 2: 4, 3: 8}
        self.pga_gain_to_reg = {1: 0, 2: 1, 4: 2, 8: 3}
        self.pga_reg_str_range = {
            1: "+/- 40 mV",
            2: "+/- 80 mV",
            4: "+/- 160 mV",
            8: "+/- 320 mV"
        }

        # print debug statements
        self.verbose = False

        # data recording information
        self.sample_id = None

        # information about this device
        self.metadata = Meta(name=name)
        self.metadata.description = 'Texas Instruments Bidirectional Current Monitor'
        self.metadata.urls = 'www.ti.com/product/ADS1115'
        self.metadata.manufacturer = 'Adafruit Industries & Texas Instruments'

        self.metadata.header = [
            'description', 'sample_n', 'voltage', 'current'
        ]
        self.metadata.dtype = ['str', 'int', 'float', 'float']
        self.metadata.units = [None, 'count', 'volt', 'amp']
        self.metadata.accuracy = [None, 1, '+/-0.2%', '+/-0.2%']
        self.metadata.precision = [None, 1, '4 mV', '10uV accross shunt']
        self.metadata.accuracy_note = 'values for model INA291A'

        self.metadata.bus_n = bus_n
        self.metadata.bus_addr = hex(bus_addr)

        # chip defaults on power up or reset command
        self.metadata.bus_voltage_range = self.bv_reg_to_bv[1]
        self.metadata.gain = self.pga_reg_to_gain[0b11]
        self.metadata.gain_string = self.pga_reg_str_range[self.metadata.gain]

        self.metadata.bus_adc_resolution = 12
        self.metadata.bus_adc_averaging = None

        self.metadata.shunt_adc_resolution = 12
        self.metadata.shunt_adc_averaging = None

        self.metadata.mode = 7
        self.mode_to_str = {
            0: "power down",
            1: "shunt voltage, triggered",
            2: "bus voltage, triggered",
            3: "shunt and bus voltages, triggered",
            4: "ADC off (disabled)",
            5: "shunt voltage, continuous",
            6: "bus voltage, continuous",
            7: "shunt and bus voltages, continuous"
        }
        self.metadata.mode_description = self.mode_to_str[self.metadata.mode]

        # Adafruit INA219 breakout board as a 0.1 ohm 1% 2W resistor
        self.metadata.r_shunt = 0.1

        # data recording method
        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata,
                                    time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata,
                                      time_format='std_time_ms')

        # intialized configuration values
        self.get_config()

    def read_register(self, reg_name):
        """Get the values from one registry

        Allowed register names:
            'config'
            'shunt_voltage'
            'bus_voltage'
            'power'
            'current'
            'calibration'

        Parameters
        ----------
        reg_name : str, name of registry to read

        Returns
        -------
        16 bit registry value
        """

        reg_addr = self.reg_map[reg_name]
        return self.bus.read_register_16bit(reg_addr)

    def get_config(self):
        r = self.read_register('config')
        self.reg_config = r
        self.metadata.bus_voltage_range = self.bv_reg_to_bv[(r >> 13) & 0b1]
        self.metadata.gain = self.pga_reg_to_gain[(r >> 11) & 0b11]

        if self.verbose:
            print("Bus Voltage Range:", self.metadata.bus_voltage_range, "V")
            print("PGA Range: {}x or {}".format(
                self.metadata.gain,
                self.pga_reg_str_range[self.metadata.gain]))
            print("Configuration Register:")
            tools.bprint(r)
        return r

    def get_shunt_voltage(self):
        """Read the shunt voltage register where LSB = 10uV

        Note: datasheet calculations result in mV, see section 8.5.1
        
        Returns
        -------
        float : shunt voltage in volts (not millivolts)
        """
        return self.read_register('shunt_voltage') * 0.00001

    def get_bus_voltage(self):
        """Read the bus voltage register where LSB = 4mV.
        
        Note: Right most 3 bits of bus voltage register are 0, CNVR, OVF
              32 volt range => 0 to 32 VDC
              16 volt range => 0 to 16 VDC
        
        Returns
        -------
        float : bus voltage in volts (not millivolts)
        """
        r = self.read_register('bus_voltage')
        return (r >> 3) * 4.0 * 0.001

    def get_power(self):
        r = self.read_register('power')
        return r

    def get_current(self):
        r = self.read_register('current')
        return r

    def get_calibration(self):
        r = self.read_register('calibration')
        return r

    def write_register(self, reg_name, data):
        """Write a 16 bits of data to register

        Allowed register names:
            'config'
            'shunt_voltage'
            'bus_voltage'
            'power'
            'current'
            'calibration'

        Parameters
        ----------
        reg_name : str, name of registry to read
        data : int, 16 bit value to write to register
        """

        reg_addr = self.reg_map[reg_name]
        if self.verbose:
            print("Writing to '{}' registry # {}".format(reg_name, reg_addr))
            tools.bprint(data)
        self.bus.write_register_16bit(reg_addr, data)

    def write_config(self, data):
        self.write_register('config', data)

    def write_calibration(self, data):
        self.write_register('calibration', data)

    def reset(self):
        self.write_config(self.reg_config | 0b1000000000000000)

    def set_bus_voltage_range(self, v=32):
        reg_value = {
            16: base.bit_clear(13, self.reg_config),
            32: base.bit_set(13, self.reg_config)
        }[v]
        self.reg_config = reg_value
        self.metadata.bus_voltage_range = v
        self.write_config(reg_value)

    def set_pga_range(self, gain=8):
        mask = 0b1110011111111111  #                 PGA  10
        reg_value = {
            1: (self.reg_config & mask) | 0b0000000000000000,
            2: (self.reg_config & mask) | 0b0000100000000000,
            4: (self.reg_config & mask) | 0b0001000000000000,
            8: (self.reg_config & mask) | 0b0001100000000000
        }[gain]
        self.reg_config = reg_value
        self.metadata.gain = gain
        self.metadata.gain_string = self.pga_reg_str_range[self.gain]
        self.write_config(reg_value)

    def set_bus_adc_resolution(self, bits=12):
        """Generate config register for setting ADC resolution"""
        mask = 0b1111100001111111  #                 BADC   4321
        reg_value = {
            9: (self.reg_config & mask) | 0b0000000000000000,
            10: (self.reg_config & mask) | 0b0000000010000000,
            11: (self.reg_config & mask) | 0b0000000100000000,
            12: (self.reg_config & mask) | 0b0000000110000000
        }[bits]
        self.reg_config = reg_value
        self.metadata.bus_adc_resolution = bits
        self.metadata.bus_adc_averaging = None
        self.write_config(reg_value)

    def set_bus_adc_samples(self, n=128):
        """Generate config register for setting ADC sample averaging"""
        mask = 0b1111100001111111  #                 BADC   4321
        reg_value = {
            2: (self.reg_config & mask) | 0b0000010010000000,
            4: (self.reg_config & mask) | 0b0000010100000000,
            8: (self.reg_config & mask) | 0b0000010110000000,
            16: (self.reg_config & mask) | 0b0000011000000000,
            32: (self.reg_config & mask) | 0b0000011010000000,
            64: (self.reg_config & mask) | 0b0000011100000000,
            128: (self.reg_config & mask) | 0b0000011110000000
        }[n]
        self.reg_config = reg_value
        self.metadata.bus_adc_resolution = None
        self.metadata.bus_adc_averaging = n
        self.write_config(reg_value)

    def set_shunt_adc_resolution(self, bits=12):
        """Generate config register for setting ADC resolution"""
        mask = 0b1111111110000111  #                 SADC       4321
        reg_value = {
            9: (self.reg_config & mask) | 0b0000000000000000,
            10: (self.reg_config & mask) | 0b0000000000001000,
            11: (self.reg_config & mask) | 0b0000000000010000,
            12: (self.reg_config & mask) | 0b0000000000011000
        }[bits]
        self.reg_config = reg_value
        self.metadata.shunt_adc_resolution = bits
        self.metadata.shunt_adc_averaging = None
        self.write_config(reg_value)

    def set_shunt_adc_samples(self, n=128):
        mask = 0b1111111110000111  #                 SADC       4321
        reg_value = {
            2: (self.reg_config & mask) | 0b0000000001001000,
            4: (self.reg_config & mask) | 0b0000000001010000,
            8: (self.reg_config & mask) | 0b0000000001011000,
            16: (self.reg_config & mask) | 0b0000000001100000,
            32: (self.reg_config & mask) | 0b0000000001101000,
            64: (self.reg_config & mask) | 0b0000000001110000,
            128: (self.reg_config & mask) | 0b0000000001111000
        }[n]
        self.reg_config = reg_value
        self.metadata.adc_resolution = None
        self.metadata.adc_averaging = n
        self.write_config(reg_value)

    def set_mode(self, n=7):
        mask = 0b1111111111111000  #                 MODE           321
        reg_value = {
            0: (self.reg_config & mask) | 0b0000000000000000,
            1: (self.reg_config & mask) | 0b0000000000000001,
            2: (self.reg_config & mask) | 0b0000000000000010,
            3: (self.reg_config & mask) | 0b0000000000000011,
            4: (self.reg_config & mask) | 0b0000000000000100,
            5: (self.reg_config & mask) | 0b0000000000000110,
            6: (self.reg_config & mask) | 0b0000000000000111
        }[n]
        self.reg_config = reg_value
        self.metadata.mode = n
        self.metadata.mode_description = self.mode_to_str[n]
        self.write_config(reg_value)

    def set_calibration(self, cal_value):
        """Set the calibration register, see datasheet for details

        cal_value : int, register value to write
        """
        self.reg_calibration = cal_value
        self.write_calibration(cal_value)

    def get_current_simple(self):
        """Calculate the current from single shot measurements"""
        return self.get_shunt_voltage() / self.metadata.r_shunt

    def get(self, description='NA', n=1, delay=None):
        """Get formatted output.

        Parameters
        ----------
        description : str, description of data sample collected
        n : int, number of samples to record in this burst
        delay : float, seconds to delay between samples if n > 1
        
        Returns
        -------
        data : list, data that will be saved to disk with self.write containing:
            description: str, description of sample under test
            sample_n : int, sample number in this burst
            voltage, float, Volts measured at the shunt resistor
            current : float, Amps of current accross the shunt resistor
        """
        data_list = []
        for m in range(1, n + 1):
            data_list.append([
                description, m,
                self.get_bus_voltage(),
                self.get_current_simple()
            ])
            if n == 1:
                return data_list[0]
            if delay is not None:
                time.sleep(delay)
        return data_list

    def publish(self, description='NA', n=1, delay=None):
        """Output relay status data in JSON.

        Parameters
        ----------
        description : str, description of data sample collected
        n : int, number of samples to record in this burst
        delay : float, seconds to delay between samples if n > 1

        Returns
        -------
        str, formatted in JSON with keys:
            description: str, description of sample under test
            sample_n : int, sample number in this burst
            voltage, float, Volts measured at the shunt resistor
            current : float, Amps of current accross the shunt resistor
        """
        data_list = []
        for m in range(n):
            data_list.append(
                self.json_writer.publish([
                    description, m,
                    self.get_bus_voltage(),
                    self.get_current_simple()
                ]))
            if n == 1:
                return data_list[0]
            if delay is not None:
                time.sleep(delay)
        return data_list

    def write(self, description='NA', n=1, delay=None):
        """Format output and save to file, formatted as either .csv or .json.

        Parameters
        ----------
        description : str, description of data sample collected
        n : int, number of samples to record in this burst
        delay : float, seconds to delay between samples if n > 1

        Returns
        -------
        None, writes to disk the following data:
            description : str, description of sample
            sample_n : int, sample number in this burst
            voltage, float, Volts measured at the shunt resistor
            current : float, Amps of current accross the shunt resistor
        """
        wr = {
            "csv": self.csv_writer,
            "json": self.json_writer
        }[self.writer_output]
        for m in range(n):
            wr.write([
                description, m,
                self.get_bus_voltage(),
                self.get_current_simple()
            ])
            if delay is not None:
                time.sleep(delay)
Esempio n. 23
0
class TestDevice(Base):
    """Non-hardware test class"""
    def __init__(self, bus_n, bus_addr=0x00, output='json', name='software_test'):

        # data bus placeholder
        self.bus = 'fake I2C bus object on bus {} and address {}'.format(bus_n, bus_addr)

        # types of verbose printing
        self.verbose = False
        self.verbose_data = False

        # thread safe deque for sharing and plotting
        self.q_maxlen = 300
        self.q = deque(maxlen=self.q_maxlen)
        self.q_prefill_zeros = False

        # realtime/stream options
        self.go = False
        self.unlimited = False
        self.max_samples = 1000

        ## Metadata information about this device
        self.metadata = Meta(name=name)

        # device/source specific descriptions
        self.metadata.description  = 'dummy_data'
        
        # URL(s) for data source reference
        self.metadata.urls         = 'www.example.com'
        
        # manufacturer of device/source of data
        self.metadata.manufacturer = 'Nikola Tesla Company'
        
        ## data output descriptions
        # names of each kind of data value being recorded
        self.metadata.header       = ['description', 'sample_n', 'degree', 'amplitude']
        
        # data types (int, float, etc) for each data value
        self.metadata.dtype        = ['str', 'int', 'float', 'float']
        
        # measured units of data values
        self.metadata.units        = [None, 'count', 'degrees', 'real numbers']
        
        # accuracy in units of data values
        self.metadata.accuracy     = [None, 1, 0.2, 0.2] 
        
        # precision in units of data values
        self.metadata.precision    = [None, 1, 0.1, 0.1]
        
        # I2C bus the device is on
        self.metadata.bus_n = bus_n
        
        # I2C bus address the device is on
        self.metadata.bus_addr = bus_addr

        ## Data writers for this device
        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata, time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata, time_format='std_time_ms')
        
        # synthetic data
        self._deg = list(range(360))
        self._amp = [sin(d * (pi/180.0)) for d in self._deg]
        self._test_data = list(zip(self._deg, self._amp))
        
    @staticmethod
    def _cycle(iterable):
        """Copied from Python 3.7 itertools.cycle example"""
        saved = []
        for element in iterable:
            yield element
            saved.append(element)
        while saved:
            for element in saved:
                yield element

    def run(self, delay=0, index='count'):
        """Run data collection
        
        Parameters
        ----------
        delay : int, seconds to delay between returned data
        index : str, 'count' or 'timestamp'
        """

        if self.verbose:
            print('Test Started')
            print('='*40)
            
        # used in non-unlimited acquisition
        count = 0

        self.q.clear()

        if self.q_prefill_zeros:
            for _ in range(self.q_maxlen):
                self.q.append((0, 0))

        tp = TimePiece()
                
        if index == 'timestamp':
            def get_index():
                return tp.get_time()
        else:
            def get_index():
                return count
        
        if self.writer_output is not None:
            wr = {"csv": self.csv_writer,
                  "json": self.json_writer}[self.writer_output]
            
        for d, a in self._cycle(self._test_data):

            if not self.go:
                if self.verbose:
                    print("="*40)
                    print('Test Stopped')
                break

            self.metadata.state = 'Test run() method'

            i = get_index()

            data = [self.metadata.description, i, d, a]

            if self.writer_output is not None:
                wr.write(data)

            q_out = self.json_writer.publish(data)
            self.q.append(q_out)

            if self.verbose_data:
                print(q_out)

            if not self.unlimited:
                count += 1
                if count == self.max_samples:
                    self.go = False

            time.sleep(delay)
Esempio n. 24
0
class SCD4x():
    def __init__(self, bus_n, bus_addr=0x62, output='csv', sensor_id='SCD4x'):
        """Initialize worker device on i2c bus.

        Parameters
        ----------
        bus_n : int, i2c bus number on Controller
        bus_addr : int, i2c bus number of this Worker device
        output : str, writer output format, either 'csv' or 'json'
        sensor_id : str, sensor id, 'BME680' by default
        """

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)

        # information about this device
        self.metadata = Meta(name='SCD4x')
        self.metadata.description = 'SCD4x CO2 gas Sensor'
        self.metadata.urls = 'https://www.sensirion.com/en/environmental-sensors/carbon-dioxide-sensors/carbon-dioxide-sensor-scd4x/'
        self.metadata.manufacturer = 'Sensirion'

        self.metadata.header = [
            'system_id', 'sensor_id', 'description', 'sample_n', 'co2', 'tC',
            'rh'
        ]
        self.metadata.dtype = [
            'str', 'str', 'str', 'int', 'int', 'float', 'int'
        ]
        self.metadata.units = [
            'NA', 'NA', 'NA', 'count', 'ppm', 'degrees Celsius', 'percent'
        ]
        self.metadata.accuracy = [
            'NA', 'NA', 'NA', '1', '+/- 40 + 5%', '+/- 1.5', '+/- 0.4'
        ]
        self.metadata.precision = [
            'NA', 'NA', 'NA', 'NA', '+/- 10', '+/- 0.1', '+/- 0.4'
        ]
        self.metadata.range = [
            'NA', 'NA', 'NA', 'NA', '0-40000', '-10-60', '0-100'
        ]

        self.metadata.bus_n = bus_n
        self.metadata.bus_addr = hex(bus_addr)

        # data recording information
        self.system_id = None
        self.sensor_id = sensor_id
        self.dt = None

        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata,
                                    time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata,
                                      time_format='std_time_ms')

    # Basic Commands, Ch 3.5
    def start_periodic_measurement(self):
        """Start periodic measurement, updating at a 5 second interval. See Ch 3.5.1"""
        self.bus.write_n_bytes([0x21, 0xB1])

    def read_measurement(self):
        """Read measurement from sensor. See Ch 3.5.2
        
        Returns
        -------
        co2 : int, CO2 concentration in ppm
        t : float, temperature in degrees Celsius
        rh : int, relative humidity in percent
        """
        self.bus.write_n_bytes([0xEC, 0x05])
        time.sleep(0.002)
        d = self.bus.read_n_bytes(9)
        d = CRC_check(d)

        co2 = d[0] << 8 | d[1]
        t = d[2] << 8 | d[3]
        rh = d[4] << 8 | d[5]

        t = -45 + 175 * t / 2**16
        rh = 100 * rh / 2**16

        d = [co2, t, rh]
        d = [round(n, 2) for n in d]
        return d

    def stop_periodic_measurement(self):
        """Stop periodic measurement to change configuration or 
        to save power. Note the sensor will only respond to other 
        commands after waiting 500ms after this command. See Ch 3.5.3"""
        self.bus.write_n_bytes([0x3F, 0x86])

    # On-chip output signal compensation, Ch 3.6
    def set_temperature_offset(self, tC):
        """Set the temperature offest, which only affects
        RH and T output. See Ch 3.6.1
        
        Parameters
        tC : float, temperature offset in degrees Celsius
        """
        tC = (tC * 2**16) / 175
        d0 = tC >> 8
        d1 = tC & 0xff
        d2 = CRC_check([d0, d1])
        self.bus.write_n_bytes([0x24, 0x1D, d0, d1, d2])
        time.sleep(0.01)

    def get_temperature_offset(self):
        """Get the temperature offset, which only affects 
        RH and T output. See Ch 3.6.2
        
        Returns
        -------
        float, temperature offset in degrees Celsius
        """
        self.bus.write_n_bytes([0x23, 0x18])
        time.sleep(0.01)
        d = self.bus.read_n_bytes(3)
        d = CRC_check(d)
        d = d[0] << 8 | d[1]
        return (175 * d) / 2**16

    def set_sensor_altitude(self, meters):
        """Set the altitude into memory. See Ch 3.6.3"""
        d0 = meters >> 8
        d1 = meters & 0xff
        d2 = CRC_check([d0, d1])
        self.bus.write_n_bytes([0x24, 0x27, d0, d1, d2])
        time.sleep(0.01)

    def get_sensor_altitude(self):
        """Get the altitude set in memory, this is not an 
        active measurement. See Ch 3.6.4
        
        Returns
        -------
        int, altitude in meters above sea level
        """
        self.bus.write_n_bytes([0x23, 0x22])
        time.sleep(0.002)
        d = self.bus.read_n_bytes(3)
        d = CRC_check(d)
        return d[0] << 8 | d[1]

    def set_ambient_pressure(self, pressure):
        """Set ambient pressure to enable continuous pressure
        compensation. See Ch 3.6.5
        
        Parameters
        ----------
        pressure : int, pressure in Pascals (Pa)
        """
        pressure = pressure / 100
        crc = CRC_calc([0x00, pressure])
        d = [0x00, pressure, crc]
        self.bus.write_n_bytes([0xE0, 0x00] + d)
        time.sleep(0.002)

    # Field Calibration, Ch 3.7
    def perform_forced_recalibration(self, target_ppm):
        """Perform forced recalibration (FRC). See Ch 3.7.1
        
        Parameters
        ----------
        target_ppm : int, target CO2 ppm
        
        Returns
        -------
        int, FRC correction in CO2 ppm
            or
        None if FRC failed 
        """
        self.bus.write_n_bytes([0x36, 0x2F])
        time.sleep(0.41)
        d = self.bus.read_n_bytes(3)
        d = CRC_check(d)
        d = d[0] << 8 | d[1]
        if d == 0xFFF:
            return None
        else:
            return d - 0x8000

    def set_automatic_self_calibration(self, asc):
        """Set Automatic Self Calibration (ASC) state. See Ch 3.7.2
        
        Parameters
        ----------
        asc : bool, True for ASC enabled, False for disabled
        """

        if asc == True:
            w0 = 0x01
        else:
            w0 = 0x00
        crc = CRC_calc([0x00, w0])
        d = [0x00, w0, crc]
        self.bus.write_n_bytes([0x24, 0x16] + d)
        time.sleep(0.002)

    def get_automatic_self_calibration(self):
        """Get Automatic Self Calibration (ASC) state. See Ch 3.7.3
        
        Returns
        -------
        bool, True if ASC is enabled, False for disabled
        """
        self.bus.write_n_bytes([0x23, 0x13])
        time.sleep(0.002)
        d = self.bus.read_n_bytes(3)
        d = CRC_check(d)
        d = d[0] << 8 | d[1]
        if d == 0:
            return False
        else:
            return True

    # Low Power, Ch 3.8
    def start_low_power_periodic_measuremet(self):
        """Start low power periodic measurement, updates in
        approximately 30 seconds. See Ch 3.8.1"""
        self.bus.write_n_bytes([0x21, 0xAC])

    def data_ready(self):
        """Check if data is ready. See Ch 3.8.2
        
        Returns
        -------
        bool, True if data is ready, otherwise False
        """
        self.bus.write_n_bytes([0xE4, 0xB8])
        d = self.bus.read_n_bytes(3)
        d = CRC_check(d)
        d = d[0] << 8 | d[1]
        d = d & 0b11111111111
        if d == 0:
            return False
        else:
            return True

    # Advanced Features, Ch 3.9
    def presist_settings(self):
        """Stores current configuration in the EEPROM making them 
        persist across power-cycling. To avoid failure of the EEPROM, 
        this feature should only be used as required and if actual 
        changes to the configuration have been made. See Ch 3.9.1"""
        self.bus.write_n_bytes([0x36, 0x15])
        time.sleep(0.8)

    def get_serial_number(self):
        """Read the serial number to identify the chip and 
        verify presense of the sensor. See Ch 3.9.2"""
        self.bus.write_n_bytes([0x36, 0x82])
        time.sleep(0.01)
        d = self.bus.read_n_bytes(9)
        d = CRC_check(d)
        da = []
        for n in range(0, len(d), 2):
            da.append(d[n] << 8 | d[n + 1])
        return da[0] << 32 | da[1] << 16 | da[2]

    def perform_self_test(self):
        """Perform a self test as an end-of-line test to check sensor
        functionality and the power supply to the sensor. See Ch 3.9.3"""
        self.bus.write_n_bytes([0x36, 0x39])
        time.sleep(11)
        d = self.bus.read_n_bytes(3)
        d = CRC_check(d)
        return d[0] << 8 | d[1]

    def perform_factory_reset(self):
        """Resets all configuration settings stored in EEPROM and 
        erases the FRC and ASC algorithm history. See Ch 3.9.4"""
        self.bus.write_n_bytes([0x36, 0x32])

    def reinit(self):
        """Reinitializes the sensor by reloading user settings from 
        EEPROM. See Ch 3.9.5"""
        self.bus.write_n_bytes([0x36, 0x46])

    # Low Power Single Shot, (SCD41 only), Ch 3.10
    def measure_single_shot(self):
        """On-demand measurement of CO2 concentration, relative humidity 
        and temperature. See Ch 3.10.1"""
        self.bus.write_n_bytes([0x21, 0x9D])
        time.sleep(5)

    def measure_single_shot_blocking(self):
        """On-demand measurement of CO2 concentration, relative humidity 
        and temperature. See Ch 3.10.1
        
        Returns
        -------
        co2 : int, CO2 concentration in ppm
        t : float, temperature in degrees Celsius
        rh : int, relative humidity in percent
        """

        t0 = time.time()
        self.measure_single_shot()
        time.sleep(5)  # 5000 ms
        self.dt = time.time() - t0
        return self.read_measurement()

    def read_measurement_blocking(self):
        """Read measurement from sensor. See Ch 3.5.2
        
        Returns
        -------
        co2 : int, CO2 concentration in ppm
        t : float, temperature in degrees Celsius
        rh : int, relative humidity in percent
        """
        pass

    def measure_single_shot_rht_only(self):
        """On-demand measurement of relative humidity and temperature only. 
        See Ch 3.10.2"""
        self.bus.write_n_bytes([0x21, 0x96])
        time.sleep(0.05)

    def publish(self, description='NA', n=1, delay=None, blocking=True):
        """Get measured air partical data and output in JSON, 
        plus metadata at intervals set by self.metadata_interval
        
        Parameters
        ----------
        description : str, description of data collected
        n : int, number of samples to record in this burst
        delay : float, seconds to delay between samples if n > 1
        blocking : bool, if True wait until data is ready. If False, 
            self.start_measurement and self.stop_measurement must be 
            called externally to this method.
        
        Returns
        -------
        str, formatted in JSON with keys:
            description : str
            n : sample number in this burst
            and values as described in self.metadata.header
        """

        if blocking:
            get = self.measure_single_shot_blocking
        else:
            get = self.read_measurement

        data_list = []
        for m in range(n):
            d = list(get())
            data_list.append(
                self.json_writer.publish(
                    [self.system_id, self.sensor_id, description, m] + d))
            if n == 1:
                return data_list[0]
            if delay is not None:
                time.sleep(delay)
        return data_list

    def write(self, description='NA', n=1, delay=None, blocking=True):
        """Get measured air partical data and save to file, 
        formatted as either CSV with extension .csv or 
        JSON and extension .jsontxt.

        Parameters
        ----------
        description : str, description of data collected
        n : int, number of samples to record in this burst
        delay : float, seconds to delay between samples if n > 1
        blocking : bool, if True wait until data is ready. If False, 
            self.start_measurement and self.stop_measurement must be 
            called externally to this method.

        Returns
        -------
        None, writes to disk the following data:
            description : str
            n : sample number in this burst
            and values as described in self.metadata.header
        """

        if blocking:
            get = self.measure_single_shot_blocking
        else:
            get = self.read_measurement

        wr = {
            "csv": self.csv_writer,
            "json": self.json_writer
        }[self.writer_output]
        for m in range(n):
            d = list(get())
            wr.write([self.sytem_id, self.sensor_id, self.description, m] + d)
            if delay is not None:
                time.sleep(delay)
Esempio n. 25
0
    def __init__(self, bus_n, bus_addr=0x40, output='csv', name='ina219'):
        """Initialize worker device on i2c bus.

        Parameters
        ----------
        bus_n : int, i2c bus number on Controller
        bus_addr : int, i2c bus number of this Worker device
        """

        # i2c bus
        self.bus = base.I2C(bus_n=bus_n, bus_addr=bus_addr)

        self.reg_config = None
        self.reg_shunt_voltage = None
        self.reg_bus_voltage = None
        self.reg_power = None
        self.reg_calibration = None

        self.reg_map = {
            'config': 0x00,
            'shunt_voltage': 0x01,
            'bus_voltage': 0x02,
            'power': 0x03,
            'current': 0x04,
            'calibration': 0x05
        }

        # bus voltage range
        self.bv_reg_to_bv = {0: 16, 1: 32}

        # programable gain amplifier
        self.pga_reg_to_gain = {0: 1, 1: 2, 2: 4, 3: 8}
        self.pga_gain_to_reg = {1: 0, 2: 1, 4: 2, 8: 3}
        self.pga_reg_str_range = {
            1: "+/- 40 mV",
            2: "+/- 80 mV",
            4: "+/- 160 mV",
            8: "+/- 320 mV"
        }

        # print debug statements
        self.verbose = False

        # data recording information
        self.sample_id = None

        # information about this device
        self.metadata = Meta(name=name)
        self.metadata.description = 'Texas Instruments Bidirectional Current Monitor'
        self.metadata.urls = 'www.ti.com/product/ADS1115'
        self.metadata.manufacturer = 'Adafruit Industries & Texas Instruments'

        self.metadata.header = [
            'description', 'sample_n', 'voltage', 'current'
        ]
        self.metadata.dtype = ['str', 'int', 'float', 'float']
        self.metadata.units = [None, 'count', 'volt', 'amp']
        self.metadata.accuracy = [None, 1, '+/-0.2%', '+/-0.2%']
        self.metadata.precision = [None, 1, '4 mV', '10uV accross shunt']
        self.metadata.accuracy_note = 'values for model INA291A'

        self.metadata.bus_n = bus_n
        self.metadata.bus_addr = hex(bus_addr)

        # chip defaults on power up or reset command
        self.metadata.bus_voltage_range = self.bv_reg_to_bv[1]
        self.metadata.gain = self.pga_reg_to_gain[0b11]
        self.metadata.gain_string = self.pga_reg_str_range[self.metadata.gain]

        self.metadata.bus_adc_resolution = 12
        self.metadata.bus_adc_averaging = None

        self.metadata.shunt_adc_resolution = 12
        self.metadata.shunt_adc_averaging = None

        self.metadata.mode = 7
        self.mode_to_str = {
            0: "power down",
            1: "shunt voltage, triggered",
            2: "bus voltage, triggered",
            3: "shunt and bus voltages, triggered",
            4: "ADC off (disabled)",
            5: "shunt voltage, continuous",
            6: "bus voltage, continuous",
            7: "shunt and bus voltages, continuous"
        }
        self.metadata.mode_description = self.mode_to_str[self.metadata.mode]

        # Adafruit INA219 breakout board as a 0.1 ohm 1% 2W resistor
        self.metadata.r_shunt = 0.1

        # data recording method
        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata,
                                    time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata,
                                      time_format='std_time_ms')

        # intialized configuration values
        self.get_config()
Esempio n. 26
0
class MCP23008:
    def __init__(self, bus_n, bus_addr=0x20, output='csv'):
        """Initialize worker device on i2c bus.

        Parameters
        ----------
        bus_n : int, i2c bus number on Controller
        bus_addr : int, i2c bus number of this Worker device
        output : str, output data format, either 'csv' (default) or 'json'
        """

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)

        # set direction of I/O to output for all pins
        self.bus.write_register_8bit(reg_addr=0x00, data=0)

        self.reg_olat = None

        # information about this device
        self.metadata = Meta('MCP23008')
        self.metadata.description = '8 channel I2C relay board by Peter Jakab'
        self.metadata.urls = 'https://jap.hu/electronic/relay_module_i2c.html'
        self.metadata.manufacturer = 'Peter Jakab'

        self.metadata.header = ['description', 'sample_n', 'relay_state']
        self.metadata.dtype = ['str', 'int', 'str']
        self.metadata.accuracy = None
        self.metadata.precision = None

        self.metadata.bus_n = bus_n
        self.metadata.bus_addr = hex(bus_addr)

        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata,
                                    time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata,
                                      time_format='std_time_ms')

    def get_all_channels(self):
        """Get all channel states, as a single 8 bit value. Each bit 
        represents one channel where 0 = On, 1 = Off.
        
        Returns
        -------
        state : int, 8 bits
        """
        self.reg_olat = self.bus.read_register_8bit(reg_addr=0x0A)
        time.sleep(0.01)
        return self.reg_olat

    def set_all_channels(self, state):
        """Set all channel state, as a single 8 bit value. Each bit 
        represents one channel where 0 = On, 1 = Off.
        
        Parameters
        ----------
        state : int, 8 bits
        """
        self.bus.write_register_8bit(reg_addr=0x0A, data=state)

    def set_channel(self, channel, state):
        """Set a single channel On, Off or Toggle it
        
        Parameters
        ----------
        channel : int, 1-8 for the channel to change
        state : int, where
            0 = On
            1 = Off
            2 = Toggle from last state
        """

        assert (channel > 0) & (channel < 9)

        bitvalue = (1 << (channel - 1))

        self.get_all_channels()

        if state == 0:
            self.reg_olat &= (~bitvalue)
        elif state == 1:
            self.reg_olat |= bitvalue
        elif state == 2:
            self.reg_olat ^= bitvalue

        self.set_all_channels(state=self.reg_olat)

    def publish(self, description='NA'):
        """Get relay state and output in JSON, plus metadata at intervals 
        set by self.metadata_interval

        Parameters
        ----------
        description : char, description of data sample collected, default='NA'

        Returns
        -------
        description : str
        n : sample number in this burst
        state : str, binary boolean state for each channel where
            0 = On
            1 = Off
        """
        m = 0
        state = self.get_all_channels()
        state = tools.left_fill(s=bin(state)[2:], n=8, x="0")
        return self.json_writer.publish([description, m, state])

    def write(self, description='NA'):
        """Get ADC output and save to file, formatted as either .csv or .json.

        Parameters
        ----------
        description : str, description of data sample collected
        n : int, number of samples to record in this burst
        delay : float, seconds to delay between samples if n > 1

        Returns
        -------
        None, writes to disk the following data:
            description : str
            n : sample number in this burst
            state : str, binary boolean state for each channel where
                0 = On
                1 = Off
        """
        wr = {
            "csv": self.csv_writer,
            "json": self.json_writer
        }[self.writer_output]
        m = 1
        state = self.get_all_channels()
        state = "0b" + tools.left_fill(s=bin(state)[2:], n=8, x="0")
        wr.write([description, m, state])
Esempio n. 27
0
    def __init__(self, bus_n, bus_addr=0x60, output='csv', name='mcp4728'):
        """Initialize worker device on i2c bus.
        
        Note 1
        ------
        Default I2C bus address is 0x60 = 0b1100000
        
        From the dataset, section 5.3:
        The first part of the address byte consists of a 4-bit device code,
        which is set to 1100 for the MCP4728 device. The device code is 
        followed by three I2C address bits (A2, A1, A0) which are 
        programmable by the users.
        
        4-bit device code: 
            0b 1 1 0 0 

        Programmable user bits:
            0b A1 A2 A3 = 0b 0 0 0 

        (a) Read the address bits using “General Call Read
        Address” Command (This is the case when the
        address is unknown). See class self.read_address().
        (b) Write I2C address bits using “Write I2C Address
        Bits” Command. The Write Address command will replace 
        the current address with a new address in both input 
        registers and EEPROM. See class method self.XXXX().
        
        Note 2
        ------
        In most I2C cases, v_dd will be either 3.3V or 5.0V. The MCP4728 can
        handle as much as 24mA current at 5V (0.12W) in short circuit. 
        By comparison, the Raspberry Pi can source at most 16mA of current 
        at 3.3V (0.05W). Unless the application output will draw a very 
        small amount of current, an external (to the I2C bus) voltage source 
        should probably be used. 
        See the Adafruit ISO1540 Bidirectional I2C Isolator as a possible solution.
        
        Note 3
        ------
        The manufacturer uses the term VDD (Vdd) for external voltage, many other 
        sources use the term VCC (Vcc). VDD is used here to be consistent with the
        datasheet, but manufacturers like Adafruit use VCC on the pinouts labels.
        
        Parameters
        ----------
        bus_n : int, i2c bus number on Controller
        bus_addr : int, i2c bus number of this Worker device
        """

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)
        self.udac = 0
        self.state = {'a': None, 'b': None, 'c': None, 'd': None}
        self.v_dd = None

        # information about this device
        self.metadata = Meta(name=name)
        self.metadata.description = 'MCP4728 12-bit Digitial to Analog Converter'
        self.metadata.urls = 'http://ww1.microchip.com/downloads/en/DeviceDoc/22187E.pdf'
        self.metadata.manufacturer = 'Microchip'

        self.metadata.header = [
            'description', 'channel', 'v_ref_source', 'v_dd', 'power_down',
            'gain', 'input_code', 'output_voltage'
        ]
        self.metadata.dtype = [
            'str', 'str', 'str', 'float', 'str', 'int', 'int', 'float'
        ]
        self.metadata.units = [
            None, None, None, 'volts', None, None, None, 'volts'
        ]
        self.metadata.accuracy = None
        self.metadata.precision = 'vref: gain 1 = 0.5mV/LSB, gain 2 = 1mV/LSB; vdd: vdd/4096'

        self.metadata.bus_n = bus_n
        self.metadata.bus_addr = hex(bus_addr)

        # data recording method
        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata,
                                    time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata,
                                      time_format='std_time_ms')
Esempio n. 28
0
    def __init__(self, bus_n, bus_addr=0x77, output='csv', sensor_id='BME680'):
        """Initialize worker device on i2c bus.

        For register memory map, see datasheet pg 28, section 5.2

        Parameters
        ----------
        bus_n : int, i2c bus number on Controller
        bus_addr : int, i2c bus number of this Worker device
        output : str, writer output format, either 'csv' or 'json'
        sensor_id : str, sensor id, 'BME680' by default
        """

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)

        # Default oversampling and filter register values.
        self.refresh_rate = 1
        self.filter = 1

        # memory mapped from 8 bit register locations
        self.mode = 0b00  # 2 bits, ctrl_meas <1:0>  operation mode
        self.osrs_p = 0b001  # 3 bits, ctrl_meas <4:2>, oversample pressure
        self.osrs_t = 0b001  # 3 bits, ctrl_meas <2:0>, oversample temperature
        self.osrs_h = 0b001  # 3 bits, ctrl_hum <2:0>,  oversample humidity
        self.run_gas = 0b0  # 1 bit,  ctrl_gas_1 <4>,  run gas measurement
        self.nb_conv = 0b000  # 4 bits, ctrl_gas_1 <3:0> conversion profile number
        self.heat_off = 0b0  # 1 bit,  ctrl_gas_0 <3>,  gas heater on/off

        # profile registers
        self.gas_wait_x = None  # gas_wait registers 9-0
        self.res_heat_x = None  # res_heat registers 9-0
        self.idac_heat_x = None  # idac_heat registers 9-0

        # calibration and state
        self._temp_calibration = None
        self._pressure_calibration = None
        self._humidity_calibration = None
        self._gas_calibration = None
        self.gas_meas_index = None
        self._new_data = None
        self._gas_measuring = None
        self._measuring = None
        self._heat_range = None
        self._heat_val = None
        self._heat_stab = None
        self._range_switch_error = None

        # raw ADC values
        self._adc_pres = None
        self._adc_temp = None
        self._adc_hum = None
        self._adc_gas = None
        self._gas_range = None
        self._t_fine = None

        # valid data flags
        self._gas_valid = None
        self._head_stab = None

        # control registers
        self._r_ctrl_meas = None
        self._r_ctrl_gas_1 = None

        # the datasheet gives two options: float or int values and equations
        # this code uses integer calculations, see table 16
        self._const_array1_int = (2147483647, 2147483647, 2147483647,
                                  2147483647, 2147483647, 2126008810,
                                  2147483647, 2130303777, 2147483647,
                                  2147483647, 2143188679, 2136746228,
                                  2147483647, 2126008810, 2147483647,
                                  2147483647)
        self._const_array2_int = (4096000000, 2048000000, 1024000000,
                                  512000000, 255744255, 127110228, 64000000,
                                  32258064, 16016016, 8000000, 4000000,
                                  2000000, 1000000, 500000, 250000, 125000)

        # Pressure in hectoPascals at sea level, used to calibrate altitude
        self.sea_level_pressure = 1013.25

        # calculated ambient temperature for res_heat target calculation
        self.amb_temp = None

        # sample collection metadata
        self._last_reading = 0
        self._min_refresh_time = 1 / self.refresh_rate

        # information about this device
        self.metadata = Meta(name='BME680')
        self.metadata.description = 'Bosch Humidity, Pressure, Temperature, VOC Sensor'
        self.metadata.urls = 'https://www.bosch-sensortec.com/products/environmental-sensors/gas-sensors-bme680/'
        self.metadata.manufacturer = 'Bosch Sensortec'

        self.metadata.header = [
            'system_id', 'sensor_id', 'description', 'sample_n', 'T', 'P',
            'RH', 'g_res', 'g_val', 'heat_stab'
        ]
        self.metadata.dtype = [
            'str', 'str', 'str', 'int', 'float', 'float', 'float', 'float',
            'bool', 'bool'
        ]
        self.metadata.units = [
            'NA',
            'NA',
            'NA',
            'count',
            'Celcius',
            'hectopascals',
            'percent',
            'ohms',
            'NA',
            'NA',
        ]
        self.metadata.accuracy = [
            'NA', 'NA', 'NA', '1', '+/-1.0', '+/-0.12', '+/-3', '+/-15%', 'NA',
            'NA'
        ]
        self.metadata.precision = [
            'NA', 'NA', 'NA', '1', '0.1', '0.18', '0.008', '0.08', 'NA', 'NA'
        ]
        self.metadata.bus_n = bus_n
        self.metadata.bus_addr = hex(bus_addr)

        # data recording information
        self.system_id = None
        self.sensor_id = sensor_id

        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata,
                                    time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata,
                                      time_format='std_time_ms')
Esempio n. 29
0
class MCP4728(object):
    def __init__(self, bus_n, bus_addr=0x60, output='csv', name='mcp4728'):
        """Initialize worker device on i2c bus.
        
        Note 1
        ------
        Default I2C bus address is 0x60 = 0b1100000
        
        From the dataset, section 5.3:
        The first part of the address byte consists of a 4-bit device code,
        which is set to 1100 for the MCP4728 device. The device code is 
        followed by three I2C address bits (A2, A1, A0) which are 
        programmable by the users.
        
        4-bit device code: 
            0b 1 1 0 0 

        Programmable user bits:
            0b A1 A2 A3 = 0b 0 0 0 

        (a) Read the address bits using “General Call Read
        Address” Command (This is the case when the
        address is unknown). See class self.read_address().
        (b) Write I2C address bits using “Write I2C Address
        Bits” Command. The Write Address command will replace 
        the current address with a new address in both input 
        registers and EEPROM. See class method self.XXXX().
        
        Note 2
        ------
        In most I2C cases, v_dd will be either 3.3V or 5.0V. The MCP4728 can
        handle as much as 24mA current at 5V (0.12W) in short circuit. 
        By comparison, the Raspberry Pi can source at most 16mA of current 
        at 3.3V (0.05W). Unless the application output will draw a very 
        small amount of current, an external (to the I2C bus) voltage source 
        should probably be used. 
        See the Adafruit ISO1540 Bidirectional I2C Isolator as a possible solution.
        
        Note 3
        ------
        The manufacturer uses the term VDD (Vdd) for external voltage, many other 
        sources use the term VCC (Vcc). VDD is used here to be consistent with the
        datasheet, but manufacturers like Adafruit use VCC on the pinouts labels.
        
        Parameters
        ----------
        bus_n : int, i2c bus number on Controller
        bus_addr : int, i2c bus number of this Worker device
        """

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)
        self.udac = 0
        self.state = {'a': None, 'b': None, 'c': None, 'd': None}
        self.v_dd = None

        # information about this device
        self.metadata = Meta(name=name)
        self.metadata.description = 'MCP4728 12-bit Digitial to Analog Converter'
        self.metadata.urls = 'http://ww1.microchip.com/downloads/en/DeviceDoc/22187E.pdf'
        self.metadata.manufacturer = 'Microchip'

        self.metadata.header = [
            'description', 'channel', 'v_ref_source', 'v_dd', 'power_down',
            'gain', 'input_code', 'output_voltage'
        ]
        self.metadata.dtype = [
            'str', 'str', 'str', 'float', 'str', 'int', 'int', 'float'
        ]
        self.metadata.units = [
            None, None, None, 'volts', None, None, None, 'volts'
        ]
        self.metadata.accuracy = None
        self.metadata.precision = 'vref: gain 1 = 0.5mV/LSB, gain 2 = 1mV/LSB; vdd: vdd/4096'

        self.metadata.bus_n = bus_n
        self.metadata.bus_addr = hex(bus_addr)

        # data recording method
        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata,
                                    time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata,
                                      time_format='std_time_ms')

    def general_call_reset(self):
        """Reset similar to power-on reset. See section 5.4.1."""
        self.bus.write_n_bytes(data=[0x00, 0x06])

    def general_call_wake_up(self):
        """Reset power-down bits. See section 5.4.2"""
        self.bus.write_n_bytes(data=[0x00, 0x09])

    def general_call_software_update(self):
        """Update all DAC analog outputs. See section 5.4.3"""
        self.bus.write_n_bytes(data=[0x00, 0x08])

    def general_call_read_address(self):
        """Return the I2C address. See section 5.4.4"""
        self.bus.write_n_bytes(data=[0x00, 0x0C])
        return self.bus.read_byte()

    def set_channel(self,
                    channel,
                    v_ref_source,
                    power_down,
                    gain,
                    input_code,
                    description='no description'):
        """Write single channel output
        
        Parameters
        ----------
        channel : str, either 'a', 'b', 'c' or 'd' corresponding 
            to the output channel
        v_ref_source : str, either 'internal' (2.048V/4.096V) or 'external' (VDD)
        power_down : str, either 'normal' or one of the power-down
            resistor to ground values of '1k' '100k' or '500k'
        gain : int, either 1 or 2 for multiplier relative to
            the internal reference voltage
        input_code : int, between 0 and 4095 to set the 
            output voltage
        """

        assert self.v_dd is not None, "External reference voltage, `v_dd` is not set."

        channel_map = {'a': 0b00, 'b': 0b01, 'c': 0b10, 'd': 0b11}
        v_ref_source_map = {'internal': 1, 'external': 0}
        power_down_map = {
            'normal': 0b00,
            '1k': 0b01,
            '100k': 0b10,
            '500k': 0b11
        }
        gain_map = {1: 0, 2: 1}

        # single write command
        # C2 = 0, C1 = 1, C0 = 0, W1 = 1, W0 = 1
        single_write_command = 0b01011000
        byte_2 = single_write_command + channel_map[channel] + self.udac

        byte_3 = ((v_ref_source_map[v_ref_source] << 7) +
                  (power_down_map[power_down] << 5) + (gain_map[gain] << 4) +
                  (input_code >> 8))

        byte_4 = input_code & 0b11111111

        self.bus.write_n_bytes(data=[byte_2, byte_3, byte_4])

        self.channel_state = self.channel_state[channel] = {
            'v_ref_source': v_ref_source,
            'power_down': power_down,
            'gain': gain,
            'input_code': input_code,
            'description': description
        }

        v_dd_to_v_ref_map = {'internal': 2.048 * gain, 'external': self.v_dd}
        v_ref_voltage = v_dd_to_v_ref_map[v_ref_source]
        output_voltage = self.output_voltage(input_code,
                                             v_ref_source=v_ref_source,
                                             gain=gain,
                                             v_dd=v_ref_voltage)

        # round to one more place than the precision of the chip
        # gain 1 = 0.5mV/LSB so 0.5mV = 0.0005V, therefore return 5 decimal places
        # gain 2 = 1mV/LSB so 1mV = 0.001V, therefore return 4 decimal places
        gain_rounder = {1: 5, 2: 4}
        output_voltage = round(output_voltage, gain_rounder[gain])

        self.state[channel] = [
            description, channel, v_ref_source, self.v_dd, power_down, gain,
            input_code, output_voltage
        ]

    @staticmethod
    def calculate_input_code(v_target, v_ref_source, gain, v_dd):
        """Calculate the required input register code
        required to produce a specific voltage

        Parameters
        ----------
        v_target : float, voltage target output on the DAC
        v_ref_source : str, either 'internal' (2.048V/4.096V) or 'external' (VDD)
        gain : int, gain of DAC, either 1 or 2
        v_dd : float, voltage source for the device. Could be I2C 3.3V or 5.0V, 
            or something else supplied on VDD

        Returns
        -------
        int, DAC inpute code required to achieve v_target
        """

        if v_ref_source == 'internal':
            if v_target > v_dd:
                return 'v_target must be <= v_dd'

            if (v_target > 2.048) & (gain == 1):
                return 'Gain must be 2 for v_target > v_ref internal'

            if (v_target > 4.096) & (gain == 2):
                return 'v_target must be <= 4.096V if using internal'

            return int((v_target * 4096) / (2.048 * gain))
        if v_ref_source == 'external':
            if v_target > v_dd:
                return 'v_target must be <= v_dd'

            return int((v_target * 4096) / v_dd)

    @staticmethod
    def output_voltage(input_code, v_ref_source, gain, v_dd=None):
        """Check output code voltage output

        Parameters
        ----------
        input_code : int, DAC inpute code required to achieve v_target
        v_ref_source : str, either 'internal' (2.048V/4.096V) or 'external' (VDD)
        gain : int, gain of DAC, either 1 or 2
        v_dd : float, voltage source for the device. Could be I2C 3.3V or 5.0V, 
            or something else supplied on VDD
        Returns
        -------
        v_target : float, DAC vol
        """

        if v_ref_source == 'internal':
            return (2.048 * input_code * gain) / 4096

        if v_ref_source == 'external':
            return (v_dd * input_code) / 4096

    @staticmethod
    def data_filler(data, cid):
        """Fill non-initialized channel data for publishing and writing

        Parameters
        ----------
        data : dict, self.state
        cid : str, channel id, one of 'a', 'b', 'c' or 'd'

        Returns
        -------
        list of lists, self.state data with None data filled 
            to match self.metadata.header
        """

        if data[cid] is None:
            return ['not_initialized', cid, None, None, None, None, None]
        else:
            return data[cid]

    def publish(self):
        """Output relay status data in JSON.

        Parameters
        ----------
        description : str, description of data sample collected
        n : int, number of samples to record in this burst
        delay : float, seconds to delay between samples if n > 1

        Returns
        -------
        str, formatted in JSON with keys:
            description: str, description of sample under test
            temperature : float, temperature in degrees Celcius
        """

        data_list = []
        for channel in ['a', 'b', 'c', 'd']:
            data = self.data_filler(self.state, channel)
            data_list.append(self.json_writer.publish(data))
        return data_list

    def write(self):
        """Format output and save to file, formatted as either
        .csv or .json.

        Parameters
        ----------
        description : str, description of data sample collected
        n : int, number of samples to record in this burst
        delay : float, seconds to delay between samples if n > 1
        
        Returns
        -------
        None, writes to disk the following data:
            description : str, description of sample
            sample_n : int, sample number in this burst
            temperature : float, temperature in degrees Celcius
        """
        wr = {
            "csv": self.csv_writer,
            "json": self.json_writer
        }[self.writer_output]
        for channel in ['a', 'b', 'c', 'd']:
            data = self.data_filler(self.state, channel)
            wr.write(data)
Esempio n. 30
0
class BME680:
    def __init__(self, bus_n, bus_addr=0x77, output='csv', sensor_id='BME680'):
        """Initialize worker device on i2c bus.

        For register memory map, see datasheet pg 28, section 5.2

        Parameters
        ----------
        bus_n : int, i2c bus number on Controller
        bus_addr : int, i2c bus number of this Worker device
        output : str, writer output format, either 'csv' or 'json'
        sensor_id : str, sensor id, 'BME680' by default
        """

        # i2c bus
        self.bus = I2C(bus_n=bus_n, bus_addr=bus_addr)

        # Default oversampling and filter register values.
        self.refresh_rate = 1
        self.filter = 1

        # memory mapped from 8 bit register locations
        self.mode = 0b00  # 2 bits, ctrl_meas <1:0>  operation mode
        self.osrs_p = 0b001  # 3 bits, ctrl_meas <4:2>, oversample pressure
        self.osrs_t = 0b001  # 3 bits, ctrl_meas <2:0>, oversample temperature
        self.osrs_h = 0b001  # 3 bits, ctrl_hum <2:0>,  oversample humidity
        self.run_gas = 0b0  # 1 bit,  ctrl_gas_1 <4>,  run gas measurement
        self.nb_conv = 0b000  # 4 bits, ctrl_gas_1 <3:0> conversion profile number
        self.heat_off = 0b0  # 1 bit,  ctrl_gas_0 <3>,  gas heater on/off

        # profile registers
        self.gas_wait_x = None  # gas_wait registers 9-0
        self.res_heat_x = None  # res_heat registers 9-0
        self.idac_heat_x = None  # idac_heat registers 9-0

        # calibration and state
        self._temp_calibration = None
        self._pressure_calibration = None
        self._humidity_calibration = None
        self._gas_calibration = None
        self.gas_meas_index = None
        self._new_data = None
        self._gas_measuring = None
        self._measuring = None
        self._heat_range = None
        self._heat_val = None
        self._heat_stab = None
        self._range_switch_error = None

        # raw ADC values
        self._adc_pres = None
        self._adc_temp = None
        self._adc_hum = None
        self._adc_gas = None
        self._gas_range = None
        self._t_fine = None

        # valid data flags
        self._gas_valid = None
        self._head_stab = None

        # control registers
        self._r_ctrl_meas = None
        self._r_ctrl_gas_1 = None

        # the datasheet gives two options: float or int values and equations
        # this code uses integer calculations, see table 16
        self._const_array1_int = (2147483647, 2147483647, 2147483647,
                                  2147483647, 2147483647, 2126008810,
                                  2147483647, 2130303777, 2147483647,
                                  2147483647, 2143188679, 2136746228,
                                  2147483647, 2126008810, 2147483647,
                                  2147483647)
        self._const_array2_int = (4096000000, 2048000000, 1024000000,
                                  512000000, 255744255, 127110228, 64000000,
                                  32258064, 16016016, 8000000, 4000000,
                                  2000000, 1000000, 500000, 250000, 125000)

        # Pressure in hectoPascals at sea level, used to calibrate altitude
        self.sea_level_pressure = 1013.25

        # calculated ambient temperature for res_heat target calculation
        self.amb_temp = None

        # sample collection metadata
        self._last_reading = 0
        self._min_refresh_time = 1 / self.refresh_rate

        # information about this device
        self.metadata = Meta(name='BME680')
        self.metadata.description = 'Bosch Humidity, Pressure, Temperature, VOC Sensor'
        self.metadata.urls = 'https://www.bosch-sensortec.com/products/environmental-sensors/gas-sensors-bme680/'
        self.metadata.manufacturer = 'Bosch Sensortec'

        self.metadata.header = [
            'system_id', 'sensor_id', 'description', 'sample_n', 'T', 'P',
            'RH', 'g_res', 'g_val', 'heat_stab'
        ]
        self.metadata.dtype = [
            'str', 'str', 'str', 'int', 'float', 'float', 'float', 'float',
            'bool', 'bool'
        ]
        self.metadata.units = [
            'NA',
            'NA',
            'NA',
            'count',
            'Celcius',
            'hectopascals',
            'percent',
            'ohms',
            'NA',
            'NA',
        ]
        self.metadata.accuracy = [
            'NA', 'NA', 'NA', '1', '+/-1.0', '+/-0.12', '+/-3', '+/-15%', 'NA',
            'NA'
        ]
        self.metadata.precision = [
            'NA', 'NA', 'NA', '1', '0.1', '0.18', '0.008', '0.08', 'NA', 'NA'
        ]
        self.metadata.bus_n = bus_n
        self.metadata.bus_addr = hex(bus_addr)

        # data recording information
        self.system_id = None
        self.sensor_id = sensor_id

        self.writer_output = output
        self.csv_writer = CSVWriter(metadata=self.metadata,
                                    time_format='std_time_ms')
        self.json_writer = JSONWriter(metadata=self.metadata,
                                      time_format='std_time_ms')

    def read_calibration(self):
        """Read chip calibration coefficients

        Coefficients are not listed in Table 20: Memory Map.  Instead they are
        referenced in Tables 11, 12, 13 and 14.
        """
        coeff = self.bus.read_register_nbit(0x89, 25)
        coeff += self.bus.read_register_nbit(0xE1, 16)

        coeff = list(
            struct.unpack('<hbBHhbBhhbbHhhBBBHbbbBbHhbb', bytes(coeff[1:39])))
        coeff = [float(i) for i in coeff]

        # 3 bytes
        self._temp_calibration = [coeff[x] for x in [23, 0, 1]]
        # 10 bytes
        self._pressure_calibration = [
            coeff[x] for x in [3, 4, 5, 7, 8, 10, 9, 12, 13, 14]
        ]
        # 7 bytes
        self._humidity_calibration = [
            coeff[x] for x in [17, 16, 18, 19, 20, 21, 22]
        ]
        # 3 bytes
        self._gas_calibration = [coeff[x] for x in [25, 24, 26]]

        # current method = 39 byte read + 3 byte setup

        # flip around H1 & H2
        self._humidity_calibration[1] *= 16
        self._humidity_calibration[1] += self._humidity_calibration[0] % 16
        self._humidity_calibration[0] /= 16

        self._range_switch_error = self.bus.read_register_8bit(0x04)
        self._heat_range = (self.bus.read_register_8bit(0x02) & 0x30) / 16
        self._heat_val = self.bus.read_register_8bit(0x00)

    def set_oversampling(self, h, t, p):
        """Set oversample rate for temperature, pressures and
        humidity.  Valid values for all three are:
            0, 1, 2, 4, 8, 16
            Note: 0 will skip measurement

        Parameters
        ----------
        h : int, oversampling rate of humidity
        t : int, oversampling rate of temperature
        p : int, oversampling rate of pressure
        """

        htp_mapper = {
            0: 0b000,
            1: 0b001,
            2: 0b010,
            4: 0b011,
            8: 0b100,
            16: 0b101
        }

        # ctrl_hum register, 0x72
        self.osrs_h = htp_mapper[h]
        self.write_r_ctrl_hum()

        # ctrl_meas register, 0x74
        self.osrs_t = htp_mapper[t]
        self.osrs_p = htp_mapper[p]
        self.write_r_ctrl_meas()

    # Register Methods
    # In the order listed in Table 20: Memory Map, pg28

    def reset(self):
        """Reset the device using a soft-reset procedure, which has the
        same effect as a power-on reset, by writing register
        'reset' at 0xE0 with value 0xB6.  Default value is 0x00."""
        self.bus.write_n_bytes([0xE0, 0xB6])
        time.sleep(0.005)

    def read_r_chip_id(self):
        """Check that the chip ID is correct.  Should return 0x61"""
        _chip_id = self.bus.read_register_8bit(0xD0)
        if _chip_id != 0x61:
            raise OSError('Expected BME680 ID 0x%x, got ID: 0x%x' %
                          (0x61, _chip_id))

    def read_r_config(self):
        """Read register 'config' at 0x75 which contains the
        'filter' contorl register.  'spi_3w_en' is always
        0b0 in I2C mode"""
        _config = self.bus.read_register_8bit(0x75)
        self.filter = (_config >> 2) & 0b111  # mask to be sure

    def read_r_ctrl_meas(self):
        """Read register 'ctrl_meas' at 0x74 which contains the
        'mode', 'osrs_p' and 'osrs_t' control registers"""
        _ctrl_meas = self.bus.read_register_8bit(0x74)
        self.mode = _ctrl_meas & 0b11
        self.osrs_p = (_ctrl_meas >> 2) & 0b111
        self.osrs_t = _ctrl_meas >> 5

    def write_r_ctrl_meas(self):
        """Write register 'ctrl_meas' at 0x74 which contains the
        'mode', 'osrs_p' and 'osrs_t' control registers"""
        _ctrl_meas = (((self.osrs_t << 5) | (self.osrs_p << 2)) | self.mode)
        self.bus.write_n_bytes([0x74, _ctrl_meas])

    def read_r_ctrl_hum(self):
        """Read register 'ctrl_hum' at 0x72 which contains the
        'osrs_h' control register.  'spi_3w_int_en' is always
        0b0 in I2C mode"""
        _ctrl_hum = self.bus.read_register_8bit(0x72)
        self.osrs_h = _ctrl_hum & 0b111  # mask just to be sure

    def write_r_ctrl_hum(self):
        """Write register 'ctrl_hum' at 0x72 which contains the
        'osrs_h' control register.  'spi_3w_int_en' is always
        0b0 in I2C mode"""
        self.bus.write_n_bytes([0x72, self.osrs_h])

    def read_r_ctrl_gas_1(self):
        """Contains 'run_gas' and 'nb_conv' control registers"""
        _ctrl_gas_1 = self.bus.read_register_8bit(0x71)
        self.run_gas = (_ctrl_gas_1 >> 4) & 0b1
        self.nb_conv = _ctrl_gas_1 & 0b1111

    def write_r_ctrl_gas_1(self):
        """Contains 'run_gas' and 'nb_conv' control registers"""
        _ctrl_gas_1 = self.nb_conv | (self.run_gas << 4)
        self.bus.write_n_bytes([0x71, _ctrl_gas_1])

    def read_r_ctrl_gas_0(self):
        """Contains the 'heat_off' control register"""
        _ctrl_gas_0 = self.bus.read_register_8bit(0x70)
        self.heat_off = (_ctrl_gas_0 >> 3) & 0b1

    def write_r_ctrl_gas_0(self):
        _ctrl_gas_0 = self.heat_off << 4
        self.bus.write_register_8bit(0x70, _ctrl_gas_0)

    def reg_gas_wait_x(self):
        """Control register for gas wait profiles"""
        self.gas_wait_x = self.bus.read_n_bytes(0x64, 10)

    def reg_res_heat_x(self):
        """Control register for heater resistance profiles"""
        self.res_heat_x = self.bus.read_n_bytes(0x5A, 10)

    def reg_idac_heat_x(self):
        """Control register for heater current profiles"""
        self.idac_heat_x = self.bus.read_n_bytes(0x50, 10)

    def mode_sleep(self):
        """Set chip mode to Sleep Mode"""
        self.bus.write_n_bytes([])

    def mode_forced(self):
        """Set chip mode to Forced Mode (active for measurement)"""
        self.bus.write_n_bytes([])

    # Operation Methods
    def gas_on(self):
        self.run_gas = 0b1
        self.write_r_ctrl_gas_1()

    def gas_off(self):
        self.run_gas = 0b0
        self.write_r_ctrl_gas_1()

    def forced_mode(self):
        self.mode = 0b01
        self.write_r_ctrl_meas()

    def sleep_mode(self):
        self.mode = 0b00
        self.write_r_ctrl_meas()

    def set_filter(self, coeff):
        """Set the temperature and pressure IIR filter

        Parameters
        ----------
        coeff : int, filter coefficient.  Valid values are:
            0, 1, 3, 7, 15, 31, 63, 127
        """
        mapper = {
            0: 0b000,
            1: 0b001,
            3: 0b010,
            7: 0b011,
            15: 0b100,
            31: 0b101,
            63: 0b110,
            127: 0b111
        }
        self.filter = coeff
        _filter = mapper[self.filter]
        self.bus.write_n_bytes([0x75, _filter << 2])  # config register

    def set_x_register(self, reg_0, n, value):
        """Set register within one of the three 10 byte registers:
            Gas_wait_x  : gas_wait_9  @ 0x6D downto  gas_wait_0 @ 0x64
            Res_heat_x  : res_heat_0  @ 0x63 downto  res_heat_0 @ 0x5A
            Idac_heat_x : idac_heat_9 @ 0x59 downto idac_heat_0 @ 0x50
        See Table 20 Memory Map for details.

        Parameters
        ----------
        n : int, set the nth register within the register block
        reg_0 : int, 0th register of the register block
        value : int, value for register
        """
        self.bus.write_register_8bit(n + reg_0, value)

    def set_gas_wait(self, n, value):
        """Set gas wait time for Gas_wait_x registers.
        Registers range from 0 = 0x64 up to 9 = 0x6D

        Parameters
        ----------
        n : int, set the nth register within the register block
        value : int, value for register
        """
        self.set_x_register(reg_0=0x64, n=n, value=value)

    def set_res_heat(self, n, value):
        """Set resistance for the heater registers.
        Registers range from 0 = 0x64 up to 9 = 0x6D

        Parameters
        ----------
        n : int, set the nth register within the register block
        value : int, value for register
        """
        self.set_x_register(reg_0=0x5A, n=n, value=value)

    def get_gas_wait(self):
        """Gas_wait_x  : gas_wait_9  @ 0x6D downto  gas_wait_0 @ 0x64

        Returns
        -------
        bytes : 10 bytes from register range reg_0 to reg_0 + 10"""
        return self.bus.read_register_nbit(0x64, 10)

    def get_res_heat(self):
        """Res_heat_x  : res_heat_0  @ 0x63 downto  res_heat_0 @ 0x5A

        Returns
        -------
        bytes : 10 bytes from register range reg_0 to reg_0 + 10"""
        return self.bus.read_register_nbit(0x5A, 10)

    def get_idac_heat(self):
        """Idac_heat_x : idac_heat_9 @ 0x59 downto idac_heat_0 @ 0x50

        Returns
        -------
        bytes : 10 bytes from register range reg_0 to reg_0 + 10"""
        return self.bus.read_register_nbit(0x50, 10)

    def calc_res_heat(self, target_temp):
        """Convert a target temperature for the heater to a resistance
        target for the chip

        Parameters
        ----------
        target_temp : int, target temperature in degrees Celcius

        Returns
        -------
        res_heat : int, register code for target temperature
        """

        if self.amb_temp is None:
            data = self.bus.read_register_nbit(0x22, 3)  # 0x22
            self._adc_temp = ((data[0] << 16) + (data[1] << 8) + data[2]) >> 4
            self.amb_temp = self.temperature()

        amb_temp = int(self.amb_temp)

        par_g1 = self.bus.read_register_8bit(0xED)
        par_g2_lsb = self.bus.read_register_8bit(0xEB)
        par_g2_msb = self.bus.read_register_8bit(0xEC)
        par_g2 = (par_g2_msb << 8) + par_g2_lsb
        par_g3 = self.bus.read_register_8bit(0xEE)

        res_heat_range = self.bus.read_register_8bit(0x02)
        res_heat_range = (res_heat_range >> 4) & 0b11
        res_heat_val = self.bus.read_register_8bit(0x00)

        var1 = ((amb_temp * par_g3) // 10) << 8
        var2 = ((par_g1 + 784) * ((((
            (par_g2 + 154009) * int(target_temp) * 5) // 100) + 3276800) // 10)
                )
        var3 = var1 + (var2 >> 1)
        var4 = var3 // (res_heat_range + 4)
        var5 = (131 * res_heat_val) + 65536
        res_heat_x100 = int(((var4 // var5) - 250) * 34)
        res_heat_x = int((res_heat_x100 + 50) // 100)

        return res_heat_x

    @staticmethod
    def calc_wait_time(t, x):
        """Calculate the wait time code for a heating profile

        Parameters
        ----------
        t : int, valued 0-63 with 1 ms step sizes, 0 = no wait
        x : int, multiplier for x, one of 1, 4, 16, 64

        Returns
        -------
        byte : register value
        """
        assert t < 63
        mapper = {1: 0b00, 4: 0b01, 16: 0b10, 64: 0b11}
        x = mapper[x]
        return (x << 6) | t

    def get_measurement_status(self):
        reg_meas_status = self.bus.read_register_8bit(0x1D)
        self.gas_meas_index = reg_meas_status & 0b1111
        self._new_data = reg_meas_status >> 7
        self._gas_measuring = (reg_meas_status >> 6) & 0b1
        self._measuring = (reg_meas_status >> 5) & 0b1

    def measure(self, verbose=False):
        """Get the temperature, pressure and humidity"""
        self._new_data = 0

        t0 = time.time()
        cx = 1
        while True:
            self.get_measurement_status()
            dt = (time.time() - t0)

            if self._measuring == 1:
                if verbose:
                    print('Still measuring, waiting 1 second...')
                time.sleep(1)

            elif self._new_data == 1:
                if verbose:
                    print('New data found!')
                break

            elif dt > 5:
                if verbose:
                    print('Timeout waiting for new data :(')
                return False
            if verbose:
                print('While loop %s' % cx)
            cx += 1

        data = self.bus.read_register_nbit(0x1F, 3)  # 0x1F
        self._adc_pres = ((data[0] << 16) + (data[1] << 8) + data[2]) >> 4

        data = self.bus.read_register_nbit(0x22, 3)  # 0x22
        self._adc_temp = ((data[0] << 16) + (data[1] << 8) + data[2]) >> 4

        data = self.bus.read_register_nbit(0x25, 3)  # 0x25
        self._adc_hum = (data[0] << 8) + data[1]

        _gas_r_msb = self.bus.read_register_8bit(0x2A)
        _gas_r_lsb = self.bus.read_register_8bit(0x2B)

        self._adc_gas = (_gas_r_msb << 2) + (_gas_r_lsb >> 6)
        self._gas_valid = (_gas_r_lsb >> 5) & 0b1
        self._heat_stab = (_gas_r_lsb >> 4) & 0b1
        self._gas_range = _gas_r_lsb & 0b1111  # 0x2B <4:0>
        return True

    def gas(self):
        """Calculate the gas resistance in ohms"""
        var1 = ((1340 + (5 * self._range_switch_error)) *
                (self._const_array1_int[self._gas_range])) >> 16

        var2 = (self._adc_gas << 15) - 16777216 + var1  # 1 << 24 = 16777216

        gas_res = (((self._const_array2_int[self._gas_range] * var1) >> 9) +
                   (var2 >> 1)) / var2
        return int(gas_res)  #, self._adc_gas, self._gas_range, var1, var2

    def temperature(self):
        """Calculate the compensated temperature in degrees celsius"""
        var1 = (self._adc_temp / 8) - (self._temp_calibration[0] * 2)
        var2 = (var1 * self._temp_calibration[1]) / 2048
        var3 = ((var1 / 2) * (var1 / 2)) / 4096
        var3 = (var3 * self._temp_calibration[2] * 16) / 16384
        self._t_fine = int(var2 + var3)
        calc_temp = (((self._t_fine * 5) + 128) / 256) / 100
        return calc_temp

    def pressure(self):
        """Calculate the barometric pressure in hectoPascals"""
        var1 = (self._t_fine / 2) - 64000
        var2 = ((var1 / 4) * (var1 / 4)) / 2048
        var2 = (var2 * self._pressure_calibration[5]) / 4
        var2 = var2 + (var1 * self._pressure_calibration[4] * 2)
        var2 = (var2 / 4) + (self._pressure_calibration[3] * 65536)
        var1 = (((((var1 / 4) * (var1 / 4)) / 8192) *
                 (self._pressure_calibration[2] * 32) / 8) +
                ((self._pressure_calibration[1] * var1) / 2))
        var1 = var1 / 262144
        var1 = ((32768 + var1) * self._pressure_calibration[0]) / 32768
        calc_pres = 1048576 - self._adc_pres
        calc_pres = (calc_pres - (var2 / 4096)) * 3125
        calc_pres = (calc_pres / var1) * 2
        var1 = (self._pressure_calibration[8] *
                (((calc_pres / 8) * (calc_pres / 8)) / 8192)) / 4096
        var2 = ((calc_pres / 4) * self._pressure_calibration[7]) / 8192
        var3 = ((
            (calc_pres / 256)**3) * self._pressure_calibration[9]) / 131072
        calc_pres += ((var1 + var2 + var3 +
                       (self._pressure_calibration[6] * 128)) / 16)
        calc_pres = calc_pres / 100
        return calc_pres

    def humidity(self):
        """The relative humidity in RH %"""
        temp_scaled = ((self._t_fine * 5) + 128) / 256
        var1 = ((self._adc_hum - (self._humidity_calibration[0] * 16)) -
                ((temp_scaled * self._humidity_calibration[2]) / 200))
        var2 = (self._humidity_calibration[1] * (
            ((temp_scaled * self._humidity_calibration[3]) / 100) +
            (((temp_scaled *
               ((temp_scaled * self._humidity_calibration[4]) / 100)) / 64) /
             100) + 16384)) / 1024
        var3 = var1 * var2
        var4 = self._humidity_calibration[5] * 128
        var4 = (var4 +
                ((temp_scaled * self._humidity_calibration[6]) / 100)) / 16
        var5 = ((var3 / 16384) * (var3 / 16384)) / 1024
        var6 = (var4 * var5) / 2
        calc_hum = (((var3 + var6) / 1024) * 1000) / 4096
        calc_hum /= 1000  # get back to RH

        if calc_hum > 100:
            calc_hum = 100
        if calc_hum < 0:
            calc_hum = 0
        return calc_hum

    def get(self, description='NA', n=1, verbose=False):
        """Get one sample of data

        Parameters
        ----------
        description : str, description of data sample collected
        n : int, number of samples to record in this burst
        verbose : bool, print debug statements

        Returns
        -------
        data : list, data containing:
            description: str, description of sample under test
            sample_n : int, sample number in this burst
            t : float, temperature in degress C
            p : float, pressure in hectoPascals
            h : float, relative humidty in percent
            g : float, gas resistence
            g_valid : int, gas value is valid
            heat_stable : int, gas heater is stable
        """
        self.forced_mode()
        time.sleep(0.2)

        if not self.measure(verbose):
            return False
        t = self.temperature()
        p = self.pressure()
        h = self.humidity()
        g = self.gas()
        d = [t, p, h, g]
        d = [round(n, 2) for n in d]
        return [self.system_id, self.sensor_id, description, n
                ] + d + [self._gas_valid, self._heat_stab]

    def publish(self, description='NA', verbose=False):
        """Get one sample of data in JSON.

        Parameters
        ----------
        description : str, description of data sample collected
        n : int, number of samples to record in this burst
        verbose : bool, print debug statements

        Returns
        -------
        str, formatted in JSON with keys:
            description: str, description of sample under test
            sample_n : int, sample number in this burst
            t : float, temperature in degress C
            p : float, pressure in hectoPascals
            h : float, relative humidty in percent
            g : float, gas resistence
            g_valid : int, gas value is valid
            heat_stable : int, gas heater is stable
        """
        data = self.get(description=description, verbose=verbose)
        json_data = self.json_writer.publish(data)
        return json_data

    def write(self, description='NA', n=1, delay=None):
        """Format output and save to file, formatted as either .csv or .json.

        Parameters
        ----------
        description : str, description of data sample collected
        n : int, number of samples to record in this burst
        delay : float, seconds to delay between samples if n > 1

        Returns
        -------
        None, writes to disk the following data:
            description: str, description of sample under test
            sample_n : int, sample number in this burst
            t : float, temperature in degress C
            p : float, pressure in hectoPascals
            h : float, relative humidty in percent
            g : float, gas resistence
            g_valid : int, gas value is valid
            heat_stable : int, gas heater is stable
        """
        wr = {
            "csv": self.csv_writer,
            "json": self.json_writer
        }[self.writer_output]
        for m in range(n):
            data = self.get(description=description)
            wr.write(data)
            if delay is not None:
                time.sleep(delay)