Merge branch 'dev' into nvds-status-info

This commit is contained in:
NP v/d Spek 2024-09-04 20:58:29 +02:00 committed by GitHub
commit d06abfdae2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
357 changed files with 5988 additions and 921 deletions

View file

@ -46,7 +46,7 @@ runs:
- name: Build and push to ghcr by digest
id: build-ghcr
uses: docker/build-push-action@v6.6.1
uses: docker/build-push-action@v6.7.0
with:
context: .
file: ./docker/Dockerfile
@ -69,7 +69,7 @@ runs:
- name: Build and push to dockerhub by digest
id: build-dockerhub
uses: docker/build-push-action@v6.6.1
uses: docker/build-push-action@v6.7.0
with:
context: .
file: ./docker/Dockerfile

View file

@ -17,7 +17,7 @@ runs:
steps:
- name: Set up Python ${{ inputs.python-version }}
id: python
uses: actions/setup-python@v5.1.1
uses: actions/setup-python@v5.2.0
with:
python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment

View file

@ -23,7 +23,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.1.0
uses: actions/setup-python@v5.2.0
with:
python-version: "3.11"

View file

@ -42,7 +42,7 @@ jobs:
steps:
- uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.1.0
uses: actions/setup-python@v5.2.0
with:
python-version: "3.9"
- name: Set up Docker Buildx

View file

@ -9,6 +9,7 @@ on:
paths:
- "**"
- "!.github/workflows/*.yml"
- "!.github/actions/build-image/*"
- ".github/workflows/ci.yml"
- "!.yamllint"
- "!.github/dependabot.yml"
@ -40,7 +41,7 @@ jobs:
run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.1.0
uses: actions/setup-python@v5.2.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
@ -396,7 +397,7 @@ jobs:
file: ${{ fromJson(needs.list-components.outputs.components) }}
steps:
- name: Install dependencies
run: sudo apt-get install libsodium-dev libsdl2-dev
run: sudo apt-get install libsdl2-dev
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
@ -450,7 +451,7 @@ jobs:
run: echo ${{ matrix.components }}
- name: Install dependencies
run: sudo apt-get install libsodium-dev libsdl2-dev
run: sudo apt-get install libsdl2-dev
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7

View file

@ -53,7 +53,7 @@ jobs:
steps:
- uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.1.0
uses: actions/setup-python@v5.2.0
with:
python-version: "3.x"
- name: Set up python environment
@ -65,7 +65,7 @@ jobs:
pip3 install build
python3 -m build
- name: Publish
uses: pypa/gh-action-pypi-publish@v1.9.0
uses: pypa/gh-action-pypi-publish@v1.10.1
deploy-docker:
name: Build ESPHome ${{ matrix.platform }}
@ -85,7 +85,7 @@ jobs:
steps:
- uses: actions/checkout@v4.1.7
- name: Set up Python
uses: actions/setup-python@v5.1.0
uses: actions/setup-python@v5.2.0
with:
python-version: "3.9"
@ -141,7 +141,7 @@ jobs:
echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
- name: Upload digests
uses: actions/upload-artifact@v4.3.4
uses: actions/upload-artifact@v4.4.0
with:
name: digests-${{ steps.sanitize.outputs.name }}
path: /tmp/digests

View file

@ -22,7 +22,7 @@ jobs:
path: lib/home-assistant
- name: Setup Python
uses: actions/setup-python@v5.1.0
uses: actions/setup-python@v5.2.0
with:
python-version: 3.12
@ -36,7 +36,7 @@ jobs:
python ./script/sync-device_class.py
- name: Commit changes
uses: peter-evans/create-pull-request@v6.1.0
uses: peter-evans/create-pull-request@v7.0.0
with:
commit-message: "Synchronise Device Classes from Home Assistant"
committer: esphomebot <esphome@nabucasa.com>

View file

@ -46,6 +46,7 @@ esphome/components/async_tcp/* @OttoWinter
esphome/components/at581x/* @X-Ryl669
esphome/components/atc_mithermometer/* @ahpohl
esphome/components/atm90e26/* @danieltwagner
esphome/components/atm90e32/* @circuitsetup @descipher
esphome/components/b_parasite/* @rbaron
esphome/components/ballu/* @bazuchan
esphome/components/bang_bang/* @OttoWinter
@ -57,6 +58,7 @@ esphome/components/beken_spi_led_strip/* @Mat931
esphome/components/bh1750/* @OttoWinter
esphome/components/binary_sensor/* @esphome/core
esphome/components/bk72xx/* @kuba2k2
esphome/components/bl0906/* @athom-tech @jesserockz @tarontop
esphome/components/bl0939/* @ziceva
esphome/components/bl0940/* @tobias-
esphome/components/bl0942/* @dbuezas
@ -81,6 +83,7 @@ esphome/components/cap1188/* @mreditor97
esphome/components/captive_portal/* @OttoWinter
esphome/components/ccs811/* @habbie
esphome/components/cd74hc4067/* @asoehlke
esphome/components/ch422g/* @jesterret
esphome/components/climate/* @esphome/core
esphome/components/climate_ir/* @glmnet
esphome/components/color_temperature/* @jesserockz
@ -168,7 +171,10 @@ esphome/components/he60r/* @clydebarrow
esphome/components/heatpumpir/* @rob-deutsch
esphome/components/hitachi_ac424/* @sourabhjaiswal
esphome/components/hm3301/* @freekode
esphome/components/homeassistant/* @OttoWinter
esphome/components/hmac_md5/* @dwmw2
esphome/components/homeassistant/* @OttoWinter @esphome/core
esphome/components/homeassistant/number/* @landonr
esphome/components/homeassistant/switch/* @Links2004
esphome/components/honeywell_hih_i2c/* @Benichou34
esphome/components/honeywellabp/* @RubyBailey
esphome/components/honeywellabp2_i2c/* @jpfaff
@ -419,6 +425,7 @@ esphome/components/tuya/switch/* @jesserockz
esphome/components/tuya/text_sensor/* @dentra
esphome/components/uart/* @esphome/core
esphome/components/uart/button/* @ssieb
esphome/components/udp/* @clydebarrow
esphome/components/ufire_ec/* @pvizeli
esphome/components/ufire_ise/* @pvizeli
esphome/components/ultrasonic/* @OttoWinter
@ -454,6 +461,7 @@ esphome/components/wl_134/* @hobbypunk90
esphome/components/x9c/* @EtienneMD
esphome/components/xgzp68xx/* @gcormier
esphome/components/xiaomi_hhccjcy10/* @fariouche
esphome/components/xiaomi_lywsd02mmc/* @juanluss31
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
esphome/components/xiaomi_mhoc303/* @drug123
esphome/components/xiaomi_mhoc401/* @vevsvevs

View file

@ -34,8 +34,8 @@ RUN \
python3-wheel=0.38.4-2 \
iputils-ping=3:20221126-1 \
git=1:2.39.2-1.1 \
curl=7.88.1-10+deb12u6 \
openssh-client=1:9.2p1-2+deb12u2 \
curl=7.88.1-10+deb12u7 \
openssh-client=1:9.2p1-2+deb12u3 \
python3-cffi=1.15.1-5 \
libcairo2=1.16.0-7 \
libmagic1=1:5.44-3 \
@ -49,7 +49,7 @@ RUN \
zlib1g-dev=1:1.2.13.dfsg-1 \
libjpeg-dev=1:2.1.5-2 \
libfreetype-dev=2.12.1+dfsg-5+deb12u3 \
libssl-dev=3.0.13-1~deb12u1 \
libssl-dev=3.0.14-1~deb12u1 \
libffi-dev=3.4.4-1 \
libopenjp2-7=2.5.0-2 \
libtiff6=4.5.0-6+deb12u1 \

View file

@ -38,7 +38,7 @@ from esphome.const import (
SECRETS_FILES,
)
from esphome.core import CORE, EsphomeError, coroutine
from esphome.helpers import indent, is_ip_address
from esphome.helpers import indent, is_ip_address, get_bool_env
from esphome.log import Fore, color, setup_log
from esphome.util import (
get_serial_ports,
@ -731,7 +731,11 @@ POST_CONFIG_ACTIONS = {
def parse_args(argv):
options_parser = argparse.ArgumentParser(add_help=False)
options_parser.add_argument(
"-v", "--verbose", help="Enable verbose ESPHome logs.", action="store_true"
"-v",
"--verbose",
help="Enable verbose ESPHome logs.",
action="store_true",
default=get_bool_env("ESPHOME_VERBOSE"),
)
options_parser.add_argument(
"-q", "--quiet", help="Disable all ESPHome logs.", action="store_true"

View file

@ -14,8 +14,6 @@ void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) {
ESP_LOGD(TAG, "version = %d", value->version);
if (value->version == 1) {
ESP_LOGD(TAG, "ambient light = %d", value->ambientLight);
if (this->humidity_sensor_ != nullptr) {
this->humidity_sensor_->publish_state(value->humidity / 2.0f);
}
@ -43,6 +41,10 @@ void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) {
if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) {
this->tvoc_sensor_->publish_state(value->voc);
}
if (this->illuminance_sensor_ != nullptr) {
this->illuminance_sensor_->publish_state(value->ambientLight);
}
} else {
ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version);
}
@ -68,6 +70,7 @@ void AirthingsWavePlus::dump_config() {
LOG_SENSOR(" ", "Radon", this->radon_sensor_);
LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_);
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
LOG_SENSOR(" ", "Illuminance", this->illuminance_sensor_);
}
AirthingsWavePlus::AirthingsWavePlus() {

View file

@ -22,6 +22,7 @@ class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; }
void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; }
void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; }
void set_illuminance(sensor::Sensor *illuminance) { illuminance_sensor_ = illuminance; }
protected:
bool is_valid_radon_value_(uint16_t radon);
@ -32,6 +33,7 @@ class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase {
sensor::Sensor *radon_sensor_{nullptr};
sensor::Sensor *radon_long_term_sensor_{nullptr};
sensor::Sensor *co2_sensor_{nullptr};
sensor::Sensor *illuminance_sensor_{nullptr};
struct WavePlusReadings {
uint8_t version;

View file

@ -12,6 +12,9 @@ from esphome.const import (
CONF_CO2,
UNIT_BECQUEREL_PER_CUBIC_METER,
UNIT_PARTS_PER_MILLION,
CONF_ILLUMINANCE,
UNIT_LUX,
DEVICE_CLASS_ILLUMINANCE,
)
DEPENDENCIES = airthings_wave_base.DEPENDENCIES
@ -45,6 +48,12 @@ CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend(
device_class=DEVICE_CLASS_CARBON_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
unit_of_measurement=UNIT_LUX,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
@ -62,3 +71,6 @@ async def to_code(config):
if config_co2 := config.get(CONF_CO2):
sens = await sensor.new_sensor(config_co2)
cg.add(var.set_co2(sens))
if config_illuminance := config.get(CONF_ILLUMINANCE):
sens = await sensor.new_sensor(config_illuminance)
cg.add(var.set_illuminance(sens))

View file

@ -155,7 +155,7 @@ async def to_code(config):
decoded = base64.b64decode(encryption_config[CONF_KEY])
cg.add(var.set_noise_psk(list(decoded)))
cg.add_define("USE_API_NOISE")
cg.add_library("esphome/noise-c", "0.1.4")
cg.add_library("esphome/noise-c", "0.1.6")
else:
cg.add_define("USE_API_PLAINTEXT")

View file

@ -686,6 +686,7 @@ message SubscribeHomeAssistantStateResponse {
option (source) = SOURCE_SERVER;
string entity_id = 1;
string attribute = 2;
bool once = 3;
}
message HomeAssistantStateResponse {
@ -1106,6 +1107,18 @@ enum MediaPlayerCommand {
MEDIA_PLAYER_COMMAND_MUTE = 3;
MEDIA_PLAYER_COMMAND_UNMUTE = 4;
}
enum MediaPlayerFormatPurpose {
MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0;
MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT = 1;
}
message MediaPlayerSupportedFormat {
option (ifdef) = "USE_MEDIA_PLAYER";
string format = 1;
uint32 sample_rate = 2;
uint32 num_channels = 3;
MediaPlayerFormatPurpose purpose = 4;
}
message ListEntitiesMediaPlayerResponse {
option (id) = 63;
option (source) = SOURCE_SERVER;
@ -1121,6 +1134,8 @@ message ListEntitiesMediaPlayerResponse {
EntityCategory entity_category = 7;
bool supports_pause = 8;
repeated MediaPlayerSupportedFormat supported_formats = 9;
}
message MediaPlayerStateResponse {
option (id) = 64;

View file

@ -179,6 +179,7 @@ void APIConnection::loop() {
SubscribeHomeAssistantStateResponse resp;
resp.entity_id = it.entity_id;
resp.attribute = it.attribute.value();
resp.once = it.once;
if (this->send_subscribe_home_assistant_state_response(resp)) {
state_subs_at_++;
}
@ -1025,6 +1026,15 @@ bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_play
auto traits = media_player->get_traits();
msg.supports_pause = traits.get_supports_pause();
for (auto &supported_format : traits.get_supported_formats()) {
MediaPlayerSupportedFormat media_format;
media_format.format = supported_format.format;
media_format.sample_rate = supported_format.sample_rate;
media_format.num_channels = supported_format.num_channels;
media_format.purpose = static_cast<enums::MediaPlayerFormatPurpose>(supported_format.purpose);
msg.supported_formats.push_back(media_format);
}
return this->send_list_entities_media_player_response(msg);
}
void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
@ -1335,6 +1345,9 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) {
case enums::UPDATE_COMMAND_CHECK:
update->check();
break;
case enums::UPDATE_COMMAND_NONE:
ESP_LOGE(TAG, "UPDATE_COMMAND_NONE not handled. Check client is sending the correct command");
break;
default:
ESP_LOGW(TAG, "Unknown update command: %" PRIu32, msg.command);
break;

View file

@ -387,6 +387,18 @@ template<> const char *proto_enum_to_string<enums::MediaPlayerCommand>(enums::Me
}
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
template<> const char *proto_enum_to_string<enums::MediaPlayerFormatPurpose>(enums::MediaPlayerFormatPurpose value) {
switch (value) {
case enums::MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT:
return "MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT";
case enums::MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT:
return "MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT";
default:
return "UNKNOWN";
}
}
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
template<>
const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::BluetoothDeviceRequestType value) {
switch (value) {
@ -3109,6 +3121,16 @@ void SubscribeHomeAssistantStatesRequest::dump_to(std::string &out) const {
out.append("SubscribeHomeAssistantStatesRequest {}");
}
#endif
bool SubscribeHomeAssistantStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 3: {
this->once = value.as_bool();
return true;
}
default:
return false;
}
}
bool SubscribeHomeAssistantStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
@ -3126,6 +3148,7 @@ bool SubscribeHomeAssistantStateResponse::decode_length(uint32_t field_id, Proto
void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->entity_id);
buffer.encode_string(2, this->attribute);
buffer.encode_bool(3, this->once);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
@ -3138,6 +3161,10 @@ void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const {
out.append(" attribute: ");
out.append("'").append(this->attribute).append("'");
out.append("\n");
out.append(" once: ");
out.append(YESNO(this->once));
out.append("\n");
out.append("}");
}
#endif
@ -5108,6 +5135,64 @@ void ButtonCommandRequest::dump_to(std::string &out) const {
out.append("}");
}
#endif
bool MediaPlayerSupportedFormat::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
this->sample_rate = value.as_uint32();
return true;
}
case 3: {
this->num_channels = value.as_uint32();
return true;
}
case 4: {
this->purpose = value.as_enum<enums::MediaPlayerFormatPurpose>();
return true;
}
default:
return false;
}
}
bool MediaPlayerSupportedFormat::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->format = value.as_string();
return true;
}
default:
return false;
}
}
void MediaPlayerSupportedFormat::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->format);
buffer.encode_uint32(2, this->sample_rate);
buffer.encode_uint32(3, this->num_channels);
buffer.encode_enum<enums::MediaPlayerFormatPurpose>(4, this->purpose);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void MediaPlayerSupportedFormat::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("MediaPlayerSupportedFormat {\n");
out.append(" format: ");
out.append("'").append(this->format).append("'");
out.append("\n");
out.append(" sample_rate: ");
sprintf(buffer, "%" PRIu32, this->sample_rate);
out.append(buffer);
out.append("\n");
out.append(" num_channels: ");
sprintf(buffer, "%" PRIu32, this->num_channels);
out.append(buffer);
out.append("\n");
out.append(" purpose: ");
out.append(proto_enum_to_string<enums::MediaPlayerFormatPurpose>(this->purpose));
out.append("\n");
out.append("}");
}
#endif
bool ListEntitiesMediaPlayerResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {
@ -5144,6 +5229,10 @@ bool ListEntitiesMediaPlayerResponse::decode_length(uint32_t field_id, ProtoLeng
this->icon = value.as_string();
return true;
}
case 9: {
this->supported_formats.push_back(value.as_message<MediaPlayerSupportedFormat>());
return true;
}
default:
return false;
}
@ -5167,6 +5256,9 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_bool(8, this->supports_pause);
for (auto &it : this->supported_formats) {
buffer.encode_message<MediaPlayerSupportedFormat>(9, it, true);
}
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const {
@ -5204,6 +5296,12 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const {
out.append(" supports_pause: ");
out.append(YESNO(this->supports_pause));
out.append("\n");
for (const auto &it : this->supported_formats) {
out.append(" supported_formats: ");
it.dump_to(out);
out.append("\n");
}
out.append("}");
}
#endif

View file

@ -156,6 +156,10 @@ enum MediaPlayerCommand : uint32_t {
MEDIA_PLAYER_COMMAND_MUTE = 3,
MEDIA_PLAYER_COMMAND_UNMUTE = 4,
};
enum MediaPlayerFormatPurpose : uint32_t {
MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0,
MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT = 1,
};
enum BluetoothDeviceRequestType : uint32_t {
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0,
BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1,
@ -836,6 +840,7 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage {
public:
std::string entity_id{};
std::string attribute{};
bool once{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@ -843,6 +848,7 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage {
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class HomeAssistantStateResponse : public ProtoMessage {
public:
@ -1265,6 +1271,21 @@ class ButtonCommandRequest : public ProtoMessage {
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
};
class MediaPlayerSupportedFormat : public ProtoMessage {
public:
std::string format{};
uint32_t sample_rate{0};
uint32_t num_channels{0};
enums::MediaPlayerFormatPurpose purpose{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ListEntitiesMediaPlayerResponse : public ProtoMessage {
public:
std::string object_id{};
@ -1275,6 +1296,7 @@ class ListEntitiesMediaPlayerResponse : public ProtoMessage {
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
bool supports_pause{false};
std::vector<MediaPlayerSupportedFormat> supported_formats{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;

View file

@ -359,8 +359,18 @@ void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<s
.entity_id = std::move(entity_id),
.attribute = std::move(attribute),
.callback = std::move(f),
.once = false,
});
}
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f) {
this->state_subs_.push_back(HomeAssistantStateSubscription{
.entity_id = std::move(entity_id),
.attribute = std::move(attribute),
.callback = std::move(f),
.once = true,
});
};
const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
return this->state_subs_;
}

View file

@ -112,10 +112,13 @@ class APIServer : public Component, public Controller {
std::string entity_id;
optional<std::string> attribute;
std::function<void(std::string)> callback;
bool once;
};
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f);
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f);
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }

View file

@ -1,34 +1,34 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, spi
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_REACTIVE_POWER,
CONF_VOLTAGE,
CONF_CURRENT,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_FREQUENCY,
CONF_ID,
CONF_LINE_FREQUENCY,
CONF_POWER,
CONF_POWER_FACTOR,
CONF_FREQUENCY,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_REACTIVE_POWER,
CONF_REVERSE_ACTIVE_ENERGY,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_VOLTAGE,
ICON_LIGHTBULB,
ICON_CURRENT_AC,
ICON_LIGHTBULB,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE,
UNIT_HERTZ,
UNIT_VOLT,
UNIT_AMPERE,
UNIT_WATT,
UNIT_VOLT_AMPS_REACTIVE,
UNIT_WATT,
UNIT_WATT_HOURS,
)
CONF_LINE_FREQUENCY = "line_frequency"
CONF_METER_CONSTANT = "meter_constant"
CONF_PL_CONST = "pl_const"
CONF_GAIN_PGA = "gain_pga"

View file

@ -0,0 +1,7 @@
import esphome.codegen as cg
CODEOWNERS = ["@circuitsetup", "@descipher"]
atm90e32_ns = cg.esphome_ns.namespace("atm90e32")
CONF_ATM90E32_ID = "atm90e32_id"

View file

@ -132,10 +132,77 @@ void ATM90E32Component::update() {
this->status_clear_warning();
}
void ATM90E32Component::restore_calibrations_() {
if (enable_offset_calibration_) {
this->pref_.load(&this->offset_phase_);
}
};
void ATM90E32Component::run_offset_calibrations() {
// Run the calibrations and
// Setup voltage and current calibration offsets for PHASE A
this->offset_phase_[PHASEA].voltage_offset_ = calibrate_voltage_offset_phase(PHASEA);
this->phase_[PHASEA].voltage_offset_ = this->offset_phase_[PHASEA].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEA].current_offset_ = calibrate_current_offset_phase(PHASEA);
this->phase_[PHASEA].current_offset_ = this->offset_phase_[PHASEA].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_); // C Current offset
// Setup voltage and current calibration offsets for PHASE B
this->offset_phase_[PHASEB].voltage_offset_ = calibrate_voltage_offset_phase(PHASEB);
this->phase_[PHASEB].voltage_offset_ = this->offset_phase_[PHASEB].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEB].current_offset_ = calibrate_current_offset_phase(PHASEB);
this->phase_[PHASEB].current_offset_ = this->offset_phase_[PHASEB].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_); // C Current offset
// Setup voltage and current calibration offsets for PHASE C
this->offset_phase_[PHASEC].voltage_offset_ = calibrate_voltage_offset_phase(PHASEC);
this->phase_[PHASEC].voltage_offset_ = this->offset_phase_[PHASEC].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEC].current_offset_ = calibrate_current_offset_phase(PHASEC);
this->phase_[PHASEC].current_offset_ = this->offset_phase_[PHASEC].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_); // C Current offset
this->pref_.save(&this->offset_phase_);
ESP_LOGI(TAG, "PhaseA Vo=%5d PhaseB Vo=%5d PhaseC Vo=%5d", this->offset_phase_[PHASEA].voltage_offset_,
this->offset_phase_[PHASEB].voltage_offset_, this->offset_phase_[PHASEC].voltage_offset_);
ESP_LOGI(TAG, "PhaseA Io=%5d PhaseB Io=%5d PhaseC Io=%5d", this->offset_phase_[PHASEA].current_offset_,
this->offset_phase_[PHASEB].current_offset_, this->offset_phase_[PHASEC].current_offset_);
}
void ATM90E32Component::clear_offset_calibrations() {
// Clear the calibrations and
this->offset_phase_[PHASEA].voltage_offset_ = 0;
this->phase_[PHASEA].voltage_offset_ = this->offset_phase_[PHASEA].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEA].current_offset_ = 0;
this->phase_[PHASEA].current_offset_ = this->offset_phase_[PHASEA].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_); // C Current offset
this->offset_phase_[PHASEB].voltage_offset_ = 0;
this->phase_[PHASEB].voltage_offset_ = this->offset_phase_[PHASEB].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEB].current_offset_ = 0;
this->phase_[PHASEB].current_offset_ = this->offset_phase_[PHASEB].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_); // C Current offset
this->offset_phase_[PHASEC].voltage_offset_ = 0;
this->phase_[PHASEC].voltage_offset_ = this->offset_phase_[PHASEC].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEC].current_offset_ = 0;
this->phase_[PHASEC].current_offset_ = this->offset_phase_[PHASEC].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_); // C Current offset
this->pref_.save(&this->offset_phase_);
ESP_LOGI(TAG, "PhaseA Vo=%5d PhaseB Vo=%5d PhaseC Vo=%5d", this->offset_phase_[PHASEA].voltage_offset_,
this->offset_phase_[PHASEB].voltage_offset_, this->offset_phase_[PHASEC].voltage_offset_);
ESP_LOGI(TAG, "PhaseA Io=%5d PhaseB Io=%5d PhaseC Io=%5d", this->offset_phase_[PHASEA].current_offset_,
this->offset_phase_[PHASEB].current_offset_, this->offset_phase_[PHASEC].current_offset_);
}
void ATM90E32Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up ATM90E32 Component...");
this->spi_setup();
if (this->enable_offset_calibration_) {
uint32_t hash = fnv1_hash(App.get_friendly_name());
this->pref_ = global_preferences->make_preference<Calibration[3]>(hash, true);
this->restore_calibrations_();
}
uint16_t mmode0 = 0x87; // 3P4W 50Hz
if (line_freq_ == 60) {
mmode0 |= 1 << 12; // sets 12th bit to 1, 60Hz
@ -167,27 +234,12 @@ void ATM90E32Component::setup() {
this->write16_(ATM90E32_REGISTER_SSTARTTH, 0x1D4C); // All Reactive Startup Power Threshold - 50%
this->write16_(ATM90E32_REGISTER_PPHASETH, 0x02EE); // Each Phase Active Phase Threshold - 0.002A/0.00032 = 750
this->write16_(ATM90E32_REGISTER_QPHASETH, 0x02EE); // Each phase Reactive Phase Threshold - 10%
// Setup voltage and current calibration offsets for PHASE A
this->phase_[PHASEA].voltage_offset_ = calibrate_voltage_offset_phase(PHASEA);
this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_); // A Voltage offset
this->phase_[PHASEA].current_offset_ = calibrate_current_offset_phase(PHASEA);
this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_); // A Current offset
// Setup voltage and current gain for PHASE A
this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[PHASEA].voltage_gain_); // A Voltage rms gain
this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[PHASEA].ct_gain_); // A line current gain
// Setup voltage and current calibration offsets for PHASE B
this->phase_[PHASEB].voltage_offset_ = calibrate_voltage_offset_phase(PHASEB);
this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_); // B Voltage offset
this->phase_[PHASEB].current_offset_ = calibrate_current_offset_phase(PHASEB);
this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_); // B Current offset
// Setup voltage and current gain for PHASE B
this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[PHASEB].voltage_gain_); // B Voltage rms gain
this->write16_(ATM90E32_REGISTER_IGAINB, this->phase_[PHASEB].ct_gain_); // B line current gain
// Setup voltage and current calibration offsets for PHASE C
this->phase_[PHASEC].voltage_offset_ = calibrate_voltage_offset_phase(PHASEC);
this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_); // C Voltage offset
this->phase_[PHASEC].current_offset_ = calibrate_current_offset_phase(PHASEC);
this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_); // C Current offset
// Setup voltage and current gain for PHASE C
this->write16_(ATM90E32_REGISTER_UGAINC, this->phase_[PHASEC].voltage_gain_); // C Voltage rms gain
this->write16_(ATM90E32_REGISTER_IGAINC, this->phase_[PHASEC].ct_gain_); // C line current gain

View file

@ -1,9 +1,12 @@
#pragma once
#include "esphome/core/component.h"
#include "atm90e32_reg.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/spi/spi.h"
#include "atm90e32_reg.h"
#include "esphome/core/application.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
namespace esphome {
namespace atm90e32 {
@ -20,7 +23,6 @@ class ATM90E32Component : public PollingComponent,
void dump_config() override;
float get_setup_priority() const override;
void update() override;
void set_voltage_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].voltage_sensor_ = obj; }
void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; }
void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; }
@ -48,9 +50,11 @@ class ATM90E32Component : public PollingComponent,
void set_line_freq(int freq) { line_freq_ = freq; }
void set_current_phases(int phases) { current_phases_ = phases; }
void set_pga_gain(uint16_t gain) { pga_gain_ = gain; }
void run_offset_calibrations();
void clear_offset_calibrations();
void set_enable_offset_calibration(bool flag) { enable_offset_calibration_ = flag; }
uint16_t calibrate_voltage_offset_phase(uint8_t /*phase*/);
uint16_t calibrate_current_offset_phase(uint8_t /*phase*/);
int32_t last_periodic_millis = millis();
protected:
@ -83,10 +87,11 @@ class ATM90E32Component : public PollingComponent,
float get_chip_temperature_();
bool get_publish_interval_flag_() { return publish_interval_flag_; };
void set_publish_interval_flag_(bool flag) { publish_interval_flag_ = flag; };
void restore_calibrations_();
struct ATM90E32Phase {
uint16_t voltage_gain_{7305};
uint16_t ct_gain_{27961};
uint16_t voltage_gain_{0};
uint16_t ct_gain_{0};
uint16_t voltage_offset_{0};
uint16_t current_offset_{0};
float voltage_{0};
@ -114,13 +119,21 @@ class ATM90E32Component : public PollingComponent,
uint32_t cumulative_reverse_active_energy_{0};
} phase_[3];
struct Calibration {
uint16_t voltage_offset_{0};
uint16_t current_offset_{0};
} offset_phase_[3];
ESPPreferenceObject pref_;
sensor::Sensor *freq_sensor_{nullptr};
sensor::Sensor *chip_temperature_sensor_{nullptr};
uint16_t pga_gain_{0x15};
int line_freq_{60};
int current_phases_{3};
bool publish_interval_flag_{true};
bool publish_interval_flag_{false};
bool peak_current_signed_{false};
bool enable_offset_calibration_{false};
};
} // namespace atm90e32

