mirror of
https://github.com/esphome/esphome.git
synced 2024-12-26 15:34:53 +01:00
Merge branch 'dev' into optolink
This commit is contained in:
commit
88d282200a
19 changed files with 523 additions and 29 deletions
2
.github/workflows/needs-docs.yml
vendored
2
.github/workflows/needs-docs.yml
vendored
|
@ -10,7 +10,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check for needs-docs label
|
- name: Check for needs-docs label
|
||||||
uses: actions/github-script@v6.4.1
|
uses: actions/github-script@v7.0.1
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
|
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
|
||||||
|
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
@ -203,7 +203,7 @@ jobs:
|
||||||
needs: [deploy-manifest]
|
needs: [deploy-manifest]
|
||||||
steps:
|
steps:
|
||||||
- name: Trigger Workflow
|
- name: Trigger Workflow
|
||||||
uses: actions/github-script@v6.4.1
|
uses: actions/github-script@v7.0.1
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
|
github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
|
|
|
@ -90,6 +90,7 @@ esphome/components/duty_time/* @dudanov
|
||||||
esphome/components/ee895/* @Stock-M
|
esphome/components/ee895/* @Stock-M
|
||||||
esphome/components/ektf2232/* @jesserockz
|
esphome/components/ektf2232/* @jesserockz
|
||||||
esphome/components/emc2101/* @ellull
|
esphome/components/emc2101/* @ellull
|
||||||
|
esphome/components/ens160/* @vincentscode
|
||||||
esphome/components/ens210/* @itn3rd77
|
esphome/components/ens210/* @itn3rd77
|
||||||
esphome/components/esp32/* @esphome/core
|
esphome/components/esp32/* @esphome/core
|
||||||
esphome/components/esp32_ble/* @Rapsssito @jesserockz
|
esphome/components/esp32_ble/* @Rapsssito @jesserockz
|
||||||
|
|
1
esphome/components/ens160/__init__.py
Normal file
1
esphome/components/ens160/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
CODEOWNERS = ["@vincentscode"]
|
321
esphome/components/ens160/ens160.cpp
Normal file
321
esphome/components/ens160/ens160.cpp
Normal file
|
@ -0,0 +1,321 @@
|
||||||
|
// ENS160 sensor with I2C interface from ScioSense
|
||||||
|
//
|
||||||
|
// Datasheet: https://www.sciosense.com/wp-content/uploads/documents/SC-001224-DS-7-ENS160-Datasheet.pdf
|
||||||
|
//
|
||||||
|
// Implementation based on:
|
||||||
|
// https://github.com/sciosense/ENS160_driver
|
||||||
|
|
||||||
|
#include "ens160.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ens160 {
|
||||||
|
|
||||||
|
static const char *const TAG = "ens160";
|
||||||
|
|
||||||
|
static const uint8_t ENS160_BOOTING = 10;
|
||||||
|
|
||||||
|
static const uint16_t ENS160_PART_ID = 0x0160;
|
||||||
|
|
||||||
|
static const uint8_t ENS160_REG_PART_ID = 0x00;
|
||||||
|
static const uint8_t ENS160_REG_OPMODE = 0x10;
|
||||||
|
static const uint8_t ENS160_REG_CONFIG = 0x11;
|
||||||
|
static const uint8_t ENS160_REG_COMMAND = 0x12;
|
||||||
|
static const uint8_t ENS160_REG_TEMP_IN = 0x13;
|
||||||
|
static const uint8_t ENS160_REG_DATA_STATUS = 0x20;
|
||||||
|
static const uint8_t ENS160_REG_DATA_AQI = 0x21;
|
||||||
|
static const uint8_t ENS160_REG_DATA_TVOC = 0x22;
|
||||||
|
static const uint8_t ENS160_REG_DATA_ECO2 = 0x24;
|
||||||
|
|
||||||
|
static const uint8_t ENS160_REG_GPR_READ_0 = 0x48;
|
||||||
|
static const uint8_t ENS160_REG_GPR_READ_4 = ENS160_REG_GPR_READ_0 + 4;
|
||||||
|
|
||||||
|
static const uint8_t ENS160_COMMAND_NOP = 0x00;
|
||||||
|
static const uint8_t ENS160_COMMAND_CLRGPR = 0xCC;
|
||||||
|
static const uint8_t ENS160_COMMAND_GET_APPVER = 0x0E;
|
||||||
|
|
||||||
|
static const uint8_t ENS160_OPMODE_RESET = 0xF0;
|
||||||
|
static const uint8_t ENS160_OPMODE_IDLE = 0x01;
|
||||||
|
static const uint8_t ENS160_OPMODE_STD = 0x02;
|
||||||
|
|
||||||
|
static const uint8_t ENS160_DATA_STATUS_STATAS = 0x80;
|
||||||
|
static const uint8_t ENS160_DATA_STATUS_STATER = 0x40;
|
||||||
|
static const uint8_t ENS160_DATA_STATUS_VALIDITY = 0x0C;
|
||||||
|
static const uint8_t ENS160_DATA_STATUS_NEWDAT = 0x02;
|
||||||
|
static const uint8_t ENS160_DATA_STATUS_NEWGPR = 0x01;
|
||||||
|
|
||||||
|
// helps remove reserved bits in aqi data register
|
||||||
|
static const uint8_t ENS160_DATA_AQI = 0x07;
|
||||||
|
|
||||||
|
void ENS160Component::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up ENS160...");
|
||||||
|
|
||||||
|
// check part_id
|
||||||
|
uint16_t part_id;
|
||||||
|
if (!this->read_bytes(ENS160_REG_PART_ID, reinterpret_cast<uint8_t *>(&part_id), 2)) {
|
||||||
|
this->error_code_ = COMMUNICATION_FAILED;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (part_id != ENS160_PART_ID) {
|
||||||
|
this->error_code_ = INVALID_ID;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set mode to reset
|
||||||
|
if (!this->write_byte(ENS160_REG_OPMODE, ENS160_OPMODE_RESET)) {
|
||||||
|
this->error_code_ = WRITE_FAILED;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delay(ENS160_BOOTING);
|
||||||
|
|
||||||
|
// check status
|
||||||
|
uint8_t status_value;
|
||||||
|
if (!this->read_byte(ENS160_REG_DATA_STATUS, &status_value)) {
|
||||||
|
this->error_code_ = READ_FAILED;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->validity_flag_ = static_cast<ValidityFlag>((ENS160_DATA_STATUS_VALIDITY & status_value) >> 2);
|
||||||
|
|
||||||
|
if (this->validity_flag_ == INVALID_OUTPUT) {
|
||||||
|
this->error_code_ = VALIDITY_INVALID;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set mode to idle
|
||||||
|
if (!this->write_byte(ENS160_REG_OPMODE, ENS160_OPMODE_IDLE)) {
|
||||||
|
this->error_code_ = WRITE_FAILED;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// clear command
|
||||||
|
if (!this->write_byte(ENS160_REG_COMMAND, ENS160_COMMAND_NOP)) {
|
||||||
|
this->error_code_ = WRITE_FAILED;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->write_byte(ENS160_REG_COMMAND, ENS160_COMMAND_CLRGPR)) {
|
||||||
|
this->error_code_ = WRITE_FAILED;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read firmware version
|
||||||
|
if (!this->write_byte(ENS160_REG_COMMAND, ENS160_COMMAND_GET_APPVER)) {
|
||||||
|
this->error_code_ = WRITE_FAILED;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint8_t version_data[3];
|
||||||
|
if (!this->read_bytes(ENS160_REG_GPR_READ_4, version_data, 3)) {
|
||||||
|
this->error_code_ = READ_FAILED;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->firmware_ver_major_ = version_data[0];
|
||||||
|
this->firmware_ver_minor_ = version_data[1];
|
||||||
|
this->firmware_ver_build_ = version_data[2];
|
||||||
|
|
||||||
|
// set mode to standard
|
||||||
|
if (!this->write_byte(ENS160_REG_OPMODE, ENS160_OPMODE_STD)) {
|
||||||
|
this->error_code_ = WRITE_FAILED;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read opmode and check standard mode is achieved before finishing Setup
|
||||||
|
uint8_t op_mode;
|
||||||
|
if (!this->read_byte(ENS160_REG_OPMODE, &op_mode)) {
|
||||||
|
this->error_code_ = READ_FAILED;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op_mode != ENS160_OPMODE_STD) {
|
||||||
|
this->error_code_ = STD_OPMODE_FAILED;
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ENS160Component::update() {
|
||||||
|
uint8_t status_value, data_ready;
|
||||||
|
|
||||||
|
if (!this->read_byte(ENS160_REG_DATA_STATUS, &status_value)) {
|
||||||
|
ESP_LOGW(TAG, "Error reading status register");
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// verbose status logging
|
||||||
|
ESP_LOGV(TAG, "Status: ENS160 STATAS bit 0x%x",
|
||||||
|
(ENS160_DATA_STATUS_STATAS & (status_value)) == ENS160_DATA_STATUS_STATAS);
|
||||||
|
ESP_LOGV(TAG, "Status: ENS160 STATER bit 0x%x",
|
||||||
|
(ENS160_DATA_STATUS_STATER & (status_value)) == ENS160_DATA_STATUS_STATER);
|
||||||
|
ESP_LOGV(TAG, "Status: ENS160 VALIDITY FLAG 0x%02x", (ENS160_DATA_STATUS_VALIDITY & status_value) >> 2);
|
||||||
|
ESP_LOGV(TAG, "Status: ENS160 NEWDAT bit 0x%x",
|
||||||
|
(ENS160_DATA_STATUS_NEWDAT & (status_value)) == ENS160_DATA_STATUS_NEWDAT);
|
||||||
|
ESP_LOGV(TAG, "Status: ENS160 NEWGPR bit 0x%x",
|
||||||
|
(ENS160_DATA_STATUS_NEWGPR & (status_value)) == ENS160_DATA_STATUS_NEWGPR);
|
||||||
|
|
||||||
|
data_ready = ENS160_DATA_STATUS_NEWDAT & status_value;
|
||||||
|
this->validity_flag_ = static_cast<ValidityFlag>((ENS160_DATA_STATUS_VALIDITY & status_value) >> 2);
|
||||||
|
|
||||||
|
switch (validity_flag_) {
|
||||||
|
case NORMAL_OPERATION:
|
||||||
|
if (data_ready != ENS160_DATA_STATUS_NEWDAT) {
|
||||||
|
ESP_LOGD(TAG, "ENS160 readings unavailable - Normal Operation but readings not ready");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case INITIAL_STARTUP:
|
||||||
|
if (!this->initial_startup_) {
|
||||||
|
this->initial_startup_ = true;
|
||||||
|
ESP_LOGI(TAG, "ENS160 readings unavailable - 1 hour startup required after first power on");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case WARMING_UP:
|
||||||
|
if (!this->warming_up_) {
|
||||||
|
this->warming_up_ = true;
|
||||||
|
ESP_LOGI(TAG, "ENS160 readings not available yet - Warming up requires 3 minutes");
|
||||||
|
this->send_env_data_();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case INVALID_OUTPUT:
|
||||||
|
ESP_LOGE(TAG, "ENS160 Invalid Status - No Invalid Output");
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read new data
|
||||||
|
uint16_t data_eco2;
|
||||||
|
if (!this->read_bytes(ENS160_REG_DATA_ECO2, reinterpret_cast<uint8_t *>(&data_eco2), 2)) {
|
||||||
|
ESP_LOGW(TAG, "Error reading eCO2 data register");
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this->co2_ != nullptr) {
|
||||||
|
this->co2_->publish_state(data_eco2);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t data_tvoc;
|
||||||
|
if (!this->read_bytes(ENS160_REG_DATA_TVOC, reinterpret_cast<uint8_t *>(&data_tvoc), 2)) {
|
||||||
|
ESP_LOGW(TAG, "Error reading TVOC data register");
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this->tvoc_ != nullptr) {
|
||||||
|
this->tvoc_->publish_state(data_tvoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t data_aqi;
|
||||||
|
if (!this->read_byte(ENS160_REG_DATA_AQI, &data_aqi)) {
|
||||||
|
ESP_LOGW(TAG, "Error reading AQI data register");
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this->aqi_ != nullptr) {
|
||||||
|
// remove reserved bits, just in case they are used in future
|
||||||
|
data_aqi = ENS160_DATA_AQI & data_aqi;
|
||||||
|
|
||||||
|
this->aqi_->publish_state(data_aqi);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->status_clear_warning();
|
||||||
|
|
||||||
|
// set temperature and humidity compensation data
|
||||||
|
this->send_env_data_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ENS160Component::send_env_data_() {
|
||||||
|
if (this->temperature_ == nullptr && this->humidity_ == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float temperature = NAN;
|
||||||
|
if (this->temperature_ != nullptr)
|
||||||
|
temperature = this->temperature_->state;
|
||||||
|
|
||||||
|
if (std::isnan(temperature) || temperature < -40.0f || temperature > 85.0f) {
|
||||||
|
ESP_LOGW(TAG, "Invalid external temperature - compensation values not updated");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
ESP_LOGV(TAG, "External temperature compensation: %.1f°C", temperature);
|
||||||
|
}
|
||||||
|
|
||||||
|
float humidity = NAN;
|
||||||
|
if (this->humidity_ != nullptr)
|
||||||
|
humidity = this->humidity_->state;
|
||||||
|
|
||||||
|
if (std::isnan(humidity) || humidity < 0.0f || humidity > 100.0f) {
|
||||||
|
ESP_LOGW(TAG, "Invalid external humidity - compensation values not updated");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
ESP_LOGV(TAG, "External humidity compensation: %.1f%%", humidity);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t t = (uint16_t) ((temperature + 273.15f) * 64.0f);
|
||||||
|
uint16_t h = (uint16_t) (humidity * 512.0f);
|
||||||
|
|
||||||
|
uint8_t data[4];
|
||||||
|
data[0] = t & 0xff;
|
||||||
|
data[1] = (t >> 8) & 0xff;
|
||||||
|
data[2] = h & 0xff;
|
||||||
|
data[3] = (h >> 8) & 0xff;
|
||||||
|
|
||||||
|
if (!this->write_bytes(ENS160_REG_TEMP_IN, data, 4)) {
|
||||||
|
ESP_LOGE(TAG, "Error writing compensation values");
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ENS160Component::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "ENS160:");
|
||||||
|
|
||||||
|
switch (this->error_code_) {
|
||||||
|
case COMMUNICATION_FAILED:
|
||||||
|
ESP_LOGE(TAG, "Communication failed! Is the sensor connected?");
|
||||||
|
break;
|
||||||
|
case READ_FAILED:
|
||||||
|
ESP_LOGE(TAG, "Error reading from register");
|
||||||
|
break;
|
||||||
|
case WRITE_FAILED:
|
||||||
|
ESP_LOGE(TAG, "Error writing to register");
|
||||||
|
break;
|
||||||
|
case INVALID_ID:
|
||||||
|
ESP_LOGE(TAG, "Sensor reported an invalid ID. Is this a ENS160?");
|
||||||
|
break;
|
||||||
|
case VALIDITY_INVALID:
|
||||||
|
ESP_LOGE(TAG, "Invalid Device Status - No valid output");
|
||||||
|
break;
|
||||||
|
case STD_OPMODE_FAILED:
|
||||||
|
ESP_LOGE(TAG, "Device failed to achieve Standard Operating Mode");
|
||||||
|
break;
|
||||||
|
case NONE:
|
||||||
|
ESP_LOGD(TAG, "Setup successful");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "Firmware Version: %d.%d.%d", this->firmware_ver_major_, this->firmware_ver_minor_,
|
||||||
|
this->firmware_ver_build_);
|
||||||
|
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
LOG_SENSOR(" ", "CO2 Sensor:", this->co2_);
|
||||||
|
LOG_SENSOR(" ", "TVOC Sensor:", this->tvoc_);
|
||||||
|
LOG_SENSOR(" ", "AQI Sensor:", this->aqi_);
|
||||||
|
|
||||||
|
if (this->temperature_ != nullptr && this->humidity_ != nullptr) {
|
||||||
|
LOG_SENSOR(" ", " Temperature Compensation:", this->temperature_);
|
||||||
|
LOG_SENSOR(" ", " Humidity Compensation:", this->humidity_);
|
||||||
|
} else {
|
||||||
|
ESP_LOGCONFIG(TAG, " Compensation: Not configured");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ens160
|
||||||
|
} // namespace esphome
|
60
esphome/components/ens160/ens160.h
Normal file
60
esphome/components/ens160/ens160.h
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ens160 {
|
||||||
|
|
||||||
|
class ENS160Component : public PollingComponent, public i2c::I2CDevice, public sensor::Sensor {
|
||||||
|
public:
|
||||||
|
void set_co2(sensor::Sensor *co2) { co2_ = co2; }
|
||||||
|
void set_tvoc(sensor::Sensor *tvoc) { tvoc_ = tvoc; }
|
||||||
|
void set_aqi(sensor::Sensor *aqi) { aqi_ = aqi; }
|
||||||
|
|
||||||
|
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
|
||||||
|
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
|
void update() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void send_env_data_();
|
||||||
|
|
||||||
|
enum ErrorCode {
|
||||||
|
NONE = 0,
|
||||||
|
COMMUNICATION_FAILED,
|
||||||
|
INVALID_ID,
|
||||||
|
VALIDITY_INVALID,
|
||||||
|
READ_FAILED,
|
||||||
|
WRITE_FAILED,
|
||||||
|
STD_OPMODE_FAILED,
|
||||||
|
} error_code_{NONE};
|
||||||
|
|
||||||
|
enum ValidityFlag {
|
||||||
|
NORMAL_OPERATION = 0,
|
||||||
|
WARMING_UP,
|
||||||
|
INITIAL_STARTUP,
|
||||||
|
INVALID_OUTPUT,
|
||||||
|
} validity_flag_;
|
||||||
|
|
||||||
|
bool warming_up_{false};
|
||||||
|
bool initial_startup_{false};
|
||||||
|
|
||||||
|
uint8_t firmware_ver_major_{0};
|
||||||
|
uint8_t firmware_ver_minor_{0};
|
||||||
|
uint8_t firmware_ver_build_{0};
|
||||||
|
|
||||||
|
sensor::Sensor *co2_{nullptr};
|
||||||
|
sensor::Sensor *tvoc_{nullptr};
|
||||||
|
sensor::Sensor *aqi_{nullptr};
|
||||||
|
|
||||||
|
sensor::Sensor *humidity_{nullptr};
|
||||||
|
sensor::Sensor *temperature_{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ens160
|
||||||
|
} // namespace esphome
|
88
esphome/components/ens160/sensor.py
Normal file
88
esphome/components/ens160/sensor.py
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import i2c, sensor
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ECO2,
|
||||||
|
CONF_HUMIDITY,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
CONF_TVOC,
|
||||||
|
DEVICE_CLASS_AQI,
|
||||||
|
DEVICE_CLASS_CARBON_DIOXIDE,
|
||||||
|
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||||
|
ICON_CHEMICAL_WEAPON,
|
||||||
|
ICON_MOLECULE_CO2,
|
||||||
|
ICON_RADIATOR,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_PARTS_PER_BILLION,
|
||||||
|
UNIT_PARTS_PER_MILLION,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@vincentscode"]
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
|
ens160_ns = cg.esphome_ns.namespace("ens160")
|
||||||
|
ENS160Component = ens160_ns.class_(
|
||||||
|
"ENS160Component", cg.PollingComponent, i2c.I2CDevice, sensor.Sensor
|
||||||
|
)
|
||||||
|
|
||||||
|
CONF_AQI = "aqi"
|
||||||
|
CONF_COMPENSATION = "compensation"
|
||||||
|
UNIT_INDEX = "index"
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(ENS160Component),
|
||||||
|
cv.Required(CONF_ECO2): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||||
|
icon=ICON_MOLECULE_CO2,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Required(CONF_TVOC): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_PARTS_PER_BILLION,
|
||||||
|
icon=ICON_RADIATOR,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Required(CONF_AQI): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_INDEX,
|
||||||
|
icon=ICON_CHEMICAL_WEAPON,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_AQI,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_COMPENSATION): cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_TEMPERATURE): cv.use_id(sensor.Sensor),
|
||||||
|
cv.Required(CONF_HUMIDITY): cv.use_id(sensor.Sensor),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x53))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
sens = await sensor.new_sensor(config[CONF_ECO2])
|
||||||
|
cg.add(var.set_co2(sens))
|
||||||
|
sens = await sensor.new_sensor(config[CONF_TVOC])
|
||||||
|
cg.add(var.set_tvoc(sens))
|
||||||
|
sens = await sensor.new_sensor(config[CONF_AQI])
|
||||||
|
cg.add(var.set_aqi(sens))
|
||||||
|
|
||||||
|
if CONF_COMPENSATION in config:
|
||||||
|
compensation_config = config[CONF_COMPENSATION]
|
||||||
|
sens = await cg.get_variable(compensation_config[CONF_TEMPERATURE])
|
||||||
|
cg.add(var.set_temperature(sens))
|
||||||
|
sens = await cg.get_variable(compensation_config[CONF_HUMIDITY])
|
||||||
|
cg.add(var.set_humidity(sens))
|
|
@ -93,7 +93,8 @@ bool Nextion::check_connect_() {
|
||||||
connect_info.push_back(response.substr(start, end - start));
|
connect_info.push_back(response.substr(start, end - start));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connect_info.size() == 7) {
|
this->is_detected_ = (connect_info.size() == 7);
|
||||||
|
if (this->is_detected_) {
|
||||||
ESP_LOGN(TAG, "Received connect_info %zu", connect_info.size());
|
ESP_LOGN(TAG, "Received connect_info %zu", connect_info.size());
|
||||||
|
|
||||||
this->device_model_ = connect_info[2];
|
this->device_model_ = connect_info[2];
|
||||||
|
|
|
@ -48,10 +48,12 @@ class NextionBase {
|
||||||
|
|
||||||
bool is_sleeping() { return this->is_sleeping_; }
|
bool is_sleeping() { return this->is_sleeping_; }
|
||||||
bool is_setup() { return this->is_setup_; }
|
bool is_setup() { return this->is_setup_; }
|
||||||
|
bool is_detected() { return this->is_detected_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool is_setup_ = false;
|
bool is_setup_ = false;
|
||||||
bool is_sleeping_ = false;
|
bool is_sleeping_ = false;
|
||||||
|
bool is_detected_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace nextion
|
} // namespace nextion
|
||||||
|
|
|
@ -75,12 +75,13 @@ def validate_rx_pin(value):
|
||||||
def validate_invert_esp32(config):
|
def validate_invert_esp32(config):
|
||||||
if (
|
if (
|
||||||
CORE.is_esp32
|
CORE.is_esp32
|
||||||
|
and CORE.using_arduino
|
||||||
and CONF_TX_PIN in config
|
and CONF_TX_PIN in config
|
||||||
and CONF_RX_PIN in config
|
and CONF_RX_PIN in config
|
||||||
and config[CONF_TX_PIN][CONF_INVERTED] != config[CONF_RX_PIN][CONF_INVERTED]
|
and config[CONF_TX_PIN][CONF_INVERTED] != config[CONF_RX_PIN][CONF_INVERTED]
|
||||||
):
|
):
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
"Different invert values for TX and RX pin are not (yet) supported for ESP32."
|
"Different invert values for TX and RX pin are not supported for ESP32 when using Arduino."
|
||||||
)
|
)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
|
@ -278,10 +278,13 @@ std::string str_snake_case(const std::string &str) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
std::string str_sanitize(const std::string &str) {
|
std::string str_sanitize(const std::string &str) {
|
||||||
std::string out;
|
std::string out = str;
|
||||||
std::copy_if(str.begin(), str.end(), std::back_inserter(out), [](const char &c) {
|
std::replace_if(
|
||||||
return c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
out.begin(), out.end(),
|
||||||
});
|
[](const char &c) {
|
||||||
|
return !(c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'));
|
||||||
|
},
|
||||||
|
'_');
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
std::string str_snprintf(const char *fmt, size_t len, ...) {
|
std::string str_snprintf(const char *fmt, size_t len, ...) {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import tornado.process
|
||||||
import tornado.queues
|
import tornado.queues
|
||||||
import tornado.web
|
import tornado.web
|
||||||
import tornado.websocket
|
import tornado.websocket
|
||||||
|
import tornado.httputil
|
||||||
import yaml
|
import yaml
|
||||||
from tornado.log import access_log
|
from tornado.log import access_log
|
||||||
|
|
||||||
|
@ -136,7 +137,15 @@ def websocket_method(name):
|
||||||
|
|
||||||
@websocket_class
|
@websocket_class
|
||||||
class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
|
class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
|
||||||
def __init__(self, application, request, **kwargs):
|
"""Base class for ESPHome websocket commands."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
application: tornado.web.Application,
|
||||||
|
request: tornado.httputil.HTTPServerRequest,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the websocket."""
|
||||||
super().__init__(application, request, **kwargs)
|
super().__init__(application, request, **kwargs)
|
||||||
self._proc = None
|
self._proc = None
|
||||||
self._queue = None
|
self._queue = None
|
||||||
|
@ -145,6 +154,12 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
|
||||||
# use Popen() with a reading thread instead
|
# use Popen() with a reading thread instead
|
||||||
self._use_popen = os.name == "nt"
|
self._use_popen = os.name == "nt"
|
||||||
|
|
||||||
|
def open(self, *args: str, **kwargs: str) -> None:
|
||||||
|
"""Handle new WebSocket connection."""
|
||||||
|
# Ensure messages from the subprocess are sent immediately
|
||||||
|
# to avoid a 200-500ms delay when nodelay is not set.
|
||||||
|
self.set_nodelay(True)
|
||||||
|
|
||||||
@authenticated
|
@authenticated
|
||||||
async def on_message( # pylint: disable=invalid-overridden-method
|
async def on_message( # pylint: disable=invalid-overridden-method
|
||||||
self, message: str
|
self, message: str
|
||||||
|
|
|
@ -357,6 +357,9 @@ def snake_case(value):
|
||||||
return value.replace(" ", "_").lower()
|
return value.replace(" ", "_").lower()
|
||||||
|
|
||||||
|
|
||||||
|
_DISALLOWED_CHARS = re.compile(r"[^a-zA-Z0-9_]")
|
||||||
|
|
||||||
|
|
||||||
def sanitize(value):
|
def sanitize(value):
|
||||||
"""Same behaviour as `helpers.cpp` method `str_sanitize`."""
|
"""Same behaviour as `helpers.cpp` method `str_sanitize`."""
|
||||||
return re.sub("[^-_0-9a-zA-Z]", r"", value)
|
return _DISALLOWED_CHARS.sub("_", value)
|
||||||
|
|
|
@ -10,7 +10,7 @@ platformio==6.1.11 # When updating platformio, also update Dockerfile
|
||||||
esptool==4.6.2
|
esptool==4.6.2
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
esphome-dashboard==20231107.0
|
esphome-dashboard==20231107.0
|
||||||
aioesphomeapi==18.5.5
|
aioesphomeapi==18.5.9
|
||||||
zeroconf==0.127.0
|
zeroconf==0.127.0
|
||||||
python-magic==0.4.27
|
python-magic==0.4.27
|
||||||
|
|
||||||
|
|
12
script/test
12
script/test
|
@ -6,12 +6,6 @@ cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
set -x
|
set -x
|
||||||
|
|
||||||
esphome compile tests/test1.yaml
|
for f in ./tests/test*.yaml; do
|
||||||
esphome compile tests/test2.yaml
|
esphome compile $f
|
||||||
esphome compile tests/test3.yaml
|
done
|
||||||
esphome compile tests/test3.1.yaml
|
|
||||||
esphome compile tests/test4.yaml
|
|
||||||
esphome compile tests/test5.yaml
|
|
||||||
esphome compile tests/test6.yaml
|
|
||||||
esphome compile tests/test7.yaml
|
|
||||||
esphome compile tests/test8.yaml
|
|
||||||
|
|
|
@ -225,6 +225,13 @@ sensor:
|
||||||
name: "ADE7953 Reactive Power B"
|
name: "ADE7953 Reactive Power B"
|
||||||
update_interval: 1s
|
update_interval: 1s
|
||||||
|
|
||||||
|
- platform: ens160
|
||||||
|
eco2:
|
||||||
|
name: "ENS160 eCO2"
|
||||||
|
tvoc:
|
||||||
|
name: "ENS160 Total Volatile Organic Compounds"
|
||||||
|
aqi:
|
||||||
|
name: "ENS160 Air Quality Index"
|
||||||
- platform: tmp102
|
- platform: tmp102
|
||||||
name: TMP102 Temperature
|
name: TMP102 Temperature
|
||||||
- platform: hm3301
|
- platform: hm3301
|
||||||
|
@ -424,7 +431,6 @@ switch:
|
||||||
direction: BACKWARD
|
direction: BACKWARD
|
||||||
id: test_motor
|
id: test_motor
|
||||||
|
|
||||||
|
|
||||||
custom_component:
|
custom_component:
|
||||||
lambda: |-
|
lambda: |-
|
||||||
auto s = new CustomComponent();
|
auto s = new CustomComponent();
|
||||||
|
@ -613,7 +619,6 @@ mcp23017:
|
||||||
mcp23008:
|
mcp23008:
|
||||||
id: mcp23008_hub
|
id: mcp23008_hub
|
||||||
|
|
||||||
|
|
||||||
light:
|
light:
|
||||||
- platform: hbridge
|
- platform: hbridge
|
||||||
name: Icicle Lights
|
name: Icicle Lights
|
||||||
|
@ -633,7 +638,6 @@ ttp229_bsf:
|
||||||
sdo_pin: D2
|
sdo_pin: D2
|
||||||
scl_pin: D1
|
scl_pin: D1
|
||||||
|
|
||||||
|
|
||||||
display:
|
display:
|
||||||
- platform: max7219digit
|
- platform: max7219digit
|
||||||
cs_pin: GPIO15
|
cs_pin: GPIO15
|
||||||
|
@ -645,7 +649,6 @@ display:
|
||||||
lambda: |-
|
lambda: |-
|
||||||
it.printdigit("hello");
|
it.printdigit("hello");
|
||||||
|
|
||||||
|
|
||||||
http_request:
|
http_request:
|
||||||
useragent: esphome/device
|
useragent: esphome/device
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
|
|
|
@ -480,7 +480,6 @@ sensor:
|
||||||
name: PZEMDC Power
|
name: PZEMDC Power
|
||||||
energy:
|
energy:
|
||||||
name: PZEMDC Energy
|
name: PZEMDC Energy
|
||||||
|
|
||||||
- platform: pmsx003
|
- platform: pmsx003
|
||||||
uart_id: uart_9
|
uart_id: uart_9
|
||||||
type: PMSX003
|
type: PMSX003
|
||||||
|
|
|
@ -41,7 +41,9 @@ uart:
|
||||||
rx_pin: 3
|
rx_pin: 3
|
||||||
baud_rate: 9600
|
baud_rate: 9600
|
||||||
- id: uart_2
|
- id: uart_2
|
||||||
tx_pin: 17
|
tx_pin:
|
||||||
|
number: 17
|
||||||
|
inverted: true
|
||||||
rx_pin: 16
|
rx_pin: 16
|
||||||
baud_rate: 19200
|
baud_rate: 19200
|
||||||
|
|
||||||
|
|
|
@ -258,9 +258,9 @@ def test_snake_case(text, expected):
|
||||||
"text, expected",
|
"text, expected",
|
||||||
(
|
(
|
||||||
("foo_bar", "foo_bar"),
|
("foo_bar", "foo_bar"),
|
||||||
('!"§$%&/()=?foo_bar', "foo_bar"),
|
('!"§$%&/()=?foo_bar', "___________foo_bar"),
|
||||||
('foo_!"§$%&/()=?bar', "foo_bar"),
|
('foo_!"§$%&/()=?bar', "foo____________bar"),
|
||||||
('foo_bar!"§$%&/()=?', "foo_bar"),
|
('foo_bar!"§$%&/()=?', "foo_bar___________"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_sanitize(text, expected):
|
def test_sanitize(text, expected):
|
||||||
|
|
Loading…
Reference in a new issue