mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Merge branch 'dev' into pr/tomaszduda23/7049
This commit is contained in:
		| @@ -37,6 +37,7 @@ esphome/components/am43/sensor/* @buxtronix | |||||||
| esphome/components/analog_threshold/* @ianchi | esphome/components/analog_threshold/* @ianchi | ||||||
| esphome/components/animation/* @syndlex | esphome/components/animation/* @syndlex | ||||||
| esphome/components/anova/* @buxtronix | esphome/components/anova/* @buxtronix | ||||||
|  | esphome/components/apds9306/* @aodrenah | ||||||
| esphome/components/api/* @OttoWinter | esphome/components/api/* @OttoWinter | ||||||
| esphome/components/as5600/* @ammmze | esphome/components/as5600/* @ammmze | ||||||
| esphome/components/as5600/sensor/* @ammmze | esphome/components/as5600/sensor/* @ammmze | ||||||
| @@ -216,6 +217,7 @@ esphome/components/lock/* @esphome/core | |||||||
| esphome/components/logger/* @esphome/core | esphome/components/logger/* @esphome/core | ||||||
| esphome/components/ltr390/* @latonita @sjtrny | esphome/components/ltr390/* @latonita @sjtrny | ||||||
| esphome/components/ltr_als_ps/* @latonita | esphome/components/ltr_als_ps/* @latonita | ||||||
|  | esphome/components/m5stack_8angle/* @rnauber | ||||||
| esphome/components/matrix_keypad/* @ssieb | esphome/components/matrix_keypad/* @ssieb | ||||||
| esphome/components/max31865/* @DAVe3283 | esphome/components/max31865/* @DAVe3283 | ||||||
| esphome/components/max44009/* @berfenger | esphome/components/max44009/* @berfenger | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid( | CONFIG_SCHEMA = cv.invalid( | ||||||
|     "The ade7953 sensor component has been renamed to ade7953_i2c." |     "The ade7953 sensor component has been renamed to ade7953_i2c." | ||||||
| ) | ) | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								esphome/components/apds9306/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								esphome/components/apds9306/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | # Based on this datasheet: | ||||||
|  | # https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@aodrenah"] | ||||||
							
								
								
									
										151
									
								
								esphome/components/apds9306/apds9306.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								esphome/components/apds9306/apds9306.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | |||||||
|  | // Based on this datasheet: | ||||||
|  | // https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf | ||||||
|  |  | ||||||
|  | #include "apds9306.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace apds9306 { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "apds9306"; | ||||||
|  |  | ||||||
|  | enum {  // APDS9306 registers | ||||||
|  |   APDS9306_MAIN_CTRL = 0x00, | ||||||
|  |   APDS9306_ALS_MEAS_RATE = 0x04, | ||||||
|  |   APDS9306_ALS_GAIN = 0x05, | ||||||
|  |   APDS9306_PART_ID = 0x06, | ||||||
|  |   APDS9306_MAIN_STATUS = 0x07, | ||||||
|  |   APDS9306_CLEAR_DATA_0 = 0x0A,  // LSB | ||||||
|  |   APDS9306_CLEAR_DATA_1 = 0x0B, | ||||||
|  |   APDS9306_CLEAR_DATA_2 = 0x0C,  // MSB | ||||||
|  |   APDS9306_ALS_DATA_0 = 0x0D,    // LSB | ||||||
|  |   APDS9306_ALS_DATA_1 = 0x0E, | ||||||
|  |   APDS9306_ALS_DATA_2 = 0x0F,  // MSB | ||||||
|  |   APDS9306_INT_CFG = 0x19, | ||||||
|  |   APDS9306_INT_PERSISTENCE = 0x1A, | ||||||
|  |   APDS9306_ALS_THRES_UP_0 = 0x21,  // LSB | ||||||
|  |   APDS9306_ALS_THRES_UP_1 = 0x22, | ||||||
|  |   APDS9306_ALS_THRES_UP_2 = 0x23,   // MSB | ||||||
|  |   APDS9306_ALS_THRES_LOW_0 = 0x24,  // LSB | ||||||
|  |   APDS9306_ALS_THRES_LOW_1 = 0x25, | ||||||
|  |   APDS9306_ALS_THRES_LOW_2 = 0x26,  // MSB | ||||||
|  |   APDS9306_ALS_THRES_VAR = 0x27 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #define APDS9306_ERROR_CHECK(func, error) \ | ||||||
|  |   if (!(func)) { \ | ||||||
|  |     ESP_LOGE(TAG, error); \ | ||||||
|  |     this->mark_failed(); \ | ||||||
|  |     return; \ | ||||||
|  |   } | ||||||
|  | #define APDS9306_WARNING_CHECK(func, warning) \ | ||||||
|  |   if (!(func)) { \ | ||||||
|  |     ESP_LOGW(TAG, warning); \ | ||||||
|  |     this->status_set_warning(); \ | ||||||
|  |     return; \ | ||||||
|  |   } | ||||||
|  | #define APDS9306_WRITE_BYTE(reg, value) \ | ||||||
|  |   ESP_LOGV(TAG, "Writing 0x%02x to 0x%02x", value, reg); \ | ||||||
|  |   if (!this->write_byte(reg, value)) { \ | ||||||
|  |     ESP_LOGE(TAG, "Failed writing 0x%02x to 0x%02x", value, reg); \ | ||||||
|  |     this->mark_failed(); \ | ||||||
|  |     return; \ | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | void APDS9306::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up APDS9306..."); | ||||||
|  |  | ||||||
|  |   uint8_t id; | ||||||
|  |   if (!this->read_byte(APDS9306_PART_ID, &id)) {  // Part ID register | ||||||
|  |     this->error_code_ = COMMUNICATION_FAILED; | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (id != 0xB1 && id != 0xB3) {  // 0xB1 for APDS9306 0xB3 for APDS9306-065 | ||||||
|  |     this->error_code_ = WRONG_ID; | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // ALS resolution and measurement, see datasheet or init.py for options | ||||||
|  |   uint8_t als_meas_rate = ((this->bit_width_ & 0x07) << 4) | (this->measurement_rate_ & 0x07); | ||||||
|  |   APDS9306_WRITE_BYTE(APDS9306_ALS_MEAS_RATE, als_meas_rate); | ||||||
|  |  | ||||||
|  |   // ALS gain, see datasheet or init.py for options | ||||||
|  |   uint8_t als_gain = (this->gain_ & 0x07); | ||||||
|  |   APDS9306_WRITE_BYTE(APDS9306_ALS_GAIN, als_gain); | ||||||
|  |  | ||||||
|  |   // Set to standby mode | ||||||
|  |   APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x00); | ||||||
|  |  | ||||||
|  |   // Check for data, clear main status | ||||||
|  |   uint8_t status; | ||||||
|  |   APDS9306_WARNING_CHECK(this->read_byte(APDS9306_MAIN_STATUS, &status), "Reading MAIN STATUS failed."); | ||||||
|  |  | ||||||
|  |   // Set to active mode | ||||||
|  |   APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x02); | ||||||
|  |  | ||||||
|  |   ESP_LOGCONFIG(TAG, "APDS9306 setup complete"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void APDS9306::dump_config() { | ||||||
|  |   LOG_SENSOR("", "APDS9306", this); | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |  | ||||||
|  |   if (this->is_failed()) { | ||||||
|  |     switch (this->error_code_) { | ||||||
|  |       case COMMUNICATION_FAILED: | ||||||
|  |         ESP_LOGE(TAG, "Communication with APDS9306 failed!"); | ||||||
|  |         break; | ||||||
|  |       case WRONG_ID: | ||||||
|  |         ESP_LOGE(TAG, "APDS9306 has invalid id!"); | ||||||
|  |         break; | ||||||
|  |       default: | ||||||
|  |         ESP_LOGE(TAG, "Setting up APDS9306 registers failed!"); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Gain: %u", AMBIENT_LIGHT_GAIN_VALUES[this->gain_]); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Measurement rate: %u", MEASUREMENT_RATE_VALUES[this->measurement_rate_]); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Measurement Resolution/Bit width: %d", MEASUREMENT_BIT_WIDTH_VALUES[this->bit_width_]); | ||||||
|  |  | ||||||
|  |   LOG_UPDATE_INTERVAL(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void APDS9306::update() { | ||||||
|  |   // Check for new data | ||||||
|  |   uint8_t status; | ||||||
|  |   APDS9306_WARNING_CHECK(this->read_byte(APDS9306_MAIN_STATUS, &status), "Reading MAIN STATUS failed."); | ||||||
|  |  | ||||||
|  |   this->status_clear_warning(); | ||||||
|  |  | ||||||
|  |   if (!(status &= 0b00001000)) {  // No new data | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Set to standby mode | ||||||
|  |   APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x00); | ||||||
|  |  | ||||||
|  |   // Clear MAIN STATUS | ||||||
|  |   APDS9306_WARNING_CHECK(this->read_byte(APDS9306_MAIN_STATUS, &status), "Reading MAIN STATUS failed."); | ||||||
|  |  | ||||||
|  |   uint8_t als_data[3]; | ||||||
|  |   APDS9306_WARNING_CHECK(this->read_bytes(APDS9306_ALS_DATA_0, als_data, 3), "Reading ALS data has failed."); | ||||||
|  |  | ||||||
|  |   // Set to active mode | ||||||
|  |   APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x02); | ||||||
|  |  | ||||||
|  |   uint32_t light_level = 0x00 | encode_uint24(als_data[2], als_data[1], als_data[0]); | ||||||
|  |  | ||||||
|  |   float lux = ((float) light_level / AMBIENT_LIGHT_GAIN_VALUES[this->gain_]) * | ||||||
|  |               (100.0f / MEASUREMENT_RATE_VALUES[this->measurement_rate_]); | ||||||
|  |  | ||||||
|  |   ESP_LOGD(TAG, "Got illuminance=%.1flx from", lux); | ||||||
|  |   this->publish_state(lux); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace apds9306 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										66
									
								
								esphome/components/apds9306/apds9306.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								esphome/components/apds9306/apds9306.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | |||||||
|  | // Based on this datasheet: | ||||||
|  | // https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace apds9306 { | ||||||
|  |  | ||||||
|  | enum MeasurementBitWidth : uint8_t { | ||||||
|  |   MEASUREMENT_BIT_WIDTH_20 = 0, | ||||||
|  |   MEASUREMENT_BIT_WIDTH_19 = 1, | ||||||
|  |   MEASUREMENT_BIT_WIDTH_18 = 2, | ||||||
|  |   MEASUREMENT_BIT_WIDTH_17 = 3, | ||||||
|  |   MEASUREMENT_BIT_WIDTH_16 = 4, | ||||||
|  |   MEASUREMENT_BIT_WIDTH_13 = 5, | ||||||
|  | }; | ||||||
|  | static const uint8_t MEASUREMENT_BIT_WIDTH_VALUES[] = {20, 19, 18, 17, 16, 13}; | ||||||
|  |  | ||||||
|  | enum MeasurementRate : uint8_t { | ||||||
|  |   MEASUREMENT_RATE_25 = 0, | ||||||
|  |   MEASUREMENT_RATE_50 = 1, | ||||||
|  |   MEASUREMENT_RATE_100 = 2, | ||||||
|  |   MEASUREMENT_RATE_200 = 3, | ||||||
|  |   MEASUREMENT_RATE_500 = 4, | ||||||
|  |   MEASUREMENT_RATE_1000 = 5, | ||||||
|  |   MEASUREMENT_RATE_2000 = 6, | ||||||
|  | }; | ||||||
|  | static const uint16_t MEASUREMENT_RATE_VALUES[] = {25, 50, 100, 200, 500, 1000, 2000}; | ||||||
|  |  | ||||||
|  | enum AmbientLightGain : uint8_t { | ||||||
|  |   AMBIENT_LIGHT_GAIN_1 = 0, | ||||||
|  |   AMBIENT_LIGHT_GAIN_3 = 1, | ||||||
|  |   AMBIENT_LIGHT_GAIN_6 = 2, | ||||||
|  |   AMBIENT_LIGHT_GAIN_9 = 3, | ||||||
|  |   AMBIENT_LIGHT_GAIN_18 = 4, | ||||||
|  | }; | ||||||
|  | static const uint8_t AMBIENT_LIGHT_GAIN_VALUES[] = {1, 3, 6, 9, 18}; | ||||||
|  |  | ||||||
|  | class APDS9306 : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { | ||||||
|  |  public: | ||||||
|  |   void setup() override; | ||||||
|  |   float get_setup_priority() const override { return setup_priority::BUS; } | ||||||
|  |   void dump_config() override; | ||||||
|  |   void update() override; | ||||||
|  |   void set_bit_width(MeasurementBitWidth bit_width) { this->bit_width_ = bit_width; } | ||||||
|  |   void set_measurement_rate(MeasurementRate measurement_rate) { this->measurement_rate_ = measurement_rate; } | ||||||
|  |   void set_ambient_light_gain(AmbientLightGain gain) { this->gain_ = gain; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   enum ErrorCode { | ||||||
|  |     NONE = 0, | ||||||
|  |     COMMUNICATION_FAILED, | ||||||
|  |     WRONG_ID, | ||||||
|  |   } error_code_{NONE}; | ||||||
|  |  | ||||||
|  |   MeasurementBitWidth bit_width_; | ||||||
|  |   MeasurementRate measurement_rate_; | ||||||
|  |   AmbientLightGain gain_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace apds9306 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										95
									
								
								esphome/components/apds9306/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								esphome/components/apds9306/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | |||||||
|  | # Based on this datasheet: | ||||||
|  | # https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf | ||||||
|  |  | ||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import i2c, sensor | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_GAIN, | ||||||
|  |     DEVICE_CLASS_ILLUMINANCE, | ||||||
|  |     ICON_LIGHTBULB, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     UNIT_LUX, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
|  | CONF_APDS9306_ID = "apds9306_id" | ||||||
|  | CONF_BIT_WIDTH = "bit_width" | ||||||
|  | CONF_MEASUREMENT_RATE = "measurement_rate" | ||||||
|  |  | ||||||
|  | apds9306_ns = cg.esphome_ns.namespace("apds9306") | ||||||
|  | APDS9306 = apds9306_ns.class_( | ||||||
|  |     "APDS9306", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | MeasurementBitWidth = apds9306_ns.enum("MeasurementBitWidth") | ||||||
|  | MeasurementRate = apds9306_ns.enum("MeasurementRate") | ||||||
|  | AmbientLightGain = apds9306_ns.enum("AmbientLightGain") | ||||||
|  |  | ||||||
|  | MEASUREMENT_BIT_WIDTHS = { | ||||||
|  |     20: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_20, | ||||||
|  |     19: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_19, | ||||||
|  |     18: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_18, | ||||||
|  |     17: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_17, | ||||||
|  |     16: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_16, | ||||||
|  |     13: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_13, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | MEASUREMENT_RATES = { | ||||||
|  |     25: MeasurementRate.MEASUREMENT_RATE_25, | ||||||
|  |     50: MeasurementRate.MEASUREMENT_RATE_50, | ||||||
|  |     100: MeasurementRate.MEASUREMENT_RATE_100, | ||||||
|  |     200: MeasurementRate.MEASUREMENT_RATE_200, | ||||||
|  |     500: MeasurementRate.MEASUREMENT_RATE_500, | ||||||
|  |     1000: MeasurementRate.MEASUREMENT_RATE_1000, | ||||||
|  |     2000: MeasurementRate.MEASUREMENT_RATE_2000, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | AMBIENT_LIGHT_GAINS = { | ||||||
|  |     1: AmbientLightGain.AMBIENT_LIGHT_GAIN_1, | ||||||
|  |     3: AmbientLightGain.AMBIENT_LIGHT_GAIN_3, | ||||||
|  |     6: AmbientLightGain.AMBIENT_LIGHT_GAIN_6, | ||||||
|  |     9: AmbientLightGain.AMBIENT_LIGHT_GAIN_9, | ||||||
|  |     18: AmbientLightGain.AMBIENT_LIGHT_GAIN_18, | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _validate_measurement_rate(value): | ||||||
|  |     value = cv.positive_time_period_milliseconds(value) | ||||||
|  |     return cv.enum(MEASUREMENT_RATES, int=True)(value.total_milliseconds) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     sensor.sensor_schema( | ||||||
|  |         APDS9306, | ||||||
|  |         unit_of_measurement=UNIT_LUX, | ||||||
|  |         accuracy_decimals=1, | ||||||
|  |         device_class=DEVICE_CLASS_ILLUMINANCE, | ||||||
|  |         state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         icon=ICON_LIGHTBULB, | ||||||
|  |     ) | ||||||
|  |     .extend( | ||||||
|  |         { | ||||||
|  |             cv.Optional(CONF_GAIN, default="1"): cv.enum(AMBIENT_LIGHT_GAINS, int=True), | ||||||
|  |             cv.Optional(CONF_BIT_WIDTH, default="18"): cv.enum( | ||||||
|  |                 MEASUREMENT_BIT_WIDTHS, int=True | ||||||
|  |             ), | ||||||
|  |             cv.Optional( | ||||||
|  |                 CONF_MEASUREMENT_RATE, default="100ms" | ||||||
|  |             ): _validate_measurement_rate, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x52)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = await sensor.new_sensor(config) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await i2c.register_i2c_device(var, config) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_bit_width(config[CONF_BIT_WIDTH])) | ||||||
|  |     cg.add(var.set_measurement_rate(config[CONF_MEASUREMENT_RATE])) | ||||||
|  |     cg.add(var.set_ambient_light_gain(config[CONF_GAIN])) | ||||||
| @@ -2,6 +2,6 @@ import esphome.config_validation as cv | |||||||
|  |  | ||||||
| CODEOWNERS = ["@latonita"] | CODEOWNERS = ["@latonita"] | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid( | CONFIG_SCHEMA = cv.invalid( | ||||||
|     "The bmp3xx sensor component has been renamed to bmp3xx_i2c." |     "The bmp3xx sensor component has been renamed to bmp3xx_i2c." | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -2,6 +2,6 @@ import esphome.config_validation as cv | |||||||
|  |  | ||||||
| CODEOWNERS = ["@latonita"] | CODEOWNERS = ["@latonita"] | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid( | CONFIG_SCHEMA = cv.invalid( | ||||||
|     "The ens160 sensor component has been renamed to ens160_i2c." |     "The ens160 sensor component has been renamed to ens160_i2c." | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -7,10 +7,10 @@ from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant | |||||||
|  |  | ||||||
| DEPENDENCIES = ["esp32"] | DEPENDENCIES = ["esp32"] | ||||||
| CODEOWNERS = ["@jesserockz", "@Rapsssito"] | CODEOWNERS = ["@jesserockz", "@Rapsssito"] | ||||||
| CONFLICTS_WITH = ["esp32_ble_beacon"] |  | ||||||
|  |  | ||||||
| CONF_BLE_ID = "ble_id" | CONF_BLE_ID = "ble_id" | ||||||
| CONF_IO_CAPABILITY = "io_capability" | CONF_IO_CAPABILITY = "io_capability" | ||||||
|  | CONF_ADVERTISING_CYCLE_TIME = "advertising_cycle_time" | ||||||
|  |  | ||||||
| NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2] | NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2] | ||||||
|  |  | ||||||
| @@ -34,6 +34,19 @@ IO_CAPABILITY = { | |||||||
|     "display_yes_no": IoCapability.IO_CAP_IO, |     "display_yes_no": IoCapability.IO_CAP_IO, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | esp_power_level_t = cg.global_ns.enum("esp_power_level_t") | ||||||
|  |  | ||||||
|  | TX_POWER_LEVELS = { | ||||||
|  |     -12: esp_power_level_t.ESP_PWR_LVL_N12, | ||||||
|  |     -9: esp_power_level_t.ESP_PWR_LVL_N9, | ||||||
|  |     -6: esp_power_level_t.ESP_PWR_LVL_N6, | ||||||
|  |     -3: esp_power_level_t.ESP_PWR_LVL_N3, | ||||||
|  |     0: esp_power_level_t.ESP_PWR_LVL_N0, | ||||||
|  |     3: esp_power_level_t.ESP_PWR_LVL_P3, | ||||||
|  |     6: esp_power_level_t.ESP_PWR_LVL_P6, | ||||||
|  |     9: esp_power_level_t.ESP_PWR_LVL_P9, | ||||||
|  | } | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema( | CONFIG_SCHEMA = cv.Schema( | ||||||
|     { |     { | ||||||
|         cv.GenerateID(): cv.declare_id(ESP32BLE), |         cv.GenerateID(): cv.declare_id(ESP32BLE), | ||||||
| @@ -41,6 +54,9 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|             IO_CAPABILITY, lower=True |             IO_CAPABILITY, lower=True | ||||||
|         ), |         ), | ||||||
|         cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean, |         cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean, | ||||||
|  |         cv.Optional( | ||||||
|  |             CONF_ADVERTISING_CYCLE_TIME, default="10s" | ||||||
|  |         ): cv.positive_time_period_milliseconds, | ||||||
|     } |     } | ||||||
| ).extend(cv.COMPONENT_SCHEMA) | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
| @@ -58,6 +74,7 @@ async def to_code(config): | |||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT])) |     cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT])) | ||||||
|     cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY])) |     cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY])) | ||||||
|  |     cg.add(var.set_advertising_cycle_time(config[CONF_ADVERTISING_CYCLE_TIME])) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|  |  | ||||||
|     if CORE.using_esp_idf: |     if CORE.using_esp_idf: | ||||||
|   | |||||||
| @@ -78,6 +78,11 @@ void ESP32BLE::advertising_set_manufacturer_data(const std::vector<uint8_t> &dat | |||||||
|   this->advertising_start(); |   this->advertising_start(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void ESP32BLE::advertising_register_raw_advertisement_callback(std::function<void(bool)> &&callback) { | ||||||
|  |   this->advertising_init_(); | ||||||
|  |   this->advertising_->register_raw_advertisement_callback(std::move(callback)); | ||||||
|  | } | ||||||
|  |  | ||||||
| void ESP32BLE::advertising_add_service_uuid(ESPBTUUID uuid) { | void ESP32BLE::advertising_add_service_uuid(ESPBTUUID uuid) { | ||||||
|   this->advertising_init_(); |   this->advertising_init_(); | ||||||
|   this->advertising_->add_service_uuid(uuid); |   this->advertising_->add_service_uuid(uuid); | ||||||
| @@ -102,7 +107,7 @@ bool ESP32BLE::ble_pre_setup_() { | |||||||
| void ESP32BLE::advertising_init_() { | void ESP32BLE::advertising_init_() { | ||||||
|   if (this->advertising_ != nullptr) |   if (this->advertising_ != nullptr) | ||||||
|     return; |     return; | ||||||
|   this->advertising_ = new BLEAdvertising();  // NOLINT(cppcoreguidelines-owning-memory) |   this->advertising_ = new BLEAdvertising(this->advertising_cycle_time_);  // NOLINT(cppcoreguidelines-owning-memory) | ||||||
|  |  | ||||||
|   this->advertising_->set_scan_response(true); |   this->advertising_->set_scan_response(true); | ||||||
|   this->advertising_->set_min_preferred_interval(0x06); |   this->advertising_->set_min_preferred_interval(0x06); | ||||||
| @@ -312,6 +317,9 @@ void ESP32BLE::loop() { | |||||||
|     delete ble_event;  // NOLINT(cppcoreguidelines-owning-memory) |     delete ble_event;  // NOLINT(cppcoreguidelines-owning-memory) | ||||||
|     ble_event = this->ble_events_.pop(); |     ble_event = this->ble_events_.pop(); | ||||||
|   } |   } | ||||||
|  |   if (this->advertising_ != nullptr) { | ||||||
|  |     this->advertising_->loop(); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { | void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { | ||||||
|   | |||||||
| @@ -3,6 +3,8 @@ | |||||||
| #include "ble_advertising.h" | #include "ble_advertising.h" | ||||||
| #include "ble_uuid.h" | #include "ble_uuid.h" | ||||||
|  |  | ||||||
|  | #include <functional> | ||||||
|  |  | ||||||
| #include "esphome/core/automation.h" | #include "esphome/core/automation.h" | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/core/defines.h" | #include "esphome/core/defines.h" | ||||||
| @@ -76,6 +78,11 @@ class ESP32BLE : public Component { | |||||||
|  public: |  public: | ||||||
|   void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; } |   void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; } | ||||||
|  |  | ||||||
|  |   void set_advertising_cycle_time(uint32_t advertising_cycle_time) { | ||||||
|  |     this->advertising_cycle_time_ = advertising_cycle_time; | ||||||
|  |   } | ||||||
|  |   uint32_t get_advertising_cycle_time() const { return this->advertising_cycle_time_; } | ||||||
|  |  | ||||||
|   void enable(); |   void enable(); | ||||||
|   void disable(); |   void disable(); | ||||||
|   bool is_active(); |   bool is_active(); | ||||||
| @@ -89,6 +96,7 @@ class ESP32BLE : public Component { | |||||||
|   void advertising_set_manufacturer_data(const std::vector<uint8_t> &data); |   void advertising_set_manufacturer_data(const std::vector<uint8_t> &data); | ||||||
|   void advertising_add_service_uuid(ESPBTUUID uuid); |   void advertising_add_service_uuid(ESPBTUUID uuid); | ||||||
|   void advertising_remove_service_uuid(ESPBTUUID uuid); |   void advertising_remove_service_uuid(ESPBTUUID uuid); | ||||||
|  |   void advertising_register_raw_advertisement_callback(std::function<void(bool)> &&callback); | ||||||
|  |  | ||||||
|   void register_gap_event_handler(GAPEventHandler *handler) { this->gap_event_handlers_.push_back(handler); } |   void register_gap_event_handler(GAPEventHandler *handler) { this->gap_event_handlers_.push_back(handler); } | ||||||
|   void register_gattc_event_handler(GATTcEventHandler *handler) { this->gattc_event_handlers_.push_back(handler); } |   void register_gattc_event_handler(GATTcEventHandler *handler) { this->gattc_event_handlers_.push_back(handler); } | ||||||
| @@ -121,6 +129,7 @@ class ESP32BLE : public Component { | |||||||
|   Queue<BLEEvent> ble_events_; |   Queue<BLEEvent> ble_events_; | ||||||
|   BLEAdvertising *advertising_; |   BLEAdvertising *advertising_; | ||||||
|   esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; |   esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; | ||||||
|  |   uint32_t advertising_cycle_time_; | ||||||
|   bool enable_on_boot_; |   bool enable_on_boot_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,9 +10,9 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace esp32_ble { | namespace esp32_ble { | ||||||
|  |  | ||||||
| static const char *const TAG = "esp32_ble"; | static const char *const TAG = "esp32_ble.advertising"; | ||||||
|  |  | ||||||
| BLEAdvertising::BLEAdvertising() { | BLEAdvertising::BLEAdvertising(uint32_t advertising_cycle_time) : advertising_cycle_time_(advertising_cycle_time) { | ||||||
|   this->advertising_data_.set_scan_rsp = false; |   this->advertising_data_.set_scan_rsp = false; | ||||||
|   this->advertising_data_.include_name = true; |   this->advertising_data_.include_name = true; | ||||||
|   this->advertising_data_.include_txpower = true; |   this->advertising_data_.include_txpower = true; | ||||||
| @@ -64,7 +64,7 @@ void BLEAdvertising::set_manufacturer_data(const std::vector<uint8_t> &data) { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void BLEAdvertising::start() { | esp_err_t BLEAdvertising::services_advertisement_() { | ||||||
|   int num_services = this->advertising_uuids_.size(); |   int num_services = this->advertising_uuids_.size(); | ||||||
|   if (num_services == 0) { |   if (num_services == 0) { | ||||||
|     this->advertising_data_.service_uuid_len = 0; |     this->advertising_data_.service_uuid_len = 0; | ||||||
| @@ -87,8 +87,8 @@ void BLEAdvertising::start() { | |||||||
|   this->advertising_data_.include_txpower = !this->scan_response_; |   this->advertising_data_.include_txpower = !this->scan_response_; | ||||||
|   err = esp_ble_gap_config_adv_data(&this->advertising_data_); |   err = esp_ble_gap_config_adv_data(&this->advertising_data_); | ||||||
|   if (err != ESP_OK) { |   if (err != ESP_OK) { | ||||||
|     ESP_LOGE(TAG, "esp_ble_gap_config_adv_data failed (Advertising): %d", err); |     ESP_LOGE(TAG, "esp_ble_gap_config_adv_data failed (Advertising): %s", esp_err_to_name(err)); | ||||||
|     return; |     return err; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (this->scan_response_) { |   if (this->scan_response_) { | ||||||
| @@ -101,8 +101,8 @@ void BLEAdvertising::start() { | |||||||
|     this->scan_response_data_.flag = 0; |     this->scan_response_data_.flag = 0; | ||||||
|     err = esp_ble_gap_config_adv_data(&this->scan_response_data_); |     err = esp_ble_gap_config_adv_data(&this->scan_response_data_); | ||||||
|     if (err != ESP_OK) { |     if (err != ESP_OK) { | ||||||
|       ESP_LOGE(TAG, "esp_ble_gap_config_adv_data failed (Scan response): %d", err); |       ESP_LOGE(TAG, "esp_ble_gap_config_adv_data failed (Scan response): %s", esp_err_to_name(err)); | ||||||
|       return; |       return err; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -113,8 +113,18 @@ void BLEAdvertising::start() { | |||||||
|  |  | ||||||
|   err = esp_ble_gap_start_advertising(&this->advertising_params_); |   err = esp_ble_gap_start_advertising(&this->advertising_params_); | ||||||
|   if (err != ESP_OK) { |   if (err != ESP_OK) { | ||||||
|     ESP_LOGE(TAG, "esp_ble_gap_start_advertising failed: %d", err); |     ESP_LOGE(TAG, "esp_ble_gap_start_advertising failed: %s", esp_err_to_name(err)); | ||||||
|     return; |     return err; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return ESP_OK; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BLEAdvertising::start() { | ||||||
|  |   if (this->current_adv_index_ == -1) { | ||||||
|  |     this->services_advertisement_(); | ||||||
|  |   } else { | ||||||
|  |     this->raw_advertisements_callbacks_[this->current_adv_index_](true); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -124,6 +134,29 @@ void BLEAdvertising::stop() { | |||||||
|     ESP_LOGE(TAG, "esp_ble_gap_stop_advertising failed: %d", err); |     ESP_LOGE(TAG, "esp_ble_gap_stop_advertising failed: %d", err); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |   if (this->current_adv_index_ != -1) { | ||||||
|  |     this->raw_advertisements_callbacks_[this->current_adv_index_](false); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BLEAdvertising::loop() { | ||||||
|  |   if (this->raw_advertisements_callbacks_.empty()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   const uint32_t now = millis(); | ||||||
|  |   if (now - this->last_advertisement_time_ > this->advertising_cycle_time_) { | ||||||
|  |     this->stop(); | ||||||
|  |     this->current_adv_index_ += 1; | ||||||
|  |     if (this->current_adv_index_ >= this->raw_advertisements_callbacks_.size()) { | ||||||
|  |       this->current_adv_index_ = -1; | ||||||
|  |     } | ||||||
|  |     this->start(); | ||||||
|  |     this->last_advertisement_time_ = now; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BLEAdvertising::register_raw_advertisement_callback(std::function<void(bool)> &&callback) { | ||||||
|  |   this->raw_advertisements_callbacks_.push_back(std::move(callback)); | ||||||
| } | } | ||||||
|  |  | ||||||
| }  // namespace esp32_ble | }  // namespace esp32_ble | ||||||
|   | |||||||
| @@ -1,20 +1,31 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
|  | #include <array> | ||||||
|  | #include <functional> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
|  | #include <esp_bt.h> | ||||||
| #include <esp_gap_ble_api.h> | #include <esp_gap_ble_api.h> | ||||||
| #include <esp_gatts_api.h> | #include <esp_gatts_api.h> | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace esp32_ble { | namespace esp32_ble { | ||||||
|  |  | ||||||
|  | using raw_adv_data_t = struct { | ||||||
|  |   uint8_t *data; | ||||||
|  |   size_t length; | ||||||
|  |   esp_power_level_t power_level; | ||||||
|  | }; | ||||||
|  |  | ||||||
| class ESPBTUUID; | class ESPBTUUID; | ||||||
|  |  | ||||||
| class BLEAdvertising { | class BLEAdvertising { | ||||||
|  public: |  public: | ||||||
|   BLEAdvertising(); |   BLEAdvertising(uint32_t advertising_cycle_time); | ||||||
|  |  | ||||||
|  |   void loop(); | ||||||
|  |  | ||||||
|   void add_service_uuid(ESPBTUUID uuid); |   void add_service_uuid(ESPBTUUID uuid); | ||||||
|   void remove_service_uuid(ESPBTUUID uuid); |   void remove_service_uuid(ESPBTUUID uuid); | ||||||
| @@ -22,16 +33,25 @@ class BLEAdvertising { | |||||||
|   void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; } |   void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; } | ||||||
|   void set_manufacturer_data(const std::vector<uint8_t> &data); |   void set_manufacturer_data(const std::vector<uint8_t> &data); | ||||||
|   void set_service_data(const std::vector<uint8_t> &data); |   void set_service_data(const std::vector<uint8_t> &data); | ||||||
|  |   void register_raw_advertisement_callback(std::function<void(bool)> &&callback); | ||||||
|  |  | ||||||
|   void start(); |   void start(); | ||||||
|   void stop(); |   void stop(); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|  |   esp_err_t services_advertisement_(); | ||||||
|  |  | ||||||
|   bool scan_response_; |   bool scan_response_; | ||||||
|   esp_ble_adv_data_t advertising_data_; |   esp_ble_adv_data_t advertising_data_; | ||||||
|   esp_ble_adv_data_t scan_response_data_; |   esp_ble_adv_data_t scan_response_data_; | ||||||
|   esp_ble_adv_params_t advertising_params_; |   esp_ble_adv_params_t advertising_params_; | ||||||
|   std::vector<ESPBTUUID> advertising_uuids_; |   std::vector<ESPBTUUID> advertising_uuids_; | ||||||
|  |  | ||||||
|  |   std::vector<std::function<void(bool)>> raw_advertisements_callbacks_; | ||||||
|  |  | ||||||
|  |   const uint32_t advertising_cycle_time_; | ||||||
|  |   uint32_t last_advertisement_time_{0}; | ||||||
|  |   int8_t current_adv_index_{-1};  // -1 means standard scan response | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace esp32_ble | }  // namespace esp32_ble | ||||||
|   | |||||||
| @@ -1,16 +1,21 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
|  | from esphome.components.esp32_ble import CONF_BLE_ID | ||||||
| from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID, CONF_TX_POWER | from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID, CONF_TX_POWER | ||||||
| from esphome.core import CORE, TimePeriod | from esphome.core import CORE, TimePeriod | ||||||
| from esphome.components.esp32 import add_idf_sdkconfig_option | from esphome.components.esp32 import add_idf_sdkconfig_option | ||||||
| from esphome.components import esp32_ble | from esphome.components import esp32_ble | ||||||
|  |  | ||||||
|  | AUTO_LOAD = ["esp32_ble"] | ||||||
| DEPENDENCIES = ["esp32"] | DEPENDENCIES = ["esp32"] | ||||||
| CONFLICTS_WITH = ["esp32_ble_tracker"] |  | ||||||
|  |  | ||||||
| esp32_ble_beacon_ns = cg.esphome_ns.namespace("esp32_ble_beacon") | esp32_ble_beacon_ns = cg.esphome_ns.namespace("esp32_ble_beacon") | ||||||
| ESP32BLEBeacon = esp32_ble_beacon_ns.class_("ESP32BLEBeacon", cg.Component) | ESP32BLEBeacon = esp32_ble_beacon_ns.class_( | ||||||
|  |     "ESP32BLEBeacon", | ||||||
|  |     cg.Component, | ||||||
|  |     esp32_ble.GAPEventHandler, | ||||||
|  |     cg.Parented.template(esp32_ble.ESP32BLE), | ||||||
|  | ) | ||||||
| CONF_MAJOR = "major" | CONF_MAJOR = "major" | ||||||
| CONF_MINOR = "minor" | CONF_MINOR = "minor" | ||||||
| CONF_MIN_INTERVAL = "min_interval" | CONF_MIN_INTERVAL = "min_interval" | ||||||
| @@ -28,6 +33,7 @@ CONFIG_SCHEMA = cv.All( | |||||||
|     cv.Schema( |     cv.Schema( | ||||||
|         { |         { | ||||||
|             cv.GenerateID(): cv.declare_id(ESP32BLEBeacon), |             cv.GenerateID(): cv.declare_id(ESP32BLEBeacon), | ||||||
|  |             cv.GenerateID(CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE), | ||||||
|             cv.Required(CONF_TYPE): cv.one_of("IBEACON", upper=True), |             cv.Required(CONF_TYPE): cv.one_of("IBEACON", upper=True), | ||||||
|             cv.Required(CONF_UUID): cv.uuid, |             cv.Required(CONF_UUID): cv.uuid, | ||||||
|             cv.Optional(CONF_MAJOR, default=10167): cv.uint16_t, |             cv.Optional(CONF_MAJOR, default=10167): cv.uint16_t, | ||||||
| @@ -48,7 +54,7 @@ CONFIG_SCHEMA = cv.All( | |||||||
|                 min=-128, max=0 |                 min=-128, max=0 | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_TX_POWER, default="3dBm"): cv.All( |             cv.Optional(CONF_TX_POWER, default="3dBm"): cv.All( | ||||||
|                 cv.decibel, cv.one_of(-12, -9, -6, -3, 0, 3, 6, 9, int=True) |                 cv.decibel, cv.enum(esp32_ble.TX_POWER_LEVELS, int=True) | ||||||
|             ), |             ), | ||||||
|         } |         } | ||||||
|     ).extend(cv.COMPONENT_SCHEMA), |     ).extend(cv.COMPONENT_SCHEMA), | ||||||
| @@ -62,6 +68,10 @@ async def to_code(config): | |||||||
|     uuid = config[CONF_UUID].hex |     uuid = config[CONF_UUID].hex | ||||||
|     uuid_arr = [cg.RawExpression(f"0x{uuid[i:i + 2]}") for i in range(0, len(uuid), 2)] |     uuid_arr = [cg.RawExpression(f"0x{uuid[i:i + 2]}") for i in range(0, len(uuid), 2)] | ||||||
|     var = cg.new_Pvariable(config[CONF_ID], uuid_arr) |     var = cg.new_Pvariable(config[CONF_ID], uuid_arr) | ||||||
|  |  | ||||||
|  |     parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID]) | ||||||
|  |     cg.add(parent.register_gap_event_handler(var)) | ||||||
|  |  | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|     cg.add(var.set_major(config[CONF_MAJOR])) |     cg.add(var.set_major(config[CONF_MAJOR])) | ||||||
|     cg.add(var.set_minor(config[CONF_MINOR])) |     cg.add(var.set_minor(config[CONF_MINOR])) | ||||||
|   | |||||||
| @@ -3,14 +3,16 @@ | |||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
| #include <nvs_flash.h> |  | ||||||
| #include <freertos/FreeRTOS.h> |  | ||||||
| #include <esp_bt_main.h> |  | ||||||
| #include <esp_bt.h> | #include <esp_bt.h> | ||||||
| #include <freertos/task.h> | #include <esp_bt_main.h> | ||||||
| #include <esp_gap_ble_api.h> | #include <esp_gap_ble_api.h> | ||||||
|  | #include <freertos/FreeRTOS.h> | ||||||
|  | #include <freertos/task.h> | ||||||
|  | #include <nvs_flash.h> | ||||||
| #include <cstring> | #include <cstring> | ||||||
|  |  | ||||||
| #include "esphome/core/hal.h" | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
| #ifdef USE_ARDUINO | #ifdef USE_ARDUINO | ||||||
| #include <esp32-hal-bt.h> | #include <esp32-hal-bt.h> | ||||||
| @@ -21,20 +23,6 @@ namespace esp32_ble_beacon { | |||||||
|  |  | ||||||
| static const char *const TAG = "esp32_ble_beacon"; | static const char *const TAG = "esp32_ble_beacon"; | ||||||
|  |  | ||||||
| // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) |  | ||||||
| static esp_ble_adv_params_t ble_adv_params = { |  | ||||||
|     .adv_int_min = 0x20, |  | ||||||
|     .adv_int_max = 0x40, |  | ||||||
|     .adv_type = ADV_TYPE_NONCONN_IND, |  | ||||||
|     .own_addr_type = BLE_ADDR_TYPE_PUBLIC, |  | ||||||
|     .peer_addr = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, |  | ||||||
|     .peer_addr_type = BLE_ADDR_TYPE_PUBLIC, |  | ||||||
|     .channel_map = ADV_CHNL_ALL, |  | ||||||
|     .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| #define ENDIAN_CHANGE_U16(x) ((((x) &0xFF00) >> 8) + (((x) &0xFF) << 8)) |  | ||||||
|  |  | ||||||
| static const esp_ble_ibeacon_head_t IBEACON_COMMON_HEAD = { | static const esp_ble_ibeacon_head_t IBEACON_COMMON_HEAD = { | ||||||
|     .flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = {0x4C, 0x00}, .beacon_type = {0x02, 0x15}}; |     .flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = {0x4C, 0x00}, .beacon_type = {0x02, 0x15}}; | ||||||
|  |  | ||||||
| @@ -53,117 +41,62 @@ void ESP32BLEBeacon::dump_config() { | |||||||
|                 "  UUID: %s, Major: %u, Minor: %u, Min Interval: %ums, Max Interval: %ums, Measured Power: %d" |                 "  UUID: %s, Major: %u, Minor: %u, Min Interval: %ums, Max Interval: %ums, Measured Power: %d" | ||||||
|                 ", TX Power: %ddBm", |                 ", TX Power: %ddBm", | ||||||
|                 uuid, this->major_, this->minor_, this->min_interval_, this->max_interval_, this->measured_power_, |                 uuid, this->major_, this->minor_, this->min_interval_, this->max_interval_, this->measured_power_, | ||||||
|                 this->tx_power_); |                 (this->tx_power_ * 3) - 12); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | float ESP32BLEBeacon::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } | ||||||
|  |  | ||||||
| void ESP32BLEBeacon::setup() { | void ESP32BLEBeacon::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up ESP32 BLE beacon..."); |   this->ble_adv_params_ = { | ||||||
|   global_esp32_ble_beacon = this; |       .adv_int_min = static_cast<uint16_t>(this->min_interval_ / 0.625f), | ||||||
|  |       .adv_int_max = static_cast<uint16_t>(this->max_interval_ / 0.625f), | ||||||
|  |       .adv_type = ADV_TYPE_NONCONN_IND, | ||||||
|  |       .own_addr_type = BLE_ADDR_TYPE_PUBLIC, | ||||||
|  |       .peer_addr = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, | ||||||
|  |       .peer_addr_type = BLE_ADDR_TYPE_PUBLIC, | ||||||
|  |       .channel_map = ADV_CHNL_ALL, | ||||||
|  |       .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   xTaskCreatePinnedToCore(ESP32BLEBeacon::ble_core_task, |   global_ble->advertising_register_raw_advertisement_callback([this](bool advertise) { | ||||||
|                           "ble_task",  // name |     this->advertising_ = advertise; | ||||||
|                           10000,       // stack size (in words) |     if (advertise) { | ||||||
|                           nullptr,     // input params |       this->on_advertise_(); | ||||||
|                           1,           // priority |     } | ||||||
|                           nullptr,     // Handle, not needed |   }); | ||||||
|                           0            // core |  | ||||||
|   ); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| float ESP32BLEBeacon::get_setup_priority() const { return setup_priority::BLUETOOTH; } | void ESP32BLEBeacon::on_advertise_() { | ||||||
| void ESP32BLEBeacon::ble_core_task(void *params) { |  | ||||||
|   ble_setup(); |  | ||||||
|  |  | ||||||
|   while (true) { |  | ||||||
|     delay(1000);  // NOLINT |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ESP32BLEBeacon::ble_setup() { |  | ||||||
|   ble_adv_params.adv_int_min = static_cast<uint16_t>(global_esp32_ble_beacon->min_interval_ / 0.625f); |  | ||||||
|   ble_adv_params.adv_int_max = static_cast<uint16_t>(global_esp32_ble_beacon->max_interval_ / 0.625f); |  | ||||||
|  |  | ||||||
|   // Initialize non-volatile storage for the bluetooth controller |  | ||||||
|   esp_err_t err = nvs_flash_init(); |  | ||||||
|   if (err != ESP_OK) { |  | ||||||
|     ESP_LOGE(TAG, "nvs_flash_init failed: %d", err); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
| #ifdef USE_ARDUINO |  | ||||||
|   if (!btStart()) { |  | ||||||
|     ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status()); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
| #else |  | ||||||
|   if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { |  | ||||||
|     // start bt controller |  | ||||||
|     if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) { |  | ||||||
|       esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); |  | ||||||
|       err = esp_bt_controller_init(&cfg); |  | ||||||
|       if (err != ESP_OK) { |  | ||||||
|         ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err)); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|       while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) |  | ||||||
|         ; |  | ||||||
|     } |  | ||||||
|     if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) { |  | ||||||
|       err = esp_bt_controller_enable(ESP_BT_MODE_BLE); |  | ||||||
|       if (err != ESP_OK) { |  | ||||||
|         ESP_LOGE(TAG, "esp_bt_controller_enable failed: %s", esp_err_to_name(err)); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { |  | ||||||
|       ESP_LOGE(TAG, "esp bt controller enable failed"); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
|   esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); |  | ||||||
|  |  | ||||||
|   err = esp_bluedroid_init(); |  | ||||||
|   if (err != ESP_OK) { |  | ||||||
|     ESP_LOGE(TAG, "esp_bluedroid_init failed: %d", err); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   err = esp_bluedroid_enable(); |  | ||||||
|   if (err != ESP_OK) { |  | ||||||
|     ESP_LOGE(TAG, "esp_bluedroid_enable failed: %d", err); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, |  | ||||||
|                              static_cast<esp_power_level_t>((global_esp32_ble_beacon->tx_power_ + 12) / 3)); |  | ||||||
|   if (err != ESP_OK) { |  | ||||||
|     ESP_LOGE(TAG, "esp_ble_tx_power_set failed: %s", esp_err_to_name(err)); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   err = esp_ble_gap_register_callback(ESP32BLEBeacon::gap_event_handler); |  | ||||||
|   if (err != ESP_OK) { |  | ||||||
|     ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", err); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   esp_ble_ibeacon_t ibeacon_adv_data; |   esp_ble_ibeacon_t ibeacon_adv_data; | ||||||
|   memcpy(&ibeacon_adv_data.ibeacon_head, &IBEACON_COMMON_HEAD, sizeof(esp_ble_ibeacon_head_t)); |   memcpy(&ibeacon_adv_data.ibeacon_head, &IBEACON_COMMON_HEAD, sizeof(esp_ble_ibeacon_head_t)); | ||||||
|   memcpy(&ibeacon_adv_data.ibeacon_vendor.proximity_uuid, global_esp32_ble_beacon->uuid_.data(), |   memcpy(&ibeacon_adv_data.ibeacon_vendor.proximity_uuid, this->uuid_.data(), | ||||||
|          sizeof(ibeacon_adv_data.ibeacon_vendor.proximity_uuid)); |          sizeof(ibeacon_adv_data.ibeacon_vendor.proximity_uuid)); | ||||||
|   ibeacon_adv_data.ibeacon_vendor.minor = ENDIAN_CHANGE_U16(global_esp32_ble_beacon->minor_); |   ibeacon_adv_data.ibeacon_vendor.minor = byteswap(this->minor_); | ||||||
|   ibeacon_adv_data.ibeacon_vendor.major = ENDIAN_CHANGE_U16(global_esp32_ble_beacon->major_); |   ibeacon_adv_data.ibeacon_vendor.major = byteswap(this->major_); | ||||||
|   ibeacon_adv_data.ibeacon_vendor.measured_power = static_cast<uint8_t>(global_esp32_ble_beacon->measured_power_); |   ibeacon_adv_data.ibeacon_vendor.measured_power = static_cast<uint8_t>(this->measured_power_); | ||||||
|  |  | ||||||
|   esp_ble_gap_config_adv_data_raw((uint8_t *) &ibeacon_adv_data, sizeof(ibeacon_adv_data)); |   ESP_LOGD(TAG, "Setting BLE TX power"); | ||||||
|  |   esp_err_t err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, this->tx_power_); | ||||||
|  |   if (err != ESP_OK) { | ||||||
|  |     ESP_LOGW(TAG, "esp_ble_tx_power_set failed: %s", esp_err_to_name(err)); | ||||||
|  |   } | ||||||
|  |   err = esp_ble_gap_config_adv_data_raw((uint8_t *) &ibeacon_adv_data, sizeof(ibeacon_adv_data)); | ||||||
|  |   if (err != ESP_OK) { | ||||||
|  |     ESP_LOGE(TAG, "esp_ble_gap_config_adv_data_raw failed: %s", esp_err_to_name(err)); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { | void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { | ||||||
|  |   if (!this->advertising_) | ||||||
|  |     return; | ||||||
|  |  | ||||||
|   esp_err_t err; |   esp_err_t err; | ||||||
|   switch (event) { |   switch (event) { | ||||||
|     case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: { |     case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: { | ||||||
|       err = esp_ble_gap_start_advertising(&ble_adv_params); |       err = esp_ble_gap_start_advertising(&this->ble_adv_params_); | ||||||
|       if (err != ESP_OK) { |       if (err != ESP_OK) { | ||||||
|         ESP_LOGE(TAG, "esp_ble_gap_start_advertising failed: %d", err); |         ESP_LOGE(TAG, "esp_ble_gap_start_advertising failed: %s", esp_err_to_name(err)); | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
| @@ -181,6 +114,7 @@ void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap | |||||||
|       } else { |       } else { | ||||||
|         ESP_LOGD(TAG, "BLE stopped advertising successfully"); |         ESP_LOGD(TAG, "BLE stopped advertising successfully"); | ||||||
|       } |       } | ||||||
|  |       // this->advertising_ = false; | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     default: |     default: | ||||||
| @@ -188,8 +122,6 @@ void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| ESP32BLEBeacon *global_esp32_ble_beacon = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) |  | ||||||
|  |  | ||||||
| }  // namespace esp32_ble_beacon | }  // namespace esp32_ble_beacon | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,39 +1,39 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/esp32_ble/ble.h" | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
| #include <esp_gap_ble_api.h> |  | ||||||
| #include <esp_bt.h> | #include <esp_bt.h> | ||||||
|  | #include <esp_gap_ble_api.h> | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace esp32_ble_beacon { | namespace esp32_ble_beacon { | ||||||
|  |  | ||||||
| // NOLINTNEXTLINE(modernize-use-using) | using esp_ble_ibeacon_head_t = struct { | ||||||
| typedef struct { |  | ||||||
|   uint8_t flags[3]; |   uint8_t flags[3]; | ||||||
|   uint8_t length; |   uint8_t length; | ||||||
|   uint8_t type; |   uint8_t type; | ||||||
|   uint8_t company_id[2]; |   uint8_t company_id[2]; | ||||||
|   uint8_t beacon_type[2]; |   uint8_t beacon_type[2]; | ||||||
| } __attribute__((packed)) esp_ble_ibeacon_head_t; | } __attribute__((packed)); | ||||||
|  |  | ||||||
| // NOLINTNEXTLINE(modernize-use-using) | using esp_ble_ibeacon_vendor_t = struct { | ||||||
| typedef struct { |  | ||||||
|   uint8_t proximity_uuid[16]; |   uint8_t proximity_uuid[16]; | ||||||
|   uint16_t major; |   uint16_t major; | ||||||
|   uint16_t minor; |   uint16_t minor; | ||||||
|   uint8_t measured_power; |   uint8_t measured_power; | ||||||
| } __attribute__((packed)) esp_ble_ibeacon_vendor_t; | } __attribute__((packed)); | ||||||
|  |  | ||||||
| // NOLINTNEXTLINE(modernize-use-using) | using esp_ble_ibeacon_t = struct { | ||||||
| typedef struct { |  | ||||||
|   esp_ble_ibeacon_head_t ibeacon_head; |   esp_ble_ibeacon_head_t ibeacon_head; | ||||||
|   esp_ble_ibeacon_vendor_t ibeacon_vendor; |   esp_ble_ibeacon_vendor_t ibeacon_vendor; | ||||||
| } __attribute__((packed)) esp_ble_ibeacon_t; | } __attribute__((packed)); | ||||||
|  |  | ||||||
| class ESP32BLEBeacon : public Component { | using namespace esp32_ble; | ||||||
|  |  | ||||||
|  | class ESP32BLEBeacon : public Component, public GAPEventHandler, public Parented<ESP32BLE> { | ||||||
|  public: |  public: | ||||||
|   explicit ESP32BLEBeacon(const std::array<uint8_t, 16> &uuid) : uuid_(uuid) {} |   explicit ESP32BLEBeacon(const std::array<uint8_t, 16> &uuid) : uuid_(uuid) {} | ||||||
|  |  | ||||||
| @@ -46,12 +46,11 @@ class ESP32BLEBeacon : public Component { | |||||||
|   void set_min_interval(uint16_t val) { this->min_interval_ = val; } |   void set_min_interval(uint16_t val) { this->min_interval_ = val; } | ||||||
|   void set_max_interval(uint16_t val) { this->max_interval_ = val; } |   void set_max_interval(uint16_t val) { this->max_interval_ = val; } | ||||||
|   void set_measured_power(int8_t val) { this->measured_power_ = val; } |   void set_measured_power(int8_t val) { this->measured_power_ = val; } | ||||||
|   void set_tx_power(int8_t val) { this->tx_power_ = val; } |   void set_tx_power(esp_power_level_t val) { this->tx_power_ = val; } | ||||||
|  |   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); |   void on_advertise_(); | ||||||
|   static void ble_core_task(void *params); |  | ||||||
|   static void ble_setup(); |  | ||||||
|  |  | ||||||
|   std::array<uint8_t, 16> uuid_; |   std::array<uint8_t, 16> uuid_; | ||||||
|   uint16_t major_{}; |   uint16_t major_{}; | ||||||
| @@ -59,12 +58,11 @@ class ESP32BLEBeacon : public Component { | |||||||
|   uint16_t min_interval_{}; |   uint16_t min_interval_{}; | ||||||
|   uint16_t max_interval_{}; |   uint16_t max_interval_{}; | ||||||
|   int8_t measured_power_{}; |   int8_t measured_power_{}; | ||||||
|   int8_t tx_power_{}; |   esp_power_level_t tx_power_{}; | ||||||
|  |   esp_ble_adv_params_t ble_adv_params_; | ||||||
|  |   bool advertising_{false}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) |  | ||||||
| extern ESP32BLEBeacon *global_esp32_ble_beacon; |  | ||||||
|  |  | ||||||
| }  // namespace esp32_ble_beacon | }  // namespace esp32_ble_beacon | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ from esphome.components.esp32 import add_idf_sdkconfig_option | |||||||
|  |  | ||||||
| AUTO_LOAD = ["esp32_ble"] | AUTO_LOAD = ["esp32_ble"] | ||||||
| CODEOWNERS = ["@jesserockz", "@clydebarrow", "@Rapsssito"] | CODEOWNERS = ["@jesserockz", "@clydebarrow", "@Rapsssito"] | ||||||
| CONFLICTS_WITH = ["esp32_ble_beacon"] |  | ||||||
| DEPENDENCIES = ["esp32"] | DEPENDENCIES = ["esp32"] | ||||||
|  |  | ||||||
| CONF_MANUFACTURER = "manufacturer" | CONF_MANUFACTURER = "manufacturer" | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ from esphome.const import CONF_ID | |||||||
|  |  | ||||||
| AUTO_LOAD = ["esp32_ble_server"] | AUTO_LOAD = ["esp32_ble_server"] | ||||||
| CODEOWNERS = ["@jesserockz"] | CODEOWNERS = ["@jesserockz"] | ||||||
| CONFLICTS_WITH = ["esp32_ble_beacon"] |  | ||||||
| DEPENDENCIES = ["wifi", "esp32"] | DEPENDENCIES = ["wifi", "esp32"] | ||||||
|  |  | ||||||
| CONF_AUTHORIZED_DURATION = "authorized_duration" | CONF_AUTHORIZED_DURATION = "authorized_duration" | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ from esphome.const import ( | |||||||
|     CONF_PROTOCOL, |     CONF_PROTOCOL, | ||||||
|     CONF_VISUAL, |     CONF_VISUAL, | ||||||
| ) | ) | ||||||
|  | from esphome.core import CORE | ||||||
|  |  | ||||||
| CODEOWNERS = ["@rob-deutsch"] | CODEOWNERS = ["@rob-deutsch"] | ||||||
|  |  | ||||||
| @@ -127,3 +128,5 @@ def to_code(config): | |||||||
|     cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE])) |     cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE])) | ||||||
|  |  | ||||||
|     cg.add_library("tonia/HeatpumpIR", "1.0.27") |     cg.add_library("tonia/HeatpumpIR", "1.0.27") | ||||||
|  |     if CORE.is_libretiny: | ||||||
|  |         CORE.add_platformio_option("lib_ignore", "IRremoteESP8266") | ||||||
|   | |||||||
| @@ -52,6 +52,7 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin | |||||||
|   config.timeout_ms = this->timeout_; |   config.timeout_ms = this->timeout_; | ||||||
|   config.disable_auto_redirect = !this->follow_redirects_; |   config.disable_auto_redirect = !this->follow_redirects_; | ||||||
|   config.max_redirection_count = this->redirect_limit_; |   config.max_redirection_count = this->redirect_limit_; | ||||||
|  |   config.auth_type = HTTP_AUTH_TYPE_BASIC; | ||||||
| #if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE | #if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE | ||||||
|   if (secure) { |   if (secure) { | ||||||
|     config.crt_bundle_attach = esp_crt_bundle_attach; |     config.crt_bundle_attach = esp_crt_bundle_attach; | ||||||
|   | |||||||
| @@ -174,7 +174,8 @@ size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) { | |||||||
|     size_t samples_read = bytes_read / sizeof(int32_t); |     size_t samples_read = bytes_read / sizeof(int32_t); | ||||||
|     samples.resize(samples_read); |     samples.resize(samples_read); | ||||||
|     for (size_t i = 0; i < samples_read; i++) { |     for (size_t i = 0; i < samples_read; i++) { | ||||||
|       samples[i] = reinterpret_cast<int32_t *>(buf)[i] >> 16; |       int32_t temp = reinterpret_cast<int32_t *>(buf)[i] >> 14; | ||||||
|  |       samples[i] = clamp<int16_t>(temp, INT16_MIN, INT16_MAX); | ||||||
|     } |     } | ||||||
|     memcpy(buf, samples.data(), samples_read * sizeof(int16_t)); |     memcpy(buf, samples.data(), samples_read * sizeof(int16_t)); | ||||||
|     return samples_read * sizeof(int16_t); |     return samples_read * sizeof(int16_t); | ||||||
|   | |||||||
| @@ -92,7 +92,9 @@ static const uint8_t ILI9XXX_GMCTRN1 = 0xE1; | |||||||
|  |  | ||||||
| static const uint8_t ILI9XXX_CSCON = 0xF0; | static const uint8_t ILI9XXX_CSCON = 0xF0; | ||||||
| static const uint8_t ILI9XXX_ADJCTL3 = 0xF7; | static const uint8_t ILI9XXX_ADJCTL3 = 0xF7; | ||||||
| static const uint8_t ILI9XXX_DELAY = 0xFF;  // followed by one byte of delay time in ms | static const uint8_t ILI9XXX_DELAY_FLAG = 0xFF; | ||||||
|  | // special marker for delay - command byte reprents ms, length byte is an impossible value | ||||||
|  | #define ILI9XXX_DELAY(ms) ((uint8_t) ((ms) | 0x80)), ILI9XXX_DELAY_FLAG | ||||||
|  |  | ||||||
| }  // namespace ili9xxx | }  // namespace ili9xxx | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -34,8 +34,8 @@ void ILI9XXXDisplay::setup() { | |||||||
|   ESP_LOGD(TAG, "Setting up ILI9xxx"); |   ESP_LOGD(TAG, "Setting up ILI9xxx"); | ||||||
|  |  | ||||||
|   this->setup_pins_(); |   this->setup_pins_(); | ||||||
|   this->init_lcd(this->init_sequence_); |   this->init_lcd_(this->init_sequence_); | ||||||
|   this->init_lcd(this->extra_init_sequence_.data()); |   this->init_lcd_(this->extra_init_sequence_.data()); | ||||||
|   switch (this->pixel_mode_) { |   switch (this->pixel_mode_) { | ||||||
|     case PIXEL_MODE_16: |     case PIXEL_MODE_16: | ||||||
|       if (this->is_18bitdisplay_) { |       if (this->is_18bitdisplay_) { | ||||||
| @@ -405,42 +405,29 @@ void ILI9XXXDisplay::reset_() { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void ILI9XXXDisplay::init_lcd(const uint8_t *addr) { | void ILI9XXXDisplay::init_lcd_(const uint8_t *addr) { | ||||||
|   if (addr == nullptr) |   if (addr == nullptr) | ||||||
|     return; |     return; | ||||||
|   uint8_t cmd, x, num_args; |   uint8_t cmd, x, num_args; | ||||||
|   while ((cmd = *addr++) != 0) { |   while ((cmd = *addr++) != 0) { | ||||||
|     x = *addr++; |     x = *addr++; | ||||||
|     if (cmd == ILI9XXX_DELAY) { |     if (x == ILI9XXX_DELAY_FLAG) { | ||||||
|       ESP_LOGD(TAG, "Delay %dms", x); |       cmd &= 0x7F; | ||||||
|       delay(x); |       ESP_LOGV(TAG, "Delay %dms", cmd); | ||||||
|  |       delay(cmd); | ||||||
|     } else { |     } else { | ||||||
|       num_args = x & 0x7F; |       num_args = x & 0x7F; | ||||||
|       ESP_LOGD(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, *addr); |       ESP_LOGV(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, *addr); | ||||||
|       this->send_command(cmd, addr, num_args); |       this->send_command(cmd, addr, num_args); | ||||||
|       addr += num_args; |       addr += num_args; | ||||||
|       if (x & 0x80) { |       if (x & 0x80) { | ||||||
|         ESP_LOGD(TAG, "Delay 150ms"); |         ESP_LOGV(TAG, "Delay 150ms"); | ||||||
|         delay(150);  // NOLINT |         delay(150);  // NOLINT | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void ILI9XXXGC9A01A::init_lcd(const uint8_t *addr) { |  | ||||||
|   if (addr == nullptr) |  | ||||||
|     return; |  | ||||||
|   uint8_t cmd, x, num_args; |  | ||||||
|   while ((cmd = *addr++) != 0) { |  | ||||||
|     x = *addr++; |  | ||||||
|     num_args = x & 0x7F; |  | ||||||
|     this->send_command(cmd, addr, num_args); |  | ||||||
|     addr += num_args; |  | ||||||
|     if (x & 0x80) |  | ||||||
|       delay(150);  // NOLINT |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Tell the display controller where we want to draw pixels. | // Tell the display controller where we want to draw pixels. | ||||||
| void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { | void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { | ||||||
|   x1 += this->offset_x_; |   x1 += this->offset_x_; | ||||||
|   | |||||||
| @@ -33,7 +33,9 @@ class ILI9XXXDisplay : public display::DisplayBuffer, | |||||||
|     uint8_t cmd, num_args, bits; |     uint8_t cmd, num_args, bits; | ||||||
|     const uint8_t *addr = init_sequence; |     const uint8_t *addr = init_sequence; | ||||||
|     while ((cmd = *addr++) != 0) { |     while ((cmd = *addr++) != 0) { | ||||||
|       num_args = *addr++ & 0x7F; |       num_args = *addr++; | ||||||
|  |       if (num_args == ILI9XXX_DELAY_FLAG) | ||||||
|  |         continue; | ||||||
|       bits = *addr; |       bits = *addr; | ||||||
|       switch (cmd) { |       switch (cmd) { | ||||||
|         case ILI9XXX_MADCTL: { |         case ILI9XXX_MADCTL: { | ||||||
| @@ -50,13 +52,10 @@ class ILI9XXXDisplay : public display::DisplayBuffer, | |||||||
|           break; |           break; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         case ILI9XXX_DELAY: |  | ||||||
|           continue;  // no args to skip |  | ||||||
|  |  | ||||||
|         default: |         default: | ||||||
|           break; |           break; | ||||||
|       } |       } | ||||||
|       addr += num_args; |       addr += (num_args & 0x7F); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -109,7 +108,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer, | |||||||
|  |  | ||||||
|   virtual void set_madctl(); |   virtual void set_madctl(); | ||||||
|   void display_(); |   void display_(); | ||||||
|   virtual void init_lcd(const uint8_t *addr); |   void init_lcd_(const uint8_t *addr); | ||||||
|   void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2); |   void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2); | ||||||
|   void reset_(); |   void reset_(); | ||||||
|  |  | ||||||
| @@ -269,7 +268,6 @@ class ILI9XXXS3BoxLite : public ILI9XXXDisplay { | |||||||
| class ILI9XXXGC9A01A : public ILI9XXXDisplay { | class ILI9XXXGC9A01A : public ILI9XXXDisplay { | ||||||
|  public: |  public: | ||||||
|   ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240, true) {} |   ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240, true) {} | ||||||
|   void init_lcd(const uint8_t *addr) override; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| //-----------   ILI9XXX_24_TFT display -------------- | //-----------   ILI9XXX_24_TFT display -------------- | ||||||
|   | |||||||
| @@ -372,9 +372,9 @@ static const uint8_t PROGMEM INITCMD_GC9A01A[] = { | |||||||
|  |  | ||||||
| static const uint8_t PROGMEM INITCMD_ST7735[] = { | static const uint8_t PROGMEM INITCMD_ST7735[] = { | ||||||
|     ILI9XXX_SWRESET, 0,         // Soft reset, then delay 10ms |     ILI9XXX_SWRESET, 0,         // Soft reset, then delay 10ms | ||||||
|     ILI9XXX_DELAY, 10, |     ILI9XXX_DELAY(10), | ||||||
|     ILI9XXX_SLPOUT  , 0,                // Exit Sleep, delay |     ILI9XXX_SLPOUT  , 0,                // Exit Sleep, delay | ||||||
|     ILI9XXX_DELAY, 10, |     ILI9XXX_DELAY(10), | ||||||
|     ILI9XXX_PIXFMT  , 1, 0x05, |     ILI9XXX_PIXFMT  , 1, 0x05, | ||||||
|     ILI9XXX_FRMCTR1, 3, //  4: Frame rate control, 3 args + delay: |     ILI9XXX_FRMCTR1, 3, //  4: Frame rate control, 3 args + delay: | ||||||
|     0x01, 0x2C, 0x2D,             //     Rate = fosc/(1x2+40) * (LINE+2C+2D) |     0x01, 0x2C, 0x2D,             //     Rate = fosc/(1x2+40) * (LINE+2C+2D) | ||||||
| @@ -415,9 +415,9 @@ static const uint8_t PROGMEM INITCMD_ST7735[] = { | |||||||
|     0x00, 0x00, 0x02, 0x10, |     0x00, 0x00, 0x02, 0x10, | ||||||
|     ILI9XXX_MADCTL  , 1, 0x00,             // Memory Access Control, BGR |     ILI9XXX_MADCTL  , 1, 0x00,             // Memory Access Control, BGR | ||||||
|     ILI9XXX_NORON  , 0, |     ILI9XXX_NORON  , 0, | ||||||
|     ILI9XXX_DELAY, 10, |     ILI9XXX_DELAY(10), | ||||||
|     ILI9XXX_DISPON  , 0,                // Display on |     ILI9XXX_DISPON  , 0,                // Display on | ||||||
|     ILI9XXX_DELAY, 10, |     ILI9XXX_DELAY(10), | ||||||
|     00,   // endo of list |     00,   // endo of list | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid( | CONFIG_SCHEMA = cv.invalid( | ||||||
|     "The kalman_combinator sensor has moved.\nPlease use the combination platform instead with type: kalman.\n" |     "The kalman_combinator sensor has moved.\nPlease use the combination platform instead with type: kalman.\n" | ||||||
|     "See https://esphome.io/components/sensor/combination.html" |     "See https://esphome.io/components/sensor/combination.html" | ||||||
| ) | ) | ||||||
|   | |||||||
							
								
								
									
										33
									
								
								esphome/components/m5stack_8angle/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								esphome/components/m5stack_8angle/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import i2c | ||||||
|  | from esphome.const import CONF_ID | ||||||
|  |  | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  | CODEOWNERS = ["@rnauber"] | ||||||
|  | MULTI_CONF = True | ||||||
|  |  | ||||||
|  | CONF_M5STACK_8ANGLE_ID = "m5stack_8angle_id" | ||||||
|  |  | ||||||
|  | m5stack_8angle_ns = cg.esphome_ns.namespace("m5stack_8angle") | ||||||
|  | M5Stack8AngleComponent = m5stack_8angle_ns.class_( | ||||||
|  |     "M5Stack8AngleComponent", | ||||||
|  |     i2c.I2CDevice, | ||||||
|  |     cg.Component, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | AnalogBits = m5stack_8angle_ns.enum("AnalogBits") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.declare_id(M5Stack8AngleComponent), | ||||||
|  |     } | ||||||
|  | ).extend(i2c.i2c_device_schema(0x43)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await i2c.register_i2c_device(var, config) | ||||||
							
								
								
									
										30
									
								
								esphome/components/m5stack_8angle/binary_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								esphome/components/m5stack_8angle/binary_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import binary_sensor | ||||||
|  |  | ||||||
|  | from .. import M5Stack8AngleComponent, m5stack_8angle_ns, CONF_M5STACK_8ANGLE_ID | ||||||
|  |  | ||||||
|  |  | ||||||
|  | M5Stack8AngleSwitchBinarySensor = m5stack_8angle_ns.class_( | ||||||
|  |     "M5Stack8AngleSwitchBinarySensor", | ||||||
|  |     binary_sensor.BinarySensor, | ||||||
|  |     cg.PollingComponent, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(CONF_M5STACK_8ANGLE_ID): cv.use_id(M5Stack8AngleComponent), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(binary_sensor.binary_sensor_schema(M5Stack8AngleSwitchBinarySensor)) | ||||||
|  |     .extend(cv.polling_component_schema("10s")) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     hub = await cg.get_variable(config[CONF_M5STACK_8ANGLE_ID]) | ||||||
|  |     sens = await binary_sensor.new_binary_sensor(config) | ||||||
|  |     cg.add(sens.set_parent(hub)) | ||||||
|  |     await cg.register_component(sens, config) | ||||||
| @@ -0,0 +1,17 @@ | |||||||
|  | #include "m5stack_8angle_binary_sensor.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace m5stack_8angle { | ||||||
|  |  | ||||||
|  | void M5Stack8AngleSwitchBinarySensor::update() { | ||||||
|  |   int8_t out = this->parent_->read_switch(); | ||||||
|  |   if (out == -1) { | ||||||
|  |     this->status_set_warning("Could not read binary sensor state from M5Stack 8Angle."); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->publish_state(out != 0); | ||||||
|  |   this->status_clear_warning(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace m5stack_8angle | ||||||
|  | }  // namespace esphome | ||||||
| @@ -0,0 +1,19 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/binary_sensor/binary_sensor.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  |  | ||||||
|  | #include "../m5stack_8angle.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace m5stack_8angle { | ||||||
|  |  | ||||||
|  | class M5Stack8AngleSwitchBinarySensor : public binary_sensor::BinarySensor, | ||||||
|  |                                         public PollingComponent, | ||||||
|  |                                         public Parented<M5Stack8AngleComponent> { | ||||||
|  |  public: | ||||||
|  |   void update() override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace m5stack_8angle | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										31
									
								
								esphome/components/m5stack_8angle/light/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								esphome/components/m5stack_8angle/light/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import light | ||||||
|  |  | ||||||
|  | from esphome.const import CONF_OUTPUT_ID | ||||||
|  |  | ||||||
|  | from .. import M5Stack8AngleComponent, m5stack_8angle_ns, CONF_M5STACK_8ANGLE_ID | ||||||
|  |  | ||||||
|  |  | ||||||
|  | M5Stack8AngleLightsComponent = m5stack_8angle_ns.class_( | ||||||
|  |     "M5Stack8AngleLightOutput", | ||||||
|  |     light.AddressableLight, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All( | ||||||
|  |     light.ADDRESSABLE_LIGHT_SCHEMA.extend( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(CONF_M5STACK_8ANGLE_ID): cv.use_id(M5Stack8AngleComponent), | ||||||
|  |             cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(M5Stack8AngleLightsComponent), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     hub = await cg.get_variable(config[CONF_M5STACK_8ANGLE_ID]) | ||||||
|  |     lights = cg.new_Pvariable(config[CONF_OUTPUT_ID]) | ||||||
|  |     await light.register_light(lights, config) | ||||||
|  |     await cg.register_component(lights, config) | ||||||
|  |     cg.add(lights.set_parent(hub)) | ||||||
| @@ -0,0 +1,45 @@ | |||||||
|  | #include "m5stack_8angle_light.h" | ||||||
|  |  | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace m5stack_8angle { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "m5stack_8angle.light"; | ||||||
|  |  | ||||||
|  | void M5Stack8AngleLightOutput::setup() { | ||||||
|  |   ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); | ||||||
|  |   this->buf_ = allocator.allocate(M5STACK_8ANGLE_NUM_LEDS * M5STACK_8ANGLE_BYTES_PER_LED); | ||||||
|  |   if (this->buf_ == nullptr) { | ||||||
|  |     ESP_LOGE(TAG, "Failed to allocate buffer of size %u", M5STACK_8ANGLE_NUM_LEDS * M5STACK_8ANGLE_BYTES_PER_LED); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   }; | ||||||
|  |   memset(this->buf_, 0xFF, M5STACK_8ANGLE_NUM_LEDS * M5STACK_8ANGLE_BYTES_PER_LED); | ||||||
|  |  | ||||||
|  |   this->effect_data_ = allocator.allocate(M5STACK_8ANGLE_NUM_LEDS); | ||||||
|  |   if (this->effect_data_ == nullptr) { | ||||||
|  |     ESP_LOGE(TAG, "Failed to allocate effect data of size %u", M5STACK_8ANGLE_NUM_LEDS); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   }; | ||||||
|  |   memset(this->effect_data_, 0x00, M5STACK_8ANGLE_NUM_LEDS); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void M5Stack8AngleLightOutput::write_state(light::LightState *state) { | ||||||
|  |   for (int i = 0; i < M5STACK_8ANGLE_NUM_LEDS; | ||||||
|  |        i++) {  // write one LED at a time, otherwise the message will be truncated | ||||||
|  |     this->parent_->write_register(M5STACK_8ANGLE_REGISTER_RGB_24B + i * M5STACK_8ANGLE_BYTES_PER_LED, | ||||||
|  |                                   this->buf_ + i * M5STACK_8ANGLE_BYTES_PER_LED, M5STACK_8ANGLE_BYTES_PER_LED); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | light::ESPColorView M5Stack8AngleLightOutput::get_view_internal(int32_t index) const { | ||||||
|  |   size_t pos = index * M5STACK_8ANGLE_BYTES_PER_LED; | ||||||
|  |   // red, green, blue, white, effect_data, color_correction | ||||||
|  |   return {this->buf_ + pos, this->buf_ + pos + 1,       this->buf_ + pos + 2, | ||||||
|  |           nullptr,          this->effect_data_ + index, &this->correction_}; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace m5stack_8angle | ||||||
|  | }  // namespace esphome | ||||||
| @@ -0,0 +1,37 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/light/addressable_light.h" | ||||||
|  | #include "esphome/components/light/light_output.h" | ||||||
|  |  | ||||||
|  | #include "../m5stack_8angle.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace m5stack_8angle { | ||||||
|  |  | ||||||
|  | static const uint8_t M5STACK_8ANGLE_NUM_LEDS = 9; | ||||||
|  | static const uint8_t M5STACK_8ANGLE_BYTES_PER_LED = 4; | ||||||
|  |  | ||||||
|  | class M5Stack8AngleLightOutput : public light::AddressableLight, public Parented<M5Stack8AngleComponent> { | ||||||
|  |  public: | ||||||
|  |   void setup() override; | ||||||
|  |  | ||||||
|  |   void write_state(light::LightState *state) override; | ||||||
|  |  | ||||||
|  |   int32_t size() const override { return M5STACK_8ANGLE_NUM_LEDS; } | ||||||
|  |   light::LightTraits get_traits() override { | ||||||
|  |     auto traits = light::LightTraits(); | ||||||
|  |     traits.set_supported_color_modes({light::ColorMode::RGB}); | ||||||
|  |     return traits; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   void clear_effect_data() override { memset(this->effect_data_, 0x00, M5STACK_8ANGLE_NUM_LEDS); }; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   light::ESPColorView get_view_internal(int32_t index) const override; | ||||||
|  |  | ||||||
|  |   uint8_t *buf_{nullptr}; | ||||||
|  |   uint8_t *effect_data_{nullptr}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace m5stack_8angle | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										74
									
								
								esphome/components/m5stack_8angle/m5stack_8angle.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								esphome/components/m5stack_8angle/m5stack_8angle.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | |||||||
|  | #include "m5stack_8angle.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace m5stack_8angle { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "m5stack_8angle"; | ||||||
|  |  | ||||||
|  | void M5Stack8AngleComponent::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up M5STACK_8ANGLE..."); | ||||||
|  |   i2c::ErrorCode err; | ||||||
|  |  | ||||||
|  |   err = this->read(nullptr, 0); | ||||||
|  |   if (err != i2c::NO_ERROR) { | ||||||
|  |     ESP_LOGE(TAG, "I2C error %02X...", err); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   err = this->read_register(M5STACK_8ANGLE_REGISTER_FW_VERSION, &this->fw_version_, 1); | ||||||
|  |   if (err != i2c::NO_ERROR) { | ||||||
|  |     ESP_LOGE(TAG, "I2C error %02X...", err); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void M5Stack8AngleComponent::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "M5STACK_8ANGLE:"); | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Firmware version: %d ", this->fw_version_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float M5Stack8AngleComponent::read_knob_pos(uint8_t channel, AnalogBits bits) { | ||||||
|  |   int32_t raw_pos = this->read_knob_pos_raw(channel, bits); | ||||||
|  |   if (raw_pos == -1) { | ||||||
|  |     return NAN; | ||||||
|  |   } | ||||||
|  |   return (float) raw_pos / ((1 << bits) - 1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int32_t M5Stack8AngleComponent::read_knob_pos_raw(uint8_t channel, AnalogBits bits) { | ||||||
|  |   uint16_t knob_pos = 0; | ||||||
|  |   i2c::ErrorCode err; | ||||||
|  |   if (bits == BITS_8) { | ||||||
|  |     err = this->read_register(M5STACK_8ANGLE_REGISTER_ANALOG_INPUT_8B + channel, (uint8_t *) &knob_pos, 1); | ||||||
|  |   } else if (bits == BITS_12) { | ||||||
|  |     err = this->read_register(M5STACK_8ANGLE_REGISTER_ANALOG_INPUT_12B + (channel * 2), (uint8_t *) &knob_pos, 2); | ||||||
|  |   } else { | ||||||
|  |     ESP_LOGE(TAG, "Invalid number of bits: %d", bits); | ||||||
|  |     return -1; | ||||||
|  |   } | ||||||
|  |   if (err == i2c::NO_ERROR) { | ||||||
|  |     return knob_pos; | ||||||
|  |   } else { | ||||||
|  |     return -1; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int8_t M5Stack8AngleComponent::read_switch() { | ||||||
|  |   uint8_t out; | ||||||
|  |   i2c::ErrorCode err = this->read_register(M5STACK_8ANGLE_REGISTER_DIGITAL_INPUT, (uint8_t *) &out, 1); | ||||||
|  |   if (err == i2c::NO_ERROR) { | ||||||
|  |     return out ? 1 : 0; | ||||||
|  |   } else { | ||||||
|  |     return -1; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float M5Stack8AngleComponent::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  | }  // namespace m5stack_8angle | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										34
									
								
								esphome/components/m5stack_8angle/m5stack_8angle.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								esphome/components/m5stack_8angle/m5stack_8angle.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace m5stack_8angle { | ||||||
|  |  | ||||||
|  | static const uint8_t M5STACK_8ANGLE_REGISTER_ANALOG_INPUT_12B = 0x00; | ||||||
|  | static const uint8_t M5STACK_8ANGLE_REGISTER_ANALOG_INPUT_8B = 0x10; | ||||||
|  | static const uint8_t M5STACK_8ANGLE_REGISTER_DIGITAL_INPUT = 0x20; | ||||||
|  | static const uint8_t M5STACK_8ANGLE_REGISTER_RGB_24B = 0x30; | ||||||
|  | static const uint8_t M5STACK_8ANGLE_REGISTER_FW_VERSION = 0xFE; | ||||||
|  |  | ||||||
|  | enum AnalogBits : uint8_t { | ||||||
|  |   BITS_8 = 8, | ||||||
|  |   BITS_12 = 12, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class M5Stack8AngleComponent : public i2c::I2CDevice, public Component { | ||||||
|  |  public: | ||||||
|  |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |   float get_setup_priority() const override; | ||||||
|  |   float read_knob_pos(uint8_t channel, AnalogBits bits = AnalogBits::BITS_8); | ||||||
|  |   int32_t read_knob_pos_raw(uint8_t channel, AnalogBits bits = AnalogBits::BITS_8); | ||||||
|  |   int8_t read_switch(); | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   uint8_t fw_version_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace m5stack_8angle | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										66
									
								
								esphome/components/m5stack_8angle/sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								esphome/components/m5stack_8angle/sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import sensor | ||||||
|  |  | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_BIT_DEPTH, | ||||||
|  |     CONF_CHANNEL, | ||||||
|  |     CONF_RAW, | ||||||
|  |     ICON_ROTATE_RIGHT, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | from .. import ( | ||||||
|  |     AnalogBits, | ||||||
|  |     M5Stack8AngleComponent, | ||||||
|  |     m5stack_8angle_ns, | ||||||
|  |     CONF_M5STACK_8ANGLE_ID, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | M5Stack8AngleKnobSensor = m5stack_8angle_ns.class_( | ||||||
|  |     "M5Stack8AngleKnobSensor", | ||||||
|  |     sensor.Sensor, | ||||||
|  |     cg.PollingComponent, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | BIT_DEPTHS = { | ||||||
|  |     8: AnalogBits.BITS_8, | ||||||
|  |     12: AnalogBits.BITS_12, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | _validate_bits = cv.float_with_unit("bits", "bit") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(M5Stack8AngleKnobSensor), | ||||||
|  |             cv.GenerateID(CONF_M5STACK_8ANGLE_ID): cv.use_id(M5Stack8AngleComponent), | ||||||
|  |             cv.Required(CONF_CHANNEL): cv.int_range(min=1, max=8), | ||||||
|  |             cv.Optional(CONF_BIT_DEPTH, default="8bit"): cv.All( | ||||||
|  |                 _validate_bits, cv.enum(BIT_DEPTHS) | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_RAW, default=False): cv.boolean, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend( | ||||||
|  |         sensor.sensor_schema( | ||||||
|  |             M5Stack8AngleKnobSensor, | ||||||
|  |             accuracy_decimals=2, | ||||||
|  |             icon=ICON_ROTATE_RIGHT, | ||||||
|  |             state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("10s")) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = await sensor.new_sensor(config) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await cg.register_parented(var, config[CONF_M5STACK_8ANGLE_ID]) | ||||||
|  |     cg.add(var.set_channel(config[CONF_CHANNEL] - 1)) | ||||||
|  |     cg.add(var.set_bit_depth(BIT_DEPTHS[config[CONF_BIT_DEPTH]])) | ||||||
|  |     cg.add(var.set_raw(config[CONF_RAW])) | ||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | #include "m5stack_8angle_sensor.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace m5stack_8angle { | ||||||
|  |  | ||||||
|  | void M5Stack8AngleKnobSensor::update() { | ||||||
|  |   if (this->parent_ != nullptr) { | ||||||
|  |     int32_t raw_pos = this->parent_->read_knob_pos_raw(this->channel_, this->bits_); | ||||||
|  |     if (raw_pos == -1) { | ||||||
|  |       this->status_set_warning("Could not read knob position from M5Stack 8Angle."); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (this->raw_) { | ||||||
|  |       this->publish_state(raw_pos); | ||||||
|  |     } else { | ||||||
|  |       float knob_pos = (float) raw_pos / ((1 << this->bits_) - 1); | ||||||
|  |       this->publish_state(knob_pos); | ||||||
|  |     } | ||||||
|  |     this->status_clear_warning(); | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace m5stack_8angle | ||||||
|  | }  // namespace esphome | ||||||
| @@ -0,0 +1,27 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  |  | ||||||
|  | #include "../m5stack_8angle.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace m5stack_8angle { | ||||||
|  |  | ||||||
|  | class M5Stack8AngleKnobSensor : public sensor::Sensor, | ||||||
|  |                                 public PollingComponent, | ||||||
|  |                                 public Parented<M5Stack8AngleComponent> { | ||||||
|  |  public: | ||||||
|  |   void update() override; | ||||||
|  |   void set_channel(uint8_t channel) { this->channel_ = channel; }; | ||||||
|  |   void set_bit_depth(AnalogBits bits) { this->bits_ = bits; }; | ||||||
|  |   void set_raw(bool raw) { this->raw_ = raw; }; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   uint8_t channel_; | ||||||
|  |   AnalogBits bits_; | ||||||
|  |   bool raw_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace m5stack_8angle | ||||||
|  | }  // namespace esphome | ||||||
| @@ -1,8 +1,16 @@ | |||||||
| import binascii | import binascii | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
|  | from esphome import automation | ||||||
| from esphome.components import modbus | from esphome.components import modbus | ||||||
| from esphome.const import CONF_ADDRESS, CONF_ID, CONF_NAME, CONF_LAMBDA, CONF_OFFSET | from esphome.const import ( | ||||||
|  |     CONF_ADDRESS, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_NAME, | ||||||
|  |     CONF_LAMBDA, | ||||||
|  |     CONF_OFFSET, | ||||||
|  |     CONF_TRIGGER_ID, | ||||||
|  | ) | ||||||
| from esphome.cpp_helpers import logging | from esphome.cpp_helpers import logging | ||||||
| from .const import ( | from .const import ( | ||||||
|     CONF_BITMASK, |     CONF_BITMASK, | ||||||
| @@ -12,6 +20,7 @@ from .const import ( | |||||||
|     CONF_CUSTOM_COMMAND, |     CONF_CUSTOM_COMMAND, | ||||||
|     CONF_FORCE_NEW_RANGE, |     CONF_FORCE_NEW_RANGE, | ||||||
|     CONF_MODBUS_CONTROLLER_ID, |     CONF_MODBUS_CONTROLLER_ID, | ||||||
|  |     CONF_ON_COMMAND_SENT, | ||||||
|     CONF_REGISTER_COUNT, |     CONF_REGISTER_COUNT, | ||||||
|     CONF_REGISTER_TYPE, |     CONF_REGISTER_TYPE, | ||||||
|     CONF_RESPONSE_SIZE, |     CONF_RESPONSE_SIZE, | ||||||
| @@ -97,6 +106,10 @@ TYPE_REGISTER_MAP = { | |||||||
|     "FP32_R": 2, |     "FP32_R": 2, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | ModbusCommandSentTrigger = modbus_controller_ns.class_( | ||||||
|  |     "ModbusCommandSentTrigger", automation.Trigger.template(cg.int_, cg.int_) | ||||||
|  | ) | ||||||
|  |  | ||||||
| _LOGGER = logging.getLogger(__name__) | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
| ModbusServerRegisterSchema = cv.Schema( | ModbusServerRegisterSchema = cv.Schema( | ||||||
| @@ -120,13 +133,19 @@ CONFIG_SCHEMA = cv.All( | |||||||
|             cv.Optional( |             cv.Optional( | ||||||
|                 CONF_SERVER_REGISTERS, |                 CONF_SERVER_REGISTERS, | ||||||
|             ): cv.ensure_list(ModbusServerRegisterSchema), |             ): cv.ensure_list(ModbusServerRegisterSchema), | ||||||
|  |             cv.Optional(CONF_ON_COMMAND_SENT): automation.validate_automation( | ||||||
|  |                 { | ||||||
|  |                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||||
|  |                         ModbusCommandSentTrigger | ||||||
|  |                     ), | ||||||
|  |                 } | ||||||
|  |             ), | ||||||
|         } |         } | ||||||
|     ) |     ) | ||||||
|     .extend(cv.polling_component_schema("60s")) |     .extend(cv.polling_component_schema("60s")) | ||||||
|     .extend(modbus.modbus_device_schema(0x01)) |     .extend(modbus.modbus_device_schema(0x01)) | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| ModbusItemBaseSchema = cv.Schema( | ModbusItemBaseSchema = cv.Schema( | ||||||
|     { |     { | ||||||
|         cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), |         cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), | ||||||
| @@ -254,6 +273,11 @@ async def to_code(config): | |||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
|     await register_modbus_device(var, config) |     await register_modbus_device(var, config) | ||||||
|  |     for conf in config.get(CONF_ON_COMMAND_SENT, []): | ||||||
|  |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|  |         await automation.build_automation( | ||||||
|  |             trigger, [(int, "function_code"), (int, "address")], conf | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def register_modbus_device(var, config): | async def register_modbus_device(var, config): | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								esphome/components/modbus_controller/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								esphome/components/modbus_controller/automation.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/automation.h" | ||||||
|  | #include "esphome/components/modbus_controller/modbus_controller.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace modbus_controller { | ||||||
|  |  | ||||||
|  | class ModbusCommandSentTrigger : public Trigger<int, int> { | ||||||
|  |  public: | ||||||
|  |   ModbusCommandSentTrigger(ModbusController *a_modbuscontroller) { | ||||||
|  |     a_modbuscontroller->add_on_command_sent_callback( | ||||||
|  |         [this](int function_code, int address) { this->trigger(function_code, address); }); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace modbus_controller | ||||||
|  | }  // namespace esphome | ||||||
| @@ -6,6 +6,7 @@ CONF_CUSTOM_COMMAND = "custom_command" | |||||||
| CONF_FORCE_NEW_RANGE = "force_new_range" | CONF_FORCE_NEW_RANGE = "force_new_range" | ||||||
| CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id" | CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id" | ||||||
| CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode" | CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode" | ||||||
|  | CONF_ON_COMMAND_SENT = "on_command_sent" | ||||||
| CONF_RAW_ENCODE = "raw_encode" | CONF_RAW_ENCODE = "raw_encode" | ||||||
| CONF_REGISTER_COUNT = "register_count" | CONF_REGISTER_COUNT = "register_count" | ||||||
| CONF_REGISTER_TYPE = "register_type" | CONF_REGISTER_TYPE = "register_type" | ||||||
|   | |||||||
| @@ -43,7 +43,11 @@ bool ModbusController::send_next_command_() { | |||||||
|       ESP_LOGV(TAG, "Sending next modbus command to device %d register 0x%02X count %d", this->address_, |       ESP_LOGV(TAG, "Sending next modbus command to device %d register 0x%02X count %d", this->address_, | ||||||
|                command->register_address, command->register_count); |                command->register_address, command->register_count); | ||||||
|       command->send(); |       command->send(); | ||||||
|  |  | ||||||
|       this->last_command_timestamp_ = millis(); |       this->last_command_timestamp_ = millis(); | ||||||
|  |  | ||||||
|  |       this->command_sent_callback_.call((int) command->function_code, command->register_address); | ||||||
|  |  | ||||||
|       // remove from queue if no handler is defined |       // remove from queue if no handler is defined | ||||||
|       if (!command->on_data_func) { |       if (!command->on_data_func) { | ||||||
|         command_queue_.pop_front(); |         command_queue_.pop_front(); | ||||||
| @@ -659,5 +663,9 @@ int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sens | |||||||
|   return value; |   return value; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void ModbusController::add_on_command_sent_callback(std::function<void(int, int)> &&callback) { | ||||||
|  |   this->command_sent_callback_.add(std::move(callback)); | ||||||
|  | } | ||||||
|  |  | ||||||
| }  // namespace modbus_controller | }  // namespace modbus_controller | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -456,6 +456,8 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { | |||||||
|   size_t get_command_queue_length() { return command_queue_.size(); } |   size_t get_command_queue_length() { return command_queue_.size(); } | ||||||
|   /// get if the module is offline, didn't respond the last command |   /// get if the module is offline, didn't respond the last command | ||||||
|   bool get_module_offline() { return module_offline_; } |   bool get_module_offline() { return module_offline_; } | ||||||
|  |   /// Set callback for commands | ||||||
|  |   void add_on_command_sent_callback(std::function<void(int, int)> &&callback); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   /// parse sensormap_ and create range of sequential addresses |   /// parse sensormap_ and create range of sequential addresses | ||||||
| @@ -488,6 +490,7 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { | |||||||
|   bool module_offline_; |   bool module_offline_; | ||||||
|   /// how many updates to skip if module is offline |   /// how many updates to skip if module is offline | ||||||
|   uint16_t offline_skip_updates_; |   uint16_t offline_skip_updates_; | ||||||
|  |   CallbackManager<void(int, int)> command_sent_callback_{}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** Convert vector<uint8_t> response payload to float. | /** Convert vector<uint8_t> response payload to float. | ||||||
|   | |||||||
| @@ -61,6 +61,7 @@ def AUTO_LOAD(): | |||||||
|     return ["json"] |     return ["json"] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONF_DISCOVER_IP = "discover_ip" | ||||||
| CONF_IDF_SEND_ASYNC = "idf_send_async" | CONF_IDF_SEND_ASYNC = "idf_send_async" | ||||||
| CONF_SKIP_CERT_CN_CHECK = "skip_cert_cn_check" | CONF_SKIP_CERT_CN_CHECK = "skip_cert_cn_check" | ||||||
|  |  | ||||||
| @@ -225,6 +226,7 @@ CONFIG_SCHEMA = cv.All( | |||||||
|                 cv.boolean, cv.one_of("CLEAN", upper=True) |                 cv.boolean, cv.one_of("CLEAN", upper=True) | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_DISCOVERY_RETAIN, default=True): cv.boolean, |             cv.Optional(CONF_DISCOVERY_RETAIN, default=True): cv.boolean, | ||||||
|  |             cv.Optional(CONF_DISCOVER_IP, default=True): cv.boolean, | ||||||
|             cv.Optional( |             cv.Optional( | ||||||
|                 CONF_DISCOVERY_PREFIX, default="homeassistant" |                 CONF_DISCOVERY_PREFIX, default="homeassistant" | ||||||
|             ): cv.publish_topic, |             ): cv.publish_topic, | ||||||
| @@ -328,8 +330,12 @@ async def to_code(config): | |||||||
|     discovery_prefix = config[CONF_DISCOVERY_PREFIX] |     discovery_prefix = config[CONF_DISCOVERY_PREFIX] | ||||||
|     discovery_unique_id_generator = config[CONF_DISCOVERY_UNIQUE_ID_GENERATOR] |     discovery_unique_id_generator = config[CONF_DISCOVERY_UNIQUE_ID_GENERATOR] | ||||||
|     discovery_object_id_generator = config[CONF_DISCOVERY_OBJECT_ID_GENERATOR] |     discovery_object_id_generator = config[CONF_DISCOVERY_OBJECT_ID_GENERATOR] | ||||||
|  |     discover_ip = config[CONF_DISCOVER_IP] | ||||||
|  |  | ||||||
|     if not discovery: |     if not discovery: | ||||||
|  |         discovery_prefix = "" | ||||||
|  |  | ||||||
|  |     if not discovery and not discover_ip: | ||||||
|         cg.add(var.disable_discovery()) |         cg.add(var.disable_discovery()) | ||||||
|     elif discovery == "CLEAN": |     elif discovery == "CLEAN": | ||||||
|         cg.add( |         cg.add( | ||||||
| @@ -338,6 +344,7 @@ async def to_code(config): | |||||||
|                 discovery_unique_id_generator, |                 discovery_unique_id_generator, | ||||||
|                 discovery_object_id_generator, |                 discovery_object_id_generator, | ||||||
|                 discovery_retain, |                 discovery_retain, | ||||||
|  |                 discover_ip, | ||||||
|                 True, |                 True, | ||||||
|             ) |             ) | ||||||
|         ) |         ) | ||||||
| @@ -348,6 +355,7 @@ async def to_code(config): | |||||||
|                 discovery_unique_id_generator, |                 discovery_unique_id_generator, | ||||||
|                 discovery_object_id_generator, |                 discovery_object_id_generator, | ||||||
|                 discovery_retain, |                 discovery_retain, | ||||||
|  |                 discover_ip, | ||||||
|             ) |             ) | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -66,7 +66,7 @@ void MQTTClientComponent::setup() { | |||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   if (this->is_discovery_enabled()) { |   if (this->is_discovery_ip_enabled()) { | ||||||
|     this->subscribe( |     this->subscribe( | ||||||
|         "esphome/discover", [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, |         "esphome/discover", [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, | ||||||
|         2); |         2); | ||||||
| @@ -82,7 +82,7 @@ void MQTTClientComponent::setup() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void MQTTClientComponent::send_device_info_() { | void MQTTClientComponent::send_device_info_() { | ||||||
|   if (!this->is_connected() or !this->is_discovery_enabled()) { |   if (!this->is_connected() or !this->is_discovery_ip_enabled()) { | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   std::string topic = "esphome/discover/"; |   std::string topic = "esphome/discover/"; | ||||||
| @@ -99,6 +99,9 @@ void MQTTClientComponent::send_device_info_() { | |||||||
|           } |           } | ||||||
|         } |         } | ||||||
|         root["name"] = App.get_name(); |         root["name"] = App.get_name(); | ||||||
|  |         if (!App.get_friendly_name().empty()) { | ||||||
|  |           root["friendly_name"] = App.get_friendly_name(); | ||||||
|  |         } | ||||||
| #ifdef USE_API | #ifdef USE_API | ||||||
|         root["port"] = api::global_api_server->get_port(); |         root["port"] = api::global_api_server->get_port(); | ||||||
| #endif | #endif | ||||||
| @@ -130,6 +133,10 @@ void MQTTClientComponent::send_device_info_() { | |||||||
| #ifdef USE_DASHBOARD_IMPORT | #ifdef USE_DASHBOARD_IMPORT | ||||||
|         root["package_import_url"] = dashboard_import::get_package_import_url(); |         root["package_import_url"] = dashboard_import::get_package_import_url(); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_API_NOISE | ||||||
|  |         root["api_encryption"] = "Noise_NNpsk0_25519_ChaChaPoly_SHA256"; | ||||||
|  | #endif | ||||||
|       }, |       }, | ||||||
|       2, this->discovery_info_.retain); |       2, this->discovery_info_.retain); | ||||||
| } | } | ||||||
| @@ -140,6 +147,9 @@ void MQTTClientComponent::dump_config() { | |||||||
|                 this->ip_.str().c_str()); |                 this->ip_.str().c_str()); | ||||||
|   ESP_LOGCONFIG(TAG, "  Username: " LOG_SECRET("'%s'"), this->credentials_.username.c_str()); |   ESP_LOGCONFIG(TAG, "  Username: " LOG_SECRET("'%s'"), this->credentials_.username.c_str()); | ||||||
|   ESP_LOGCONFIG(TAG, "  Client ID: " LOG_SECRET("'%s'"), this->credentials_.client_id.c_str()); |   ESP_LOGCONFIG(TAG, "  Client ID: " LOG_SECRET("'%s'"), this->credentials_.client_id.c_str()); | ||||||
|  |   if (this->is_discovery_ip_enabled()) { | ||||||
|  |     ESP_LOGCONFIG(TAG, "  Discovery IP enabled"); | ||||||
|  |   } | ||||||
|   if (!this->discovery_info_.prefix.empty()) { |   if (!this->discovery_info_.prefix.empty()) { | ||||||
|     ESP_LOGCONFIG(TAG, "  Discovery prefix: '%s'", this->discovery_info_.prefix.c_str()); |     ESP_LOGCONFIG(TAG, "  Discovery prefix: '%s'", this->discovery_info_.prefix.c_str()); | ||||||
|     ESP_LOGCONFIG(TAG, "  Discovery retain: %s", YESNO(this->discovery_info_.retain)); |     ESP_LOGCONFIG(TAG, "  Discovery retain: %s", YESNO(this->discovery_info_.retain)); | ||||||
| @@ -581,6 +591,7 @@ void MQTTClientComponent::disable_shutdown_message() { | |||||||
|   this->recalculate_availability_(); |   this->recalculate_availability_(); | ||||||
| } | } | ||||||
| bool MQTTClientComponent::is_discovery_enabled() const { return !this->discovery_info_.prefix.empty(); } | bool MQTTClientComponent::is_discovery_enabled() const { return !this->discovery_info_.prefix.empty(); } | ||||||
|  | bool MQTTClientComponent::is_discovery_ip_enabled() const { return this->discovery_info_.discover_ip; } | ||||||
| const Availability &MQTTClientComponent::get_availability() { return this->availability_; } | const Availability &MQTTClientComponent::get_availability() { return this->availability_; } | ||||||
| void MQTTClientComponent::recalculate_availability_() { | void MQTTClientComponent::recalculate_availability_() { | ||||||
|   if (this->birth_message_.topic.empty() || this->birth_message_.topic != this->last_will_.topic) { |   if (this->birth_message_.topic.empty() || this->birth_message_.topic != this->last_will_.topic) { | ||||||
| @@ -606,8 +617,9 @@ void MQTTClientComponent::set_shutdown_message(MQTTMessage &&message) { this->sh | |||||||
|  |  | ||||||
| void MQTTClientComponent::set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, | void MQTTClientComponent::set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, | ||||||
|                                              MQTTDiscoveryObjectIdGenerator object_id_generator, bool retain, |                                              MQTTDiscoveryObjectIdGenerator object_id_generator, bool retain, | ||||||
|                                              bool clean) { |                                              bool discover_ip, bool clean) { | ||||||
|   this->discovery_info_.prefix = std::move(prefix); |   this->discovery_info_.prefix = std::move(prefix); | ||||||
|  |   this->discovery_info_.discover_ip = discover_ip; | ||||||
|   this->discovery_info_.unique_id_generator = unique_id_generator; |   this->discovery_info_.unique_id_generator = unique_id_generator; | ||||||
|   this->discovery_info_.object_id_generator = object_id_generator; |   this->discovery_info_.object_id_generator = object_id_generator; | ||||||
|   this->discovery_info_.retain = retain; |   this->discovery_info_.retain = retain; | ||||||
|   | |||||||
| @@ -79,6 +79,7 @@ enum MQTTDiscoveryObjectIdGenerator { | |||||||
| struct MQTTDiscoveryInfo { | struct MQTTDiscoveryInfo { | ||||||
|   std::string prefix;  ///< The Home Assistant discovery prefix. Empty means disabled. |   std::string prefix;  ///< The Home Assistant discovery prefix. Empty means disabled. | ||||||
|   bool retain;         ///< Whether to retain discovery messages. |   bool retain;         ///< Whether to retain discovery messages. | ||||||
|  |   bool discover_ip;    ///< Enable the Home Assistant device discovery. | ||||||
|   bool clean; |   bool clean; | ||||||
|   MQTTDiscoveryUniqueIdGenerator unique_id_generator; |   MQTTDiscoveryUniqueIdGenerator unique_id_generator; | ||||||
|   MQTTDiscoveryObjectIdGenerator object_id_generator; |   MQTTDiscoveryObjectIdGenerator object_id_generator; | ||||||
| @@ -122,12 +123,14 @@ class MQTTClientComponent : public Component { | |||||||
|    * @param retain Whether to retain discovery messages. |    * @param retain Whether to retain discovery messages. | ||||||
|    */ |    */ | ||||||
|   void set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, |   void set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, | ||||||
|                           MQTTDiscoveryObjectIdGenerator object_id_generator, bool retain, bool clean = false); |                           MQTTDiscoveryObjectIdGenerator object_id_generator, bool retain, bool discover_ip, | ||||||
|  |                           bool clean = false); | ||||||
|   /// Get Home Assistant discovery info. |   /// Get Home Assistant discovery info. | ||||||
|   const MQTTDiscoveryInfo &get_discovery_info() const; |   const MQTTDiscoveryInfo &get_discovery_info() const; | ||||||
|   /// Globally disable Home Assistant discovery. |   /// Globally disable Home Assistant discovery. | ||||||
|   void disable_discovery(); |   void disable_discovery(); | ||||||
|   bool is_discovery_enabled() const; |   bool is_discovery_enabled() const; | ||||||
|  |   bool is_discovery_ip_enabled() const; | ||||||
|  |  | ||||||
| #if ASYNC_TCP_SSL_ENABLED | #if ASYNC_TCP_SSL_ENABLED | ||||||
|   /** Add a SSL fingerprint to use for TCP SSL connections to the MQTT broker. |   /** Add a SSL fingerprint to use for TCP SSL connections to the MQTT broker. | ||||||
| @@ -290,6 +293,7 @@ class MQTTClientComponent : public Component { | |||||||
|   MQTTDiscoveryInfo discovery_info_{ |   MQTTDiscoveryInfo discovery_info_{ | ||||||
|       .prefix = "homeassistant", |       .prefix = "homeassistant", | ||||||
|       .retain = true, |       .retain = true, | ||||||
|  |       .discover_ip = true, | ||||||
|       .clean = false, |       .clean = false, | ||||||
|       .unique_id_generator = MQTT_LEGACY_UNIQUE_ID_GENERATOR, |       .unique_id_generator = MQTT_LEGACY_UNIQUE_ID_GENERATOR, | ||||||
|       .object_id_generator = MQTT_NONE_OBJECT_ID_GENERATOR, |       .object_id_generator = MQTT_NONE_OBJECT_ID_GENERATOR, | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ bool SmlFile::setup_node(SmlNode *node) { | |||||||
|   uint8_t parse_length = length; |   uint8_t parse_length = length; | ||||||
|   if (has_extended_length) { |   if (has_extended_length) { | ||||||
|     length = (length << 4) + (this->buffer_[this->pos_ + 1] & 0x0f); |     length = (length << 4) + (this->buffer_[this->pos_ + 1] & 0x0f); | ||||||
|     parse_length = length - 1; |     parse_length = length; | ||||||
|     this->pos_ += 1; |     this->pos_ += 1; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -37,7 +37,9 @@ bool SmlFile::setup_node(SmlNode *node) { | |||||||
|   node->type = type & 0x07; |   node->type = type & 0x07; | ||||||
|   node->nodes.clear(); |   node->nodes.clear(); | ||||||
|   node->value_bytes.clear(); |   node->value_bytes.clear(); | ||||||
|   if (this->buffer_[this->pos_] == 0x00) {  // end of message |  | ||||||
|  |   // if the list is a has_extended_length list with e.g. 16 elements this is a 0x00 byte but not the end of message | ||||||
|  |   if (!has_extended_length && this->buffer_[this->pos_] == 0x00) {  // end of message | ||||||
|     this->pos_ += 1; |     this->pos_ += 1; | ||||||
|   } else if (is_list) {  // list |   } else if (is_list) {  // list | ||||||
|     this->pos_ += 1; |     this->pos_ += 1; | ||||||
|   | |||||||
| @@ -832,7 +832,6 @@ def time_of_day(value): | |||||||
|  |  | ||||||
|  |  | ||||||
| def date_time(date: bool, time: bool): | def date_time(date: bool, time: bool): | ||||||
|  |  | ||||||
|     pattern_str = r"^"  # Start of string |     pattern_str = r"^"  # Start of string | ||||||
|     if date: |     if date: | ||||||
|         pattern_str += r"\d{4}-\d{1,2}-\d{1,2}" |         pattern_str += r"\d{4}-\d{1,2}-\d{1,2}" | ||||||
| @@ -2038,6 +2037,7 @@ def require_framework_version( | |||||||
|     esp32_arduino=None, |     esp32_arduino=None, | ||||||
|     esp8266_arduino=None, |     esp8266_arduino=None, | ||||||
|     rp2040_arduino=None, |     rp2040_arduino=None, | ||||||
|  |     host=None, | ||||||
|     max_version=False, |     max_version=False, | ||||||
|     extra_message=None, |     extra_message=None, | ||||||
| ): | ): | ||||||
| @@ -2072,6 +2072,13 @@ def require_framework_version( | |||||||
|                     msg += f". {extra_message}" |                     msg += f". {extra_message}" | ||||||
|                 raise Invalid(msg) |                 raise Invalid(msg) | ||||||
|             required = rp2040_arduino |             required = rp2040_arduino | ||||||
|  |         elif CORE.is_host and framework == "host": | ||||||
|  |             if host is None: | ||||||
|  |                 msg = "This feature is incompatible with host platform" | ||||||
|  |                 if extra_message: | ||||||
|  |                     msg += f". {extra_message}" | ||||||
|  |                 raise Invalid(msg) | ||||||
|  |             required = host | ||||||
|         else: |         else: | ||||||
|             raise Invalid( |             raise Invalid( | ||||||
|                 f""" |                 f""" | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								tests/components/apds9306/common.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								tests/components/apds9306/common.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | i2c: | ||||||
|  |   - id: i2c_apds9306 | ||||||
|  |     scl: ${scl_pin} | ||||||
|  |     sda: ${sda_pin} | ||||||
|  |  | ||||||
|  | sensor: | ||||||
|  |   - platform: apds9306 | ||||||
|  |     name: "APDS9306 Light Level" | ||||||
|  |     gain: 3 | ||||||
|  |     bit_width: 16 | ||||||
|  |     measurement_rate: 2000ms | ||||||
|  |     update_interval: 60s | ||||||
							
								
								
									
										5
									
								
								tests/components/apds9306/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/apds9306/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO22 | ||||||
|  |   sda_pin: GPIO21 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/apds9306/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/apds9306/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO5 | ||||||
|  |   sda_pin: GPIO4 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/apds9306/test.esp32-c3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/apds9306/test.esp32-c3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO5 | ||||||
|  |   sda_pin: GPIO4 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/apds9306/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/apds9306/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO22 | ||||||
|  |   sda_pin: GPIO21 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/apds9306/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/apds9306/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO5 | ||||||
|  |   sda_pin: GPIO4 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/apds9306/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/apds9306/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   scl_pin: GPIO5 | ||||||
|  |   sda_pin: GPIO4 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
| @@ -3,6 +3,13 @@ remote_transmitter: | |||||||
|   carrier_duty_percent: 50% |   carrier_duty_percent: 50% | ||||||
|  |  | ||||||
| climate: | climate: | ||||||
|  |   - platform: heatpumpir | ||||||
|  |     protocol: mitsubishi_heavy_zm | ||||||
|  |     horizontal_default: left | ||||||
|  |     vertical_default: up | ||||||
|  |     name: HeatpumpIR Climate | ||||||
|  |     min_temperature: 18 | ||||||
|  |     max_temperature: 30 | ||||||
|   - platform: heatpumpir |   - platform: heatpumpir | ||||||
|     protocol: daikin |     protocol: daikin | ||||||
|     horizontal_default: mleft |     horizontal_default: mleft | ||||||
|   | |||||||
							
								
								
									
										30
									
								
								tests/components/m5stack_8angle/common.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								tests/components/m5stack_8angle/common.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | i2c: | ||||||
|  |   sda: 0 | ||||||
|  |   scl: 1 | ||||||
|  |   id: bus_external | ||||||
|  |  | ||||||
|  | m5stack_8angle: | ||||||
|  |   i2c_id: bus_external | ||||||
|  |   id: m5stack_8angle_base | ||||||
|  |  | ||||||
|  | light: | ||||||
|  |   - platform: m5stack_8angle | ||||||
|  |     m5stack_8angle_id: m5stack_8angle_base | ||||||
|  |     id: m8_angle_leds | ||||||
|  |     name: Lights | ||||||
|  |     effects: | ||||||
|  |       - addressable_scan: | ||||||
|  |  | ||||||
|  | sensor: | ||||||
|  |   - platform: m5stack_8angle | ||||||
|  |     m5stack_8angle_id: m5stack_8angle_base | ||||||
|  |     channel: 1 | ||||||
|  |     name: Knob 1 | ||||||
|  |   - platform: m5stack_8angle | ||||||
|  |     m5stack_8angle_id: m5stack_8angle_base | ||||||
|  |     channel: 2 | ||||||
|  |     name: Knob 2 | ||||||
|  | binary_sensor: | ||||||
|  |   - platform: m5stack_8angle | ||||||
|  |     m5stack_8angle_id: m5stack_8angle_base | ||||||
|  |     name: Switch | ||||||
							
								
								
									
										1
									
								
								tests/components/m5stack_8angle/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/components/m5stack_8angle/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										1
									
								
								tests/components/m5stack_8angle/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/components/m5stack_8angle/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										1
									
								
								tests/components/m5stack_8angle/test.esp32-c3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/components/m5stack_8angle/test.esp32-c3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										1
									
								
								tests/components/m5stack_8angle/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/components/m5stack_8angle/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										1
									
								
								tests/components/m5stack_8angle/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/components/m5stack_8angle/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										1
									
								
								tests/components/m5stack_8angle/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/components/m5stack_8angle/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <<: !include common.yaml | ||||||
		Reference in New Issue
	
	Block a user