View file

@ -1,5 +1,7 @@
#pragma once
#include <cinttypes>
namespace esphome {
namespace atm90e32 {

View file

@ -0,0 +1,43 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import CONF_ID, ENTITY_CATEGORY_CONFIG, ICON_CHIP, ICON_SCALE
from .. import atm90e32_ns
from ..sensor import ATM90E32Component
CONF_RUN_OFFSET_CALIBRATION = "run_offset_calibration"
CONF_CLEAR_OFFSET_CALIBRATION = "clear_offset_calibration"
ATM90E32CalibrationButton = atm90e32_ns.class_(
"ATM90E32CalibrationButton",
button.Button,
)
ATM90E32ClearCalibrationButton = atm90e32_ns.class_(
"ATM90E32ClearCalibrationButton",
button.Button,
)
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.use_id(ATM90E32Component),
cv.Optional(CONF_RUN_OFFSET_CALIBRATION): button.button_schema(
ATM90E32CalibrationButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_SCALE,
),
cv.Optional(CONF_CLEAR_OFFSET_CALIBRATION): button.button_schema(
ATM90E32ClearCalibrationButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_CHIP,
),
}
async def to_code(config):
parent = await cg.get_variable(config[CONF_ID])
if run_offset := config.get(CONF_RUN_OFFSET_CALIBRATION):
b = await button.new_button(run_offset)
await cg.register_parented(b, parent)
if clear_offset := config.get(CONF_CLEAR_OFFSET_CALIBRATION):
b = await button.new_button(clear_offset)
await cg.register_parented(b, parent)

View file

@ -0,0 +1,20 @@
#include "atm90e32_button.h"
#include "esphome/core/log.h"
namespace esphome {
namespace atm90e32 {
static const char *const TAG = "atm90e32.button";
void ATM90E32CalibrationButton::press_action() {
ESP_LOGI(TAG, "Running offset calibrations, Note: CTs and ACVs must be 0 during this process...");
this->parent_->run_offset_calibrations();
}
void ATM90E32ClearCalibrationButton::press_action() {
ESP_LOGI(TAG, "Offset calibrations cleared.");
this->parent_->clear_offset_calibrations();
}
} // namespace atm90e32
} // namespace esphome

View file

@ -0,0 +1,27 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/atm90e32/atm90e32.h"
#include "esphome/components/button/button.h"
namespace esphome {
namespace atm90e32 {
class ATM90E32CalibrationButton : public button::Button, public Parented<ATM90E32Component> {
public:
ATM90E32CalibrationButton() = default;
protected:
void press_action() override;
};
class ATM90E32ClearCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
public:
ATM90E32ClearCalibrationButton() = default;
protected:
void press_action() override;
};
} // namespace atm90e32
} // namespace esphome

View file

@ -1,21 +1,22 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, spi
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_REACTIVE_POWER,
CONF_VOLTAGE,
CONF_APPARENT_POWER,
CONF_CURRENT,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_FREQUENCY,
CONF_ID,
CONF_LINE_FREQUENCY,
CONF_PHASE_A,
CONF_PHASE_ANGLE,
CONF_PHASE_B,
CONF_PHASE_C,
CONF_PHASE_ANGLE,
CONF_POWER,
CONF_POWER_FACTOR,
CONF_APPARENT_POWER,
CONF_FREQUENCY,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_REACTIVE_POWER,
CONF_REVERSE_ACTIVE_ENERGY,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
@ -23,13 +24,13 @@ from esphome.const import (
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_LIGHTBULB,
ICON_CURRENT_AC,
ICON_LIGHTBULB,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE,
UNIT_DEGREES,
UNIT_CELSIUS,
UNIT_DEGREES,
UNIT_HERTZ,
UNIT_VOLT,
UNIT_VOLT_AMPS_REACTIVE,
@ -37,7 +38,8 @@ from esphome.const import (
UNIT_WATT_HOURS,
)
CONF_LINE_FREQUENCY = "line_frequency"
from . import atm90e32_ns
CONF_CHIP_TEMPERATURE = "chip_temperature"
CONF_GAIN_PGA = "gain_pga"
CONF_CURRENT_PHASES = "current_phases"
@ -46,6 +48,7 @@ CONF_GAIN_CT = "gain_ct"
CONF_HARMONIC_POWER = "harmonic_power"
CONF_PEAK_CURRENT = "peak_current"
CONF_PEAK_CURRENT_SIGNED = "peak_current_signed"
CONF_ENABLE_OFFSET_CALIBRATION = "enable_offset_calibration"
UNIT_DEG = "degrees"
LINE_FREQS = {
"50HZ": 50,
@ -61,7 +64,6 @@ PGA_GAINS = {
"4X": 0x2A,
}
atm90e32_ns = cg.esphome_ns.namespace("atm90e32")
ATM90E32Component = atm90e32_ns.class_(
"ATM90E32Component", cg.PollingComponent, spi.SPIDevice
)
@ -164,6 +166,7 @@ CONFIG_SCHEMA = (
),
cv.Optional(CONF_GAIN_PGA, default="2X"): cv.enum(PGA_GAINS, upper=True),
cv.Optional(CONF_PEAK_CURRENT_SIGNED, default=False): cv.boolean,
cv.Optional(CONF_ENABLE_OFFSET_CALIBRATION, default=False): cv.boolean,
}
)
.extend(cv.polling_component_schema("60s"))
@ -227,3 +230,4 @@ async def to_code(config):
cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES]))
cg.add(var.set_pga_gain(config[CONF_GAIN_PGA]))
cg.add(var.set_peak_current_signed(config[CONF_PEAK_CURRENT_SIGNED]))
cg.add(var.set_enable_offset_calibration(config[CONF_ENABLE_OFFSET_CALIBRATION]))

