|
| 1 | +import time |
| 2 | +from pslab.bus import I2CSlave |
| 3 | + |
| 4 | + |
| 5 | +class CCS811(I2CSlave): |
| 6 | + MODE_IDLE = 0 # Idle (Measurements are disabled in this mode) |
| 7 | + MODE_CONTINUOUS = 1 # Constant power mode, IAQ measurement every 1s |
| 8 | + MODE_PULSE = 2 # Pulse heating mode IAQ measurement every 10 seconds |
| 9 | + MODE_LOW_POWER = 3 # Low power pulse heating mode IAQ measurement every 60 seconds |
| 10 | + MODE_CONTINUOUS_FAST = 4 # Constant power mode, sensor measurement every 250ms |
| 11 | + |
| 12 | + _ADDRESS = 0x5A |
| 13 | + |
| 14 | + # Figure 14: CCS811 Application Register Map |
| 15 | + _STATUS = 0x00 # STATUS # R 1 byte Status register |
| 16 | + # MEAS_MODE # R/W 1 byte Measurement mode and conditions register Algorithm result. The most significant 2 bytes contain a up to ppm estimate of the equivalent CO2 (eCO2) level, and |
| 17 | + _MEAS_MODE = 0x01 |
| 18 | + _ALG_RESULT_DATA = 0x02 # ALG_RESULT_DATA # R 8 bytes the next two bytes contain a ppb estimate of the total VOC level. Raw ADC data values for resistance and current source |
| 19 | + _RAW_DATA = 0x03 # RAW_DATA # R 2 bytes used. Temperature and humidity data can be written to |
| 20 | + _ENV_DATA = 0x05 # ENV_DATA # W 4 bytes enable compensation Thresholds for operation when interrupts are only |
| 21 | + _THRESHOLDS = 0x10 # THRESHOLDS # W 4 bytes generated when eCO2 ppm crosses a threshold The encoded current baseline value can be read. A |
| 22 | + # BASELINE # R/W 2 bytes previously saved encoded baseline can be written. |
| 23 | + _BASELINE = 0x11 |
| 24 | + _HW_ID = 0x20 # HW_ID # R 1 byte Hardware ID. The value is 0x81 |
| 25 | + _HW = 0x21 # HW Version # R 1 byte Hardware Version. The value is 0x1X Firmware Boot Version. The first 2 bytes contain the |
| 26 | + _FW_BOOT_VERSION = 0x23 # FW_Boot_Version # R 2 bytes firmware version number for the boot code. Firmware Application Version. The first 2 bytes contain |
| 27 | + # FW_App_Version # R 2 bytes the firmware version number for the application code |
| 28 | + _FW_APP_VERSION = 0x24 |
| 29 | + # Internal_State # R 1 byte Internal Status register Error ID. When the status register reports an error its |
| 30 | + _INTERNAL_STATE = 0xA0 |
| 31 | + # ERROR_ID # R 1 byte source is located in this register If the correct 4 bytes ( 0x11 0xE5 0x72 0x8A) are written |
| 32 | + _ERROR_ID = 0xE0 |
| 33 | + # SW_RESET # W 4 bytes to this register in a single sequence the device will reset and return to BOOT mode. |
| 34 | + _SW_RESET = 0xFF |
| 35 | + |
| 36 | + # Figure 25: CCS811 Bootloader Register Map |
| 37 | + # Address Register R/W Size Description |
| 38 | + _STATUS = 0x00 |
| 39 | + _HW_ID = 0x20 |
| 40 | + _HW_Version = 0x21 |
| 41 | + _APP_ERASE = 0xF1 |
| 42 | + _APP_DATA = 0xF2 |
| 43 | + _APP_VERIFY = 0xF3 |
| 44 | + _APP_START = 0xF4 |
| 45 | + _SW_RESET = 0xFF |
| 46 | + |
| 47 | + def __init__(self, address=None, device=None): |
| 48 | + super().__init__(address or self._ADDRESS, device) |
| 49 | + self.fetchID() |
| 50 | + self.softwareReset() |
| 51 | + |
| 52 | + def softwareReset(self): |
| 53 | + self.write([0x11, 0xE5, 0x72, 0x8A], self._SW_RESET) |
| 54 | + |
| 55 | + def fetchID(self): |
| 56 | + hardware_id = (self.read(1, self._HW_ID))[0] |
| 57 | + time.sleep(0.02) # 20ms |
| 58 | + hardware_version = (self.read(1, self._HW_Version))[0] |
| 59 | + time.sleep(0.02) # 20ms |
| 60 | + boot_version = (self.read(2, self._FW_BOOT_VERSION))[0] |
| 61 | + time.sleep(0.02) # 20ms |
| 62 | + app_version = (self.read(2, self._FW_APP_VERSION))[0] |
| 63 | + |
| 64 | + return { |
| 65 | + "hardware_id": hardware_id, |
| 66 | + "hardware_version": hardware_version, |
| 67 | + "boot_version": boot_version, |
| 68 | + "app_version": app_version, |
| 69 | + } |
| 70 | + |
| 71 | + def app_erase(self): |
| 72 | + ignore = self.write([0xE7, 0xA7, 0xE6, 0x09], self._APP_ERASE) |
| 73 | + time.sleep(0.3) |
| 74 | + |
| 75 | + def app_start(self): |
| 76 | + ignore = self.write([], self._APP_START) |
| 77 | + |
| 78 | + def set_measure_mode(self, mode): |
| 79 | + self.write([mode << 4], self._MEAS_MODE) |
| 80 | + |
| 81 | + def get_measure_mode(self): |
| 82 | + print(self.read(10, self._MEAS_MODE)) |
| 83 | + |
| 84 | + def get_status(self): |
| 85 | + status = (self.read(1, self._STATUS))[0] |
| 86 | + return status |
| 87 | + |
| 88 | + def decode_status(self, status): |
| 89 | + s = "" |
| 90 | + if (status & (1 << 7)) > 0: |
| 91 | + s += "Sensor is in application mode" |
| 92 | + else: |
| 93 | + s += "Sensor is in boot mode" |
| 94 | + if (status & (1 << 6)) > 0: |
| 95 | + s += ", APP_ERASE" |
| 96 | + if (status & (1 << 5)) > 0: |
| 97 | + s += ", APP_VERIFY" |
| 98 | + if (status & (1 << 4)) > 0: |
| 99 | + s += ", APP_VALID" |
| 100 | + if (status & (1 << 3)) > 0: |
| 101 | + s += ", DATA_READY" |
| 102 | + if (status & 1) > 0: |
| 103 | + s += ", ERROR" |
| 104 | + return s |
| 105 | + |
| 106 | + def decode_error(self, error_id): |
| 107 | + if (error_id & (1 << 0)) > 0: |
| 108 | + s += ", The CCS811 received an I²C write request addressed to this station but with invalid register address ID" |
| 109 | + if (error_id & (1 << 1)) > 0: |
| 110 | + s += ", The CCS811 received an I²C read request to a mailbox ID that is invalid" |
| 111 | + if (error_id & (1 << 2)) > 0: |
| 112 | + s += ", The CCS811 received an I²C request to write an unsupported mode to MEAS_MODE" |
| 113 | + if (error_id & (1 << 3)) > 0: |
| 114 | + s += ", The sensor resistance measurement has reached or exceeded the maximum range" |
| 115 | + if (error_id & (1 << 4)) > 0: |
| 116 | + s += ", The Heater current in the CCS811 is not in range" |
| 117 | + if (error_id & (1 << 5)) > 0: |
| 118 | + s += ", The Heater voltage is not being applied correctly" |
| 119 | + return "Error: " + s[2:] |
| 120 | + |
| 121 | + def measure(self): |
| 122 | + data = self.read(8, self._ALG_RESULT_DATA) |
| 123 | + eCO2 = data[0] * 256 + data[1] |
| 124 | + eTVOC = data[2] * 256 + data[3] |
| 125 | + status = data[4] |
| 126 | + error_id = data[5] |
| 127 | + raw_data = 256 * data[6] + data[7] |
| 128 | + raw_current = raw_data >> 10 |
| 129 | + raw_voltage = (raw_data & ((1 << 10) - 1)) * (1.65 / 1023) |
| 130 | + |
| 131 | + result = {"eCO2": eCO2, "eTVOC": eTVOC, "status": status, "error_id": error_id} |
| 132 | + |
| 133 | + if error_id > 0: |
| 134 | + raise RuntimeError(self.decodeError(error_id)) |
| 135 | + return result |
0 commit comments