Compare commits

...

16 Commits

Author SHA1 Message Date
Jesse Hills
d407c6fe69 gitpod changes 2022-10-19 20:34:23 +00:00
Jesse Hills
4deef55257 Try run inside esphome container image 2022-10-19 20:15:12 +00:00
hagak
d7576f67e8 Added component Daikin BRC to support ceiling cassette heatpumps (#3743) 2022-10-19 20:29:22 +13:00
Maximilian
138de643a2 Add adc128s102 sensor (#3822)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-10-19 17:06:22 +13:00
Carlos Gustavo Sarmiento
f30e54d177 Implementation for Atlas Scientific Peristaltic Pump (#3528)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-10-19 16:08:27 +13:00
Jadson Santos
41b5cb06d3 New platform ethernet_info from component text_sensor (#3811)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-10-19 13:44:48 +13:00
Marcel Hoppe
4ac72d7d08 Add support for wl-134 (#3569)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-10-19 13:44:26 +13:00
Jesse Hills
06ac4980ba Merge branch 'release' into dev 2022-10-19 11:15:19 +13:00
Jesse Hills
8bb670521d Remove address type map from bluetooth proxy (#3905) 2022-10-15 08:35:35 +13:00
Frédéric Jouault
225b3c1494 Send true and not RSSI in ble_presence (#3904) 2022-10-14 12:47:05 +13:00
Kuba Szczodrzyński
4bf94e0757 Allow preserving WiFi credentials entered with captive_portal (#3813) 2022-10-14 08:58:42 +13:00
Brian Kaufman
3b21d1d81e Don't Use Base Network Manual IP for WiFi AP (#3902) 2022-10-14 08:55:59 +13:00
Frank Riley
5ec1588110 Update the ibeacon code (#3859) 2022-10-13 12:59:07 +13:00
Sergey Dudanov
71387be72e Modbus QWORD fix (#3856) 2022-10-13 12:50:45 +13:00
Guillermo Ruffino
98171c9f49 fix never calling preset change trigger (#3864)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2022-10-13 10:11:59 +13:00
Jesse Hills
bf15b1d302 Bump version to 2022.11.0-dev 2022-10-13 09:18:46 +13:00
39 changed files with 2301 additions and 23 deletions

View File

@@ -1,8 +1,9 @@
---
image: ghcr.io/esphome/esphome-lint:dev
ports:
- port: 6052
onOpen: open-preview
onOpen: open-browser
tasks:
# yamllint disable-line rule:line-length
- before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup
command: python -m esphome dashboard config
- before: script/devcontainer-post-create
command: python3 -m esphome dashboard config

View File

@@ -13,6 +13,7 @@ esphome/core/* @esphome/core
# Integrations
esphome/components/ac_dimmer/* @glmnet
esphome/components/adc/* @esphome/core
esphome/components/adc128s102/* @DeerMaximum
esphome/components/addressable_light/* @justfalter
esphome/components/airthings_ble/* @jeromelaban
esphome/components/airthings_wave_mini/* @ncareau
@@ -57,6 +58,7 @@ esphome/components/cse7761/* @berfenger
esphome/components/ct_clamp/* @jesserockz
esphome/components/current_based/* @djwmarcx
esphome/components/dac7678/* @NickB1
esphome/components/daikin_brc/* @hagak
esphome/components/daly_bms/* @s1lvi0
esphome/components/dashboard_import/* @esphome/core
esphome/components/debug/* @OttoWinter
@@ -76,8 +78,10 @@ esphome/components/esp32_camera_web_server/* @ayufan
esphome/components/esp32_can/* @Sympatron
esphome/components/esp32_improv/* @jesserockz
esphome/components/esp8266/* @esphome/core
esphome/components/ethernet_info/* @gtjadsonsantos
esphome/components/exposure_notifications/* @OttoWinter
esphome/components/ezo/* @ssieb
esphome/components/ezo_pmp/* @carlos-sarmiento
esphome/components/factory_reset/* @anatoly-savchenkov
esphome/components/fastled_base/* @OttoWinter
esphome/components/feedback/* @ianchi
@@ -254,6 +258,7 @@ esphome/components/wake_on_lan/* @willwill2will54
esphome/components/web_server_base/* @OttoWinter
esphome/components/whirlpool/* @glmnet
esphome/components/whynter/* @aeonsablaze
esphome/components/wl_134/* @hobbypunk90
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
esphome/components/xiaomi_mhoc303/* @drug123
esphome/components/xiaomi_mhoc401/* @vevsvevs

View File

@@ -0,0 +1,23 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import spi
from esphome.const import CONF_ID
DEPENDENCIES = ["spi"]
MULTI_CONF = True
CODEOWNERS = ["@DeerMaximum"]
adc128s102_ns = cg.esphome_ns.namespace("adc128s102")
ADC128S102 = adc128s102_ns.class_("ADC128S102", cg.Component, spi.SPIDevice)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(ADC128S102),
}
).extend(spi.spi_device_schema(cs_pin_required=True))
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)

View File

@@ -0,0 +1,35 @@
#include "adc128s102.h"
#include "esphome/core/log.h"
namespace esphome {
namespace adc128s102 {
static const char *const TAG = "adc128s102";
float ADC128S102::get_setup_priority() const { return setup_priority::HARDWARE; }
void ADC128S102::setup() {
ESP_LOGCONFIG(TAG, "Setting up adc128s102");
this->spi_setup();
}
void ADC128S102::dump_config() {
ESP_LOGCONFIG(TAG, "ADC128S102:");
LOG_PIN(" CS Pin:", this->cs_);
}
uint16_t ADC128S102::read_data(uint8_t channel) {
uint8_t control = channel << 3;
this->enable();
uint8_t adc_primary_byte = this->transfer_byte(control);
uint8_t adc_secondary_byte = this->transfer_byte(0x00);
this->disable();
uint16_t digital_value = adc_primary_byte << 8 | adc_secondary_byte;
return digital_value;
}
} // namespace adc128s102
} // namespace esphome

View File

@@ -0,0 +1,23 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/spi/spi.h"
namespace esphome {
namespace adc128s102 {
class ADC128S102 : public Component,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
spi::DATA_RATE_10MHZ> {
public:
ADC128S102() = default;
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
uint16_t read_data(uint8_t channel);
};
} // namespace adc128s102
} // namespace esphome

View File

@@ -0,0 +1,35 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, voltage_sampler
from esphome.const import CONF_ID, CONF_CHANNEL
from .. import adc128s102_ns, ADC128S102
AUTO_LOAD = ["voltage_sampler"]
DEPENDENCIES = ["adc128s102"]
ADC128S102Sensor = adc128s102_ns.class_(
"ADC128S102Sensor",
sensor.Sensor,
cg.PollingComponent,
voltage_sampler.VoltageSampler,
)
CONF_ADC128S102_ID = "adc128s102_id"
CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(ADC128S102Sensor),
cv.GenerateID(CONF_ADC128S102_ID): cv.use_id(ADC128S102),
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7),
}
).extend(cv.polling_component_schema("60s"))
async def to_code(config):
var = cg.new_Pvariable(
config[CONF_ID],
config[CONF_CHANNEL],
)
await cg.register_parented(var, config[CONF_ADC128S102_ID])
await cg.register_component(var, config)
await sensor.register_sensor(var, config)

View File

@@ -0,0 +1,24 @@
#include "adc128s102_sensor.h"
#include "esphome/core/log.h"
namespace esphome {
namespace adc128s102 {
static const char *const TAG = "adc128s102.sensor";
ADC128S102Sensor::ADC128S102Sensor(uint8_t channel) : channel_(channel) {}
float ADC128S102Sensor::get_setup_priority() const { return setup_priority::DATA; }
void ADC128S102Sensor::dump_config() {
LOG_SENSOR("", "ADC128S102 Sensor", this);
ESP_LOGCONFIG(TAG, " Pin: %u", this->channel_);
LOG_UPDATE_INTERVAL(this);
}
float ADC128S102Sensor::sample() { return this->parent_->read_data(this->channel_); }
void ADC128S102Sensor::update() { this->publish_state(this->sample()); }
} // namespace adc128s102
} // namespace esphome

View File

@@ -0,0 +1,29 @@
#pragma once
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/voltage_sampler/voltage_sampler.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "../adc128s102.h"
namespace esphome {
namespace adc128s102 {
class ADC128S102Sensor : public PollingComponent,
public Parented<ADC128S102>,
public sensor::Sensor,
public voltage_sampler::VoltageSampler {
public:
ADC128S102Sensor(uint8_t channel);
void update() override;
void dump_config() override;
float get_setup_priority() const override;
float sample() override;
protected:
uint8_t channel_;
};
} // namespace adc128s102
} // namespace esphome

View File

@@ -1,8 +1,9 @@
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome.components import web_server_base
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
from esphome.const import CONF_ID
from esphome.const import CONF_ID, CONF_NETWORKS, CONF_PASSWORD, CONF_SSID, CONF_WIFI
from esphome.core import coroutine_with_priority, CORE
AUTO_LOAD = ["web_server_base"]
@@ -12,6 +13,7 @@ CODEOWNERS = ["@OttoWinter"]
captive_portal_ns = cg.esphome_ns.namespace("captive_portal")
CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component)
CONF_KEEP_USER_CREDENTIALS = "keep_user_credentials"
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
@@ -19,12 +21,29 @@ CONFIG_SCHEMA = cv.All(
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
web_server_base.WebServerBase
),
cv.Optional(CONF_KEEP_USER_CREDENTIALS, default=False): cv.boolean,
}
).extend(cv.COMPONENT_SCHEMA),
cv.only_with_arduino,
)
def validate_wifi(config):
wifi_conf = fv.full_config.get()[CONF_WIFI]
if config.get(CONF_KEEP_USER_CREDENTIALS, False) and (
CONF_SSID in wifi_conf
or CONF_PASSWORD in wifi_conf
or CONF_NETWORKS in wifi_conf
):
raise cv.Invalid(
f"WiFi credentials cannot be used together with {CONF_KEEP_USER_CREDENTIALS}"
)
return config
FINAL_VALIDATE_SCHEMA = validate_wifi
@coroutine_with_priority(64.0)
async def to_code(config):
paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
@@ -38,3 +57,6 @@ async def to_code(config):
cg.add_library("WiFi", None)
if CORE.is_esp8266:
cg.add_library("DNSServer", None)
if config.get(CONF_KEEP_USER_CREDENTIALS, False):
cg.add_define("USE_CAPTIVE_PORTAL_KEEP_USER_CREDENTIALS")

View File

@@ -0,0 +1 @@
CODEOWNERS = ["@hagak"]

View File

@@ -0,0 +1,24 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import climate_ir
from esphome.const import CONF_ID
AUTO_LOAD = ["climate_ir"]
daikin_brc_ns = cg.esphome_ns.namespace("daikin_brc")
DaikinBrcClimate = daikin_brc_ns.class_("DaikinBrcClimate", climate_ir.ClimateIR)
CONF_USE_FAHRENHEIT = "use_fahrenheit"
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(DaikinBrcClimate),
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
}
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await climate_ir.register_climate_ir(var, config)
cg.add(var.set_fahrenheit(config[CONF_USE_FAHRENHEIT]))

View File

@@ -0,0 +1,273 @@
#include "daikin_brc.h"
#include "esphome/components/remote_base/remote_base.h"
namespace esphome {
namespace daikin_brc {
static const char *const TAG = "daikin_brc.climate";
void DaikinBrcClimate::control(const climate::ClimateCall &call) {
this->mode_button_ = 0x00;
if (call.get_mode().has_value()) {
// Need to determine if this is call due to Mode button pressed so that we can set the Mode button byte
this->mode_button_ = DAIKIN_BRC_IR_MODE_BUTTON;
}
ClimateIR::control(call);
}
void DaikinBrcClimate::transmit_state() {
uint8_t remote_state[DAIKIN_BRC_TRANSMIT_FRAME_SIZE] = {0x11, 0xDA, 0x17, 0x18, 0x04, 0x00, 0x1E, 0x11,
0xDA, 0x17, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x20, 0x00};
remote_state[12] = this->alt_mode_();
remote_state[13] = this->mode_button_;
remote_state[14] = this->operation_mode_();
remote_state[17] = this->temperature_();
remote_state[18] = this->fan_speed_swing_();
// Calculate checksum
for (int i = DAIKIN_BRC_PREAMBLE_SIZE; i < DAIKIN_BRC_TRANSMIT_FRAME_SIZE - 1; i++) {
remote_state[DAIKIN_BRC_TRANSMIT_FRAME_SIZE - 1] += remote_state[i];
}
auto transmit = this->transmitter_->transmit();
auto *data = transmit.get_data();
data->set_carrier_frequency(DAIKIN_BRC_IR_FREQUENCY);
data->mark(DAIKIN_BRC_HEADER_MARK);
data->space(DAIKIN_BRC_HEADER_SPACE);
for (int i = 0; i < DAIKIN_BRC_PREAMBLE_SIZE; i++) {
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
data->mark(DAIKIN_BRC_BIT_MARK);
bool bit = remote_state[i] & mask;
data->space(bit ? DAIKIN_BRC_ONE_SPACE : DAIKIN_BRC_ZERO_SPACE);
}
}
data->mark(DAIKIN_BRC_BIT_MARK);
data->space(DAIKIN_BRC_MESSAGE_SPACE);
data->mark(DAIKIN_BRC_HEADER_MARK);
data->space(DAIKIN_BRC_HEADER_SPACE);
for (int i = DAIKIN_BRC_PREAMBLE_SIZE; i < DAIKIN_BRC_TRANSMIT_FRAME_SIZE; i++) {
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
data->mark(DAIKIN_BRC_BIT_MARK);
bool bit = remote_state[i] & mask;
data->space(bit ? DAIKIN_BRC_ONE_SPACE : DAIKIN_BRC_ZERO_SPACE);
}
}
data->mark(DAIKIN_BRC_BIT_MARK);
data->space(0);
transmit.perform();
}
uint8_t DaikinBrcClimate::alt_mode_() {
uint8_t alt_mode = 0x00;
switch (this->mode) {
case climate::CLIMATE_MODE_DRY:
alt_mode = 0x23;
break;
case climate::CLIMATE_MODE_FAN_ONLY:
alt_mode = 0x63;
break;
case climate::CLIMATE_MODE_HEAT_COOL:
case climate::CLIMATE_MODE_COOL:
case climate::CLIMATE_MODE_HEAT:
default:
alt_mode = 0x73;
break;
}
return alt_mode;
}
uint8_t DaikinBrcClimate::operation_mode_() {
uint8_t operating_mode = DAIKIN_BRC_MODE_ON;
switch (this->mode) {
case climate::CLIMATE_MODE_COOL:
operating_mode |= DAIKIN_BRC_MODE_COOL;
break;
case climate::CLIMATE_MODE_DRY:
operating_mode |= DAIKIN_BRC_MODE_DRY;
break;
case climate::CLIMATE_MODE_HEAT:
operating_mode |= DAIKIN_BRC_MODE_HEAT;
break;
case climate::CLIMATE_MODE_HEAT_COOL:
operating_mode |= DAIKIN_BRC_MODE_AUTO;
break;
case climate::CLIMATE_MODE_FAN_ONLY:
operating_mode |= DAIKIN_BRC_MODE_FAN;
break;
case climate::CLIMATE_MODE_OFF:
default:
operating_mode = DAIKIN_BRC_MODE_OFF;
break;
}
return operating_mode;
}
uint8_t DaikinBrcClimate::fan_speed_swing_() {
uint16_t fan_speed;
switch (this->fan_mode.value()) {
case climate::CLIMATE_FAN_LOW:
fan_speed = DAIKIN_BRC_FAN_1;
break;
case climate::CLIMATE_FAN_MEDIUM:
fan_speed = DAIKIN_BRC_FAN_2;
break;
case climate::CLIMATE_FAN_HIGH:
fan_speed = DAIKIN_BRC_FAN_3;
break;
default:
fan_speed = DAIKIN_BRC_FAN_1;
}
// If swing is enabled switch first 4 bits to 1111
switch (this->swing_mode) {
case climate::CLIMATE_SWING_BOTH:
fan_speed |= DAIKIN_BRC_IR_SWING_ON;
break;
default:
fan_speed |= DAIKIN_BRC_IR_SWING_OFF;
break;
}
return fan_speed;
}
uint8_t DaikinBrcClimate::temperature_() {
// Force special temperatures depending on the mode
switch (this->mode) {
case climate::CLIMATE_MODE_FAN_ONLY:
case climate::CLIMATE_MODE_DRY:
if (this->fahrenheit_) {
return DAIKIN_BRC_IR_DRY_FAN_TEMP_F;
}
return DAIKIN_BRC_IR_DRY_FAN_TEMP_C;
case climate::CLIMATE_MODE_HEAT_COOL:
default:
uint8_t temperature;
// Temperature in remote is in F
if (this->fahrenheit_) {
temperature = (uint8_t) roundf(
clamp<float>(((this->target_temperature * 1.8) + 32), DAIKIN_BRC_TEMP_MIN_F, DAIKIN_BRC_TEMP_MAX_F));
} else {
temperature = ((uint8_t) roundf(this->target_temperature) - 9) << 1;
}
return temperature;
}
}
bool DaikinBrcClimate::parse_state_frame_(const uint8_t frame[]) {
uint8_t checksum = 0;
for (int i = 0; i < (DAIKIN_BRC_STATE_FRAME_SIZE - 1); i++) {
checksum += frame[i];
}
if (frame[DAIKIN_BRC_STATE_FRAME_SIZE - 1] != checksum) {
ESP_LOGCONFIG(TAG, "Bad CheckSum %x", checksum);
return false;
}
uint8_t mode = frame[7];
if (mode & DAIKIN_BRC_MODE_ON) {
switch (mode & 0xF0) {
case DAIKIN_BRC_MODE_COOL:
this->mode = climate::CLIMATE_MODE_COOL;
break;
case DAIKIN_BRC_MODE_DRY:
this->mode = climate::CLIMATE_MODE_DRY;
break;
case DAIKIN_BRC_MODE_HEAT:
this->mode = climate::CLIMATE_MODE_HEAT;
break;
case DAIKIN_BRC_MODE_AUTO:
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
break;
case DAIKIN_BRC_MODE_FAN:
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
break;
}
} else {
this->mode = climate::CLIMATE_MODE_OFF;
}
uint8_t temperature = frame[10];
float temperature_c;
if (this->fahrenheit_) {
temperature_c = clamp<float>(((temperature - 32) / 1.8), DAIKIN_BRC_TEMP_MIN_C, DAIKIN_BRC_TEMP_MAX_C);
} else {
temperature_c = (temperature >> 1) + 9;
}
this->target_temperature = temperature_c;
uint8_t fan_mode = frame[11];
uint8_t swing_mode = frame[11];
switch (swing_mode & 0xF) {
case DAIKIN_BRC_IR_SWING_ON:
this->swing_mode = climate::CLIMATE_SWING_BOTH;
break;
case DAIKIN_BRC_IR_SWING_OFF:
this->swing_mode = climate::CLIMATE_SWING_OFF;
break;
}
switch (fan_mode & 0xF0) {
case DAIKIN_BRC_FAN_1:
this->fan_mode = climate::CLIMATE_FAN_LOW;
break;
case DAIKIN_BRC_FAN_2:
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
break;
case DAIKIN_BRC_FAN_3:
this->fan_mode = climate::CLIMATE_FAN_HIGH;
break;
}
this->publish_state();
return true;
}
bool DaikinBrcClimate::on_receive(remote_base::RemoteReceiveData data) {
uint8_t state_frame[DAIKIN_BRC_STATE_FRAME_SIZE] = {};
if (!data.expect_item(DAIKIN_BRC_HEADER_MARK, DAIKIN_BRC_HEADER_SPACE)) {
return false;
}
for (uint8_t pos = 0; pos < DAIKIN_BRC_STATE_FRAME_SIZE; pos++) {
uint8_t byte = 0;
for (int8_t bit = 0; bit < 8; bit++) {
if (data.expect_item(DAIKIN_BRC_BIT_MARK, DAIKIN_BRC_ONE_SPACE)) {
byte |= 1 << bit;
} else if (!data.expect_item(DAIKIN_BRC_BIT_MARK, DAIKIN_BRC_ZERO_SPACE)) {
return false;
}
}
state_frame[pos] = byte;
if (pos == 0) {
// frame header
if (byte != 0x11)
return false;
} else if (pos == 1) {
// frame header
if (byte != 0xDA)
return false;
} else if (pos == 2) {
// frame header
if (byte != 0x17)
return false;
} else if (pos == 3) {
// frame header
if (byte != 0x18)
return false;
} else if (pos == 4) {
// frame type
if (byte != 0x00)
return false;
}
}
return this->parse_state_frame_(state_frame);
}
} // namespace daikin_brc
} // namespace esphome

View File

@@ -0,0 +1,82 @@
#pragma once
#include "esphome/components/climate_ir/climate_ir.h"
namespace esphome {
namespace daikin_brc {
// Values for Daikin BRC4CXXX IR Controllers
// Temperature
const uint8_t DAIKIN_BRC_TEMP_MIN_F = 60; // fahrenheit
const uint8_t DAIKIN_BRC_TEMP_MAX_F = 90; // fahrenheit
const float DAIKIN_BRC_TEMP_MIN_C = (DAIKIN_BRC_TEMP_MIN_F - 32) / 1.8; // fahrenheit
const float DAIKIN_BRC_TEMP_MAX_C = (DAIKIN_BRC_TEMP_MAX_F - 32) / 1.8; // fahrenheit
// Modes
const uint8_t DAIKIN_BRC_MODE_AUTO = 0x30;
const uint8_t DAIKIN_BRC_MODE_COOL = 0x20;
const uint8_t DAIKIN_BRC_MODE_HEAT = 0x10;
const uint8_t DAIKIN_BRC_MODE_DRY = 0x70;
const uint8_t DAIKIN_BRC_MODE_FAN = 0x00;
const uint8_t DAIKIN_BRC_MODE_OFF = 0x00;
const uint8_t DAIKIN_BRC_MODE_ON = 0x01;
// Fan Speed
const uint8_t DAIKIN_BRC_FAN_1 = 0x10;
const uint8_t DAIKIN_BRC_FAN_2 = 0x30;
const uint8_t DAIKIN_BRC_FAN_3 = 0x50;
const uint8_t DAIKIN_BRC_FAN_AUTO = 0xA0;
// IR Transmission
const uint32_t DAIKIN_BRC_IR_FREQUENCY = 38000;
const uint32_t DAIKIN_BRC_HEADER_MARK = 5070;
const uint32_t DAIKIN_BRC_HEADER_SPACE = 2140;
const uint32_t DAIKIN_BRC_BIT_MARK = 370;
const uint32_t DAIKIN_BRC_ONE_SPACE = 1780;
const uint32_t DAIKIN_BRC_ZERO_SPACE = 710;
const uint32_t DAIKIN_BRC_MESSAGE_SPACE = 29410;
const uint8_t DAIKIN_BRC_IR_DRY_FAN_TEMP_F = 72; // Dry/Fan mode is always 17 Celsius.
const uint8_t DAIKIN_BRC_IR_DRY_FAN_TEMP_C = (17 - 9) * 2; // Dry/Fan mode is always 17 Celsius.
const uint8_t DAIKIN_BRC_IR_SWING_ON = 0x5;
const uint8_t DAIKIN_BRC_IR_SWING_OFF = 0x6;
const uint8_t DAIKIN_BRC_IR_MODE_BUTTON = 0x4; // This is set after a mode action
// State Frame size
const uint8_t DAIKIN_BRC_STATE_FRAME_SIZE = 15;
// Preamble size
const uint8_t DAIKIN_BRC_PREAMBLE_SIZE = 7;
// Transmit Frame size - includes a preamble
const uint8_t DAIKIN_BRC_TRANSMIT_FRAME_SIZE = DAIKIN_BRC_PREAMBLE_SIZE + DAIKIN_BRC_STATE_FRAME_SIZE;
class DaikinBrcClimate : public climate_ir::ClimateIR {
public:
DaikinBrcClimate()
: climate_ir::ClimateIR(DAIKIN_BRC_TEMP_MIN_C, DAIKIN_BRC_TEMP_MAX_C, 0.5f, true, true,
{climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH}) {}
/// Set use of Fahrenheit units
void set_fahrenheit(bool value) {
this->fahrenheit_ = value;
this->temperature_step_ = value ? 0.5f : 1.0f;
}
protected:
uint8_t mode_button_ = 0x00;
// Capture if the MODE was changed
void control(const climate::ClimateCall &call) override;
// Transmit via IR the state of this climate controller.
void transmit_state() override;
uint8_t alt_mode_();
uint8_t operation_mode_();
uint8_t fan_speed_swing_();
uint8_t temperature_();
// Handle received IR Buffer
bool on_receive(remote_base::RemoteReceiveData data) override;
bool parse_state_frame_(const uint8_t frame[]);
bool fahrenheit_{false};
};
} // namespace daikin_brc
} // namespace esphome

View File

@@ -1,7 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID
from esphome.core import CORE
from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID, CONF_TX_POWER
from esphome.core import CORE, TimePeriod
from esphome.components.esp32 import add_idf_sdkconfig_option
DEPENDENCIES = ["esp32"]
@@ -12,16 +12,47 @@ ESP32BLEBeacon = esp32_ble_beacon_ns.class_("ESP32BLEBeacon", cg.Component)
CONF_MAJOR = "major"
CONF_MINOR = "minor"
CONF_MIN_INTERVAL = "min_interval"
CONF_MAX_INTERVAL = "max_interval"
CONF_MEASURED_POWER = "measured_power"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(ESP32BLEBeacon),
cv.Required(CONF_TYPE): cv.one_of("IBEACON", upper=True),
cv.Required(CONF_UUID): cv.uuid,
cv.Optional(CONF_MAJOR, default=10167): cv.uint16_t,
cv.Optional(CONF_MINOR, default=61958): cv.uint16_t,
}
).extend(cv.COMPONENT_SCHEMA)
def validate_config(config):
if config[CONF_MIN_INTERVAL] > config.get(CONF_MAX_INTERVAL):
raise cv.Invalid("min_interval must be <= max_interval")
return config
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(ESP32BLEBeacon),
cv.Required(CONF_TYPE): cv.one_of("IBEACON", upper=True),
cv.Required(CONF_UUID): cv.uuid,
cv.Optional(CONF_MAJOR, default=10167): cv.uint16_t,
cv.Optional(CONF_MINOR, default=61958): cv.uint16_t,
cv.Optional(CONF_MIN_INTERVAL, default="100ms"): cv.All(
cv.positive_time_period_milliseconds,
cv.Range(
min=TimePeriod(milliseconds=20), max=TimePeriod(milliseconds=10240)
),
),
cv.Optional(CONF_MAX_INTERVAL, default="100ms"): cv.All(
cv.positive_time_period_milliseconds,
cv.Range(
min=TimePeriod(milliseconds=20), max=TimePeriod(milliseconds=10240)
),
),
cv.Optional(CONF_MEASURED_POWER, default=-59): cv.int_range(
min=-128, max=0
),
cv.Optional(CONF_TX_POWER, default="3dBm"): cv.All(
cv.decibel, cv.one_of(-12, -9, -6, -3, 0, 3, 6, 9, int=True)
),
}
).extend(cv.COMPONENT_SCHEMA),
validate_config,
)
async def to_code(config):
@@ -31,6 +62,10 @@ async def to_code(config):
await cg.register_component(var, config)
cg.add(var.set_major(config[CONF_MAJOR]))
cg.add(var.set_minor(config[CONF_MINOR]))
cg.add(var.set_min_interval(config[CONF_MIN_INTERVAL]))
cg.add(var.set_max_interval(config[CONF_MAX_INTERVAL]))
cg.add(var.set_measured_power(config[CONF_MEASURED_POWER]))
cg.add(var.set_tx_power(config[CONF_TX_POWER]))
if CORE.using_esp_idf:
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)

View File

@@ -36,11 +36,24 @@ static esp_ble_adv_params_t ble_adv_params = {
#define ENDIAN_CHANGE_U16(x) ((((x) &0xFF00) >> 8) + (((x) &0xFF) << 8))
static const esp_ble_ibeacon_head_t IBEACON_COMMON_HEAD = {
.flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = 0x004C, .beacon_type = 0x1502};
.flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = {0x4C, 0x00}, .beacon_type = {0x02, 0x15}};
void ESP32BLEBeacon::dump_config() {
ESP_LOGCONFIG(TAG, "ESP32 BLE Beacon:");
ESP_LOGCONFIG(TAG, " Major: %u, Minor: %u", this->major_, this->minor_);
char uuid[37];
char *bpos = uuid;
for (int8_t ii = 0; ii < 16; ++ii) {
bpos += sprintf(bpos, "%02X", this->uuid_[ii]);
if (ii == 3 || ii == 5 || ii == 7 || ii == 9) {
bpos += sprintf(bpos, "-");
}
}
uuid[36] = '\0';
ESP_LOGCONFIG(TAG,
" UUID: %s, Major: %u, Minor: %u, Min Interval: %ums, Max Interval: %ums, Measured Power: %d"
", TX Power: %ddBm",
uuid, this->major_, this->minor_, this->min_interval_, this->max_interval_, this->measured_power_,
this->tx_power_);
}
void ESP32BLEBeacon::setup() {
@@ -67,6 +80,9 @@ void ESP32BLEBeacon::ble_core_task(void *params) {
}
void ESP32BLEBeacon::ble_setup() {
ble_adv_params.adv_int_min = static_cast<uint16_t>(global_esp32_ble_beacon->min_interval_ / 0.625f);
ble_adv_params.adv_int_max = static_cast<uint16_t>(global_esp32_ble_beacon->max_interval_ / 0.625f);
// Initialize non-volatile storage for the bluetooth controller
esp_err_t err = nvs_flash_init();
if (err != ESP_OK) {
@@ -118,6 +134,12 @@ void ESP32BLEBeacon::ble_setup() {
ESP_LOGE(TAG, "esp_bluedroid_enable failed: %d", err);
return;
}
err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV,
static_cast<esp_power_level_t>((global_esp32_ble_beacon->tx_power_ + 12) / 3));
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_tx_power_set failed: %s", esp_err_to_name(err));
return;
}
err = esp_ble_gap_register_callback(ESP32BLEBeacon::gap_event_handler);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", err);
@@ -130,7 +152,7 @@ void ESP32BLEBeacon::ble_setup() {
sizeof(ibeacon_adv_data.ibeacon_vendor.proximity_uuid));
ibeacon_adv_data.ibeacon_vendor.minor = ENDIAN_CHANGE_U16(global_esp32_ble_beacon->minor_);
ibeacon_adv_data.ibeacon_vendor.major = ENDIAN_CHANGE_U16(global_esp32_ble_beacon->major_);
ibeacon_adv_data.ibeacon_vendor.measured_power = 0xC5;
ibeacon_adv_data.ibeacon_vendor.measured_power = static_cast<uint8_t>(global_esp32_ble_beacon->measured_power_);
esp_ble_gap_config_adv_data_raw((uint8_t *) &ibeacon_adv_data, sizeof(ibeacon_adv_data));
}
@@ -153,7 +175,7 @@ void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap
break;
}
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: {
err = param->adv_start_cmpl.status;
err = param->adv_stop_cmpl.status;
if (err != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(TAG, "BLE adv stop failed: %s", esp_err_to_name(err));
} else {

View File

@@ -5,6 +5,7 @@
#ifdef USE_ESP32
#include <esp_gap_ble_api.h>
#include <esp_bt.h>
namespace esphome {
namespace esp32_ble_beacon {
@@ -14,8 +15,8 @@ typedef struct {
uint8_t flags[3];
uint8_t length;
uint8_t type;
uint16_t company_id;
uint16_t beacon_type;
uint8_t company_id[2];
uint8_t beacon_type[2];
} __attribute__((packed)) esp_ble_ibeacon_head_t;
// NOLINTNEXTLINE(modernize-use-using)
@@ -42,6 +43,10 @@ class ESP32BLEBeacon : public Component {
void set_major(uint16_t major) { this->major_ = major; }
void set_minor(uint16_t minor) { this->minor_ = minor; }
void set_min_interval(uint16_t val) { this->min_interval_ = val; }
void set_max_interval(uint16_t val) { this->max_interval_ = val; }
void set_measured_power(int8_t val) { this->measured_power_ = val; }
void set_tx_power(int8_t val) { this->tx_power_ = val; }
protected:
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
@@ -51,6 +56,10 @@ class ESP32BLEBeacon : public Component {
std::array<uint8_t, 16> uuid_;
uint16_t major_{};
uint16_t minor_{};
uint16_t min_interval_{};
uint16_t max_interval_{};
int8_t measured_power_{};
int8_t tx_power_{};
};
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -0,0 +1 @@
CODEOWNERS = ["@gtjadsonsantos"]

View File

@@ -0,0 +1,16 @@
#include "ethernet_info_text_sensor.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
namespace esphome {
namespace ethernet_info {
static const char *const TAG = "ethernet_info";
void IPAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo IPAddress", this); }
} // namespace ethernet_info
} // namespace esphome
#endif // USE_ESP32_FRAMEWORK_ARDUINO

View File

@@ -0,0 +1,35 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/components/ethernet/ethernet_component.h"
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
namespace esphome {
namespace ethernet_info {
class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextSensor {
public:
void update() override {
tcpip_adapter_ip_info_t tcpip;
tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &tcpip);
auto ip = tcpip.ip.addr;
if (ip != this->last_ip_) {
this->last_ip_ = ip;
this->publish_state(network::IPAddress(ip).str());
}
}
float get_setup_priority() const override { return setup_priority::ETHERNET; }
std::string unique_id() override { return get_mac_address() + "-ethernetinfo"; }
void dump_config() override;
protected:
network::IPAddress last_ip_;
};
} // namespace ethernet_info
} // namespace esphome
#endif // USE_ESP32_FRAMEWORK_ARDUINO

View File

@@ -0,0 +1,34 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import (
CONF_IP_ADDRESS,
ENTITY_CATEGORY_DIAGNOSTIC,
)
DEPENDENCIES = ["ethernet"]
ethernet_info_ns = cg.esphome_ns.namespace("ethernet_info")
IPAddressEsthernetInfo = ethernet_info_ns.class_(
"IPAddressEthernetInfo", text_sensor.TextSensor, cg.PollingComponent
)
CONFIG_SCHEMA = cv.Schema(
{
cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema(
IPAddressEsthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
).extend(cv.polling_component_schema("1s"))
}
)
async def setup_conf(config, key):
if key in config:
conf = config[key]
var = await text_sensor.new_text_sensor(conf)
await cg.register_component(var, conf)
async def to_code(config):
await setup_conf(config, CONF_IP_ADDRESS)

View File

@@ -0,0 +1,291 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c
from esphome.const import CONF_ADDRESS, CONF_COMMAND, CONF_ID, CONF_DURATION
from esphome import automation
from esphome.automation import maybe_simple_id
CODEOWNERS = ["@carlos-sarmiento"]
DEPENDENCIES = ["i2c"]
MULTI_CONF = True
CONF_VOLUME = "volume"
CONF_VOLUME_PER_MINUTE = "volume_per_minute"
ezo_pmp_ns = cg.esphome_ns.namespace("ezo_pmp")
EzoPMP = ezo_pmp_ns.class_("EzoPMP", cg.PollingComponent, i2c.I2CDevice)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(EzoPMP),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(103))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
EZO_PMP_NO_ARGS_ACTION_SCHEMA = maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(EzoPMP),
}
)
# Actions that do not require more arguments
EzoPMPFindAction = ezo_pmp_ns.class_("EzoPMPFindAction", automation.Action)
EzoPMPClearTotalVolumeDispensedAction = ezo_pmp_ns.class_(
"EzoPMPClearTotalVolumeDispensedAction", automation.Action
)
EzoPMPClearCalibrationAction = ezo_pmp_ns.class_(
"EzoPMPClearCalibrationAction", automation.Action
)
EzoPMPPauseDosingAction = ezo_pmp_ns.class_(
"EzoPMPPauseDosingAction", automation.Action
)
EzoPMPStopDosingAction = ezo_pmp_ns.class_("EzoPMPStopDosingAction", automation.Action)
EzoPMPDoseContinuouslyAction = ezo_pmp_ns.class_(
"EzoPMPDoseContinuouslyAction", automation.Action
)
# Actions that require more arguments
EzoPMPDoseVolumeAction = ezo_pmp_ns.class_("EzoPMPDoseVolumeAction", automation.Action)
EzoPMPDoseVolumeOverTimeAction = ezo_pmp_ns.class_(
"EzoPMPDoseVolumeOverTimeAction", automation.Action
)
EzoPMPDoseWithConstantFlowRateAction = ezo_pmp_ns.class_(
"EzoPMPDoseWithConstantFlowRateAction", automation.Action
)
EzoPMPSetCalibrationVolumeAction = ezo_pmp_ns.class_(
"EzoPMPSetCalibrationVolumeAction", automation.Action
)
EzoPMPChangeI2CAddressAction = ezo_pmp_ns.class_(
"EzoPMPChangeI2CAddressAction", automation.Action
)
EzoPMPArbitraryCommandAction = ezo_pmp_ns.class_(
"EzoPMPArbitraryCommandAction", automation.Action
)
@automation.register_action(
"ezo_pmp.find", EzoPMPFindAction, EZO_PMP_NO_ARGS_ACTION_SCHEMA
)
async def ezo_pmp_find_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)
@automation.register_action(
"ezo_pmp.dose_continuously",
EzoPMPDoseContinuouslyAction,
EZO_PMP_NO_ARGS_ACTION_SCHEMA,
)
async def ezo_pmp_dose_continuously_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)
@automation.register_action(
"ezo_pmp.clear_total_volume_dosed",
EzoPMPClearTotalVolumeDispensedAction,
EZO_PMP_NO_ARGS_ACTION_SCHEMA,
)
async def ezo_pmp_clear_total_volume_dosed_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)
@automation.register_action(
"ezo_pmp.clear_calibration",
EzoPMPClearCalibrationAction,
EZO_PMP_NO_ARGS_ACTION_SCHEMA,
)
async def ezo_pmp_clear_calibration_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)
@automation.register_action(
"ezo_pmp.pause_dosing", EzoPMPPauseDosingAction, EZO_PMP_NO_ARGS_ACTION_SCHEMA
)
async def ezo_pmp_pause_dosing_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)
@automation.register_action(
"ezo_pmp.stop_dosing", EzoPMPStopDosingAction, EZO_PMP_NO_ARGS_ACTION_SCHEMA
)
async def ezo_pmp_stop_dosing_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)
# Actions that require Multiple Args
EZO_PMP_DOSE_VOLUME_ACTION_SCHEMA = cv.All(
{
cv.Required(CONF_ID): cv.use_id(EzoPMP),
cv.Required(CONF_VOLUME): cv.templatable(
cv.float_range()
), # Any way to represent as proper volume (vs. raw int)
}
)
@automation.register_action(
"ezo_pmp.dose_volume", EzoPMPDoseVolumeAction, EZO_PMP_DOSE_VOLUME_ACTION_SCHEMA
)
async def ezo_pmp_dose_volume_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
template_ = await cg.templatable(config[CONF_VOLUME], args, cg.double)
cg.add(var.set_volume(template_))
return var
EZO_PMP_DOSE_VOLUME_OVER_TIME_ACTION_SCHEMA = cv.All(
{
cv.Required(CONF_ID): cv.use_id(EzoPMP),
cv.Required(CONF_VOLUME): cv.templatable(
cv.float_range()
), # Any way to represent as proper volume (vs. raw int)
cv.Required(CONF_DURATION): cv.templatable(
cv.int_range(1)
), # Any way to represent it as minutes (vs. raw int)
}
)
@automation.register_action(
"ezo_pmp.dose_volume_over_time",
EzoPMPDoseVolumeOverTimeAction,
EZO_PMP_DOSE_VOLUME_OVER_TIME_ACTION_SCHEMA,
)
async def ezo_pmp_dose_volume_over_time_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
template_ = await cg.templatable(config[CONF_VOLUME], args, cg.double)
cg.add(var.set_volume(template_))
template_ = await cg.templatable(config[CONF_DURATION], args, int)
cg.add(var.set_duration(template_))
return var
EZO_PMP_DOSE_WITH_CONSTANT_FLOW_RATE_ACTION_SCHEMA = cv.All(
{
cv.Required(CONF_ID): cv.use_id(EzoPMP),
cv.Required(CONF_VOLUME_PER_MINUTE): cv.templatable(
cv.float_range()
), # Any way to represent as proper volume (vs. raw int)
cv.Required(CONF_DURATION): cv.templatable(
cv.int_range(1)
), # Any way to represent it as minutes (vs. raw int)
}
)
@automation.register_action(
"ezo_pmp.dose_with_constant_flow_rate",
EzoPMPDoseWithConstantFlowRateAction,
EZO_PMP_DOSE_WITH_CONSTANT_FLOW_RATE_ACTION_SCHEMA,
)
async def ezo_pmp_dose_with_constant_flow_rate_to_code(
config, action_id, template_arg, args
):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
template_ = await cg.templatable(config[CONF_VOLUME_PER_MINUTE], args, cg.double)
cg.add(var.set_volume(template_))
template_ = await cg.templatable(config[CONF_DURATION], args, int)
cg.add(var.set_duration(template_))
return var
EZO_PMP_SET_CALIBRATION_VOLUME_ACTION_SCHEMA = cv.All(
{
cv.Required(CONF_ID): cv.use_id(EzoPMP),
cv.Required(CONF_VOLUME): cv.templatable(
cv.float_range()
), # Any way to represent as proper volume (vs. raw int)
}
)
@automation.register_action(
"ezo_pmp.set_calibration_volume",
EzoPMPSetCalibrationVolumeAction,
EZO_PMP_SET_CALIBRATION_VOLUME_ACTION_SCHEMA,
)
async def ezo_pmp_set_calibration_volume_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
template_ = await cg.templatable(config[CONF_VOLUME], args, cg.double)
cg.add(var.set_volume(template_))
return var
EZO_PMP_CHANGE_I2C_ADDRESS_ACTION_SCHEMA = cv.All(
{
cv.Required(CONF_ID): cv.use_id(EzoPMP),
cv.Required(CONF_ADDRESS): cv.templatable(cv.int_range(min=1, max=127)),
}
)
@automation.register_action(
"ezo_pmp.change_i2c_address",
EzoPMPChangeI2CAddressAction,
EZO_PMP_CHANGE_I2C_ADDRESS_ACTION_SCHEMA,
)
async def ezo_pmp_change_i2c_address_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.double)
cg.add(var.set_address(template_))
return var
EZO_PMP_ARBITRARY_COMMAND_ACTION_SCHEMA = cv.All(
{
cv.Required(CONF_ID): cv.use_id(EzoPMP),
cv.Required(CONF_COMMAND): cv.templatable(cv.string_strict),
}
)
@automation.register_action(
"ezo_pmp.arbitrary_command",
EzoPMPArbitraryCommandAction,
EZO_PMP_ARBITRARY_COMMAND_ACTION_SCHEMA,
)
async def ezo_pmp_arbitrary_command_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
template_ = await cg.templatable(config[CONF_COMMAND], args, cg.std_string)
cg.add(var.set_command(template_))
return var

View File

@@ -0,0 +1,42 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import (
ENTITY_CATEGORY_NONE,
DEVICE_CLASS_RUNNING,
DEVICE_CLASS_EMPTY,
CONF_ID,
)
from . import EzoPMP
DEPENDENCIES = ["ezo_pmp"]
CONF_PUMP_STATE = "pump_state"
CONF_IS_PAUSED = "is_paused"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(EzoPMP),
cv.Optional(CONF_PUMP_STATE): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_RUNNING,
entity_category=ENTITY_CATEGORY_NONE,
),
cv.Optional(CONF_IS_PAUSED): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_EMPTY,
entity_category=ENTITY_CATEGORY_NONE,
),
}
)
async def to_code(config):
parent = await cg.get_variable(config[CONF_ID])
if CONF_PUMP_STATE in config:
sens = await binary_sensor.new_binary_sensor(config[CONF_PUMP_STATE])
cg.add(parent.set_is_dosing(sens))
if CONF_IS_PAUSED in config:
sens = await binary_sensor.new_binary_sensor(config[CONF_IS_PAUSED])
cg.add(parent.set_is_paused(sens))

View File

@@ -0,0 +1,542 @@
#include "ezo_pmp.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace ezo_pmp {
static const char *const TAG = "ezo-pmp";
static const uint16_t EZO_PMP_COMMAND_NONE = 0;
static const uint16_t EZO_PMP_COMMAND_TYPE_READ = 1;
static const uint16_t EZO_PMP_COMMAND_FIND = 2;
static const uint16_t EZO_PMP_COMMAND_DOSE_CONTINUOUSLY = 4;
static const uint16_t EZO_PMP_COMMAND_DOSE_VOLUME = 8;
static const uint16_t EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME = 16;
static const uint16_t EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE = 32;
static const uint16_t EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME = 64;
static const uint16_t EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED = 128;
static const uint16_t EZO_PMP_COMMAND_CLEAR_CALIBRATION = 256;
static const uint16_t EZO_PMP_COMMAND_PAUSE_DOSING = 512;
static const uint16_t EZO_PMP_COMMAND_STOP_DOSING = 1024;
static const uint16_t EZO_PMP_COMMAND_CHANGE_I2C_ADDRESS = 2048;
static const uint16_t EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS = 4096;
static const uint16_t EZO_PMP_COMMAND_READ_DOSING = 3;
static const uint16_t EZO_PMP_COMMAND_READ_SINGLE_REPORT = 5;
static const uint16_t EZO_PMP_COMMAND_READ_MAX_FLOW_RATE = 9;
static const uint16_t EZO_PMP_COMMAND_READ_PAUSE_STATUS = 17;
static const uint16_t EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED = 33;
static const uint16_t EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED = 65;
static const uint16_t EZO_PMP_COMMAND_READ_CALIBRATION_STATUS = 129;
static const uint16_t EZO_PMP_COMMAND_READ_PUMP_VOLTAGE = 257;
static const std::string DOSING_MODE_NONE = "None";
static const std::string DOSING_MODE_VOLUME = "Volume";
static const std::string DOSING_MODE_VOLUME_OVER_TIME = "Volume/Time";
static const std::string DOSING_MODE_CONSTANT_FLOW_RATE = "Constant Flow Rate";
static const std::string DOSING_MODE_CONTINUOUS = "Continuous";
void EzoPMP::dump_config() {
LOG_I2C_DEVICE(this);
if (this->is_failed())
ESP_LOGE(TAG, "Communication with EZO-PMP circuit failed!");
LOG_UPDATE_INTERVAL(this);
}
void EzoPMP::update() {
if (this->is_waiting_) {
return;
}
if (this->is_first_read_) {
this->queue_command_(EZO_PMP_COMMAND_READ_CALIBRATION_STATUS, 0, 0, (bool) this->calibration_status_);
this->queue_command_(EZO_PMP_COMMAND_READ_MAX_FLOW_RATE, 0, 0, (bool) this->max_flow_rate_);
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
this->queue_command_(EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED, 0, 0, (bool) this->total_volume_dosed_);
this->queue_command_(EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED, 0, 0,
(bool) this->absolute_total_volume_dosed_);
this->queue_command_(EZO_PMP_COMMAND_READ_PAUSE_STATUS, 0, 0, true);
this->is_first_read_ = false;
}
if (!this->is_waiting_ && this->peek_next_command_() == EZO_PMP_COMMAND_NONE) {
this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
if (this->is_dosing_flag_) {
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
this->queue_command_(EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED, 0, 0, (bool) this->total_volume_dosed_);
this->queue_command_(EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED, 0, 0,
(bool) this->absolute_total_volume_dosed_);
}
this->queue_command_(EZO_PMP_COMMAND_READ_PUMP_VOLTAGE, 0, 0, (bool) this->pump_voltage_);
} else {
ESP_LOGV(TAG, "Not Scheduling new Command during update()");
}
}
void EzoPMP::loop() {
// If we are not waiting for anything and there is no command to be sent, return
if (!this->is_waiting_ && this->peek_next_command_() == EZO_PMP_COMMAND_NONE) {
return;
}
// If we are not waiting for anything and there IS a command to be sent, do it.
if (!this->is_waiting_ && this->peek_next_command_() != EZO_PMP_COMMAND_NONE) {
this->send_next_command_();
}
// If we are waiting for something but it isn't ready yet, then return
if (this->is_waiting_ && millis() - this->start_time_ < this->wait_time_) {
return;
}
// We are waiting for something and it should be ready.
this->read_command_result_();
}
void EzoPMP::clear_current_command_() {
this->current_command_ = EZO_PMP_COMMAND_NONE;
this->is_waiting_ = false;
}
void EzoPMP::read_command_result_() {
uint8_t response_buffer[21] = {'\0'};
response_buffer[0] = 0;
if (!this->read_bytes_raw(response_buffer, 20)) {
ESP_LOGE(TAG, "read error");
this->clear_current_command_();
return;
}
switch (response_buffer[0]) {
case 254:
return; // keep waiting
case 1:
break;
case 2:
ESP_LOGE(TAG, "device returned a syntax error");
this->clear_current_command_();
return;
case 255:
ESP_LOGE(TAG, "device returned no data");
this->clear_current_command_();
return;
default:
ESP_LOGE(TAG, "device returned an unknown response: %d", response_buffer[0]);
this->clear_current_command_();
return;
}
char first_parameter_buffer[10] = {'\0'};
char second_parameter_buffer[10] = {'\0'};
char third_parameter_buffer[10] = {'\0'};
first_parameter_buffer[0] = '\0';
second_parameter_buffer[0] = '\0';
third_parameter_buffer[0] = '\0';
int current_parameter = 1;
size_t position_in_parameter_buffer = 0;
// some sensors return multiple comma-separated values, terminate string after first one
for (size_t i = 1; i < sizeof(response_buffer) - 1; i++) {
char current_char = response_buffer[i];
if (current_char == '\0') {
ESP_LOGV(TAG, "Read Response from device: %s", (char *) response_buffer);
ESP_LOGV(TAG, "First Component: %s", (char *) first_parameter_buffer);
ESP_LOGV(TAG, "Second Component: %s", (char *) second_parameter_buffer);
ESP_LOGV(TAG, "Third Component: %s", (char *) third_parameter_buffer);
break;
}
if (current_char == ',') {
current_parameter++;
position_in_parameter_buffer = 0;
continue;
}
switch (current_parameter) {
case 1:
first_parameter_buffer[position_in_parameter_buffer] = current_char;
first_parameter_buffer[position_in_parameter_buffer + 1] = '\0';
break;
case 2:
second_parameter_buffer[position_in_parameter_buffer] = current_char;
second_parameter_buffer[position_in_parameter_buffer + 1] = '\0';
break;
case 3:
third_parameter_buffer[position_in_parameter_buffer] = current_char;
third_parameter_buffer[position_in_parameter_buffer + 1] = '\0';
break;
}
position_in_parameter_buffer++;
}
auto parsed_first_parameter = parse_number<float>(first_parameter_buffer);
auto parsed_second_parameter = parse_number<float>(second_parameter_buffer);
auto parsed_third_parameter = parse_number<float>(third_parameter_buffer);
switch (this->current_command_) {
// Read Commands
case EZO_PMP_COMMAND_READ_DOSING: // Page 54
if (parsed_third_parameter.has_value())
this->is_dosing_flag_ = parsed_third_parameter.value_or(0) == 1;
if (this->is_dosing_)
this->is_dosing_->publish_state(this->is_dosing_flag_);
if (parsed_second_parameter.has_value() && this->last_volume_requested_) {
this->last_volume_requested_->publish_state(parsed_second_parameter.value_or(0));
}
if (!this->is_dosing_flag_ && !this->is_paused_flag_) {
// If pump is not paused and not dispensing
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_NONE)
this->dosing_mode_->publish_state(DOSING_MODE_NONE);
}
break;
case EZO_PMP_COMMAND_READ_SINGLE_REPORT: // Single Report (page 53)
if (parsed_first_parameter.has_value() && (bool) this->current_volume_dosed_) {
this->current_volume_dosed_->publish_state(parsed_first_parameter.value_or(0));
}
break;
case EZO_PMP_COMMAND_READ_MAX_FLOW_RATE: // Constant Flow Rate (page 57)
if (parsed_second_parameter.has_value() && this->max_flow_rate_)
this->max_flow_rate_->publish_state(parsed_second_parameter.value_or(0));
break;
case EZO_PMP_COMMAND_READ_PAUSE_STATUS: // Pause (page 61)
if (parsed_second_parameter.has_value())
this->is_paused_flag_ = parsed_second_parameter.value_or(0) == 1;
if (this->is_paused_)
this->is_paused_->publish_state(this->is_paused_flag_);
break;
case EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED: // Total Volume Dispensed (page 64)
if (parsed_second_parameter.has_value() && this->total_volume_dosed_)
this->total_volume_dosed_->publish_state(parsed_second_parameter.value_or(0));
break;
case EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED: // Total Volume Dispensed (page 64)
if (parsed_second_parameter.has_value() && this->absolute_total_volume_dosed_)
this->absolute_total_volume_dosed_->publish_state(parsed_second_parameter.value_or(0));
break;
case EZO_PMP_COMMAND_READ_CALIBRATION_STATUS: // Calibration (page 65)
if (parsed_second_parameter.has_value() && this->calibration_status_) {
if (parsed_second_parameter.value_or(0) == 1) {
this->calibration_status_->publish_state("Fixed Volume");
} else if (parsed_second_parameter.value_or(0) == 2) {
this->calibration_status_->publish_state("Volume/Time");
} else if (parsed_second_parameter.value_or(0) == 3) {
this->calibration_status_->publish_state("Fixed Volume & Volume/Time");
} else {
this->calibration_status_->publish_state("Uncalibrated");
}
}
break;
case EZO_PMP_COMMAND_READ_PUMP_VOLTAGE: // Pump Voltage (page 67)
if (parsed_second_parameter.has_value() && this->pump_voltage_)
this->pump_voltage_->publish_state(parsed_second_parameter.value_or(0));
break;
// Non-Read Commands
case EZO_PMP_COMMAND_DOSE_VOLUME: // Volume Dispensing (page 55)
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_VOLUME)
this->dosing_mode_->publish_state(DOSING_MODE_VOLUME);
break;
case EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME: // Dose over time (page 56)
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_VOLUME_OVER_TIME)
this->dosing_mode_->publish_state(DOSING_MODE_VOLUME_OVER_TIME);
break;
case EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE: // Constant Flow Rate (page 57)
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_CONSTANT_FLOW_RATE)
this->dosing_mode_->publish_state(DOSING_MODE_CONSTANT_FLOW_RATE);
break;
case EZO_PMP_COMMAND_DOSE_CONTINUOUSLY: // Continuous Dispensing (page 54)
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_CONTINUOUS)
this->dosing_mode_->publish_state(DOSING_MODE_CONTINUOUS);
break;
case EZO_PMP_COMMAND_STOP_DOSING: // Stop (page 62)
this->is_paused_flag_ = false;
if (this->is_paused_)
this->is_paused_->publish_state(this->is_paused_flag_);
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_NONE)
this->dosing_mode_->publish_state(DOSING_MODE_NONE);
break;
case EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS:
ESP_LOGI(TAG, "Arbitrary Command Response: %s", (char *) response_buffer);
break;
case EZO_PMP_COMMAND_CLEAR_CALIBRATION: // Clear Calibration (page 65)
case EZO_PMP_COMMAND_PAUSE_DOSING: // Pause (page 61)
case EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME: // Set Calibration Volume (page 65)
case EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED: // Clear Total Volume Dosed (page 64)
case EZO_PMP_COMMAND_FIND: // Find (page 52)
// Nothing to do here
break;
case EZO_PMP_COMMAND_TYPE_READ:
case EZO_PMP_COMMAND_NONE:
default:
ESP_LOGE(TAG, "Unsupported command received: %d", this->current_command_);
return;
}
this->clear_current_command_();
}
void EzoPMP::send_next_command_() {
int wait_time_for_command = 400; // milliseconds
uint8_t command_buffer[21];
int command_buffer_length = 0;
this->pop_next_command_(); // this->next_command will be updated.
switch (this->next_command_) {
// Read Commands
case EZO_PMP_COMMAND_READ_DOSING: // Page 54
command_buffer_length = sprintf((char *) command_buffer, "D,?");
break;
case EZO_PMP_COMMAND_READ_SINGLE_REPORT: // Single Report (page 53)
command_buffer_length = sprintf((char *) command_buffer, "R");
break;
case EZO_PMP_COMMAND_READ_MAX_FLOW_RATE:
command_buffer_length = sprintf((char *) command_buffer, "DC,?");
break;
case EZO_PMP_COMMAND_READ_PAUSE_STATUS:
command_buffer_length = sprintf((char *) command_buffer, "P,?");
break;
case EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED:
command_buffer_length = sprintf((char *) command_buffer, "TV,?");
break;
case EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED:
command_buffer_length = sprintf((char *) command_buffer, "ATV,?");
break;
case EZO_PMP_COMMAND_READ_CALIBRATION_STATUS:
command_buffer_length = sprintf((char *) command_buffer, "Cal,?");
break;
case EZO_PMP_COMMAND_READ_PUMP_VOLTAGE:
command_buffer_length = sprintf((char *) command_buffer, "PV,?");
break;
// Non-Read Commands
case EZO_PMP_COMMAND_FIND: // Find (page 52)
command_buffer_length = sprintf((char *) command_buffer, "Find");
wait_time_for_command = 60000; // This command will block all updates for a minute
break;
case EZO_PMP_COMMAND_DOSE_CONTINUOUSLY: // Continuous Dispensing (page 54)
command_buffer_length = sprintf((char *) command_buffer, "D,*");
break;
case EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED: // Clear Total Volume Dosed (page 64)
command_buffer_length = sprintf((char *) command_buffer, "Clear");
break;
case EZO_PMP_COMMAND_CLEAR_CALIBRATION: // Clear Calibration (page 65)
command_buffer_length = sprintf((char *) command_buffer, "Cal,clear");
break;
case EZO_PMP_COMMAND_PAUSE_DOSING: // Pause (page 61)
command_buffer_length = sprintf((char *) command_buffer, "P");
break;
case EZO_PMP_COMMAND_STOP_DOSING: // Stop (page 62)
command_buffer_length = sprintf((char *) command_buffer, "X");
break;
// Non-Read commands with parameters
case EZO_PMP_COMMAND_DOSE_VOLUME: // Volume Dispensing (page 55)
command_buffer_length = sprintf((char *) command_buffer, "D,%0.1f", this->next_command_volume_);
break;
case EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME: // Dose over time (page 56)
command_buffer_length =
sprintf((char *) command_buffer, "D,%0.1f,%i", this->next_command_volume_, this->next_command_duration_);
break;
case EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE: // Constant Flow Rate (page 57)
command_buffer_length =
sprintf((char *) command_buffer, "DC,%0.1f,%i", this->next_command_volume_, this->next_command_duration_);
break;
case EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME: // Set Calibration Volume (page 65)
command_buffer_length = sprintf((char *) command_buffer, "Cal,%0.2f", this->next_command_volume_);
break;
case EZO_PMP_COMMAND_CHANGE_I2C_ADDRESS: // Change I2C Address (page 73)
command_buffer_length = sprintf((char *) command_buffer, "I2C,%i", this->next_command_duration_);
break;
case EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS: // Run an arbitrary command
command_buffer_length = sprintf((char *) command_buffer, this->arbitrary_command_, this->next_command_duration_);
ESP_LOGI(TAG, "Sending arbitrary command: %s", (char *) command_buffer);
break;
case EZO_PMP_COMMAND_TYPE_READ:
case EZO_PMP_COMMAND_NONE:
default:
ESP_LOGE(TAG, "Unsupported command received: %d", this->next_command_);
return;
}
// Send command
ESP_LOGV(TAG, "Sending command to device: %s", (char *) command_buffer);
this->write(command_buffer, command_buffer_length);
this->current_command_ = this->next_command_;
this->next_command_ = EZO_PMP_COMMAND_NONE;
this->is_waiting_ = true;
this->start_time_ = millis();
this->wait_time_ = wait_time_for_command;
}
void EzoPMP::pop_next_command_() {
if (this->next_command_queue_length_ <= 0) {
ESP_LOGE(TAG, "Tried to dequeue command from empty queue");
this->next_command_ = EZO_PMP_COMMAND_NONE;
this->next_command_volume_ = 0;
this->next_command_duration_ = 0;
return;
}
// Read from Head
this->next_command_ = this->next_command_queue_[this->next_command_queue_head_];
this->next_command_volume_ = this->next_command_volume_queue_[this->next_command_queue_head_];
this->next_command_duration_ = this->next_command_duration_queue_[this->next_command_queue_head_];
// Move positions
next_command_queue_head_++;
if (next_command_queue_head_ >= 10) {
next_command_queue_head_ = 0;
}
next_command_queue_length_--;
}
uint16_t EzoPMP::peek_next_command_() {
if (this->next_command_queue_length_ <= 0) {
return EZO_PMP_COMMAND_NONE;
}
return this->next_command_queue_[this->next_command_queue_head_];
}
void EzoPMP::queue_command_(uint16_t command, double volume, int duration, bool should_schedule) {
if (!should_schedule) {
return;
}
if (this->next_command_queue_length_ >= 10) {
ESP_LOGE(TAG, "Tried to queue command '%d' but queue is full", command);
return;
}
this->next_command_queue_[this->next_command_queue_last_] = command;
this->next_command_volume_queue_[this->next_command_queue_last_] = volume;
this->next_command_duration_queue_[this->next_command_queue_last_] = duration;
ESP_LOGV(TAG, "Queue command '%d' in position '%d'", command, next_command_queue_last_);
// Move positions
next_command_queue_last_++;
if (next_command_queue_last_ >= 10) {
next_command_queue_last_ = 0;
}
next_command_queue_length_++;
}
// Actions
void EzoPMP::find() { this->queue_command_(EZO_PMP_COMMAND_FIND, 0, 0, true); }
void EzoPMP::dose_continuously() {
this->queue_command_(EZO_PMP_COMMAND_DOSE_CONTINUOUSLY, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
}
void EzoPMP::dose_volume(double volume) {
this->queue_command_(EZO_PMP_COMMAND_DOSE_VOLUME, volume, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
}
void EzoPMP::dose_volume_over_time(double volume, int duration) {
this->queue_command_(EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME, volume, duration, true);
this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
}
void EzoPMP::dose_with_constant_flow_rate(double volume, int duration) {
this->queue_command_(EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE, volume, duration, true);
this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
}
void EzoPMP::set_calibration_volume(double volume) {
this->queue_command_(EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME, volume, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_CALIBRATION_STATUS, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_MAX_FLOW_RATE, 0, 0, true);
}
void EzoPMP::clear_total_volume_dosed() {
this->queue_command_(EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED, 0, 0, true);
}
void EzoPMP::clear_calibration() {
this->queue_command_(EZO_PMP_COMMAND_CLEAR_CALIBRATION, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_CALIBRATION_STATUS, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_MAX_FLOW_RATE, 0, 0, true);
}
void EzoPMP::pause_dosing() {
this->queue_command_(EZO_PMP_COMMAND_PAUSE_DOSING, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_PAUSE_STATUS, 0, 0, true);
}
void EzoPMP::stop_dosing() { this->queue_command_(EZO_PMP_COMMAND_STOP_DOSING, 0, 0, true); }
void EzoPMP::change_i2c_address(int address) {
this->queue_command_(EZO_PMP_COMMAND_CHANGE_I2C_ADDRESS, 0, address, true);
}
void EzoPMP::exec_arbitrary_command(const std::basic_string<char> &command) {
this->arbitrary_command_ = command.c_str();
this->queue_command_(EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS, 0, 0, true);
}
} // namespace ezo_pmp
} // namespace esphome

View File

@@ -0,0 +1,252 @@
#pragma once
#include "esphome/core/defines.h"
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/i2c/i2c.h"
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif
namespace esphome {
namespace ezo_pmp {
class EzoPMP : public PollingComponent, public i2c::I2CDevice {
public:
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; };
void loop() override;
void update() override;
#ifdef USE_SENSOR
void set_current_volume_dosed(sensor::Sensor *current_volume_dosed) { current_volume_dosed_ = current_volume_dosed; }
void set_total_volume_dosed(sensor::Sensor *total_volume_dosed) { total_volume_dosed_ = total_volume_dosed; }
void set_absolute_total_volume_dosed(sensor::Sensor *absolute_total_volume_dosed) {
absolute_total_volume_dosed_ = absolute_total_volume_dosed;
}
void set_pump_voltage(sensor::Sensor *pump_voltage) { pump_voltage_ = pump_voltage; }
void set_last_volume_requested(sensor::Sensor *last_volume_requested) {
last_volume_requested_ = last_volume_requested;
}
void set_max_flow_rate(sensor::Sensor *max_flow_rate) { max_flow_rate_ = max_flow_rate; }
#endif
#ifdef USE_BINARY_SENSOR
void set_is_dosing(binary_sensor::BinarySensor *is_dosing) { is_dosing_ = is_dosing; }
void set_is_paused(binary_sensor::BinarySensor *is_paused) { is_paused_ = is_paused; }
#endif
#ifdef USE_TEXT_SENSOR
void set_dosing_mode(text_sensor::TextSensor *dosing_mode) { dosing_mode_ = dosing_mode; }
void set_calibration_status(text_sensor::TextSensor *calibration_status) { calibration_status_ = calibration_status; }
#endif
// Actions for EZO-PMP
void find();
void dose_continuously();
void dose_volume(double volume);
void dose_volume_over_time(double volume, int duration);
void dose_with_constant_flow_rate(double volume, int duration);
void set_calibration_volume(double volume);
void clear_total_volume_dosed();
void clear_calibration();
void pause_dosing();
void stop_dosing();
void change_i2c_address(int address);
void exec_arbitrary_command(const std::basic_string<char> &command);
protected:
uint32_t start_time_ = 0;
uint32_t wait_time_ = 0;
bool is_waiting_ = false;
bool is_first_read_ = true;
uint16_t next_command_ = 0;
double next_command_volume_ = 0; // might be negative
int next_command_duration_ = 0;
uint16_t next_command_queue_[10];
double next_command_volume_queue_[10];
int next_command_duration_queue_[10];
int next_command_queue_head_ = 0;
int next_command_queue_last_ = 0;
int next_command_queue_length_ = 0;
uint16_t current_command_ = 0;
bool is_paused_flag_ = false;
bool is_dosing_flag_ = false;
const char *arbitrary_command_{nullptr};
void send_next_command_();
void read_command_result_();
void clear_current_command_();
void queue_command_(uint16_t command, double volume, int duration, bool should_schedule);
void pop_next_command_();
uint16_t peek_next_command_();
#ifdef USE_SENSOR
sensor::Sensor *current_volume_dosed_{nullptr};
sensor::Sensor *total_volume_dosed_{nullptr};
sensor::Sensor *absolute_total_volume_dosed_{nullptr};
sensor::Sensor *pump_voltage_{nullptr};
sensor::Sensor *max_flow_rate_{nullptr};
sensor::Sensor *last_volume_requested_{nullptr};
#endif
#ifdef USE_BINARY_SENSOR
binary_sensor::BinarySensor *is_dosing_{nullptr};
binary_sensor::BinarySensor *is_paused_{nullptr};
#endif
#ifdef USE_TEXT_SENSOR
text_sensor::TextSensor *dosing_mode_{nullptr};
text_sensor::TextSensor *calibration_status_{nullptr};
#endif
};
// Action Templates
template<typename... Ts> class EzoPMPFindAction : public Action<Ts...> {
public:
EzoPMPFindAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override { this->ezopmp_->find(); }
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPDoseContinuouslyAction : public Action<Ts...> {
public:
EzoPMPDoseContinuouslyAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override { this->ezopmp_->dose_continuously(); }
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPDoseVolumeAction : public Action<Ts...> {
public:
EzoPMPDoseVolumeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override { this->ezopmp_->dose_volume(this->volume_.value(x...)); }
TEMPLATABLE_VALUE(double, volume)
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPDoseVolumeOverTimeAction : public Action<Ts...> {
public:
EzoPMPDoseVolumeOverTimeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override {
this->ezopmp_->dose_volume_over_time(this->volume_.value(x...), this->duration_.value(x...));
}
TEMPLATABLE_VALUE(double, volume)
TEMPLATABLE_VALUE(int, duration)
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPDoseWithConstantFlowRateAction : public Action<Ts...> {
public:
EzoPMPDoseWithConstantFlowRateAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override {
this->ezopmp_->dose_with_constant_flow_rate(this->volume_.value(x...), this->duration_.value(x...));
}
TEMPLATABLE_VALUE(double, volume)
TEMPLATABLE_VALUE(int, duration)
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPSetCalibrationVolumeAction : public Action<Ts...> {
public:
EzoPMPSetCalibrationVolumeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override { this->ezopmp_->set_calibration_volume(this->volume_.value(x...)); }
TEMPLATABLE_VALUE(double, volume)
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPClearTotalVolumeDispensedAction : public Action<Ts...> {
public:
EzoPMPClearTotalVolumeDispensedAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override { this->ezopmp_->clear_total_volume_dosed(); }
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPClearCalibrationAction : public Action<Ts...> {
public:
EzoPMPClearCalibrationAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override { this->ezopmp_->clear_calibration(); }
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPPauseDosingAction : public Action<Ts...> {
public:
EzoPMPPauseDosingAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override { this->ezopmp_->pause_dosing(); }
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPStopDosingAction : public Action<Ts...> {
public:
EzoPMPStopDosingAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override { this->ezopmp_->stop_dosing(); }
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPChangeI2CAddressAction : public Action<Ts...> {
public:
EzoPMPChangeI2CAddressAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override { this->ezopmp_->change_i2c_address(this->address_.value(x...)); }
TEMPLATABLE_VALUE(int, address)
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPArbitraryCommandAction : public Action<Ts...> {
public:
EzoPMPArbitraryCommandAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override { this->ezopmp_->exec_arbitrary_command(this->command_.value(x...)); }
TEMPLATABLE_VALUE(std::string, command)
protected:
EzoPMP *ezopmp_;
};
} // namespace ezo_pmp
} // namespace esphome

View File

@@ -0,0 +1,104 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
ENTITY_CATEGORY_DIAGNOSTIC,
ENTITY_CATEGORY_NONE,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_NONE,
CONF_ID,
UNIT_VOLT,
)
from . import EzoPMP
DEPENDENCIES = ["ezo_pmp"]
CONF_CURRENT_VOLUME_DOSED = "current_volume_dosed"
CONF_TOTAL_VOLUME_DOSED = "total_volume_dosed"
CONF_ABSOLUTE_TOTAL_VOLUME_DOSED = "absolute_total_volume_dosed"
CONF_PUMP_VOLTAGE = "pump_voltage"
CONF_LAST_VOLUME_REQUESTED = "last_volume_requested"
CONF_MAX_FLOW_RATE = "max_flow_rate"
UNIT_MILILITER = "ml"
UNIT_MILILITERS_PER_MINUTE = "ml/min"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(EzoPMP),
cv.Optional(CONF_CURRENT_VOLUME_DOSED): sensor.sensor_schema(
unit_of_measurement=UNIT_MILILITER,
accuracy_decimals=2,
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_NONE,
),
cv.Optional(CONF_LAST_VOLUME_REQUESTED): sensor.sensor_schema(
unit_of_measurement=UNIT_MILILITER,
accuracy_decimals=2,
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_NONE,
),
cv.Optional(CONF_MAX_FLOW_RATE): sensor.sensor_schema(
unit_of_measurement=UNIT_MILILITERS_PER_MINUTE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_NONE,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_TOTAL_VOLUME_DOSED): sensor.sensor_schema(
unit_of_measurement=UNIT_MILILITER,
accuracy_decimals=2,
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_ABSOLUTE_TOTAL_VOLUME_DOSED): sensor.sensor_schema(
unit_of_measurement=UNIT_MILILITER,
accuracy_decimals=2,
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_PUMP_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
async def to_code(config):
parent = await cg.get_variable(config[CONF_ID])
if CONF_CURRENT_VOLUME_DOSED in config:
sens = await sensor.new_sensor(config[CONF_CURRENT_VOLUME_DOSED])
cg.add(parent.set_current_volume_dosed(sens))
if CONF_LAST_VOLUME_REQUESTED in config:
sens = await sensor.new_sensor(config[CONF_LAST_VOLUME_REQUESTED])
cg.add(parent.set_last_volume_requested(sens))
if CONF_TOTAL_VOLUME_DOSED in config:
sens = await sensor.new_sensor(config[CONF_TOTAL_VOLUME_DOSED])
cg.add(parent.set_total_volume_dosed(sens))
if CONF_ABSOLUTE_TOTAL_VOLUME_DOSED in config:
sens = await sensor.new_sensor(config[CONF_ABSOLUTE_TOTAL_VOLUME_DOSED])
cg.add(parent.set_absolute_total_volume_dosed(sens))
if CONF_PUMP_VOLTAGE in config:
sens = await sensor.new_sensor(config[CONF_PUMP_VOLTAGE])
cg.add(parent.set_pump_voltage(sens))
if CONF_MAX_FLOW_RATE in config:
sens = await sensor.new_sensor(config[CONF_MAX_FLOW_RATE])
cg.add(parent.set_max_flow_rate(sens))

View File

@@ -0,0 +1,39 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import (
ENTITY_CATEGORY_NONE,
ENTITY_CATEGORY_DIAGNOSTIC,
CONF_ID,
)
from . import EzoPMP
DEPENDENCIES = ["ezo_pmp"]
CONF_DOSING_MODE = "dosing_mode"
CONF_CALIBRATION_STATUS = "calibration_status"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(EzoPMP),
cv.Optional(CONF_DOSING_MODE): text_sensor.text_sensor_schema(
entity_category=ENTITY_CATEGORY_NONE,
),
cv.Optional(CONF_CALIBRATION_STATUS): text_sensor.text_sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
async def to_code(config):
parent = await cg.get_variable(config[CONF_ID])
if CONF_DOSING_MODE in config:
sens = await text_sensor.new_text_sensor(config[CONF_DOSING_MODE])
cg.add(parent.set_dosing_mode(sens))
if CONF_CALIBRATION_STATUS in config:
sens = await text_sensor.new_text_sensor(config[CONF_CALIBRATION_STATUS])
cg.add(parent.set_calibration_status(sens))

View File

@@ -368,7 +368,7 @@ async def to_code(config):
if CONF_AP in config:
conf = config[CONF_AP]
ip_config = conf.get(CONF_MANUAL_IP, config.get(CONF_MANUAL_IP))
ip_config = conf.get(CONF_MANUAL_IP)
cg.with_local_variable(
conf[CONF_ID],
WiFiAP(),

View File

@@ -38,7 +38,11 @@ void WiFiComponent::setup() {
this->last_connected_ = millis();
this->wifi_pre_setup_();
#ifndef USE_CAPTIVE_PORTAL_KEEP_USER_CREDENTIALS
uint32_t hash = fnv1_hash(App.get_compilation_time());
#else
uint32_t hash = 88491487UL;
#endif
this->pref_ = global_preferences->make_preference<wifi::SavedWifiSettings>(hash, true);
SavedWifiSettings save{};

View File

View File

@@ -0,0 +1,31 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor, uart
from esphome.const import (
ICON_FINGERPRINT,
)
CODEOWNERS = ["@hobbypunk90"]
DEPENDENCIES = ["uart"]
CONF_RESET = "reset"
wl134_ns = cg.esphome_ns.namespace("wl_134")
Wl134Component = wl134_ns.class_(
"Wl134Component", text_sensor.TextSensor, cg.Component, uart.UARTDevice
)
CONFIG_SCHEMA = (
text_sensor.text_sensor_schema(
Wl134Component,
icon=ICON_FINGERPRINT,
)
.extend({cv.Optional(CONF_RESET, default=False): cv.boolean})
.extend(uart.UART_DEVICE_SCHEMA)
)
async def to_code(config):
var = await text_sensor.new_text_sensor(config)
await cg.register_component(var, config)
cg.add(var.set_do_reset(config[CONF_RESET]))
await uart.register_uart_device(var, config)

View File

@@ -0,0 +1,111 @@
#include "wl_134.h"
#include "esphome/core/log.h"
namespace esphome {
namespace wl_134 {
static const char *const TAG = "wl_134.sensor";
static const uint8_t ASCII_CR = 0x0D;
static const uint8_t ASCII_NBSP = 0xFF;
static const int MAX_DATA_LENGTH_BYTES = 6;
void Wl134Component::setup() { this->publish_state(""); }
void Wl134Component::loop() {
while (this->available() >= RFID134_PACKET_SIZE) {
Wl134Component::Rfid134Error error = this->read_packet_();
if (error != RFID134_ERROR_NONE) {
ESP_LOGW(TAG, "Error: %d", error);
}
}
}
Wl134Component::Rfid134Error Wl134Component::read_packet_() {
uint8_t packet[RFID134_PACKET_SIZE];
packet[RFID134_PACKET_START_CODE] = this->read();
// check for the first byte being the packet start code
if (packet[RFID134_PACKET_START_CODE] != 0x02) {
// just out of sync, ignore until we are synced up
return RFID134_ERROR_NONE;
}
if (!this->read_array(&(packet[RFID134_PACKET_ID]), RFID134_PACKET_SIZE - 1)) {
return RFID134_ERROR_PACKET_SIZE;
}
if (packet[RFID134_PACKET_END_CODE] != 0x03) {
return RFID134_ERROR_PACKET_END_CODE_MISSMATCH;
}
// calculate checksum
uint8_t checksum = 0;
for (uint8_t i = RFID134_PACKET_ID; i < RFID134_PACKET_CHECKSUM; i++) {
checksum = checksum ^ packet[i];
}
// test checksum
if (checksum != packet[RFID134_PACKET_CHECKSUM]) {
return RFID134_ERROR_PACKET_CHECKSUM;
}
if (static_cast<uint8_t>(~checksum) != static_cast<uint8_t>(packet[RFID134_PACKET_CHECKSUM_INVERT])) {
return RFID134_ERROR_PACKET_CHECKSUM_INVERT;
}
Rfid134Reading reading;
// convert packet into the reading struct
reading.id = this->hex_lsb_ascii_to_uint64_(&(packet[RFID134_PACKET_ID]), RFID134_PACKET_COUNTRY - RFID134_PACKET_ID);
reading.country = this->hex_lsb_ascii_to_uint64_(&(packet[RFID134_PACKET_COUNTRY]),
RFID134_PACKET_DATA_FLAG - RFID134_PACKET_COUNTRY);
reading.isData = packet[RFID134_PACKET_DATA_FLAG] == '1';
reading.isAnimal = packet[RFID134_PACKET_ANIMAL_FLAG] == '1';
reading.reserved0 = this->hex_lsb_ascii_to_uint64_(&(packet[RFID134_PACKET_RESERVED0]),
RFID134_PACKET_RESERVED1 - RFID134_PACKET_RESERVED0);
reading.reserved1 = this->hex_lsb_ascii_to_uint64_(&(packet[RFID134_PACKET_RESERVED1]),
RFID134_PACKET_CHECKSUM - RFID134_PACKET_RESERVED1);
ESP_LOGV(TAG, "Tag id: %012lld", reading.id);
ESP_LOGV(TAG, "Country: %03d", reading.country);
ESP_LOGV(TAG, "isData: %s", reading.isData ? "true" : "false");
ESP_LOGV(TAG, "isAnimal: %s", reading.isAnimal ? "true" : "false");
ESP_LOGV(TAG, "Reserved0: %d", reading.reserved0);
ESP_LOGV(TAG, "Reserved1: %d", reading.reserved1);
char buf[20];
sprintf(buf, "%03d%012lld", reading.country, reading.id);
this->publish_state(buf);
if (this->do_reset_) {
this->set_timeout(1000, [this]() { this->publish_state(""); });
}
return RFID134_ERROR_NONE;
}
uint64_t Wl134Component::hex_lsb_ascii_to_uint64_(const uint8_t *text, uint8_t text_size) {
uint64_t value = 0;
uint8_t i = text_size;
do {
i--;
uint8_t digit = text[i];
if (digit >= 'A') {
digit = digit - 'A' + 10;
} else {
digit = digit - '0';
}
value = (value << 4) + digit;
} while (i != 0);
return value;
}
void Wl134Component::dump_config() {
ESP_LOGCONFIG(TAG, "WL-134 Sensor:");
LOG_TEXT_SENSOR("", "Tag", this);
// As specified in the sensor's data sheet
this->check_uart_settings(9600, 1, esphome::uart::UART_CONFIG_PARITY_NONE, 8);
}
} // namespace wl_134
} // namespace esphome

View File

@@ -0,0 +1,63 @@
#pragma once
#include <string>
#include "esphome/core/component.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/components/uart/uart.h"
namespace esphome {
namespace wl_134 {
class Wl134Component : public text_sensor::TextSensor, public Component, public uart::UARTDevice {
public:
enum Rfid134Error {
RFID134_ERROR_NONE,
// from library
RFID134_ERROR_PACKET_SIZE = 0x81,
RFID134_ERROR_PACKET_END_CODE_MISSMATCH,
RFID134_ERROR_PACKET_CHECKSUM,
RFID134_ERROR_PACKET_CHECKSUM_INVERT
};
struct Rfid134Reading {
uint16_t country;
uint64_t id;
bool isData;
bool isAnimal;
uint16_t reserved0;
uint32_t reserved1;
};
// Nothing really public.
// ========== INTERNAL METHODS ==========
void setup() override;
void loop() override;
void dump_config() override;
void set_do_reset(bool do_reset) { this->do_reset_ = do_reset; }
private:
enum DfMp3Packet {
RFID134_PACKET_START_CODE,
RFID134_PACKET_ID = 1,
RFID134_PACKET_COUNTRY = 11,
RFID134_PACKET_DATA_FLAG = 15,
RFID134_PACKET_ANIMAL_FLAG = 16,
RFID134_PACKET_RESERVED0 = 17,
RFID134_PACKET_RESERVED1 = 21,
RFID134_PACKET_CHECKSUM = 27,
RFID134_PACKET_CHECKSUM_INVERT = 28,
RFID134_PACKET_END_CODE = 29,
RFID134_PACKET_SIZE
};
bool do_reset_;
Rfid134Error read_packet_();
uint64_t hex_lsb_ascii_to_uint64_(const uint8_t *text, uint8_t text_size);
};
} // namespace wl_134
} // namespace esphome

View File

@@ -1,6 +1,6 @@
"""Constants used by esphome."""
__version__ = "2022.10.0"
__version__ = "2022.11.0-dev"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"

View File

@@ -20,6 +20,7 @@ const float PROCESSOR = 400.0;
const float BLUETOOTH = 350.0f;
const float AFTER_BLUETOOTH = 300.0f;
const float WIFI = 250.0f;
const float ETHERNET = 250.0f;
const float BEFORE_CONNECTION = 220.0f;
const float AFTER_WIFI = 200.0f;
const float AFTER_CONNECTION = 100.0f;

View File

@@ -29,6 +29,7 @@ extern const float PROCESSOR;
extern const float BLUETOOTH;
extern const float AFTER_BLUETOOTH;
extern const float WIFI;
extern const float ETHERNET;
/// For components that should be initialized after WiFi and before API is connected.
extern const float BEFORE_CONNECTION;
/// For components that should be initialized after WiFi is connected.

View File

@@ -1862,6 +1862,9 @@ climate:
name: Fujitsu General Climate
- platform: daikin
name: Daikin Climate
- platform: daikin_brc
name: Daikin BRC Climate
use_fahrenheit: true
- platform: delonghi
name: Delonghi Climate
- platform: yashima

View File

@@ -789,6 +789,11 @@ sensor:
voltage:
name: Voltage
update_interval: 60s
- platform: adc128s102
id: adc128s102_channel_0
channel: 0
time:
- platform: homeassistant
@@ -1540,3 +1545,6 @@ cd74hc4067:
pin_s1: GPIO13
pin_s2: GPIO14
pin_s3: GPIO15
adc128s102:
cs_pin: GPIO12

View File

@@ -532,6 +532,9 @@ text_sensor:
- platform: copy
source_id: inverter0_device_mode
name: Inverter Text Sensor Copy
- platform: ethernet_info
ip_address:
name: IP Address
output:
- platform: pipsolar

View File

@@ -165,6 +165,12 @@ binary_sensor:
- platform: ezo_pmp
pump_state:
name: "Pump State"
is_paused:
name: "Is Paused"
tlc5947:
data_pin: GPIO12
clock_pin: GPIO14
@@ -220,6 +226,10 @@ esp32_improv:
authorized_duration: 1min
status_indicator: built_in_led
ezo_pmp:
id: hcl_pump
update_interval: 1s
number:
- platform: template
name: My template number
@@ -440,6 +450,20 @@ sensor:
cold_junction:
name: Ambient Temperature
- platform: ezo_pmp
current_volume_dosed:
name: Current Volume Dosed
total_volume_dosed:
name: Total Volume Dosed
absolute_total_volume_dosed:
name: Absolute Total Volume Dosed
pump_voltage:
name: Pump Voltage
last_volume_requested:
name: Last Volume Requested
max_flow_rate:
name: Max Flow Rate
script:
- id: automation_test
then:
@@ -487,3 +511,33 @@ display:
lambda: |-
it.print("81818181");
text_sensor:
- platform: ezo_pmp
dosing_mode:
name: Dosing Mode
calibration_status:
name: Calibration Status
on_value:
- ezo_pmp.dose_volume:
id: hcl_pump
volume: 10
- ezo_pmp.dose_volume_over_time:
id: hcl_pump
volume: 10
duration: 2
- ezo_pmp.dose_with_constant_flow_rate:
id: hcl_pump
volume_per_minute: 10
duration: 2
- ezo_pmp.set_calibration_volume:
id: hcl_pump
volume: 10
- ezo_pmp.find: hcl_pump
- ezo_pmp.dose_continuously: hcl_pump
- ezo_pmp.clear_total_volume_dosed: hcl_pump
- ezo_pmp.clear_calibration: hcl_pump
- ezo_pmp.pause_dosing: hcl_pump
- ezo_pmp.stop_dosing: hcl_pump
- ezo_pmp.arbitrary_command:
id: hcl_pump
command: D,?