View file

@ -90,7 +90,7 @@ struct BedjetStatusPacket {
int unused_6 : 1; // 0x4
bool is_dual_zone : 1; /// Is part of a Dual Zone configuration
int unused_7 : 1; // 0x1
} dual_zone_flags;
} dual_zone_flags; // NOLINT(clang-diagnostic-unaligned-access)
uint8_t unused_4 : 8; // Unknown 23-24 = 0x1310
uint8_t unused_5 : 8; // Unknown 23-24 = 0x1310

View file

@ -18,10 +18,11 @@ class BinaryLightOutput : public light::LightOutput {
void write_state(light::LightState *state) override {
bool binary;
state->current_values_as_binary(&binary);
if (binary)
if (binary) {
this->output_->turn_on();
else
} else {
this->output_->turn_off();
}
}
protected:

View file

@ -0,0 +1 @@
CODEOWNERS = ["@athom-tech", "@tarontop", "@jesserockz"]

View file

@ -0,0 +1,238 @@
#include "bl0906.h"
#include "constants.h"
#include "esphome/core/log.h"
namespace esphome {
namespace bl0906 {
static const char *const TAG = "bl0906";
constexpr uint32_t to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; }
constexpr int32_t to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; }
// The SUM byte is (Addr+Data_L+Data_M+Data_H)&0xFF negated;
constexpr uint8_t bl0906_checksum(const uint8_t address, const DataPacket *data) {
return (address + data->l + data->m + data->h) ^ 0xFF;
}
void BL0906::loop() {
if (this->current_channel_ == UINT8_MAX) {
return;
}
while (this->available())
this->flush();
if (this->current_channel_ == 0) {
// Temperature
this->read_data_(BL0906_TEMPERATURE, BL0906_TREF, this->temperature_sensor_);
} else if (this->current_channel_ == 1) {
this->read_data_(BL0906_I_1_RMS, BL0906_IREF, this->current_1_sensor_);
this->read_data_(BL0906_WATT_1, BL0906_PREF, this->power_1_sensor_);
this->read_data_(BL0906_CF_1_CNT, BL0906_EREF, this->energy_1_sensor_);
} else if (this->current_channel_ == 2) {
this->read_data_(BL0906_I_2_RMS, BL0906_IREF, this->current_2_sensor_);
this->read_data_(BL0906_WATT_2, BL0906_PREF, this->power_2_sensor_);
this->read_data_(BL0906_CF_2_CNT, BL0906_EREF, this->energy_2_sensor_);
} else if (this->current_channel_ == 3) {
this->read_data_(BL0906_I_3_RMS, BL0906_IREF, this->current_3_sensor_);
this->read_data_(BL0906_WATT_3, BL0906_PREF, this->power_3_sensor_);
this->read_data_(BL0906_CF_3_CNT, BL0906_EREF, this->energy_3_sensor_);
} else if (this->current_channel_ == 4) {
this->read_data_(BL0906_I_4_RMS, BL0906_IREF, this->current_4_sensor_);
this->read_data_(BL0906_WATT_4, BL0906_PREF, this->power_4_sensor_);
this->read_data_(BL0906_CF_4_CNT, BL0906_EREF, this->energy_4_sensor_);
} else if (this->current_channel_ == 5) {
this->read_data_(BL0906_I_5_RMS, BL0906_IREF, this->current_5_sensor_);
this->read_data_(BL0906_WATT_5, BL0906_PREF, this->power_5_sensor_);
this->read_data_(BL0906_CF_5_CNT, BL0906_EREF, this->energy_5_sensor_);
} else if (this->current_channel_ == 6) {
this->read_data_(BL0906_I_6_RMS, BL0906_IREF, this->current_6_sensor_);
this->read_data_(BL0906_WATT_6, BL0906_PREF, this->power_6_sensor_);
this->read_data_(BL0906_CF_6_CNT, BL0906_EREF, this->energy_6_sensor_);
} else if (this->current_channel_ == UINT8_MAX - 2) {
// Frequency
this->read_data_(BL0906_FREQUENCY, BL0906_FREF, frequency_sensor_);
// Voltage
this->read_data_(BL0906_V_RMS, BL0906_UREF, voltage_sensor_);
} else if (this->current_channel_ == UINT8_MAX - 1) {
// Total power
this->read_data_(BL0906_WATT_SUM, BL0906_WATT, this->total_power_sensor_);
// Total Energy
this->read_data_(BL0906_CF_SUM_CNT, BL0906_CF, this->total_energy_sensor_);
} else {
this->current_channel_ = UINT8_MAX - 2; // Go to frequency and voltage
return;
}
this->current_channel_++;
this->handle_actions_();
}
void BL0906::setup() {
while (this->available())
this->flush();
this->write_array(USR_WRPROT_WITABLE, sizeof(USR_WRPROT_WITABLE));
// Calibration (1: register address; 2: value before calibration; 3: value after calibration)
this->bias_correction_(BL0906_RMSOS_1, 0.01600, 0); // Calibration current_1
this->bias_correction_(BL0906_RMSOS_2, 0.01500, 0);
this->bias_correction_(BL0906_RMSOS_3, 0.01400, 0);
this->bias_correction_(BL0906_RMSOS_4, 0.01300, 0);
this->bias_correction_(BL0906_RMSOS_5, 0.01200, 0);
this->bias_correction_(BL0906_RMSOS_6, 0.01200, 0); // Calibration current_6
this->write_array(USR_WRPROT_ONLYREAD, sizeof(USR_WRPROT_ONLYREAD));
}
void BL0906::update() { this->current_channel_ = 0; }
size_t BL0906::enqueue_action_(ActionCallbackFuncPtr function) {
this->action_queue_.push_back(function);
return this->action_queue_.size();
}
void BL0906::handle_actions_() {
if (this->action_queue_.empty()) {
return;
}
ActionCallbackFuncPtr ptr_func = nullptr;
for (int i = 0; i < this->action_queue_.size(); i++) {
ptr_func = this->action_queue_[i];
if (ptr_func) {
ESP_LOGI(TAG, "HandleActionCallback[%d]...", i);
(this->*ptr_func)();
}
}
while (this->available()) {
this->read();
}
this->action_queue_.clear();
}
// Reset energy
void BL0906::reset_energy_() {
this->write_array(BL0906_INIT[0], 6);
delay(1);
this->flush();
ESP_LOGW(TAG, "RMSOS:%02X%02X%02X%02X%02X%02X", BL0906_INIT[0][0], BL0906_INIT[0][1], BL0906_INIT[0][2],
BL0906_INIT[0][3], BL0906_INIT[0][4], BL0906_INIT[0][5]);
}
// Read data
void BL0906::read_data_(const uint8_t address, const float reference, sensor::Sensor *sensor) {
if (sensor == nullptr) {
return;
}
DataPacket buffer;
ube24_t data_u24;
sbe24_t data_s24;
float value = 0;
bool signed_result = reference == BL0906_TREF || reference == BL0906_WATT || reference == BL0906_PREF;
this->write_byte(BL0906_READ_COMMAND);
this->write_byte(address);
if (this->read_array((uint8_t *) &buffer, sizeof(buffer) - 1)) {
if (bl0906_checksum(address, &buffer) == buffer.checksum) {
if (signed_result) {
data_s24.l = buffer.l;
data_s24.m = buffer.m;
data_s24.h = buffer.h;
} else {
data_u24.l = buffer.l;
data_u24.m = buffer.m;
data_u24.h = buffer.h;
}
} else {
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
while (read() >= 0)
;
return;
}
}
// Power
if (reference == BL0906_PREF) {
value = (float) to_int32_t(data_s24) * reference;
}
// Total power
if (reference == BL0906_WATT) {
value = (float) to_int32_t(data_s24) * reference;
}
// Voltage, current, power, total power
if (reference == BL0906_UREF || reference == BL0906_IREF || reference == BL0906_EREF || reference == BL0906_CF) {
value = (float) to_uint32_t(data_u24) * reference;
}
// Frequency
if (reference == BL0906_FREF) {
value = reference / (float) to_uint32_t(data_u24);
}
// Chip temperature
if (reference == BL0906_TREF) {
value = (float) to_int32_t(data_s24);
value = (value - 64) * 12.5 / 59 - 40;
}
sensor->publish_state(value);
}
// RMS offset correction
void BL0906::bias_correction_(uint8_t address, float measurements, float correction) {
DataPacket data;
float ki = 12875 * 1 * (5.1 + 5.1) * 1000 / 2000 / 1.097; // Current coefficient
float i_rms0 = measurements * ki;
float i_rms = correction * ki;
int32_t value = (i_rms * i_rms - i_rms0 * i_rms0) / 256;
data.l = value << 24 >> 24;
data.m = value << 16 >> 24;
if (value < 0) {
data.h = (value << 8 >> 24) | 0b10000000;
}
data.address = bl0906_checksum(address, &data);
ESP_LOGV(TAG, "RMSOS:%02X%02X%02X%02X%02X%02X", BL0906_WRITE_COMMAND, address, data.l, data.m, data.h, data.address);
this->write_byte(BL0906_WRITE_COMMAND);
this->write_byte(address);
this->write_byte(data.l);
this->write_byte(data.m);
this->write_byte(data.h);
this->write_byte(data.address);
}
void BL0906::dump_config() {
ESP_LOGCONFIG(TAG, "BL0906:");
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
LOG_SENSOR(" ", "Current1", this->current_1_sensor_);
LOG_SENSOR(" ", "Current2", this->current_2_sensor_);
LOG_SENSOR(" ", "Current3", this->current_3_sensor_);
LOG_SENSOR(" ", "Current4", this->current_4_sensor_);
LOG_SENSOR(" ", "Current5", this->current_5_sensor_);
LOG_SENSOR(" ", "Current6", this->current_6_sensor_);
LOG_SENSOR(" ", "Power1", this->power_1_sensor_);
LOG_SENSOR(" ", "Power2", this->power_2_sensor_);
LOG_SENSOR(" ", "Power3", this->power_3_sensor_);
LOG_SENSOR(" ", "Power4", this->power_4_sensor_);
LOG_SENSOR(" ", "Power5", this->power_5_sensor_);
LOG_SENSOR(" ", "Power6", this->power_6_sensor_);
LOG_SENSOR(" ", "Energy1", this->energy_1_sensor_);
LOG_SENSOR(" ", "Energy2", this->energy_2_sensor_);
LOG_SENSOR(" ", "Energy3", this->energy_3_sensor_);
LOG_SENSOR(" ", "Energy4", this->energy_4_sensor_);
LOG_SENSOR(" ", "Energy5", this->energy_5_sensor_);
LOG_SENSOR(" ", "Energy6", this->energy_6_sensor_);
LOG_SENSOR(" ", "Total Power", this->total_power_sensor_);
LOG_SENSOR(" ", "Total Energy", this->total_energy_sensor_);
LOG_SENSOR(" ", "Frequency", this->frequency_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
}
} // namespace bl0906
} // namespace esphome

View file

@ -0,0 +1,96 @@
#pragma once
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/datatypes.h"
// https://www.belling.com.cn/media/file_object/bel_product/BL0906/datasheet/BL0906_V1.02_cn.pdf
// https://www.belling.com.cn/media/file_object/bel_product/BL0906/guide/BL0906%20APP%20Note_V1.02.pdf
namespace esphome {
namespace bl0906 {
struct DataPacket { // NOLINT(altera-struct-pack-align)
uint8_t l{0};
uint8_t m{0};
uint8_t h{0};
uint8_t checksum; // checksum
uint8_t address;
} __attribute__((packed));
struct ube24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
uint8_t l{0};
uint8_t m{0};
uint8_t h{0};
} __attribute__((packed));
struct sbe24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
uint8_t l{0};
uint8_t m{0};
int8_t h{0};
} __attribute__((packed));
template<typename... Ts> class ResetEnergyAction;
class BL0906;
using ActionCallbackFuncPtr = void (BL0906::*)();
class BL0906 : public PollingComponent, public uart::UARTDevice {
SUB_SENSOR(voltage)
SUB_SENSOR(current_1)
SUB_SENSOR(current_2)
SUB_SENSOR(current_3)
SUB_SENSOR(current_4)
SUB_SENSOR(current_5)
SUB_SENSOR(current_6)
SUB_SENSOR(power_1)
SUB_SENSOR(power_2)
SUB_SENSOR(power_3)
SUB_SENSOR(power_4)
SUB_SENSOR(power_5)
SUB_SENSOR(power_6)
SUB_SENSOR(total_power)
SUB_SENSOR(energy_1)
SUB_SENSOR(energy_2)
SUB_SENSOR(energy_3)
SUB_SENSOR(energy_4)
SUB_SENSOR(energy_5)
SUB_SENSOR(energy_6)
SUB_SENSOR(total_energy)
SUB_SENSOR(frequency)
SUB_SENSOR(temperature)
public:
void loop() override;
void update() override;
void setup() override;
void dump_config() override;
protected:
template<typename... Ts> friend class ResetEnergyAction;
void reset_energy_();
void read_data_(uint8_t address, float reference, sensor::Sensor *sensor);
void bias_correction_(uint8_t address, float measurements, float correction);
uint8_t current_channel_{0};
size_t enqueue_action_(ActionCallbackFuncPtr function);
void handle_actions_();
private:
std::vector<ActionCallbackFuncPtr> action_queue_{};
};
template<typename... Ts> class ResetEnergyAction : public Action<Ts...>, public Parented<BL0906> {
public:
void play(Ts... x) override { this->parent_->enqueue_action_(&BL0906::reset_energy_); }
};
} // namespace bl0906
} // namespace esphome

View file

@ -0,0 +1,4 @@
# const.py
ICON_ENERGY = "mdi:lightning-bolt"
ICON_FREQUENCY = "mdi:cosine-wave"
ICON_VOLTAGE = "mdi:sine-wave"

View file

