mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	CCS811 support (#536)
* CCS811 * Move define, add test * Remove sun artifact * Lint * Lint
This commit is contained in:
		
							
								
								
									
										0
									
								
								esphome/components/ccs811/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/ccs811/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										123
									
								
								esphome/components/ccs811/ccs811.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								esphome/components/ccs811/ccs811.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | ||||
| #include "ccs811.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ccs811 { | ||||
|  | ||||
| static const char *TAG = "ccs811"; | ||||
|  | ||||
| // based on | ||||
| //  - https://cdn.sparkfun.com/datasheets/BreakoutBoards/CCS811_Programming_Guide.pdf | ||||
|  | ||||
| #define CHECK_TRUE(f, error_code) \ | ||||
|   if (!(f)) { \ | ||||
|     this->mark_failed(); \ | ||||
|     this->error_code_ = (error_code); \ | ||||
|     return; \ | ||||
|   } | ||||
|  | ||||
| #define CHECKED_IO(f) CHECK_TRUE(f, COMMUNICAITON_FAILED) | ||||
|  | ||||
| void CCS811Component::setup() { | ||||
|   // page 9 programming guide - hwid is always 0x81 | ||||
|   uint8_t hw_id; | ||||
|   CHECKED_IO(this->read_byte(0x20, &hw_id)) | ||||
|   CHECK_TRUE(hw_id == 0x81, INVALID_ID) | ||||
|  | ||||
|   // software reset, page 3 - allowed to fail | ||||
|   this->write_bytes(0xFF, {0x11, 0xE5, 0x72, 0x8A}); | ||||
|   delay(5); | ||||
|  | ||||
|   // page 10, APP_START | ||||
|   CHECK_TRUE(!this->status_has_error_(), SENSOR_REPORTED_ERROR) | ||||
|   CHECK_TRUE(this->status_app_is_valid_(), APP_INVALID) | ||||
|   CHECK_TRUE(this->write_bytes(0xF4, {}), APP_START_FAILED) | ||||
|   // App setup, wait for it to load | ||||
|   delay(1); | ||||
|  | ||||
|   // set MEAS_MODE (page 5) | ||||
|   uint8_t meas_mode = 0; | ||||
|   uint32_t interval = this->get_update_interval(); | ||||
|   if (interval <= 1000) | ||||
|     meas_mode = 1 << 4; | ||||
|   else if (interval <= 10000) | ||||
|     meas_mode = 2 << 4; | ||||
|   else | ||||
|     meas_mode = 3 << 4; | ||||
|  | ||||
|   CHECKED_IO(this->write_byte(0x01, meas_mode)) | ||||
|  | ||||
|   if (this->baseline_.has_value()) { | ||||
|     // baseline available, write to sensor | ||||
|     this->write_bytes(0x11, decode_uint16(*this->baseline_)); | ||||
|   } | ||||
| } | ||||
| void CCS811Component::update() { | ||||
|   if (!this->status_has_data_()) | ||||
|     this->status_set_warning(); | ||||
|  | ||||
|   // page 12 - alg result data | ||||
|   auto alg_data = this->read_bytes<4>(0x02); | ||||
|   if (!alg_data.has_value()) { | ||||
|     ESP_LOGW(TAG, "Reading CCS811 data failed!"); | ||||
|     this->status_set_warning(); | ||||
|     return; | ||||
|   } | ||||
|   auto res = *alg_data; | ||||
|   uint16_t co2 = encode_uint16(res[0], res[1]); | ||||
|   uint16_t tvoc = encode_uint16(res[2], res[3]); | ||||
|  | ||||
|   // also print baseline | ||||
|   auto baseline_data = this->read_bytes<2>(0x11); | ||||
|   uint16_t baseline = 0; | ||||
|   if (baseline_data.has_value()) { | ||||
|     baseline = encode_uint16((*baseline_data)[0], (*baseline_data)[1]); | ||||
|   } | ||||
|  | ||||
|   ESP_LOGD(TAG, "Got co2=%u ppm, tvoc=%u ppb, baseline=0x%04X", co2, tvoc, baseline); | ||||
|  | ||||
|   if (this->co2_ != nullptr) | ||||
|     this->co2_->publish_state(co2); | ||||
|   if (this->tvoc_ != nullptr) | ||||
|     this->tvoc_->publish_state(tvoc); | ||||
|  | ||||
|   this->status_clear_warning(); | ||||
| } | ||||
| void CCS811Component::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "CCS811"); | ||||
|   LOG_I2C_DEVICE(this) | ||||
|   LOG_UPDATE_INTERVAL(this) | ||||
|   LOG_SENSOR("  ", "CO2 Sesnor", this->co2_) | ||||
|   LOG_SENSOR("  ", "TVOC Sensor", this->tvoc_) | ||||
|   if (this->baseline_) { | ||||
|     ESP_LOGCONFIG(TAG, "  Baseline: %04X", *this->baseline_); | ||||
|   } else { | ||||
|     ESP_LOGCONFIG(TAG, "  Baseline: NOT SET"); | ||||
|   } | ||||
|   if (this->is_failed()) { | ||||
|     switch (this->error_code_) { | ||||
|       case COMMUNICAITON_FAILED: | ||||
|         ESP_LOGW(TAG, "Communication failed! Is the sensor connected?"); | ||||
|         break; | ||||
|       case INVALID_ID: | ||||
|         ESP_LOGW(TAG, "Sensor reported an invalid ID. Is this a CCS811?"); | ||||
|         break; | ||||
|       case SENSOR_REPORTED_ERROR: | ||||
|         ESP_LOGW(TAG, "Sensor reported internal error"); | ||||
|         break; | ||||
|       case APP_INVALID: | ||||
|         ESP_LOGW(TAG, "Sensor reported invalid APP installed."); | ||||
|         break; | ||||
|       case APP_START_FAILED: | ||||
|         ESP_LOGW(TAG, "Sensor reported APP start failed."); | ||||
|         break; | ||||
|       case UNKNOWN: | ||||
|       default: | ||||
|         ESP_LOGW(TAG, "Unknown setup error!"); | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| }  // namespace ccs811 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										47
									
								
								esphome/components/ccs811/ccs811.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								esphome/components/ccs811/ccs811.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/preferences.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ccs811 { | ||||
|  | ||||
| class CCS811Component : public PollingComponent, public i2c::I2CDevice { | ||||
|  public: | ||||
|   void set_co2(sensor::Sensor *co2) { co2_ = co2; } | ||||
|   void set_tvoc(sensor::Sensor *tvoc) { tvoc_ = tvoc; } | ||||
|   void set_baseline(uint16_t baseline) { baseline_ = baseline; } | ||||
|  | ||||
|   /// Setup the sensor and test for a connection. | ||||
|   void setup() override; | ||||
|   /// Schedule temperature+pressure readings. | ||||
|   void update() override; | ||||
|  | ||||
|   void dump_config() override; | ||||
|  | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|  protected: | ||||
|   optional<uint8_t> read_status_() { return this->read_byte(0x00); } | ||||
|   bool status_has_error_() { return this->read_status_().value_or(1) & 1; } | ||||
|   bool status_app_is_valid_() { return this->read_status_().value_or(0) & (1 << 4); } | ||||
|   bool status_has_data_() { return this->read_status_().value_or(0) & (1 << 3); } | ||||
|  | ||||
|   enum ErrorCode { | ||||
|     UNKNOWN, | ||||
|     COMMUNICAITON_FAILED, | ||||
|     INVALID_ID, | ||||
|     SENSOR_REPORTED_ERROR, | ||||
|     APP_INVALID, | ||||
|     APP_START_FAILED, | ||||
|   } error_code_{UNKNOWN}; | ||||
|  | ||||
|   sensor::Sensor *co2_{nullptr}; | ||||
|   sensor::Sensor *tvoc_{nullptr}; | ||||
|   optional<uint16_t> baseline_{}; | ||||
| }; | ||||
|  | ||||
| }  // namespace ccs811 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										35
									
								
								esphome/components/ccs811/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								esphome/components/ccs811/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.const import CONF_ID, ICON_GAS_CYLINDER, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \ | ||||
|     UNIT_PARTS_PER_BILLION | ||||
|  | ||||
| DEPENDENCIES = ['i2c'] | ||||
|  | ||||
| ccs811_ns = cg.esphome_ns.namespace('ccs811') | ||||
| CCS811Component = ccs811_ns.class_('CCS811Component', cg.PollingComponent, i2c.I2CDevice) | ||||
|  | ||||
| CONF_ECO2 = 'eco2' | ||||
| CONF_TVOC = 'tvoc' | ||||
| CONF_BASELINE = 'baseline' | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema({ | ||||
|     cv.GenerateID(): cv.declare_id(CCS811Component), | ||||
|     cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_GAS_CYLINDER, 0), | ||||
|     cv.Required(CONF_TVOC): sensor.sensor_schema(UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0), | ||||
|     cv.Optional(CONF_BASELINE): cv.hex_uint16_t, | ||||
| }).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x5A)) | ||||
|  | ||||
|  | ||||
| def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     yield cg.register_component(var, config) | ||||
|     yield i2c.register_i2c_device(var, config) | ||||
|  | ||||
|     sens = yield sensor.new_sensor(config[CONF_ECO2]) | ||||
|     cg.add(var.set_co2(sens)) | ||||
|     sens = yield sensor.new_sensor(config[CONF_TVOC]) | ||||
|     cg.add(var.set_tvoc(sens)) | ||||
|  | ||||
|     if CONF_BASELINE in config: | ||||
|         cg.add(var.set_baseline(config[CONF_BASELINE])) | ||||
| @@ -163,6 +163,14 @@ class I2CDevice { | ||||
|    */ | ||||
|   bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion = 0);  // NOLINT | ||||
|  | ||||
|   template<size_t N> optional<std::array<uint8_t, N>> read_bytes(uint8_t a_register) {  // NOLINT | ||||
|     std::array<uint8_t, N> res; | ||||
|     if (!this->read_bytes(a_register, res.data(), N)) { | ||||
|       return {}; | ||||
|     } | ||||
|     return res; | ||||
|   } | ||||
|  | ||||
|   /** Read len amount of 16-bit words (MSB first) from a register into data. | ||||
|    * | ||||
|    * @param a_register The register number to write to the bus before reading. | ||||
| @@ -176,6 +184,13 @@ class I2CDevice { | ||||
|   /// Read a single byte from a register into the data variable. Return true if successful. | ||||
|   bool read_byte(uint8_t a_register, uint8_t *data, uint32_t conversion = 0);  // NOLINT | ||||
|  | ||||
|   optional<uint8_t> read_byte(uint8_t a_register) {  // NOLINT | ||||
|     uint8_t data; | ||||
|     if (!this->read_byte(a_register, &data)) | ||||
|       return {}; | ||||
|     return data; | ||||
|   } | ||||
|  | ||||
|   /// Read a single 16-bit words (MSB first) from a register into the data variable. Return true if successful. | ||||
|   bool read_byte_16(uint8_t a_register, uint16_t *data, uint32_t conversion = 0);  // NOLINT | ||||
|  | ||||
| @@ -188,6 +203,20 @@ class I2CDevice { | ||||
|    */ | ||||
|   bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len);  // NOLINT | ||||
|  | ||||
|   /** Write a vector of data to a register. | ||||
|    * | ||||
|    * @param a_register The register to write to. | ||||
|    * @param data The data to write. | ||||
|    * @return If the operation was successful. | ||||
|    */ | ||||
|   bool write_bytes(uint8_t a_register, const std::vector<uint8_t> &data) {  // NOLINT | ||||
|     return this->write_bytes(a_register, data.data(), data.size()); | ||||
|   } | ||||
|  | ||||
|   template<size_t N> bool write_bytes(uint8_t a_register, const std::array<uint8_t, N> &data) {  // NOLINT | ||||
|     return this->write_bytes(a_register, data.data(), data.size()); | ||||
|   } | ||||
|  | ||||
|   /** Write len amount of 16-bit words (MSB first) to the specified register. | ||||
|    * | ||||
|    * @param a_register The register to write the values to. | ||||
|   | ||||
| @@ -463,6 +463,7 @@ ICON_PERCENT = 'mdi:percent' | ||||
| ICON_PERIODIC_TABLE_CO2 = 'mdi:periodic-table-co2' | ||||
| ICON_POWER = 'mdi:power' | ||||
| ICON_PULSE = 'mdi:pulse' | ||||
| ICON_RADIATOR = 'mdi:radiator' | ||||
| ICON_RESTART = 'mdi:restart' | ||||
| ICON_ROTATE_RIGHT = 'mdi:rotate-right' | ||||
| ICON_SCALE = 'mdi:scale' | ||||
| @@ -492,6 +493,7 @@ UNIT_MICROSIEMENS_PER_CENTIMETER = u'µS/cm' | ||||
| UNIT_MICROTESLA = u'µT' | ||||
| UNIT_OHM = u'Ω' | ||||
| UNIT_PARTS_PER_MILLION = 'ppm' | ||||
| UNIT_PARTS_PER_BILLION = 'ppb' | ||||
| UNIT_PERCENT = '%' | ||||
| UNIT_PULSES_PER_MINUTE = 'pulses/min' | ||||
| UNIT_SECOND = 's' | ||||
|   | ||||
| @@ -307,4 +307,11 @@ bool str_endswith(const std::string &full, const std::string &ending) { | ||||
|   return full.rfind(ending) == (full.size() - ending.size()); | ||||
| } | ||||
|  | ||||
| uint16_t encode_uint16(uint8_t msb, uint8_t lsb) { return (uint16_t(msb) << 8) | uint16_t(lsb); } | ||||
| std::array<uint8_t, 2> decode_uint16(uint16_t value) { | ||||
|   uint8_t msb = (value >> 8) & 0xFF; | ||||
|   uint8_t lsb = (value >> 0) & 0xFF; | ||||
|   return {msb, lsb}; | ||||
| } | ||||
|  | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -127,6 +127,11 @@ uint8_t reverse_bits_8(uint8_t x); | ||||
| uint16_t reverse_bits_16(uint16_t x); | ||||
| uint32_t reverse_bits_32(uint32_t x); | ||||
|  | ||||
| /// Encode a 16-bit unsigned integer given a most and least-significant byte. | ||||
| uint16_t encode_uint16(uint8_t msb, uint8_t lsb); | ||||
| /// Decode a 16-bit unsigned integer into an array of two values: most significant byte, least significant byte. | ||||
| std::array<uint8_t, 2> decode_uint16(uint16_t value); | ||||
|  | ||||
| /** Cross-platform method to disable interrupts. | ||||
|  * | ||||
|  * Useful when you need to do some timing-dependent communication. | ||||
|   | ||||
| @@ -511,6 +511,13 @@ sensor: | ||||
|       name: "SDS011 PM10.0" | ||||
|     update_interval: 5min | ||||
|     rx_only: false | ||||
|   - platform: ccs811 | ||||
|     eco2: | ||||
|       name: CCS811 eCO2 | ||||
|     tvoc: | ||||
|       name: CCS811 TVOC | ||||
|     update_interval: 30s | ||||
|     baseline: 0x4242 | ||||
|  | ||||
|  | ||||
| esp32_touch: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user