@ -0,0 +1,122 @@
#pragma once
#include <cstdint>
namespace esphome {
namespace bl0906 {
// Total power conversion
static const float BL0906_WATT = 16 * 1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) /
(40.41259 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000);
// Total Energy conversion
static const float BL0906_CF = 16 * 4194304 * 0.032768 * 16 /
(3600000 * 16 *
(40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 /
(1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000))));
// Frequency conversion
static const float BL0906_FREF = 10000000;
// Temperature conversion
static const float BL0906_TREF = 12.5 / 59 - 40;
// Current conversion
static const float BL0906_IREF = 1.097 / (12875 * 1 * (5.1 + 5.1) * 1000 / 2000);
// Voltage conversion
static const float BL0906_UREF = 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) / (13162 * 1 * 100 * 1000);
// Power conversion
static const float BL0906_PREF = 1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) /
(40.41259 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000);
// Energy conversion
static const float BL0906_EREF = 4194304 * 0.032768 * 16 /
(3600000 * 16 *
(40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 /
(1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000))));
// Current coefficient
static const float BL0906_KI = 12875 * 1 * (5.1 + 5.1) * 1000 / 2000 / 1.097;
// Power coefficient
static const float BL0906_KP = 40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 / 1.097 / 1.097 /
(20000 + 20000 + 20000 + 20000 + 20000);
static const uint8_t USR_WRPROT_WITABLE[6] = {0xCA, 0x9E, 0x55, 0x55, 0x00, 0xB7};
static const uint8_t USR_WRPROT_ONLYREAD[6] = {0xCA, 0x9E, 0x00, 0x00, 0x00, 0x61};
static const uint8_t BL0906_READ_COMMAND = 0x35;
static const uint8_t BL0906_WRITE_COMMAND = 0xCA;
// Register address
// Voltage
static const uint8_t BL0906_V_RMS = 0x16;
// Total power
static const uint8_t BL0906_WATT_SUM = 0X2C;
// Current1~6
static const uint8_t BL0906_I_1_RMS = 0x0D; // current_1
static const uint8_t BL0906_I_2_RMS = 0x0E;
static const uint8_t BL0906_I_3_RMS = 0x0F;
static const uint8_t BL0906_I_4_RMS = 0x10;
static const uint8_t BL0906_I_5_RMS = 0x13;
static const uint8_t BL0906_I_6_RMS = 0x14; // current_6
// Power1~6
static const uint8_t BL0906_WATT_1 = 0X23; // power_1
static const uint8_t BL0906_WATT_2 = 0X24;
static const uint8_t BL0906_WATT_3 = 0X25;
static const uint8_t BL0906_WATT_4 = 0X26;
static const uint8_t BL0906_WATT_5 = 0X29;
static const uint8_t BL0906_WATT_6 = 0X2A; // power_6
// Active pulse count, unsigned
static const uint8_t BL0906_CF_1_CNT = 0X30; // Channel_1
static const uint8_t BL0906_CF_2_CNT = 0X31;
static const uint8_t BL0906_CF_3_CNT = 0X32;
static const uint8_t BL0906_CF_4_CNT = 0X33;
static const uint8_t BL0906_CF_5_CNT = 0X36;
static const uint8_t BL0906_CF_6_CNT = 0X37; // Channel_6
// Total active pulse count, unsigned
static const uint8_t BL0906_CF_SUM_CNT = 0X39;
// Voltage frequency cycle
static const uint8_t BL0906_FREQUENCY = 0X4E;
// Internal temperature
static const uint8_t BL0906_TEMPERATURE = 0X5E;
// Calibration register
// RMS gain adjustment register
static const uint8_t BL0906_RMSGN_1 = 0x6D; // Channel_1
static const uint8_t BL0906_RMSGN_2 = 0x6E;
static const uint8_t BL0906_RMSGN_3 = 0x6F;
static const uint8_t BL0906_RMSGN_4 = 0x70;
static const uint8_t BL0906_RMSGN_5 = 0x73;
static const uint8_t BL0906_RMSGN_6 = 0x74; // Channel_6
// RMS offset correction register
static const uint8_t BL0906_RMSOS_1 = 0x78; // Channel_1
static const uint8_t BL0906_RMSOS_2 = 0x79;
static const uint8_t BL0906_RMSOS_3 = 0x7A;
static const uint8_t BL0906_RMSOS_4 = 0x7B;
static const uint8_t BL0906_RMSOS_5 = 0x7E;
static const uint8_t BL0906_RMSOS_6 = 0x7F; // Channel_6
// Active power gain adjustment register
static const uint8_t BL0906_WATTGN_1 = 0xB7; // Channel_1
static const uint8_t BL0906_WATTGN_2 = 0xB8;
static const uint8_t BL0906_WATTGN_3 = 0xB9;
static const uint8_t BL0906_WATTGN_4 = 0xBA;
static const uint8_t BL0906_WATTGN_5 = 0xBD;
static const uint8_t BL0906_WATTGN_6 = 0xBE; // Channel_6
// User write protection setting register,
// You must first write 0x5555 to the write protection setting register before writing to other registers.
static const uint8_t BL0906_USR_WRPROT = 0x9E;
// Reset Register
static const uint8_t BL0906_SOFT_RESET = 0x9F;
const uint8_t BL0906_INIT[2][6] = {
// Reset to default
{BL0906_WRITE_COMMAND, BL0906_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x52},
// Enable User Operation Write
{BL0906_WRITE_COMMAND, BL0906_USR_WRPROT, 0x55, 0x55, 0x00, 0xB7}};
} // namespace bl0906
} // namespace esphome

View file

@ -0,0 +1,184 @@
from esphome import automation
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
from esphome.components import sensor, uart
import esphome.config_validation as cv
from esphome.const import (
CONF_CHANNEL,
CONF_CURRENT,
CONF_ENERGY,
CONF_FREQUENCY,
CONF_ID,
CONF_NAME,
CONF_POWER,
CONF_TEMPERATURE,
CONF_TOTAL_POWER,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_FREQUENCY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ICON_CURRENT_AC,
ICON_POWER,
ICON_THERMOMETER,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE,
UNIT_CELSIUS,
UNIT_HERTZ,
UNIT_KILOWATT_HOURS,
UNIT_VOLT,
UNIT_WATT,
)
# Import ICONS not included in esphome's const.py, from the local components const.py
from .const import ICON_ENERGY, ICON_FREQUENCY, ICON_VOLTAGE
DEPENDENCIES = ["uart"]
AUTO_LOAD = ["bl0906"]
CONF_TOTAL_ENERGY = "total_energy"
bl0906_ns = cg.esphome_ns.namespace("bl0906")
BL0906 = bl0906_ns.class_("BL0906", cg.PollingComponent, uart.UARTDevice)
ResetEnergyAction = bl0906_ns.class_("ResetEnergyAction", automation.Action)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(BL0906),
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
icon=ICON_FREQUENCY,
accuracy_decimals=0,
device_class=DEVICE_CLASS_FREQUENCY,
unit_of_measurement=UNIT_HERTZ,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
icon=ICON_THERMOMETER,
accuracy_decimals=0,
device_class=DEVICE_CLASS_TEMPERATURE,
unit_of_measurement=UNIT_CELSIUS,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
icon=ICON_VOLTAGE,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
unit_of_measurement=UNIT_VOLT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TOTAL_POWER): sensor.sensor_schema(
icon=ICON_POWER,
accuracy_decimals=3,
device_class=DEVICE_CLASS_POWER,
unit_of_measurement=UNIT_WATT,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_TOTAL_ENERGY): sensor.sensor_schema(
icon=ICON_ENERGY,
accuracy_decimals=3,
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
unit_of_measurement=UNIT_KILOWATT_HOURS,
),
}
)
.extend(
cv.Schema(
{
cv.Optional(f"{CONF_CHANNEL}_{i + 1}"): cv.Schema(
{
cv.Optional(CONF_CURRENT): cv.maybe_simple_value(
sensor.sensor_schema(
icon=ICON_CURRENT_AC,
accuracy_decimals=3,
device_class=DEVICE_CLASS_CURRENT,
unit_of_measurement=UNIT_AMPERE,
state_class=STATE_CLASS_MEASUREMENT,
),
key=CONF_NAME,
),
cv.Optional(CONF_POWER): cv.maybe_simple_value(
sensor.sensor_schema(
icon=ICON_POWER,
accuracy_decimals=0,
device_class=DEVICE_CLASS_POWER,
unit_of_measurement=UNIT_WATT,
state_class=STATE_CLASS_MEASUREMENT,
),
key=CONF_NAME,
),
cv.Optional(CONF_ENERGY): cv.maybe_simple_value(
sensor.sensor_schema(
icon=ICON_ENERGY,
accuracy_decimals=3,
device_class=DEVICE_CLASS_ENERGY,
unit_of_measurement=UNIT_KILOWATT_HOURS,
state_class=STATE_CLASS_TOTAL_INCREASING,
),
key=CONF_NAME,
),
}
)
for i in range(6)
}
)
)
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.polling_component_schema("60s"))
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"bl0906", baud_rate=19200, require_tx=True, require_rx=True
)
@automation.register_action(
"bl0906.reset_energy",
ResetEnergyAction,
maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(BL0906),
}
),
)
async def reset_energy_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
if frequency_config := config.get(CONF_FREQUENCY):
sens = await sensor.new_sensor(frequency_config)
cg.add(var.set_frequency_sensor(sens))
if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature_sensor(sens))
if voltage_config := config.get(CONF_VOLTAGE):
sens = await sensor.new_sensor(voltage_config)
cg.add(var.set_voltage_sensor(sens))
for i in range(6):
if channel_config := config.get(f"{CONF_CHANNEL}_{i + 1}"):
if current_config := channel_config.get(CONF_CURRENT):
sens = await sensor.new_sensor(current_config)
cg.add(getattr(var, f"set_current_{i + 1}_sensor")(sens))
if power_config := channel_config.get(CONF_POWER):
sens = await sensor.new_sensor(power_config)
cg.add(getattr(var, f"set_power_{i + 1}_sensor")(sens))
if energy_config := channel_config.get(CONF_ENERGY):
sens = await sensor.new_sensor(energy_config)
cg.add(getattr(var, f"set_energy_{i + 1}_sensor")(sens))
if total_power_config := config.get(CONF_TOTAL_POWER):
sens = await sensor.new_sensor(total_power_config)
cg.add(var.set_total_power_sensor(sens))
if total_energy_config := config.get(CONF_TOTAL_ENERGY):
sens = await sensor.new_sensor(total_energy_config)
cg.add(var.set_total_energy_sensor(sens))

View file

@ -2,6 +2,8 @@
#include "esphome/core/log.h"
#include <cinttypes>
// Datasheet: https://www.belling.com.cn/media/file_object/bel_product/BL0942/datasheet/BL0942_V1.06_en.pdf
namespace esphome {
namespace bl0942 {
@ -12,43 +14,64 @@ static const uint8_t BL0942_FULL_PACKET = 0xAA;
static const uint8_t BL0942_PACKET_HEADER = 0x55;
static const uint8_t BL0942_WRITE_COMMAND = 0xA8;
static const uint8_t BL0942_REG_I_FAST_RMS_CTRL = 0x10;
static const uint8_t BL0942_REG_MODE = 0x18;
static const uint8_t BL0942_REG_SOFT_RESET = 0x19;
static const uint8_t BL0942_REG_USR_WRPROT = 0x1A;
static const uint8_t BL0942_REG_I_RMSOS = 0x12;
static const uint8_t BL0942_REG_WA_CREEP = 0x14;
static const uint8_t BL0942_REG_I_FAST_RMS_TH = 0x15;
static const uint8_t BL0942_REG_I_FAST_RMS_CYC = 0x16;
static const uint8_t BL0942_REG_FREQ_CYC = 0x17;
static const uint8_t BL0942_REG_OT_FUNX = 0x18;
static const uint8_t BL0942_REG_MODE = 0x19;
static const uint8_t BL0942_REG_SOFT_RESET = 0x1C;
static const uint8_t BL0942_REG_USR_WRPROT = 0x1D;
static const uint8_t BL0942_REG_TPS_CTRL = 0x1B;
// TODO: Confirm insialisation works as intended
const uint8_t BL0942_INIT[5][6] = {
// Reset to default
{BL0942_WRITE_COMMAND, BL0942_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38},
// Enable User Operation Write
{BL0942_WRITE_COMMAND, BL0942_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0},
// 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS
{BL0942_WRITE_COMMAND, BL0942_REG_MODE, 0x00, 0x10, 0x00, 0x37},
// 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS
{BL0942_WRITE_COMMAND, BL0942_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE},
// 0x181C = Half cycle, Fast RMS threshold 6172
{BL0942_WRITE_COMMAND, BL0942_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
static const uint32_t BL0942_REG_MODE_RESV = 0x03;
static const uint32_t BL0942_REG_MODE_CF_EN = 0x04;
static const uint32_t BL0942_REG_MODE_RMS_UPDATE_SEL = 0x08;
static const uint32_t BL0942_REG_MODE_FAST_RMS_SEL = 0x10;
static const uint32_t BL0942_REG_MODE_AC_FREQ_SEL = 0x20;
static const uint32_t BL0942_REG_MODE_CF_CNT_CLR_SEL = 0x40;
static const uint32_t BL0942_REG_MODE_CF_CNT_ADD_SEL = 0x80;
static const uint32_t BL0942_REG_MODE_UART_RATE_19200 = 0x200;
static const uint32_t BL0942_REG_MODE_UART_RATE_38400 = 0x300;
static const uint32_t BL0942_REG_MODE_DEFAULT =
BL0942_REG_MODE_RESV | BL0942_REG_MODE_CF_EN | BL0942_REG_MODE_CF_CNT_ADD_SEL;
static const uint32_t BL0942_REG_SOFT_RESET_MAGIC = 0x5a5a5a;
static const uint32_t BL0942_REG_USR_WRPROT_MAGIC = 0x55;
// 23-byte packet, 11 bits per byte, 2400 baud: about 105ms
static const uint32_t PKT_TIMEOUT_MS = 200;
void BL0942::loop() {
DataPacket buffer;
if (!this->available()) {
int avail = this->available();
if (!avail) {
return;
}
if (read_array((uint8_t *) &buffer, sizeof(buffer))) {
if (validate_checksum(&buffer)) {
received_package_(&buffer);
if (avail < sizeof(buffer)) {
if (!this->rx_start_) {
this->rx_start_ = millis();
} else if (millis() > this->rx_start_ + PKT_TIMEOUT_MS) {
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message (%d bytes)", avail);
this->read_array((uint8_t *) &buffer, avail);
this->rx_start_ = 0;
}
} else {
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
while (read() >= 0)
;
return;
}
if (this->read_array((uint8_t *) &buffer, sizeof(buffer))) {
if (this->validate_checksum_(&buffer)) {
this->received_package_(&buffer);
}
}
this->rx_start_ = 0;
}
bool BL0942::validate_checksum(DataPacket *data) {
uint8_t checksum = BL0942_READ_COMMAND;
bool BL0942::validate_checksum_(DataPacket *data) {
uint8_t checksum = BL0942_READ_COMMAND | this->address_;
// Whole package but checksum
uint8_t *raw = (uint8_t *) data;
for (uint32_t i = 0; i < sizeof(*data) - 1; i++) {
@ -61,17 +84,58 @@ bool BL0942::validate_checksum(DataPacket *data) {
return checksum == data->checksum;
}
void BL0942::update() {
void BL0942::write_reg_(uint8_t reg, uint32_t val) {
uint8_t pkt[6];
this->flush();
this->write_byte(BL0942_READ_COMMAND);
pkt[0] = BL0942_WRITE_COMMAND | this->address_;
pkt[1] = reg;
pkt[2] = (val & 0xff);
pkt[3] = (val >> 8) & 0xff;
pkt[4] = (val >> 16) & 0xff;
pkt[5] = (pkt[0] + pkt[1] + pkt[2] + pkt[3] + pkt[4]) ^ 0xff;
this->write_array(pkt, 6);
delay(1);
}
int BL0942::read_reg_(uint8_t reg) {
union {
uint8_t b[4];
uint32_le_t le32;
} resp;
this->write_byte(BL0942_READ_COMMAND | this->address_);
this->write_byte(reg);
this->flush();
if (this->read_array(resp.b, 4) &&
resp.b[3] ==
(uint8_t) ((BL0942_READ_COMMAND + this->address_ + reg + resp.b[0] + resp.b[1] + resp.b[2]) ^ 0xff)) {
resp.b[3] = 0;
return resp.le32;
}
return -1;
}
void BL0942::update() {
this->write_byte(BL0942_READ_COMMAND | this->address_);
this->write_byte(BL0942_FULL_PACKET);
}
void BL0942::setup() {
for (auto *i : BL0942_INIT) {
this->write_array(i, 6);
delay(1);
}
this->write_reg_(BL0942_REG_USR_WRPROT, BL0942_REG_USR_WRPROT_MAGIC);
this->write_reg_(BL0942_REG_SOFT_RESET, BL0942_REG_SOFT_RESET_MAGIC);
uint32_t mode = BL0942_REG_MODE_DEFAULT;
mode |= BL0942_REG_MODE_RMS_UPDATE_SEL; /* 800ms refresh time */
if (this->line_freq_ == LINE_FREQUENCY_60HZ)
mode |= BL0942_REG_MODE_AC_FREQ_SEL;
this->write_reg_(BL0942_REG_MODE, mode);
this->write_reg_(BL0942_REG_USR_WRPROT, 0);
if (this->read_reg_(BL0942_REG_MODE) != mode)
this->status_set_warning("BL0942 setup failed!");
this->flush();
}
@ -82,10 +146,17 @@ void BL0942::received_package_(DataPacket *data) {
return;
}
// cf_cnt is only 24 bits, so track overflows
uint32_t cf_cnt = (uint24_t) data->cf_cnt;
cf_cnt |= this->prev_cf_cnt_ & 0xff000000;
if (cf_cnt < this->prev_cf_cnt_) {
cf_cnt += 0x1000000;
}
this->prev_cf_cnt_ = cf_cnt;
float v_rms = (uint24_t) data->v_rms / voltage_reference_;
float i_rms = (uint24_t) data->i_rms / current_reference_;
float watt = (int24_t) data->watt / power_reference_;
uint32_t cf_cnt = (uint24_t) data->cf_cnt;
float total_energy_consumption = cf_cnt / energy_reference_;
float frequency = 1000000.0f / data->frequency;
@ -104,13 +175,15 @@ void BL0942::received_package_(DataPacket *data) {
if (frequency_sensor_ != nullptr) {
frequency_sensor_->publish_state(frequency);
}
this->status_clear_warning();
ESP_LOGV(TAG, "BL0942: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, frequency %fHz, status 0x%08X", v_rms, i_rms,
watt, cf_cnt, total_energy_consumption, frequency, data->status);
}
void BL0942::dump_config() { // NOLINT(readability-function-cognitive-complexity)
ESP_LOGCONFIG(TAG, "BL0942:");
ESP_LOGCONFIG(TAG, " Address: %d", this->address_);
ESP_LOGCONFIG(TAG, " Nominal line frequency: %d Hz", this->line_freq_);
LOG_SENSOR("", "Voltage", this->voltage_sensor_);
LOG_SENSOR("", "Current", this->current_sensor_);
LOG_SENSOR("", "Power", this->power_sensor_);

View file

@ -28,6 +28,11 @@ struct DataPacket {
uint8_t checksum;
} __attribute__((packed));
enum LineFrequency : uint8_t {
LINE_FREQUENCY_50HZ = 50,
LINE_FREQUENCY_60HZ = 60,
};
class BL0942 : public PollingComponent, public uart::UARTDevice {
public:
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
@ -35,9 +40,10 @@ class BL0942 : public PollingComponent, public uart::UARTDevice {
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; }
void set_line_freq(LineFrequency freq) { this->line_freq_ = freq; }
void set_address(uint8_t address) { this->address_ = address; }
void loop() override;
void update() override;
void setup() override;
void dump_config() override;
@ -59,9 +65,14 @@ class BL0942 : public PollingComponent, public uart::UARTDevice {
float current_reference_ = BL0942_IREF;
// Divide by this to turn into kWh
float energy_reference_ = BL0942_EREF;
uint8_t address_ = 0;
LineFrequency line_freq_ = LINE_FREQUENCY_50HZ;
uint32_t rx_start_ = 0;
uint32_t prev_cf_cnt_ = 0;
static bool validate_checksum(DataPacket *data);
bool validate_checksum_(DataPacket *data);
int read_reg_(uint8_t reg);
void write_reg_(uint8_t reg, uint32_t val);
void received_package_(DataPacket *data);
};
} // namespace bl0942

View file

@ -1,25 +1,27 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, uart
import esphome.config_validation as cv
from esphome.const import (
CONF_ADDRESS,
CONF_CURRENT,
CONF_ENERGY,
CONF_FREQUENCY,
CONF_ID,
CONF_LINE_FREQUENCY,
CONF_POWER,
CONF_VOLTAGE,
CONF_FREQUENCY,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_FREQUENCY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_FREQUENCY,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE,
UNIT_HERTZ,
UNIT_KILOWATT_HOURS,
UNIT_VOLT,
UNIT_WATT,
UNIT_HERTZ,
STATE_CLASS_TOTAL_INCREASING,
)
DEPENDENCIES = ["uart"]
@ -27,6 +29,12 @@ DEPENDENCIES = ["uart"]
bl0942_ns = cg.esphome_ns.namespace("bl0942")
BL0942 = bl0942_ns.class_("BL0942", cg.PollingComponent, uart.UARTDevice)
LineFrequency = bl0942_ns.enum("LineFrequency")
LINE_FREQS = {
50: LineFrequency.LINE_FREQUENCY_50HZ,
60: LineFrequency.LINE_FREQUENCY_60HZ,
}
CONFIG_SCHEMA = (
cv.Schema(
{
@ -61,6 +69,14 @@ CONFIG_SCHEMA = (
device_class=DEVICE_CLASS_FREQUENCY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_LINE_FREQUENCY, default="50HZ"): cv.All(
cv.frequency,
cv.enum(
LINE_FREQS,
int=True,
),
),
cv.Optional(CONF_ADDRESS, default=0): cv.int_range(min=0, max=3),
}
)
.extend(cv.polling_component_schema("60s"))
@ -88,3 +104,5 @@ async def to_code(config):
if frequency_config := config.get(CONF_FREQUENCY):
sens = await sensor.new_sensor(frequency_config)
cg.add(var.set_frequency_sensor(sens))
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
cg.add(var.set_address(config[CONF_ADDRESS]))

View file

@ -65,9 +65,7 @@ CONF_ON_PASSKEY_NOTIFICATION = "on_passkey_notification"
CONF_ON_NUMERIC_COMPARISON_REQUEST = "on_numeric_comparison_request"
CONF_AUTO_CONNECT = "auto_connect"
# Espressif platformio framework is built with MAX_BLE_CONN to 3, so
# enforce this in yaml checks.
MULTI_CONF = 3
MULTI_CONF = True
CONFIG_SCHEMA = (
cv.Schema(

View file

@ -54,6 +54,9 @@ bool BluetoothProxy::parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_p
}
resp.advertisements.push_back(std::move(adv));
ESP_LOGV(TAG, "Proxying raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0],
result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi);
}
ESP_LOGV(TAG, "Proxying %d packets", count);
this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp);
@ -87,6 +90,8 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi
void BluetoothProxy::dump_config() {
ESP_LOGCONFIG(TAG, "Bluetooth Proxy:");
ESP_LOGCONFIG(TAG, " Active: %s", YESNO(this->active_));
ESP_LOGCONFIG(TAG, " Connections: %d", this->connections_.size());
ESP_LOGCONFIG(TAG, " Raw advertisements: %s", YESNO(this->raw_advertisements_));
}
int BluetoothProxy::get_bluetooth_connections_free() {

View file

@ -1,4 +1,5 @@
#include "captive_portal.h"
#ifdef USE_CAPTIVE_PORTAL
#include "esphome/core/log.h"
#include "esphome/core/application.h"
#include "esphome/components/wifi/wifi_component.h"
@ -91,3 +92,4 @@ CaptivePortal *global_captive_portal = nullptr; // NOLINT(cppcoreguidelines-avo
} // namespace captive_portal
} // namespace esphome
#endif

View file

@ -1,5 +1,6 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_CAPTIVE_PORTAL
#include <memory>
#ifdef USE_ARDUINO
#include <DNSServer.h>
@ -71,3 +72,4 @@ extern CaptivePortal *global_captive_portal; // NOLINT(cppcoreguidelines-avoid-
} // namespace captive_portal
} // namespace esphome
#endif

View file

@ -0,0 +1,67 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components import i2c
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_INPUT,
CONF_INVERTED,
CONF_MODE,
CONF_NUMBER,
CONF_OUTPUT,
CONF_RESTORE_VALUE,
)
CODEOWNERS = ["@jesterret"]
DEPENDENCIES = ["i2c"]
MULTI_CONF = True
ch422g_ns = cg.esphome_ns.namespace("ch422g")
CH422GComponent = ch422g_ns.class_("CH422GComponent", cg.Component, i2c.I2CDevice)
CH422GGPIOPin = ch422g_ns.class_(
"CH422GGPIOPin", cg.GPIOPin, cg.Parented.template(CH422GComponent)
)
CONF_CH422G = "ch422g"
CONFIG_SCHEMA = (
cv.Schema(
{
cv.Required(CONF_ID): cv.declare_id(CH422GComponent),
cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x24))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE]))
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
CH422G_PIN_SCHEMA = pins.gpio_base_schema(
CH422GGPIOPin,
cv.int_range(min=0, max=7),
modes=[CONF_INPUT, CONF_OUTPUT],
).extend(
{
cv.Required(CONF_CH422G): cv.use_id(CH422GComponent),
}
)
@pins.PIN_SCHEMA_REGISTRY.register(CONF_CH422G, CH422G_PIN_SCHEMA)
async def ch422g_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
parent = await cg.get_variable(config[CONF_CH422G])
cg.add(var.set_parent(parent))
num = config[CONF_NUMBER]
cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var

View file

@ -0,0 +1,122 @@
#include "ch422g.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ch422g {
const uint8_t CH422G_REG_IN = 0x26;
const uint8_t CH422G_REG_OUT = 0x38;
const uint8_t OUT_REG_DEFAULT_VAL = 0xdf;
static const char *const TAG = "ch422g";
void CH422GComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up CH422G...");
// Test to see if device exists
if (!this->read_inputs_()) {
ESP_LOGE(TAG, "CH422G not detected at 0x%02X", this->address_);
this->mark_failed();
return;
}
// restore defaults over whatever got saved on last boot
if (!this->restore_value_) {
this->write_output_(OUT_REG_DEFAULT_VAL);
}
ESP_LOGD(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(),
this->status_has_error());
}
void CH422GComponent::loop() {
// Clear all the previously read flags.
this->pin_read_cache_ = 0x00;
}
void CH422GComponent::dump_config() {
ESP_LOGCONFIG(TAG, "CH422G:");
LOG_I2C_DEVICE(this)
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with CH422G failed!");
}
}
// ch422g doesn't have any flag support (needs docs?)
void CH422GComponent::pin_mode(uint8_t pin, gpio::Flags flags) {}
bool CH422GComponent::digital_read(uint8_t pin) {
if (this->pin_read_cache_ == 0 || this->pin_read_cache_ & (1 << pin)) {
// Read values on first access or in case it's being read again in the same loop
this->read_inputs_();
}
this->pin_read_cache_ |= (1 << pin);
return this->state_mask_ & (1 << pin);
}
void CH422GComponent::digital_write(uint8_t pin, bool value) {
if (value) {
this->write_output_(this->state_mask_ | (1 << pin));
} else {
this->write_output_(this->state_mask_ & ~(1 << pin));
}
}
bool CH422GComponent::read_inputs_() {
if (this->is_failed()) {
return false;
}
uint8_t temp = 0;
if ((this->last_error_ = this->read(&temp, 1)) != esphome::i2c::ERROR_OK) {
this->status_set_warning(str_sprintf("read_inputs_(): I2C I/O error: %d", (int) this->last_error_).c_str());
return false;
}
uint8_t output = 0;
if ((this->last_error_ = this->bus_->read(CH422G_REG_IN, &output, 1)) != esphome::i2c::ERROR_OK) {
this->status_set_warning(str_sprintf("read_inputs_(): I2C I/O error: %d", (int) this->last_error_).c_str());
return false;
}
this->state_mask_ = output;
this->status_clear_warning();
return true;
}
bool CH422GComponent::write_output_(uint8_t value) {
const uint8_t temp = 1;
if ((this->last_error_ = this->write(&temp, 1, false)) != esphome::i2c::ERROR_OK) {
this->status_set_warning(str_sprintf("write_output_(): I2C I/O error: %d", (int) this->last_error_).c_str());
return false;
}
uint8_t write_mask = value;
if ((this->last_error_ = this->bus_->write(CH422G_REG_OUT, &write_mask, 1)) != esphome::i2c::ERROR_OK) {
this->status_set_warning(
str_sprintf("write_output_(): I2C I/O error: %d for write_mask: %d", (int) this->last_error_, (int) write_mask)
.c_str());
return false;
}
this->state_mask_ = value;
this->status_clear_warning();
return true;
}
float CH422GComponent::get_setup_priority() const { return setup_priority::IO; }
// Run our loop() method very early in the loop, so that we cache read values
// before other components call our digital_read() method.
float CH422GComponent::get_loop_priority() const { return 9.0f; } // Just after WIFI
void CH422GGPIOPin::setup() { pin_mode(flags_); }
void CH422GGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
bool CH422GGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
void CH422GGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
std::string CH422GGPIOPin::dump_summary() const { return str_sprintf("EXIO%u via CH422G", pin_); }
} // namespace ch422g
} // namespace esphome

View file

@ -0,0 +1,70 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace ch422g {
class CH422GComponent : public Component, public i2c::I2CDevice {
public:
CH422GComponent() = default;
/// Check i2c availability and setup masks
void setup() override;
/// Poll for input changes periodically
void loop() override;
/// Helper function to read the value of a pin.
bool digital_read(uint8_t pin);
/// Helper function to write the value of a pin.
void digital_write(uint8_t pin, bool value);
/// Helper function to set the pin mode of a pin.
void pin_mode(uint8_t pin, gpio::Flags flags);
float get_setup_priority() const override;
float get_loop_priority() const override;
void dump_config() override;
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
protected:
bool read_inputs_();
bool write_output_(uint8_t value);
/// The mask to write as output state - 1 means HIGH, 0 means LOW
uint8_t state_mask_{0x00};
/// Flags to check if read previously during this loop
uint8_t pin_read_cache_ = {0x00};
/// Storage for last I2C error seen
esphome::i2c::ErrorCode last_error_;
/// Whether we want to override stored values on expander
bool restore_value_{false};
};
/// Helper class to expose a CH422G pin as an internal input GPIO pin.
class CH422GGPIOPin : public GPIOPin {
public:
void setup() override;
void pin_mode(gpio::Flags flags) override;
bool digital_read() override;
void digital_write(bool value) override;
std::string dump_summary() const override;
void set_parent(CH422GComponent *parent) { parent_ = parent; }
void set_pin(uint8_t pin) { pin_ = pin; }
void set_inverted(bool inverted) { inverted_ = inverted; }
void set_flags(gpio::Flags flags) { flags_ = flags; }
protected:
CH422GComponent *parent_;
uint8_t pin_;
bool inverted_;
gpio::Flags flags_;
};
} // namespace ch422g
} // namespace esphome

View file

@ -186,7 +186,7 @@ async def datetime_date_set_to_code(config, action_id, template_arg, args):
date_config = config[CONF_DATE]
if cg.is_template(date_config):
template_ = await cg.templatable(date_config, [], cg.ESPTime)
template_ = await cg.templatable(date_config, args, cg.ESPTime)
cg.add(action_var.set_date(template_))
else:
date_struct = cg.StructInitializer(
@ -217,7 +217,7 @@ async def datetime_time_set_to_code(config, action_id, template_arg, args):
time_config = config[CONF_TIME]
if cg.is_template(time_config):
template_ = await cg.templatable(time_config, [], cg.ESPTime)
template_ = await cg.templatable(time_config, args, cg.ESPTime)
cg.add(action_var.set_time(template_))
else:
time_struct = cg.StructInitializer(
@ -248,7 +248,7 @@ async def datetime_datetime_set_to_code(config, action_id, template_arg, args):
datetime_config = config[CONF_DATETIME]
if cg.is_template(datetime_config):
template_ = await cg.templatable(datetime_config, [], cg.ESPTime)
template_ = await cg.templatable(datetime_config, args, cg.ESPTime)
cg.add(action_var.set_datetime(template_))
else:
datetime_struct = cg.StructInitializer(

View file

@ -16,6 +16,8 @@
#include <esp32s2/rom/rtc.h>
#elif defined(USE_ESP32_VARIANT_ESP32S3)
#include <esp32s3/rom/rtc.h>
#elif defined(USE_ESP32_VARIANT_ESP32H2)
#include <esp32h2/rom/rtc.h>
#endif
#ifdef USE_ARDUINO
#include <Esp.h>
@ -61,7 +63,7 @@ std::string DebugComponent::get_reset_reason_() {
case RTCWDT_SYS_RESET:
reset_reason = "RTC Watch Dog Reset Digital Core";
break;
#if !defined(USE_ESP32_VARIANT_ESP32C6)
#if !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2)
case INTRUSION_RESET:
reset_reason = "Intrusion Reset CPU";
break;

View file

@ -16,10 +16,11 @@ class DemoSensor : public sensor::Sensor, public PollingComponent {
float base = std::isnan(this->state) ? 0.0f : this->state;
this->publish_state(base + val * 10);
} else {
if (val < 0.1)
if (val < 0.1) {
this->publish_state(NAN);
else
} else {
this->publish_state(val * 100);
}
}
}
};

View file

@ -1,15 +1,15 @@
from esphome import automation, core
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import core, automation
from esphome.automation import maybe_simple_id
from esphome.const import (
CONF_AUTO_CLEAR_ENABLED,
CONF_FROM,
CONF_ID,
CONF_LAMBDA,
CONF_PAGES,
CONF_PAGE_ID,
CONF_PAGES,
CONF_ROTATION,
CONF_FROM,
CONF_TO,
CONF_TRIGGER_ID,
)
@ -195,3 +195,4 @@ async def display_is_displaying_page_to_code(config, condition_id, template_arg,
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(display_ns.using)
cg.add_define("USE_DISPLAY")

View file

@ -1,4 +1,5 @@
#include "e131.h"
#ifdef USE_NETWORK
#include "e131_addressable_light_effect.h"
#include "esphome/core/log.h"
@ -118,3 +119,4 @@ bool E131Component::process_(int universe, const E131Packet &packet) {
} // namespace e131
} // namespace esphome
#endif

View file

@ -1,5 +1,6 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_NETWORK
#include "esphome/components/socket/socket.h"
#include "esphome/core/component.h"
@ -53,3 +54,4 @@ class E131Component : public esphome::Component {
} // namespace e131
} // namespace esphome
#endif

View file

@ -1,5 +1,6 @@
#include "e131_addressable_light_effect.h"
#include "e131.h"
#ifdef USE_NETWORK
#include "esphome/core/log.h"
namespace esphome {
@ -90,3 +91,4 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet
} // namespace e131
} // namespace esphome
#endif

View file

@ -2,7 +2,7 @@
#include "esphome/core/component.h"
#include "esphome/components/light/addressable_light_effect.h"
#ifdef USE_NETWORK
namespace esphome {
namespace e131 {
@ -42,3 +42,4 @@ class E131AddressableLightEffect : public light::AddressableLightEffect {
} // namespace e131
} // namespace esphome
#endif

View file

@ -1,5 +1,6 @@
#include <cstring>
#include "e131.h"
#ifdef USE_NETWORK
#include "esphome/components/network/ip_address.h"
#include "esphome/core/log.h"
#include "esphome/core/util.h"
@ -137,3 +138,4 @@ bool E131Component::packet_(const std::vector<uint8_t> &data, int &universe, E13
} // namespace e131
} // namespace esphome
#endif

View file

@ -172,6 +172,19 @@ def add_idf_component(
KEY_COMPONENTS: components,
KEY_SUBMODULES: submodules,
}
else:
component_config = CORE.data[KEY_ESP32][KEY_COMPONENTS][name]
if components is not None:
component_config[KEY_COMPONENTS] = list(
set(component_config[KEY_COMPONENTS] + components)
)
if submodules is not None:
if component_config[KEY_SUBMODULES] is None:
component_config[KEY_SUBMODULES] = submodules
else:
component_config[KEY_SUBMODULES] = list(
set(component_config[KEY_SUBMODULES] + submodules)
)
def add_extra_script(stage: str, filename: str, path: str):

View file

@ -1,18 +1,23 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
import esphome.codegen as cg
from esphome.components import canbus
from esphome.const import CONF_ID, CONF_RX_PIN, CONF_TX_PIN
from esphome.components.canbus import CanbusComponent, CanSpeed, CONF_BIT_RATE
from esphome.components.canbus import CONF_BIT_RATE, CanbusComponent, CanSpeed
from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32.const import (
VARIANT_ESP32,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
VARIANT_ESP32C3,
VARIANT_ESP32C6,
VARIANT_ESP32H2,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_RX_PIN,
CONF_RX_QUEUE_LEN,
CONF_TX_PIN,
CONF_TX_QUEUE_LEN,
)
CODEOWNERS = ["@Sympatron"]
@ -77,6 +82,8 @@ CONFIG_SCHEMA = canbus.CANBUS_SCHEMA.extend(
cv.Optional(CONF_BIT_RATE, default="125KBPS"): validate_bit_rate,
cv.Required(CONF_RX_PIN): pins.internal_gpio_input_pin_number,
cv.Required(CONF_TX_PIN): pins.internal_gpio_output_pin_number,
cv.Optional(CONF_RX_QUEUE_LEN): cv.uint32_t,
cv.Optional(CONF_TX_QUEUE_LEN): cv.uint32_t,
}
)
@ -87,3 +94,7 @@ async def to_code(config):
cg.add(var.set_rx(config[CONF_RX_PIN]))
cg.add(var.set_tx(config[CONF_TX_PIN]))
if (rx_queue_len := config.get(CONF_RX_QUEUE_LEN)) is not None:
cg.add(var.set_rx_queue_len(rx_queue_len))
if (tx_queue_len := config.get(CONF_TX_QUEUE_LEN)) is not None:
cg.add(var.set_tx_queue_len(tx_queue_len))

View file

@ -69,6 +69,13 @@ static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config
bool ESP32Can::setup_internal() {
twai_general_config_t g_config =
TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, TWAI_MODE_NORMAL);
if (this->tx_queue_len_.has_value()) {
g_config.tx_queue_len = this->tx_queue_len_.value();
}
if (this->rx_queue_len_.has_value()) {
g_config.rx_queue_len = this->rx_queue_len_.value();
}
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
twai_timing_config_t t_config;
@ -111,6 +118,7 @@ canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) {
.flags = flags,
.identifier = frame->can_id,
.data_length_code = frame->can_data_length_code,
.data = {}, // to suppress warning, data is initialized properly below
};
if (!frame->remote_transmission_request) {
memcpy(message.data, frame->data, frame->can_data_length_code);

View file

@ -12,6 +12,8 @@ class ESP32Can : public canbus::Canbus {
public:
void set_rx(int rx) { rx_ = rx; }
void set_tx(int tx) { tx_ = tx; }
void set_tx_queue_len(uint32_t tx_queue_len) { this->tx_queue_len_ = tx_queue_len; }
void set_rx_queue_len(uint32_t rx_queue_len) { this->rx_queue_len_ = rx_queue_len; }
ESP32Can(){};
protected:
@ -21,6 +23,8 @@ class ESP32Can : public canbus::Canbus {
int rx_{-1};
int tx_{-1};
optional<uint32_t> tx_queue_len_{};
optional<uint32_t> rx_queue_len_{};
};
} // namespace esp32_can

View file

@ -38,7 +38,8 @@ void ESP32RMTLEDStripLightOutput::setup() {
}
ExternalRAMAllocator<rmt_item32_t> rmt_allocator(ExternalRAMAllocator<rmt_item32_t>::ALLOW_FAILURE);
this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8); // 8 bits per byte, 1 rmt_item32_t per bit
this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 +
1); // 8 bits per byte, 1 rmt_item32_t per bit + 1 rmt_item32_t for reset
rmt_config_t config;
memset(&config, 0, sizeof(config));
@ -66,7 +67,7 @@ void ESP32RMTLEDStripLightOutput::setup() {
}
void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high,
uint32_t bit1_low) {
uint32_t bit1_low, uint32_t reset_time_high, uint32_t reset_time_low) {
float ratio = (float) RMT_CLK_FREQ / RMT_CLK_DIV / 1e09f;
// 0-bit
@ -79,6 +80,11 @@ void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bi
this->bit1_.level0 = 1;
this->bit1_.duration1 = (uint32_t) (ratio * bit1_low);
this->bit1_.level1 = 0;
// reset
this->reset_.duration0 = (uint32_t) (ratio * reset_time_high);
this->reset_.level0 = 1;
this->reset_.duration1 = (uint32_t) (ratio * reset_time_low);
this->reset_.level1 = 0;
}
void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
@ -118,6 +124,12 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
psrc++;
}
if (this->reset_.duration0 > 0 || this->reset_.duration1 > 0) {
pdest->val = this->reset_.val;
pdest++;
len++;
}
if (rmt_write_items(this->channel_, this->rmt_buf_, len, false) != ESP_OK) {
ESP_LOGE(TAG, "RMT TX error");
this->status_set_warning();

View file

@ -49,7 +49,8 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
/// Set a maximum refresh rate in µs as some lights do not like being updated too often.
void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; }
void set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, uint32_t bit1_low);
void set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, uint32_t bit1_low,
uint32_t reset_time_high, uint32_t reset_time_low);
void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; }
void set_rmt_channel(rmt_channel_t channel) { this->channel_ = channel; }
@ -75,7 +76,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
bool is_rgbw_;
bool is_wrgb_;
rmt_item32_t bit0_, bit1_;
rmt_item32_t bit0_, bit1_, reset_;
RGBOrder rgb_order_;
rmt_channel_t channel_;

View file

@ -43,13 +43,16 @@ class LEDStripTimings:
bit0_low: int
bit1_high: int
bit1_low: int
reset_high: int
reset_low: int
CHIPSETS = {
"WS2812": LEDStripTimings(400, 1000, 1000, 400),
"SK6812": LEDStripTimings(300, 900, 600, 600),
"APA106": LEDStripTimings(350, 1360, 1360, 350),
"SM16703": LEDStripTimings(300, 900, 900, 300),
"WS2811": LEDStripTimings(300, 1090, 1090, 320, 0, 300000),
"WS2812": LEDStripTimings(400, 1000, 1000, 400, 0, 0),
"SK6812": LEDStripTimings(300, 900, 600, 600, 0, 0),
"APA106": LEDStripTimings(350, 1360, 1360, 350, 0, 0),
"SM16703": LEDStripTimings(300, 900, 900, 300, 0, 0),
}
@ -58,6 +61,8 @@ CONF_BIT0_HIGH = "bit0_high"
CONF_BIT0_LOW = "bit0_low"
CONF_BIT1_HIGH = "bit1_high"
CONF_BIT1_LOW = "bit1_low"
CONF_RESET_HIGH = "reset_high"
CONF_RESET_LOW = "reset_low"
CONFIG_SCHEMA = cv.All(
@ -88,6 +93,14 @@ CONFIG_SCHEMA = cv.All(
CONF_BIT1_LOW,
"custom",
): cv.positive_time_period_nanoseconds,
cv.Optional(
CONF_RESET_HIGH,
default="0 us",
): cv.positive_time_period_nanoseconds,
cv.Optional(
CONF_RESET_LOW,
default="0 us",
): cv.positive_time_period_nanoseconds,
}
),
cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH),
@ -113,6 +126,8 @@ async def to_code(config):
chipset.bit0_low,
chipset.bit1_high,
chipset.bit1_low,
chipset.reset_high,
chipset.reset_low,
)
)
else:
@ -122,6 +137,8 @@ async def to_code(config):
config[CONF_BIT0_LOW],
config[CONF_BIT1_HIGH],
config[CONF_BIT1_LOW],
config[CONF_RESET_HIGH],
config[CONF_RESET_LOW],
)
)

View file

@ -1,5 +1,5 @@
#include "ota_esphome.h"
#ifdef USE_OTA
#include "esphome/components/md5/md5.h"
#include "esphome/components/network/util.h"
#include "esphome/components/ota/ota_backend.h"
@ -410,3 +410,4 @@ float ESPHomeOTAComponent::get_setup_priority() const { return setup_priority::A
uint16_t ESPHomeOTAComponent::get_port() const { return this->port_; }
void ESPHomeOTAComponent::set_port(uint16_t port) { this->port_ = port; }
} // namespace esphome
#endif

View file

@ -1,6 +1,7 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_OTA
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
#include "esphome/components/ota/ota_backend.h"
@ -41,3 +42,4 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
};
} // namespace esphome
#endif

View file

@ -472,13 +472,13 @@ void EthernetComponent::start_connect_() {
if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) {
ESPHL_ERROR_CHECK(err, "DHCPC start error");
}
#if USE_NETWORK_IPV6
err = esp_netif_create_ip6_linklocal(this->eth_netif_);
if (err != ESP_OK) {
ESPHL_ERROR_CHECK(err, "Enable IPv6 link local failed");
}
#endif /* USE_NETWORK_IPV6 */
}
#if USE_NETWORK_IPV6
err = esp_netif_create_ip6_linklocal(this->eth_netif_);
if (err != ESP_OK) {
ESPHL_ERROR_CHECK(err, "Enable IPv6 link local failed");
}
#endif /* USE_NETWORK_IPV6 */
this->connect_begin_ = millis();
this->status_set_warning();

View file

@ -307,7 +307,7 @@ void FingerprintGrowComponent::delete_fingerprint(uint16_t finger_id) {
void FingerprintGrowComponent::delete_all_fingerprints() {
ESP_LOGI(TAG, "Deleting all stored fingerprints");
this->data_ = {EMPTY};
this->data_ = {DELETE_ALL};
switch (this->send_command_()) {
case OK:
ESP_LOGI(TAG, "Deleted all fingerprints");

View file

@ -36,7 +36,7 @@ enum GrowCommand {
LOAD = 0x07,
UPLOAD = 0x08,
DELETE = 0x0C,
EMPTY = 0x0D,
DELETE_ALL = 0x0D, // aka EMPTY
READ_SYS_PARAM = 0x0F,
SET_PASSWORD = 0x12,
VERIFY_PASSWORD = 0x13,

View file

@ -1,43 +1,35 @@
import functools
import hashlib
import logging
import functools
from pathlib import Path
import os
from pathlib import Path
import re
from packaging import version
import requests
from esphome import core
from esphome import external_files
import esphome.config_validation as cv
from esphome import core, external_files
import esphome.codegen as cg
from esphome.helpers import (
copy_file_if_changed,
cpp_string_escape,
)
import esphome.config_validation as cv
from esphome.const import (
CONF_FAMILY,
CONF_FILE,
CONF_GLYPHS,
CONF_ID,
CONF_PATH,
CONF_RAW_DATA_ID,
CONF_TYPE,
CONF_REFRESH,
CONF_SIZE,
CONF_PATH,
CONF_WEIGHT,
CONF_TYPE,
CONF_URL,
CONF_WEIGHT,
)
from esphome.core import (
CORE,
HexInt,
)
from esphome.core import CORE, HexInt
from esphome.helpers import copy_file_if_changed, cpp_string_escape
_LOGGER = logging.getLogger(__name__)
DOMAIN = "font"
DEPENDENCIES = ["display"]
MULTI_CONF = True
CODEOWNERS = ["@esphome/core", "@clydebarrow"]
@ -400,10 +392,7 @@ class EFont:
def convert_bitmap_to_pillow_font(filepath):
from PIL import (
PcfFontFile,
BdfFontFile,
)
from PIL import BdfFontFile, PcfFontFile
local_bitmap_font_file = external_files.compute_local_file_dir(
DOMAIN,

View file

@ -1,9 +1,8 @@
#include "font.h"
#include "esphome/core/color.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/color.h"
#include "esphome/components/display/display_buffer.h"
namespace esphome {
namespace font {
@ -68,6 +67,7 @@ int Font::match_next_glyph(const uint8_t *str, int *match_length) {
return -1;
return lo;
}
#ifdef USE_DISPLAY
void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) {
*baseline = this->baseline_;
*height = this->height_;
@ -164,6 +164,7 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
i += match_length;
}
}
#endif
} // namespace font
} // namespace esphome

View file

@ -1,8 +1,11 @@
#pragma once
#include "esphome/core/datatypes.h"
#include "esphome/core/color.h"
#include "esphome/components/display/display_buffer.h"
#include "esphome/core/datatypes.h"
#include "esphome/core/defines.h"
#ifdef USE_DISPLAY
#include "esphome/components/display/display.h"
#endif
namespace esphome {
namespace font {
@ -38,7 +41,11 @@ class Glyph {
const GlyphData *glyph_data_;
};
class Font : public display::BaseFont {
class Font
#ifdef USE_DISPLAY
: public display::BaseFont
#endif
{
public:
/** Construct the font with the given glyphs.
*
@ -50,9 +57,11 @@ class Font : public display::BaseFont {
int match_next_glyph(const uint8_t *str, int *match_length);
#ifdef USE_DISPLAY
void print(int x_start, int y_start, display::Display *display, Color color, const char *text,
Color background) override;
void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) override;
#endif
inline int get_baseline() { return this->baseline_; }
inline int get_height() { return this->height_; }
inline int get_bpp() { return this->bpp_; }

View file

@ -1,6 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import climate_ir
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MODEL
CODEOWNERS = ["@orestismers"]
@ -17,6 +17,7 @@ MODELS = {
"yaa": Model.GREE_YAA,
"yac": Model.GREE_YAC,
"yac1fb9": Model.GREE_YAC1FB9,
"yx1ff": Model.GREE_YX1FF,
}
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(

View file

@ -6,7 +6,15 @@ namespace gree {
static const char *const TAG = "gree.climate";
void GreeClimate::set_model(Model model) { this->model_ = model; }
void GreeClimate::set_model(Model model) {
if (model == GREE_YX1FF) {
this->fan_modes_.insert(climate::CLIMATE_FAN_QUIET); // YX1FF 4 speed
this->presets_.insert(climate::CLIMATE_PRESET_NONE); // YX1FF sleep mode
this->presets_.insert(climate::CLIMATE_PRESET_SLEEP); // YX1FF sleep mode
}
this->model_ = model;
}
void GreeClimate::transmit_state() {
uint8_t remote_state[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00};
@ -14,7 +22,7 @@ void GreeClimate::transmit_state() {
remote_state[0] = this->fan_speed_() | this->operation_mode_();
remote_state[1] = this->temperature_();
if (this->model_ == GREE_YAN) {
if (this->model_ == GREE_YAN || this->model_ == GREE_YX1FF) {
remote_state[2] = 0x60;
remote_state[3] = 0x50;
remote_state[4] = this->vertical_swing_();
@ -36,8 +44,18 @@ void GreeClimate::transmit_state() {
}
}
if (this->model_ == GREE_YX1FF) {
if (this->fan_speed_() == GREE_FAN_TURBO) {
remote_state[2] |= GREE_FAN_TURBO_BIT;
}
if (this->preset_() == GREE_PRESET_SLEEP) {
remote_state[0] |= GREE_PRESET_SLEEP_BIT;
}
}
// Calculate the checksum
if (this->model_ == GREE_YAN) {
if (this->model_ == GREE_YAN || this->model_ == GREE_YX1FF) {
remote_state[7] = ((remote_state[0] << 4) + (remote_state[1] << 4) + 0xC0);
} else {
remote_state[7] =
@ -124,6 +142,23 @@ uint8_t GreeClimate::operation_mode_() {
}
uint8_t GreeClimate::fan_speed_() {
// YX1FF has 4 fan speeds -- we treat low as quiet and turbo as high
if (this->model_ == GREE_YX1FF) {
switch (this->fan_mode.value()) {
case climate::CLIMATE_FAN_QUIET:
return GREE_FAN_1;
case climate::CLIMATE_FAN_LOW:
return GREE_FAN_2;
case climate::CLIMATE_FAN_MEDIUM:
return GREE_FAN_3;
case climate::CLIMATE_FAN_HIGH:
return GREE_FAN_TURBO;
case climate::CLIMATE_FAN_AUTO:
default:
return GREE_FAN_AUTO;
}
}
switch (this->fan_mode.value()) {
case climate::CLIMATE_FAN_LOW:
return GREE_FAN_1;
@ -161,5 +196,21 @@ uint8_t GreeClimate::temperature_() {
return (uint8_t) roundf(clamp<float>(this->target_temperature, GREE_TEMP_MIN, GREE_TEMP_MAX));
}
uint8_t GreeClimate::preset_() {
// YX1FF has sleep preset
if (this->model_ == GREE_YX1FF) {
switch (this->preset.value()) {
case climate::CLIMATE_PRESET_NONE:
return GREE_PRESET_NONE;
case climate::CLIMATE_PRESET_SLEEP:
return GREE_PRESET_SLEEP;
default:
return GREE_PRESET_NONE;
}
}
return GREE_PRESET_NONE;
}
} // namespace gree
} // namespace esphome

View file

@ -25,7 +25,6 @@ const uint8_t GREE_FAN_AUTO = 0x00;
const uint8_t GREE_FAN_1 = 0x10;
const uint8_t GREE_FAN_2 = 0x20;
const uint8_t GREE_FAN_3 = 0x30;
const uint8_t GREE_FAN_TURBO = 0x80;
// IR Transmission
const uint32_t GREE_IR_FREQUENCY = 38000;
@ -70,8 +69,16 @@ const uint8_t GREE_HDIR_MIDDLE = 0x04;
const uint8_t GREE_HDIR_MRIGHT = 0x05;
const uint8_t GREE_HDIR_RIGHT = 0x06;
// Only available on YX1FF
// Turbo (high) fan mode + sleep preset mode
const uint8_t GREE_FAN_TURBO = 0x80;
const uint8_t GREE_FAN_TURBO_BIT = 0x10;
const uint8_t GREE_PRESET_NONE = 0x00;
const uint8_t GREE_PRESET_SLEEP = 0x01;
const uint8_t GREE_PRESET_SLEEP_BIT = 0x80;
// Model codes
enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC, GREE_YAC1FB9 };
enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC, GREE_YAC1FB9, GREE_YX1FF };
class GreeClimate : public climate_ir::ClimateIR {
public:
@ -93,6 +100,7 @@ class GreeClimate : public climate_ir::ClimateIR {
uint8_t horizontal_swing_();
uint8_t vertical_swing_();
uint8_t temperature_();
uint8_t preset_();
Model model_{};
};

View file

@ -1,11 +1,10 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
import esphome.codegen as cg
from esphome.components import i2c, touchscreen
from esphome.const import CONF_INTERRUPT_PIN, CONF_ID
from .. import gt911_ns
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN
from .. import gt911_ns
GT911ButtonListener = gt911_ns.class_("GT911ButtonListener")
GT911Touchscreen = gt911_ns.class_(
@ -18,6 +17,7 @@ CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(GT911Touchscreen),
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
}
).extend(i2c.i2c_device_schema(0x5D))
@ -29,3 +29,5 @@ async def to_code(config):
if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin)))
if reset_pin := config.get(CONF_RESET_PIN):
cg.add(var.set_reset_pin(await cg.gpio_pin_expression(reset_pin)))

View file

@ -26,6 +26,23 @@ static const size_t MAX_BUTTONS = 4; // max number of buttons scanned
void GT911Touchscreen::setup() {
i2c::ErrorCode err;
ESP_LOGCONFIG(TAG, "Setting up GT911 Touchscreen...");
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(false);
if (this->interrupt_pin_ != nullptr) {
// The interrupt pin is used as an input during reset to select the I2C address.
this->interrupt_pin_->pin_mode(gpio::FLAG_OUTPUT);
this->interrupt_pin_->setup();
this->interrupt_pin_->digital_write(false);
}
delay(2);
this->reset_pin_->digital_write(true);
delay(50); // NOLINT
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT);
this->interrupt_pin_->setup();
}
}
// check the configuration of the int line.
uint8_t data[4];

View file

@ -19,12 +19,14 @@ class GT911Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
void dump_config() override;
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
void register_button_listener(GT911ButtonListener *listener) { this->button_listeners_.push_back(listener); }
protected:
void update_touches() override;
InternalGPIOPin *interrupt_pin_{};
GPIOPin *reset_pin_{};
std::vector<GT911ButtonListener *> button_listeners_;
uint8_t button_state_{0xFF}; // last button state. Initial FF guarantees first update.
};

View file

@ -80,8 +80,8 @@ class HaierClimateBase : public esphome::Component,
const char *phase_to_string_(ProtocolPhases phase);
virtual void set_handlers() = 0;
virtual void process_phase(std::chrono::steady_clock::time_point now) = 0;
virtual haier_protocol::HaierMessage get_control_message() = 0;
virtual haier_protocol::HaierMessage get_power_message(bool state) = 0;
virtual haier_protocol::HaierMessage get_control_message() = 0; // NOLINT(readability-identifier-naming)
virtual haier_protocol::HaierMessage get_power_message(bool state) = 0; // NOLINT(readability-identifier-naming)
virtual void initialization(){};
virtual bool prepare_pending_action();
virtual void process_protocol_reset();

View file

@ -0,0 +1,2 @@
AUTO_LOAD = ["md5"]
CODEOWNERS = ["@dwmw2"]

View file

@ -0,0 +1,56 @@
#include <cstdio>
#include <cstring>
#include "hmac_md5.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace hmac_md5 {
void HmacMD5::init(const uint8_t *key, size_t len) {
uint8_t ipad[64], opad[64];
memset(ipad, 0, sizeof(ipad));
if (len > 64) {
md5::MD5Digest keymd5;
keymd5.init();
keymd5.add(key, len);
keymd5.calculate();
keymd5.get_bytes(ipad);
} else {
memcpy(ipad, key, len);
}
memcpy(opad, ipad, sizeof(opad));
for (int i = 0; i < 64; i++) {
ipad[i] ^= 0x36;
opad[i] ^= 0x5c;
}
this->ihash_.init();
this->ihash_.add(ipad, sizeof(ipad));
this->ohash_.init();
this->ohash_.add(opad, sizeof(opad));
}
void HmacMD5::add(const uint8_t *data, size_t len) { this->ihash_.add(data, len); }
void HmacMD5::calculate() {
uint8_t ibytes[16];
this->ihash_.calculate();
this->ihash_.get_bytes(ibytes);
this->ohash_.add(ibytes, sizeof(ibytes));
this->ohash_.calculate();
}
void HmacMD5::get_bytes(uint8_t *output) { this->ohash_.get_bytes(output); }
void HmacMD5::get_hex(char *output) { this->ohash_.get_hex(output); }
bool HmacMD5::equals_bytes(const uint8_t *expected) { return this->ohash_.equals_bytes(expected); }
bool HmacMD5::equals_hex(const char *expected) { return this->ohash_.equals_hex(expected); }
} // namespace hmac_md5
} // namespace esphome

View file

@ -0,0 +1,48 @@
#pragma once
#include "esphome/core/defines.h"
#include "esphome/components/md5/md5.h"
#include <string>
namespace esphome {
namespace hmac_md5 {
class HmacMD5 {
public:
HmacMD5() = default;
~HmacMD5() = default;
/// Initialize a new MD5 digest computation.
void init(const uint8_t *key, size_t len);
void init(const char *key, size_t len) { this->init((const uint8_t *) key, len); }
void init(const std::string &key) { this->init(key.c_str(), key.length()); }
/// Add bytes of data for the digest.
void add(const uint8_t *data, size_t len);
void add(const char *data, size_t len) { this->add((const uint8_t *) data, len); }
/// Compute the digest, based on the provided data.
void calculate();
/// Retrieve the HMAC-MD5 digest as bytes.
/// The output must be able to hold 16 bytes or more.
void get_bytes(uint8_t *output);
/// Retrieve the HMAC-MD5 digest as hex characters.
/// The output must be able to hold 32 bytes or more.
void get_hex(char *output);
/// Compare the digest against a provided byte-encoded digest (16 bytes).
bool equals_bytes(const uint8_t *expected);
/// Compare the digest against a provided hex-encoded digest (32 bytes).
bool equals_hex(const char *expected);
protected:
md5::MD5Digest ihash_;
md5::MD5Digest ohash_;
};
} // namespace hmac_md5
} // namespace esphome

View file

@ -2,7 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_INTERNAL
CODEOWNERS = ["@OttoWinter"]
CODEOWNERS = ["@OttoWinter", "@esphome/core"]
homeassistant_ns = cg.esphome_ns.namespace("homeassistant")
HOME_ASSISTANT_IMPORT_SCHEMA = cv.Schema(
@ -13,6 +13,13 @@ HOME_ASSISTANT_IMPORT_SCHEMA = cv.Schema(
}
)
HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA = cv.Schema(
{
cv.Required(CONF_ENTITY_ID): cv.entity_id,
cv.Optional(CONF_INTERNAL, default=True): cv.boolean,
}
)
def setup_home_assistant_entity(var, config):
cg.add(var.set_entity_id(config[CONF_ENTITY_ID]))

View file

@ -0,0 +1,33 @@
import esphome.codegen as cg
from esphome.components import number
import esphome.config_validation as cv
from .. import (
HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA,
homeassistant_ns,
setup_home_assistant_entity,
)
CODEOWNERS = ["@landonr"]
DEPENDENCIES = ["api"]
HomeassistantNumber = homeassistant_ns.class_(
"HomeassistantNumber", number.Number, cg.Component
)
CONFIG_SCHEMA = (
number.number_schema(HomeassistantNumber)
.extend(HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = await number.new_number(
config,
min_value=0,
max_value=0,
step=0,
)
await cg.register_component(var, config)
setup_home_assistant_entity(var, config)

View file

@ -0,0 +1,100 @@
#include "homeassistant_number.h"
#include "esphome/components/api/api_pb2.h"
#include "esphome/components/api/api_server.h"
#include "esphome/core/log.h"
namespace esphome {
namespace homeassistant {
static const char *const TAG = "homeassistant.number";
void HomeassistantNumber::state_changed_(const std::string &state) {
auto number_value = parse_number<float>(state);
if (!number_value.has_value()) {
ESP_LOGW(TAG, "'%s': Can't convert '%s' to number!", this->entity_id_.c_str(), state.c_str());
this->publish_state(NAN);
return;
}
if (this->state == number_value.value()) {
return;
}
ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_.c_str(), state.c_str());
this->publish_state(number_value.value());
}
void HomeassistantNumber::min_retrieved_(const std::string &min) {
auto min_value = parse_number<float>(min);
if (!min_value.has_value()) {
ESP_LOGE(TAG, "'%s': Can't convert 'min' value '%s' to number!", this->entity_id_.c_str(), min.c_str());
}
ESP_LOGD(TAG, "'%s': Min retrieved: %s", get_name().c_str(), min.c_str());
this->traits.set_min_value(min_value.value());
}
void HomeassistantNumber::max_retrieved_(const std::string &max) {
auto max_value = parse_number<float>(max);
if (!max_value.has_value()) {
ESP_LOGE(TAG, "'%s': Can't convert 'max' value '%s' to number!", this->entity_id_.c_str(), max.c_str());
}
ESP_LOGD(TAG, "'%s': Max retrieved: %s", get_name().c_str(), max.c_str());
this->traits.set_max_value(max_value.value());
}
void HomeassistantNumber::step_retrieved_(const std::string &step) {
auto step_value = parse_number<float>(step);
if (!step_value.has_value()) {
ESP_LOGE(TAG, "'%s': Can't convert 'step' value '%s' to number!", this->entity_id_.c_str(), step.c_str());
}
ESP_LOGD(TAG, "'%s': Step Retrieved %s", get_name().c_str(), step.c_str());
this->traits.set_step(step_value.value());
}
void HomeassistantNumber::setup() {
api::global_api_server->subscribe_home_assistant_state(
this->entity_id_, nullopt, std::bind(&HomeassistantNumber::state_changed_, this, std::placeholders::_1));
api::global_api_server->get_home_assistant_state(
this->entity_id_, optional<std::string>("min"),
std::bind(&HomeassistantNumber::min_retrieved_, this, std::placeholders::_1));
api::global_api_server->get_home_assistant_state(
this->entity_id_, optional<std::string>("max"),
std::bind(&HomeassistantNumber::max_retrieved_, this, std::placeholders::_1));
api::global_api_server->get_home_assistant_state(
this->entity_id_, optional<std::string>("step"),
std::bind(&HomeassistantNumber::step_retrieved_, this, std::placeholders::_1));
}
void HomeassistantNumber::dump_config() {
LOG_NUMBER("", "Homeassistant Number", this);
ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_.c_str());
}
float HomeassistantNumber::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
void HomeassistantNumber::control(float value) {
if (!api::global_api_server->is_connected()) {
ESP_LOGE(TAG, "No clients connected to API server");
return;
}
this->publish_state(value);
api::HomeassistantServiceResponse resp;
resp.service = "number.set_value";
api::HomeassistantServiceMap entity_id;
entity_id.key = "entity_id";
entity_id.value = this->entity_id_;
resp.data.push_back(entity_id);
api::HomeassistantServiceMap entity_value;
entity_value.key = "value";
entity_value.value = to_string(value);
resp.data.push_back(entity_value);
api::global_api_server->send_homeassistant_service_call(resp);
}
} // namespace homeassistant
} // namespace esphome

View file

@ -0,0 +1,31 @@
#pragma once
#include <map>
#include <string>
#include "esphome/components/number/number.h"
#include "esphome/core/component.h"
namespace esphome {
namespace homeassistant {
class HomeassistantNumber : public number::Number, public Component {
public:
void set_entity_id(const std::string &entity_id) { this->entity_id_ = entity_id; }
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
protected:
void state_changed_(const std::string &state);
void min_retrieved_(const std::string &min);
void max_retrieved_(const std::string &max);
void step_retrieved_(const std::string &step);
void control(float value) override;
std::string entity_id_;
};
} // namespace homeassistant
} // namespace esphome

View file

@ -0,0 +1,30 @@
import esphome.codegen as cg
from esphome.components import switch
import esphome.config_validation as cv
from esphome.const import CONF_ID
from .. import (
HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA,
homeassistant_ns,
setup_home_assistant_entity,
)
CODEOWNERS = ["@Links2004"]
DEPENDENCIES = ["api"]
HomeassistantSwitch = homeassistant_ns.class_(
"HomeassistantSwitch", switch.Switch, cg.Component
)
CONFIG_SCHEMA = (
switch.switch_schema(HomeassistantSwitch)
.extend(cv.COMPONENT_SCHEMA)
.extend(HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA)
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await switch.register_switch(var, config)
setup_home_assistant_entity(var, config)

View file

@ -0,0 +1,59 @@
#include "homeassistant_switch.h"
#include "esphome/components/api/api_server.h"
#include "esphome/core/log.h"
namespace esphome {
namespace homeassistant {
static const char *const TAG = "homeassistant.switch";
using namespace esphome::switch_;
void HomeassistantSwitch::setup() {
api::global_api_server->subscribe_home_assistant_state(this->entity_id_, nullopt, [this](const std::string &state) {
auto val = parse_on_off(state.c_str());
switch (val) {
case PARSE_NONE:
case PARSE_TOGGLE:
ESP_LOGW(TAG, "Can't convert '%s' to binary state!", state.c_str());
break;
case PARSE_ON:
case PARSE_OFF:
bool new_state = val == PARSE_ON;
ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_.c_str(), ONOFF(new_state));
this->publish_state(new_state);
break;
}
});
}
void HomeassistantSwitch::dump_config() {
LOG_SWITCH("", "Homeassistant Switch", this);
ESP_LOGCONFIG(TAG, " Entity ID: '%s'", this->entity_id_.c_str());
}
float HomeassistantSwitch::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
void HomeassistantSwitch::write_state(bool state) {
if (!api::global_api_server->is_connected()) {
ESP_LOGE(TAG, "No clients connected to API server");
return;
}
api::HomeassistantServiceResponse resp;
if (state) {
resp.service = "switch.turn_on";
} else {
resp.service = "switch.turn_off";
}
api::HomeassistantServiceMap entity_id_kv;
entity_id_kv.key = "entity_id";
entity_id_kv.value = this->entity_id_;
resp.data.push_back(entity_id_kv);
api::global_api_server->send_homeassistant_service_call(resp);
}
} // namespace homeassistant
} // namespace esphome

View file

@ -0,0 +1,22 @@
#pragma once
#include "esphome/components/switch/switch.h"
#include "esphome/core/component.h"
namespace esphome {
namespace homeassistant {
class HomeassistantSwitch : public switch_::Switch, public Component {
public:
void set_entity_id(const std::string &entity_id) { this->entity_id_ = entity_id; }
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
protected:
void write_state(bool state) override;
std::string entity_id_;
};
} // namespace homeassistant
} // namespace esphome

View file

@ -1,15 +1,14 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
CONF_MAC_ADDRESS,
KEY_CORE,
KEY_FRAMEWORK_VERSION,
KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM,
PLATFORM_HOST,
CONF_MAC_ADDRESS,
)
from esphome.core import CORE
from esphome.helpers import IS_MACOS
import esphome.config_validation as cv
import esphome.codegen as cg
from .const import KEY_HOST
@ -42,8 +41,5 @@ async def to_code(config):
cg.add_build_flag("-DUSE_HOST")
cg.add_define("USE_ESPHOME_HOST_MAC_ADDRESS", config[CONF_MAC_ADDRESS].parts)
cg.add_build_flag("-std=c++17")
cg.add_build_flag("-lsodium")
if IS_MACOS:
cg.add_build_flag("-L/opt/homebrew/lib")
cg.add_define("ESPHOME_BOARD", "host")
cg.add_platformio_option("platform", "platformio/native")

View file

@ -180,7 +180,11 @@ void I2SAudioSpeaker::player_task(void *params) {
}
}
void I2SAudioSpeaker::stop() {
void I2SAudioSpeaker::stop() { this->stop_(false); }
void I2SAudioSpeaker::finish() { this->stop_(true); }
void I2SAudioSpeaker::stop_(bool wait_on_empty) {
if (this->is_failed())
return;
if (this->state_ == speaker::STATE_STOPPED)
@ -192,7 +196,11 @@ void I2SAudioSpeaker::stop() {
this->state_ = speaker::STATE_STOPPING;
DataEvent data;
data.stop = true;
xQueueSendToFront(this->buffer_queue_, &data, portMAX_DELAY);
if (wait_on_empty) {
xQueueSend(this->buffer_queue_, &data, portMAX_DELAY);
} else {
xQueueSendToFront(this->buffer_queue_, &data, portMAX_DELAY);
}
}
void I2SAudioSpeaker::watch_() {

View file

@ -53,6 +53,7 @@ class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAud
void start() override;
void stop() override;
void finish() override;
size_t play(const uint8_t *data, size_t length) override;
@ -60,6 +61,7 @@ class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAud
protected:
void start_();
void stop_(bool wait_on_empty);
void watch_();
static void player_task(void *params);

View file

@ -1,31 +1,31 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import core, pins
from esphome.components import display, spi, font
import esphome.codegen as cg
from esphome.components import display, font, spi
from esphome.components.display import validate_rotation
from esphome.core import CORE, HexInt
import esphome.config_validation as cv
from esphome.const import (
CONF_COLOR_ORDER,
CONF_COLOR_PALETTE,
CONF_DC_PIN,
CONF_ID,
CONF_LAMBDA,
CONF_MODEL,
CONF_RAW_DATA_ID,
CONF_PAGES,
CONF_RESET_PIN,
CONF_DIMENSIONS,
CONF_WIDTH,
CONF_HEIGHT,
CONF_ROTATION,
CONF_ID,
CONF_INVERT_COLORS,
CONF_LAMBDA,
CONF_MIRROR_X,
CONF_MIRROR_Y,
CONF_SWAP_XY,
CONF_COLOR_ORDER,
CONF_MODEL,
CONF_OFFSET_HEIGHT,
CONF_OFFSET_WIDTH,
CONF_PAGES,
CONF_RAW_DATA_ID,
CONF_RESET_PIN,
CONF_ROTATION,
CONF_SWAP_XY,
CONF_TRANSFORM,
CONF_INVERT_COLORS,
CONF_WIDTH,
)
from esphome.core import CORE, HexInt
DEPENDENCIES = ["spi"]
@ -177,7 +177,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_INVERT_DISPLAY): cv.invalid(
"'invert_display' has been replaced by 'invert_colors'"
),
cv.Optional(CONF_INVERT_COLORS): cv.boolean,
cv.Required(CONF_INVERT_COLORS): cv.boolean,
cv.Optional(CONF_COLOR_ORDER): cv.one_of(*COLOR_ORDERS.keys(), upper=True),
cv.Exclusive(CONF_ROTATION, CONF_ROTATION): validate_rotation,
cv.Exclusive(CONF_TRANSFORM, CONF_ROTATION): cv.Schema(
@ -287,5 +287,4 @@ async def to_code(config):
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
cg.add(var.set_palette(prog_arr))
if CONF_INVERT_COLORS in config:
cg.add(var.invert_colors(config[CONF_INVERT_COLORS]))
cg.add(var.invert_colors(config[CONF_INVERT_COLORS]))

View file

@ -118,6 +118,7 @@ void ILI9XXXDisplay::dump_config() {
ESP_LOGCONFIG(TAG, " Swap_xy: %s", YESNO(this->swap_xy_));
ESP_LOGCONFIG(TAG, " Mirror_x: %s", YESNO(this->mirror_x_));
ESP_LOGCONFIG(TAG, " Mirror_y: %s", YESNO(this->mirror_y_));
ESP_LOGCONFIG(TAG, " Invert colors: %s", YESNO(this->pre_invertcolors_));
if (this->is_failed()) {
ESP_LOGCONFIG(TAG, " => Failed to init Memory: YES!");
@ -154,7 +155,6 @@ void ILI9XXXDisplay::fill(Color color) {
}
}
return;
break;
default:
new_color = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB);
break;

View file

@ -28,8 +28,8 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_40MHZ> {
public:
ILI9XXXDisplay() = default;
ILI9XXXDisplay(uint8_t const *init_sequence, int16_t width, int16_t height, bool invert_colors)
: init_sequence_{init_sequence}, width_{width}, height_{height}, pre_invertcolors_{invert_colors} {
ILI9XXXDisplay(uint8_t const *init_sequence, int16_t width, int16_t height)
: init_sequence_{init_sequence}, width_{width}, height_{height} {
uint8_t cmd, num_args, bits;
const uint8_t *addr = init_sequence;
while ((cmd = *addr++) != 0) {
@ -144,7 +144,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
bool need_update_ = false;
bool is_18bitdisplay_ = false;
PixelMode pixel_mode_{};
bool pre_invertcolors_ = false;
bool pre_invertcolors_{};
display::ColorOrder color_order_{display::COLOR_ORDER_BGR};
bool swap_xy_{};
bool mirror_x_{};
@ -154,54 +154,54 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
//----------- M5Stack display --------------
class ILI9XXXM5Stack : public ILI9XXXDisplay {
public:
ILI9XXXM5Stack() : ILI9XXXDisplay(INITCMD_M5STACK, 320, 240, true) {}
ILI9XXXM5Stack() : ILI9XXXDisplay(INITCMD_M5STACK, 320, 240) {}
};
//----------- M5Stack display --------------
class ILI9XXXM5CORE : public ILI9XXXDisplay {
public:
ILI9XXXM5CORE() : ILI9XXXDisplay(INITCMD_M5CORE, 320, 240, true) {}
ILI9XXXM5CORE() : ILI9XXXDisplay(INITCMD_M5CORE, 320, 240) {}
};
//----------- ST7789V display --------------
class ILI9XXXST7789V : public ILI9XXXDisplay {
public:
ILI9XXXST7789V() : ILI9XXXDisplay(INITCMD_ST7789V, 240, 320, false) {}
ILI9XXXST7789V() : ILI9XXXDisplay(INITCMD_ST7789V, 240, 320) {}
};
//----------- ILI9XXX_24_TFT display --------------
class ILI9XXXILI9341 : public ILI9XXXDisplay {
public:
ILI9XXXILI9341() : ILI9XXXDisplay(INITCMD_ILI9341, 240, 320, false) {}
ILI9XXXILI9341() : ILI9XXXDisplay(INITCMD_ILI9341, 240, 320) {}
};
//----------- ILI9XXX_24_TFT rotated display --------------
class ILI9XXXILI9342 : public ILI9XXXDisplay {
public:
ILI9XXXILI9342() : ILI9XXXDisplay(INITCMD_ILI9341, 320, 240, false) {}
ILI9XXXILI9342() : ILI9XXXDisplay(INITCMD_ILI9341, 320, 240) {}
};
//----------- ILI9XXX_??_TFT rotated display --------------
class ILI9XXXILI9481 : public ILI9XXXDisplay {
public:
ILI9XXXILI9481() : ILI9XXXDisplay(INITCMD_ILI9481, 480, 320, false) {}
ILI9XXXILI9481() : ILI9XXXDisplay(INITCMD_ILI9481, 480, 320) {}
};
//----------- ILI9481 in 18 bit mode --------------
class ILI9XXXILI948118 : public ILI9XXXDisplay {
public:
ILI9XXXILI948118() : ILI9XXXDisplay(INITCMD_ILI9481_18, 320, 480, true) {}
ILI9XXXILI948118() : ILI9XXXDisplay(INITCMD_ILI9481_18, 320, 480) {}
};
//----------- ILI9XXX_35_TFT rotated display --------------
class ILI9XXXILI9486 : public ILI9XXXDisplay {
public:
ILI9XXXILI9486() : ILI9XXXDisplay(INITCMD_ILI9486, 480, 320, false) {}
ILI9XXXILI9486() : ILI9XXXDisplay(INITCMD_ILI9486, 480, 320) {}
};
class ILI9XXXILI9488 : public ILI9XXXDisplay {
public:
ILI9XXXILI9488(const uint8_t *seq = INITCMD_ILI9488) : ILI9XXXDisplay(seq, 480, 320, true) {}
ILI9XXXILI9488(const uint8_t *seq = INITCMD_ILI9488) : ILI9XXXDisplay(seq, 480, 320) {}
protected:
void set_madctl() override {
@ -246,34 +246,34 @@ class WAVESHARERES35 : public ILI9XXXILI9488 {
//----------- ILI9XXX_35_TFT origin colors rotated display --------------
class ILI9XXXILI9488A : public ILI9XXXDisplay {
public:
ILI9XXXILI9488A() : ILI9XXXDisplay(INITCMD_ILI9488_A, 480, 320, true) {}
ILI9XXXILI9488A() : ILI9XXXDisplay(INITCMD_ILI9488_A, 480, 320) {}
};
//----------- ILI9XXX_35_TFT rotated display --------------
class ILI9XXXST7796 : public ILI9XXXDisplay {
public:
ILI9XXXST7796() : ILI9XXXDisplay(INITCMD_ST7796, 320, 480, false) {}
ILI9XXXST7796() : ILI9XXXDisplay(INITCMD_ST7796, 320, 480) {}
};
class ILI9XXXS3Box : public ILI9XXXDisplay {
public:
ILI9XXXS3Box() : ILI9XXXDisplay(INITCMD_S3BOX, 320, 240, false) {}
ILI9XXXS3Box() : ILI9XXXDisplay(INITCMD_S3BOX, 320, 240) {}
};
class ILI9XXXS3BoxLite : public ILI9XXXDisplay {
public:
ILI9XXXS3BoxLite() : ILI9XXXDisplay(INITCMD_S3BOXLITE, 320, 240, true) {}
ILI9XXXS3BoxLite() : ILI9XXXDisplay(INITCMD_S3BOXLITE, 320, 240) {}
};
class ILI9XXXGC9A01A : public ILI9XXXDisplay {
public:
ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240, true) {}
ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240) {}
};
//----------- ILI9XXX_24_TFT display --------------
class ILI9XXXST7735 : public ILI9XXXDisplay {
public:
ILI9XXXST7735() : ILI9XXXDisplay(INITCMD_ST7735, 128, 160, false) {}
ILI9XXXST7735() : ILI9XXXDisplay(INITCMD_ST7735, 128, 160) {}
};
} // namespace ili9xxx

View file

@ -101,7 +101,6 @@ static const uint8_t PROGMEM INITCMD_ILI9481[] = {
ILI9XXX_MADCTL , 1, MADCTL_MV | MADCTL_BGR, // Memory Access Control
ILI9XXX_CSCON , 1, 0x01,
ILI9XXX_PIXFMT, 1, 0x55, // 16 bit mode
ILI9XXX_INVON, 0,
ILI9XXX_DISPON, 0x80, // Set display on
0x00 // end
};
@ -121,7 +120,6 @@ static const uint8_t PROGMEM INITCMD_ILI9481_18[] = {
ILI9XXX_MADCTL , 1, MADCTL_MX| MADCTL_BGR, // Memory Access Control
ILI9XXX_CSCON , 1, 0x01,
ILI9XXX_PIXFMT, 1, 0x66, // 18 bit mode
ILI9XXX_INVON, 0,
ILI9XXX_DISPON, 0x80, // Set display on
0x00 // end
};
@ -204,7 +202,6 @@ static const uint8_t PROGMEM INITCMD_ILI9488_A[] = {
ILI9XXX_SLPOUT, 0x80, // Exit sleep mode
//ILI9XXX_INVON , 0,
ILI9XXX_DISPON, 0x80, // Set display on
0x00 // end
};

View file

@ -1,5 +1,5 @@
#include "improv_serial_component.h"
#ifdef USE_WIFI
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
@ -313,3 +313,4 @@ ImprovSerialComponent *global_improv_serial_component = // NOLINT(cppcoreguidel
} // namespace improv_serial
} // namespace esphome
#endif

View file

@ -5,7 +5,7 @@
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#ifdef USE_WIFI
#include <improv.h>
#include <vector>
@ -78,3 +78,4 @@ extern ImprovSerialComponent
} // namespace improv_serial
} // namespace esphome
#endif

View file

@ -8,6 +8,8 @@
#endif
#include <driver/ledc.h>
#include <cinttypes>
#define CLOCK_FREQUENCY 80e6f
#ifdef USE_ARDUINO
@ -115,20 +117,22 @@ void LEDCOutput::write_state(float state) {
const uint32_t max_duty = (uint32_t(1) << this->bit_depth_) - 1;
const float duty_rounded = roundf(state * max_duty);
auto duty = static_cast<uint32_t>(duty_rounded);
ESP_LOGV(TAG, "Setting duty: %" PRIu32 " on channel %u", duty, this->channel_);
#ifdef USE_ARDUINO
ESP_LOGV(TAG, "Setting duty: %u on channel %u", duty, this->channel_);
ledcWrite(this->channel_, duty);
#endif
#ifdef USE_ESP_IDF
// ensure that 100% on is not 99.975% on
if ((duty == max_duty) && (max_duty != 1)) {
duty = max_duty + 1;
}
auto speed_mode = get_speed_mode(channel_);
auto chan_num = static_cast<ledc_channel_t>(channel_ % 8);
int hpoint = ledc_angle_to_htop(this->phase_angle_, this->bit_depth_);
ledc_set_duty_with_hpoint(speed_mode, chan_num, duty, hpoint);
ledc_update_duty(speed_mode, chan_num);
if (duty == max_duty) {
ledc_stop(speed_mode, chan_num, 1);
} else if (duty == 0) {
ledc_stop(speed_mode, chan_num, 0);
} else {
ledc_set_duty_with_hpoint(speed_mode, chan_num, duty, hpoint);
ledc_update_duty(speed_mode, chan_num);
}
#endif
}

View file

@ -114,10 +114,11 @@ class AddressableColorWipeEffect : public AddressableLightEffect {
if (now - this->last_add_ < this->add_led_interval_)
return;
this->last_add_ = now;
if (this->reverse_)
if (this->reverse_) {
it.shift_left(1);
else
} else {
it.shift_right(1);
}
const AddressableColorWipeEffectColor &color = this->colors_[this->at_color_];
Color esp_color = Color(color.r, color.g, color.b, color.w);
if (color.gradient) {
@ -127,10 +128,11 @@ class AddressableColorWipeEffect : public AddressableLightEffect {
uint8_t gradient = 255 * ((float) this->leds_added_ / color.num_leds);
esp_color = esp_color.gradient(next_esp_color, gradient);
}
if (this->reverse_)
if (this->reverse_) {
it[-1] = esp_color;
else
} else {
it[0] = esp_color;
}
if (++this->leds_added_ >= color.num_leds) {
this->leds_added_ = 0;
this->at_color_ = (this->at_color_ + 1) % this->colors_.size();
@ -207,10 +209,11 @@ class AddressableTwinkleEffect : public AddressableLightEffect {
const uint8_t sine = half_sin8(view.get_effect_data());
view = current_color * sine;
const uint8_t new_pos = view.get_effect_data() + pos_add;
if (new_pos < view.get_effect_data())
if (new_pos < view.get_effect_data()) {
view.set_effect_data(0);
else
} else {
view.set_effect_data(new_pos);
}
} else {
view = Color::BLACK;
}
@ -254,10 +257,11 @@ class AddressableRandomTwinkleEffect : public AddressableLightEffect {
view = Color(((color >> 2) & 1) * sine, ((color >> 1) & 1) * sine, ((color >> 0) & 1) * sine);
}
const uint8_t new_x = x + pos_add;
if (new_x > 0b11111)
if (new_x > 0b11111) {
view.set_effect_data(0);
else
} else {
view.set_effect_data((new_x << 3) | color);
}
} else {
view = Color(0, 0, 0, 0);
}

View file

@ -7,6 +7,8 @@
namespace esphome {
namespace light {
enum class LimitMode { CLAMP, DO_NOTHING };
template<typename... Ts> class ToggleAction : public Action<Ts...> {
public:
explicit ToggleAction(LightState *state) : state_(state) {}
@ -77,7 +79,10 @@ template<typename... Ts> class DimRelativeAction : public Action<Ts...> {
float rel = this->relative_brightness_.value(x...);
float cur;
this->parent_->remote_values.as_brightness(&cur);
float new_brightness = clamp(cur + rel, 0.0f, 1.0f);
if ((limit_mode_ == LimitMode::DO_NOTHING) && ((cur < min_brightness_) || (cur > max_brightness_))) {
return;
}
float new_brightness = clamp(cur + rel, min_brightness_, max_brightness_);
call.set_state(new_brightness != 0.0f);
call.set_brightness(new_brightness);
@ -85,8 +90,18 @@ template<typename... Ts> class DimRelativeAction : public Action<Ts...> {
call.perform();
}
void set_min_max_brightness(float min, float max) {
this->min_brightness_ = min;
this->max_brightness_ = max;
}
void set_limit_mode(LimitMode limit_mode) { this->limit_mode_ = limit_mode; }
protected:
LightState *parent_;
float min_brightness_{0.0};
float max_brightness_{1.0};
LimitMode limit_mode_{LimitMode::CLAMP};
};
template<typename... Ts> class LightIsOnCondition : public Condition<Ts...> {

Some files were not shown because too many files have changed in this diff Show more