Add PZEM004T/PZEMAC/PZEMDC Support (#587)

* Add PZEM004T Support

* Don't flush as much

* Update pzem004t.cpp

* Add generalized modbus

* Add PZEMAC

* Add PZEMDC

* Fix file modes

* Lint

* Fix

* Fix

* Add check_uart_settings
This commit is contained in:
Otto Winter 2019-10-20 19:24:20 +02:00
parent 644aec791e
commit d2c7afeef0
No known key found for this signature in database
GPG key ID: DB66C0BE6013F97E
26 changed files with 738 additions and 7 deletions

View file

@ -172,6 +172,7 @@ void CSE7766Component::dump_config() {
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_); LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
LOG_SENSOR(" ", "Current", this->current_sensor_); LOG_SENSOR(" ", "Current", this->current_sensor_);
LOG_SENSOR(" ", "Power", this->power_sensor_); LOG_SENSOR(" ", "Power", this->power_sensor_);
this->check_uart_settings(4800);
} }
} // namespace cse7766 } // namespace cse7766

View file

@ -94,6 +94,7 @@ void MHZ19Component::dump_config() {
ESP_LOGCONFIG(TAG, "MH-Z19:"); ESP_LOGCONFIG(TAG, "MH-Z19:");
LOG_SENSOR(" ", "CO2", this->co2_sensor_); LOG_SENSOR(" ", "CO2", this->co2_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
this->check_uart_settings(9600);
if (this->abc_boot_logic_ == MHZ19_ABC_ENABLED) { if (this->abc_boot_logic_ == MHZ19_ABC_ENABLED) {
ESP_LOGCONFIG(TAG, " Automatic baseline calibration enabled on boot"); ESP_LOGCONFIG(TAG, " Automatic baseline calibration enabled on boot");

View file

@ -0,0 +1,43 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import uart
from esphome.const import CONF_ID, CONF_ADDRESS
from esphome.core import coroutine
DEPENDENCIES = ['uart']
modbus_ns = cg.esphome_ns.namespace('modbus')
Modbus = modbus_ns.class_('Modbus', cg.Component, uart.UARTDevice)
ModbusDevice = modbus_ns.class_('ModbusDevice')
MULTI_CONF = True
CONF_MODBUS_ID = 'modbus_id'
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(Modbus),
}).extend(cv.COMPONENT_SCHEMA).extend(uart.UART_DEVICE_SCHEMA)
def to_code(config):
cg.add_global(modbus_ns.using)
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield uart.register_uart_device(var, config)
def modbus_device_schema(default_address):
schema = {
cv.GenerateID(CONF_MODBUS_ID): cv.use_id(Modbus),
}
if default_address is None:
schema[cv.Required(CONF_ADDRESS)] = cv.hex_uint8_t
else:
schema[cv.Optional(CONF_ADDRESS, default=default_address)] = cv.hex_uint8_t
return cv.Schema(schema)
@coroutine
def register_modbus_device(var, config):
parent = yield cg.get_variable(config[CONF_MODBUS_ID])
cg.add(var.set_parent(parent))
cg.add(var.set_address(config[CONF_ADDRESS]))

View file

@ -0,0 +1,119 @@
#include "modbus.h"
#include "esphome/core/log.h"
namespace esphome {
namespace modbus {
static const char *TAG = "modbus";
void Modbus::loop() {
const uint32_t now = millis();
if (now - this->last_modbus_byte_ > 50) {
this->rx_buffer_.clear();
this->last_modbus_byte_ = now;
}
while (this->available()) {
uint8_t byte;
this->read_byte(&byte);
if (this->parse_modbus_byte_(byte)) {
this->last_modbus_byte_ = now;
} else {
this->rx_buffer_.clear();
}
}
}
uint16_t crc16(uint8_t *data, uint8_t len) {
uint16_t crc = 0xFFFF;
while (len--) {
crc ^= *data++;
for (uint8_t i = 0; i < 8; i++) {
if ((crc & 0x01) != 0) {
crc >>= 1;
crc ^= 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
bool Modbus::parse_modbus_byte_(uint8_t byte) {
size_t at = this->rx_buffer_.size();
this->rx_buffer_.push_back(byte);
uint8_t *raw = &this->rx_buffer_[0];
// Byte 0: modbus address (match all)
if (at == 0)
return true;
uint8_t address = raw[0];
// Byte 1: Function (msb indicates error)
if (at == 1)
return (byte & 0x80) != 0x80;
// Byte 2: Size (with modbus rtu function code 4/3)
// See also https://en.wikipedia.org/wiki/Modbus
if (at == 2)
return true;
uint8_t data_len = raw[2];
// Byte 3..3+data_len-1: Data
if (at < 3 + data_len)
return true;
// Byte 3+data_len: CRC_LO (over all bytes)
if (at == 3 + data_len)
return true;
// Byte 3+len+1: CRC_HI (over all bytes)
uint16_t computed_crc = crc16(raw, 3 + data_len);
uint16_t remote_crc = uint16_t(raw[3 + data_len]) | (uint16_t(raw[3 + data_len]) << 8);
if (computed_crc != remote_crc) {
ESP_LOGW(TAG, "Modbus CRC Check failed! %02X!=%02X", computed_crc, remote_crc);
return false;
}
std::vector<uint8_t> data(this->rx_buffer_.begin() + 3, this->rx_buffer_.begin() + 3 + data_len);
bool found = false;
for (auto *device : this->devices_) {
if (device->address_ == address) {
device->on_modbus_data(data);
found = true;
}
}
if (!found) {
ESP_LOGW(TAG, "Got Modbus frame from unknown address 0x%02X!", address);
}
// return false to reset buffer
return false;
}
void Modbus::dump_config() {
ESP_LOGCONFIG(TAG, "Modbus:");
this->check_uart_settings(9600, 2);
}
float Modbus::get_setup_priority() const {
// After UART bus
return setup_priority::BUS - 1.0f;
}
void Modbus::send(uint8_t address, uint8_t function, uint16_t start_address, uint16_t register_count) {
uint8_t frame[8];
frame[0] = address;
frame[1] = function;
frame[2] = start_address >> 8;
frame[3] = start_address >> 0;
frame[4] = register_count >> 8;
frame[5] = register_count >> 0;
auto crc = crc16(frame, 6);
frame[6] = crc >> 0;
frame[7] = crc >> 8;
this->write_array(frame, 8);
}
} // namespace modbus
} // namespace esphome

View file

@ -0,0 +1,51 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
namespace esphome {
namespace modbus {
class ModbusDevice;
class Modbus : public uart::UARTDevice, public Component {
public:
Modbus() = default;
void loop() override;
void dump_config() override;
void register_device(ModbusDevice *device) { this->devices_.push_back(device); }
float get_setup_priority() const override;
void send(uint8_t address, uint8_t function, uint16_t start_address, uint16_t register_count);
protected:
bool parse_modbus_byte_(uint8_t byte);
std::vector<uint8_t> rx_buffer_;
uint32_t last_modbus_byte_{0};
std::vector<ModbusDevice *> devices_;
};
class ModbusDevice {
public:
void set_parent(Modbus *parent) { parent_ = parent; }
void set_address(uint8_t address) { address_ = address; }
virtual void on_modbus_data(const std::vector<uint8_t> &data) = 0;
void send(uint8_t function, uint16_t start_address, uint16_t register_count) {
this->parent_->send(this->address_, function, start_address, register_count);
}
protected:
friend Modbus;
Modbus *parent_;
uint8_t address_;
};
} // namespace modbus
} // namespace esphome

View file

@ -169,6 +169,7 @@ void PMSX003Component::dump_config() {
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
LOG_SENSOR(" ", "Formaldehyde", this->formaldehyde_sensor_); LOG_SENSOR(" ", "Formaldehyde", this->formaldehyde_sensor_);
this->check_uart_settings(9600);
} }
} // namespace pmsx003 } // namespace pmsx003

View file

View file

@ -0,0 +1,103 @@
#include "pzem004t.h"
#include "esphome/core/log.h"
namespace esphome {
namespace pzem004t {
static const char *TAG = "pzem004t";
void PZEM004T::loop() {
const uint32_t now = millis();
if (now - this->last_read_ > 500 && this->available()) {
while (this->available())
this->read();
this->last_read_ = now;
}
// PZEM004T packet size is 7 byte
while (this->available() >= 7) {
auto resp = *this->read_array<7>();
// packet format:
// 0: packet type
// 1-5: data
// 6: checksum (sum of other bytes)
// see https://github.com/olehs/PZEM004T
uint8_t sum = 0;
for (int i = 0; i < 6; i++)
sum += resp[i];
if (sum != resp[6]) {
ESP_LOGV(TAG, "PZEM004T invalid checksum! 0x%02X != 0x%02X", sum, resp[6]);
continue;
}
switch (resp[0]) {
case 0xA4: { // Set Module Address Response
this->write_state_(READ_VOLTAGE);
break;
}
case 0xA0: { // Voltage Response
uint16_t int_voltage = (uint16_t(resp[1]) << 8) | (uint16_t(resp[2]) << 0);
float voltage = int_voltage + (resp[3] / 10.0f);
if (this->voltage_sensor_ != nullptr)
this->voltage_sensor_->publish_state(voltage);
ESP_LOGD(TAG, "Got Voltage %.1f V", voltage);
this->write_state_(READ_CURRENT);
break;
}
case 0xA1: { // Current Response
uint16_t int_current = (uint16_t(resp[1]) << 8) | (uint16_t(resp[2]) << 0);
float current = int_current + (resp[3] / 100.0f);
if (this->current_sensor_ != nullptr)
this->current_sensor_->publish_state(current);
ESP_LOGD(TAG, "Got Current %.2f A", current);
this->write_state_(READ_POWER);
break;
}
case 0xA2: { // Active Power Response
uint16_t power = (uint16_t(resp[1]) << 8) | (uint16_t(resp[2]) << 0);
if (this->power_sensor_ != nullptr)
this->power_sensor_->publish_state(power);
ESP_LOGD(TAG, "Got Power %u W", power);
this->write_state_(DONE);
break;
}
case 0xA3: // Energy Response
case 0xA5: // Set Power Alarm Response
case 0xB0: // Voltage Request
case 0xB1: // Current Request
case 0xB2: // Active Power Response
case 0xB3: // Energy Request
case 0xB4: // Set Module Address Request
case 0xB5: // Set Power Alarm Request
default:
break;
}
this->last_read_ = now;
}
}
void PZEM004T::update() { this->write_state_(SET_ADDRESS); }
void PZEM004T::write_state_(PZEM004T::PZEM004TReadState state) {
if (state == DONE) {
this->read_state_ = state;
return;
}
std::array<uint8_t, 7> data{};
data[0] = state;
data[1] = 192;
data[2] = 168;
data[3] = 1;
data[4] = 1;
data[5] = 0;
data[6] = 0;
for (int i = 0; i < 6; i++)
data[6] += data[i];
this->write_array(data);
this->read_state_ = state;
}
} // namespace pzem004t
} // namespace esphome

View file

@ -0,0 +1,39 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace pzem004t {
class PZEM004T : public PollingComponent, public uart::UARTDevice {
public:
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
void loop() override;
void update() override;
protected:
sensor::Sensor *voltage_sensor_;
sensor::Sensor *current_sensor_;
sensor::Sensor *power_sensor_;
enum PZEM004TReadState {
SET_ADDRESS = 0xB4,
READ_VOLTAGE = 0xB0,
READ_CURRENT = 0xB1,
READ_POWER = 0xB2,
DONE = 0x00,
} read_state_{DONE};
void write_state_(PZEM004TReadState state);
uint32_t last_read_{0};
};
} // namespace pzem004t
} // namespace esphome

View file

@ -0,0 +1,37 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, uart
from esphome.const import CONF_CURRENT, CONF_ID, CONF_POWER, CONF_VOLTAGE, \
UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT
DEPENDENCIES = ['uart']
pzem004t_ns = cg.esphome_ns.namespace('pzem004t')
PZEM004T = pzem004t_ns.class_('PZEM004T', cg.PollingComponent, uart.UARTDevice)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(PZEM004T),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2),
cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 0),
}).extend(cv.polling_component_schema('60s')).extend(uart.UART_DEVICE_SCHEMA)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield uart.register_uart_device(var, config)
if CONF_VOLTAGE in config:
conf = config[CONF_VOLTAGE]
sens = yield sensor.new_sensor(conf)
cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config:
conf = config[CONF_CURRENT]
sens = yield sensor.new_sensor(conf)
cg.add(var.set_current_sensor(sens))
if CONF_POWER in config:
conf = config[CONF_POWER]
sens = yield sensor.new_sensor(conf)
cg.add(var.set_power_sensor(sens))

View file

View file

@ -0,0 +1,62 @@
#include "pzemac.h"
#include "esphome/core/log.h"
namespace esphome {
namespace pzemac {
static const char *TAG = "pzemac";
static const uint8_t PZEM_CMD_READ_IN_REGISTERS = 0x04;
static const uint8_t PZEM_REGISTER_COUNT = 10; // 10x 16-bit registers
void PZEMAC::on_modbus_data(const std::vector<uint8_t> &data) {
if (data.size() < 20) {
ESP_LOGW(TAG, "Invalid size for PZEM AC!");
return;
}
// See https://github.com/esphome/feature-requests/issues/49#issuecomment-538636809
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
// 01 04 14 08 D1 00 6C 00 00 00 F4 00 00 00 26 00 00 01 F4 00 64 00 00 51 34
// Id Cc Sz Volt- Current---- Power------ Energy----- Frequ PFact Alarm Crc--
auto pzem_get_16bit = [&](size_t i) -> uint16_t {
return (uint16_t(data[i + 0]) << 8) | (uint16_t(data[i + 1]) << 0);
};
auto pzem_get_32bit = [&](size_t i) -> uint32_t {
return (uint32_t(pzem_get_16bit(i + 2)) << 16) | (uint32_t(pzem_get_16bit(i + 0)) << 0);
};
uint16_t raw_voltage = pzem_get_16bit(0);
float voltage = raw_voltage / 10.0f; // max 6553.5 V
uint32_t raw_current = pzem_get_32bit(2);
float current = raw_current / 1000.0f; // max 4294967.295 A
uint32_t raw_active_power = pzem_get_32bit(6);
float active_power = raw_active_power / 10.0f; // max 429496729.5 W
uint16_t raw_frequency = pzem_get_16bit(14);
float frequency = raw_frequency / 10.0f;
uint16_t raw_power_factor = pzem_get_16bit(16);
float power_factor = raw_power_factor / 100.0f;
ESP_LOGD(TAG, "PZEM AC: V=%.1f V, I=%.3f A, P=%.1f W, F=%.1f Hz, PF=%.2f", voltage, current, active_power, frequency,
power_factor);
if (this->voltage_sensor_ != nullptr)
this->voltage_sensor_->publish_state(voltage);
if (this->current_sensor_ != nullptr)
this->current_sensor_->publish_state(current);
if (this->power_sensor_ != nullptr)
this->power_sensor_->publish_state(active_power);
if (this->frequency_sensor_ != nullptr)
this->frequency_sensor_->publish_state(frequency);
if (this->power_factor_sensor_ != nullptr)
this->power_factor_sensor_->publish_state(power_factor);
}
void PZEMAC::update() { this->send(PZEM_CMD_READ_IN_REGISTERS, 0, PZEM_REGISTER_COUNT); }
} // namespace pzemac
} // namespace esphome

View file

@ -0,0 +1,31 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/modbus/modbus.h"
namespace esphome {
namespace pzemac {
class PZEMAC : public PollingComponent, public modbus::ModbusDevice {
public:
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; }
void set_power_factor_sensor(sensor::Sensor *power_factor_sensor) { power_factor_sensor_ = power_factor_sensor; }
void update() override;
void on_modbus_data(const std::vector<uint8_t> &data) override;
protected:
sensor::Sensor *voltage_sensor_;
sensor::Sensor *current_sensor_;
sensor::Sensor *power_sensor_;
sensor::Sensor *frequency_sensor_;
sensor::Sensor *power_factor_sensor_;
};
} // namespace pzemac
} // namespace esphome

View file

@ -0,0 +1,47 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, modbus
from esphome.const import CONF_CURRENT, CONF_ID, CONF_POWER, CONF_VOLTAGE, \
CONF_FREQUENCY, UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT, UNIT_EMPTY, \
ICON_POWER, CONF_POWER_FACTOR, ICON_CURRENT_AC
AUTO_LOAD = ['modbus']
pzemac_ns = cg.esphome_ns.namespace('pzemac')
PZEMAC = pzemac_ns.class_('PZEMAC', cg.PollingComponent, modbus.ModbusDevice)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(PZEMAC),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_CURRENT_AC, 3),
cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_POWER, 1),
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(UNIT_EMPTY, ICON_CURRENT_AC, 1),
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(UNIT_EMPTY, ICON_FLASH, 2),
}).extend(cv.polling_component_schema('60s')).extend(modbus.modbus_device_schema(0x01))
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield modbus.register_modbus_device(var, config)
if CONF_VOLTAGE in config:
conf = config[CONF_VOLTAGE]
sens = yield sensor.new_sensor(conf)
cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config:
conf = config[CONF_CURRENT]
sens = yield sensor.new_sensor(conf)
cg.add(var.set_current_sensor(sens))
if CONF_POWER in config:
conf = config[CONF_POWER]
sens = yield sensor.new_sensor(conf)
cg.add(var.set_power_sensor(sens))
if CONF_FREQUENCY in config:
conf = config[CONF_FREQUENCY]
sens = yield sensor.new_sensor(conf)
cg.add(var.set_frequency_sensor(sens))
if CONF_POWER_FACTOR in config:
conf = config[CONF_POWER_FACTOR]
sens = yield sensor.new_sensor(conf)
cg.add(var.set_power_factor_sensor(sens))

View file

View file

@ -0,0 +1,52 @@
#include "pzemdc.h"
#include "esphome/core/log.h"
namespace esphome {
namespace pzemdc {
static const char *TAG = "pzemdc";
static const uint8_t PZEM_CMD_READ_IN_REGISTERS = 0x04;
static const uint8_t PZEM_REGISTER_COUNT = 10; // 10x 16-bit registers
void PZEMDC::on_modbus_data(const std::vector<uint8_t> &data) {
if (data.size() < 16) {
ESP_LOGW(TAG, "Invalid size for PZEM DC!");
return;
}
// See https://github.com/esphome/feature-requests/issues/49#issuecomment-538636809
// 0 1 2 3 4 5 6 7 = ModBus register
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 = Buffer index
// 01 04 10 05 40 00 0A 00 0D 00 00 00 02 00 00 00 00 00 00 D6 29
// Id Cc Sz Volt- Curre Power------ Energy----- HiAlm LoAlm Crc--
auto pzem_get_16bit = [&](size_t i) -> uint16_t {
return (uint16_t(data[i + 0]) << 8) | (uint16_t(data[i + 1]) << 0);
};
auto pzem_get_32bit = [&](size_t i) -> uint32_t {
return (uint32_t(pzem_get_16bit(i + 2)) << 16) | (uint32_t(pzem_get_16bit(i + 0)) << 0);
};
uint16_t raw_voltage = pzem_get_16bit(0);
float voltage = raw_voltage / 100.0f; // max 655.35 V
uint16_t raw_current = pzem_get_16bit(2);
float current = raw_current / 100.0f; // max 655.35 A
uint32_t raw_power = pzem_get_32bit(4);
float power = raw_power / 10.0f; // max 429496729.5 W
ESP_LOGD(TAG, "PZEM DC: V=%.1f V, I=%.3f A, P=%.1f W", voltage, current, power);
if (this->voltage_sensor_ != nullptr)
this->voltage_sensor_->publish_state(voltage);
if (this->current_sensor_ != nullptr)
this->current_sensor_->publish_state(current);
if (this->power_sensor_ != nullptr)
this->power_sensor_->publish_state(power);
}
void PZEMDC::update() { this->send(PZEM_CMD_READ_IN_REGISTERS, 0, 8); }
} // namespace pzemdc
} // namespace esphome

View file

@ -0,0 +1,31 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/modbus/modbus.h"
namespace esphome {
namespace pzemdc {
class PZEMDC : public PollingComponent, public modbus::ModbusDevice {
public:
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; }
void set_powerfactor_sensor(sensor::Sensor *powerfactor_sensor) { power_factor_sensor_ = powerfactor_sensor; }
void update() override;
void on_modbus_data(const std::vector<uint8_t> &data) override;
protected:
sensor::Sensor *voltage_sensor_;
sensor::Sensor *current_sensor_;
sensor::Sensor *power_sensor_;
sensor::Sensor *frequency_sensor_;
sensor::Sensor *power_factor_sensor_;
};
} // namespace pzemdc
} // namespace esphome

View file

@ -0,0 +1,36 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, modbus
from esphome.const import CONF_CURRENT, CONF_ID, CONF_POWER, CONF_VOLTAGE, \
UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT, ICON_POWER, ICON_CURRENT_AC
AUTO_LOAD = ['modbus']
pzemdc_ns = cg.esphome_ns.namespace('pzemdc')
PZEMDC = pzemdc_ns.class_('PZEMDC', cg.PollingComponent, modbus.ModbusDevice)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(PZEMDC),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_CURRENT_AC, 3),
cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_POWER, 1),
}).extend(cv.polling_component_schema('60s')).extend(modbus.modbus_device_schema(0x01))
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield modbus.register_modbus_device(var, config)
if CONF_VOLTAGE in config:
conf = config[CONF_VOLTAGE]
sens = yield sensor.new_sensor(conf)
cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config:
conf = config[CONF_CURRENT]
sens = yield sensor.new_sensor(conf)
cg.add(var.set_current_sensor(sens))
if CONF_POWER in config:
conf = config[CONF_POWER]
sens = yield sensor.new_sensor(conf)
cg.add(var.set_power_sensor(sens))

View file

@ -56,6 +56,7 @@ void SDS011Component::dump_config() {
ESP_LOGCONFIG(TAG, " RX-only mode: %s", ONOFF(this->rx_mode_only_)); ESP_LOGCONFIG(TAG, " RX-only mode: %s", ONOFF(this->rx_mode_only_));
LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_); LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_);
LOG_SENSOR(" ", "PM10.0", this->pm_10_0_sensor_); LOG_SENSOR(" ", "PM10.0", this->pm_10_0_sensor_);
this->check_uart_settings(9600);
} }
void SDS011Component::loop() { void SDS011Component::loop() {

View file

@ -73,6 +73,7 @@ bool SenseAirComponent::senseair_write_command_(const uint8_t *command, uint8_t
void SenseAirComponent::dump_config() { void SenseAirComponent::dump_config() {
ESP_LOGCONFIG(TAG, "SenseAir:"); ESP_LOGCONFIG(TAG, "SenseAir:");
LOG_SENSOR(" ", "CO2", this->co2_sensor_); LOG_SENSOR(" ", "CO2", this->co2_sensor_);
this->check_uart_settings(9600);
} }
} // namespace senseair } // namespace senseair

View file

@ -36,6 +36,7 @@ void Tuya::dump_config() {
else else
ESP_LOGCONFIG(TAG, " Datapoint %d: unknown", info.id); ESP_LOGCONFIG(TAG, " Datapoint %d: unknown", info.id);
} }
this->check_uart_settings(9600);
} }
bool Tuya::validate_message_() { bool Tuya::validate_message_() {

View file

@ -29,11 +29,13 @@ def validate_rx_pin(value):
return value return value
CONF_STOP_BITS = 'stop_bits'
CONFIG_SCHEMA = cv.All(cv.Schema({ CONFIG_SCHEMA = cv.All(cv.Schema({
cv.GenerateID(): cv.declare_id(UARTComponent), cv.GenerateID(): cv.declare_id(UARTComponent),
cv.Required(CONF_BAUD_RATE): cv.int_range(min=1), cv.Required(CONF_BAUD_RATE): cv.int_range(min=1),
cv.Optional(CONF_TX_PIN): pins.output_pin, cv.Optional(CONF_TX_PIN): pins.output_pin,
cv.Optional(CONF_RX_PIN): validate_rx_pin, cv.Optional(CONF_RX_PIN): validate_rx_pin,
cv.Optional(CONF_STOP_BITS, default=1): cv.one_of(1, 2, int=True),
}).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN)) }).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_TX_PIN, CONF_RX_PIN))
@ -48,6 +50,7 @@ def to_code(config):
cg.add(var.set_tx_pin(config[CONF_TX_PIN])) cg.add(var.set_tx_pin(config[CONF_TX_PIN]))
if CONF_RX_PIN in config: if CONF_RX_PIN in config:
cg.add(var.set_rx_pin(config[CONF_RX_PIN])) cg.add(var.set_rx_pin(config[CONF_RX_PIN]))
cg.add(var.set_stop_bits(config[CONF_STOP_BITS]))
# A schema to use for all UART devices, all UART integrations must extend this! # A schema to use for all UART devices, all UART integrations must extend this!

View file

@ -25,7 +25,10 @@ void UARTComponent::setup() {
} }
int8_t tx = this->tx_pin_.has_value() ? *this->tx_pin_ : -1; int8_t tx = this->tx_pin_.has_value() ? *this->tx_pin_ : -1;
int8_t rx = this->rx_pin_.has_value() ? *this->rx_pin_ : -1; int8_t rx = this->rx_pin_.has_value() ? *this->rx_pin_ : -1;
this->hw_serial_->begin(this->baud_rate_, SERIAL_8N1, rx, tx); uint32_t config = SERIAL_8N1;
if (this->stop_bits_ == 2)
config = SERIAL_8N2;
this->hw_serial_->begin(this->baud_rate_, config, rx, tx);
} }
void UARTComponent::dump_config() { void UARTComponent::dump_config() {
@ -37,6 +40,7 @@ void UARTComponent::dump_config() {
ESP_LOGCONFIG(TAG, " RX Pin: GPIO%d", *this->rx_pin_); ESP_LOGCONFIG(TAG, " RX Pin: GPIO%d", *this->rx_pin_);
} }
ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_); ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_);
ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_);
} }
void UARTComponent::write_byte(uint8_t data) { void UARTComponent::write_byte(uint8_t data) {
@ -102,21 +106,27 @@ void UARTComponent::setup() {
// Use Arduino HardwareSerial UARTs if all used pins match the ones // Use Arduino HardwareSerial UARTs if all used pins match the ones
// preconfigured by the platform. For example if RX disabled but TX pin // preconfigured by the platform. For example if RX disabled but TX pin
// is 1 we still want to use Serial. // is 1 we still want to use Serial.
uint32_t mode = UART_NB_BIT_8 | UART_PARITY_NONE;
if (this->stop_bits_ == 1)
mode |= UART_NB_STOP_BIT_1;
else
mode |= UART_NB_STOP_BIT_2;
SerialConfig config = static_cast<SerialConfig>(mode);
if (this->tx_pin_.value_or(1) == 1 && this->rx_pin_.value_or(3) == 3) { if (this->tx_pin_.value_or(1) == 1 && this->rx_pin_.value_or(3) == 3) {
this->hw_serial_ = &Serial; this->hw_serial_ = &Serial;
this->hw_serial_->begin(this->baud_rate_); this->hw_serial_->begin(this->baud_rate_, config);
} else if (this->tx_pin_.value_or(15) == 15 && this->rx_pin_.value_or(13) == 13) { } else if (this->tx_pin_.value_or(15) == 15 && this->rx_pin_.value_or(13) == 13) {
this->hw_serial_ = &Serial; this->hw_serial_ = &Serial;
this->hw_serial_->begin(this->baud_rate_); this->hw_serial_->begin(this->baud_rate_, config);
this->hw_serial_->swap(); this->hw_serial_->swap();
} else if (this->tx_pin_.value_or(2) == 2 && this->rx_pin_.value_or(8) == 8) { } else if (this->tx_pin_.value_or(2) == 2 && this->rx_pin_.value_or(8) == 8) {
this->hw_serial_ = &Serial1; this->hw_serial_ = &Serial1;
this->hw_serial_->begin(this->baud_rate_); this->hw_serial_->begin(this->baud_rate_, config);
} else { } else {
this->sw_serial_ = new ESP8266SoftwareSerial(); this->sw_serial_ = new ESP8266SoftwareSerial();
int8_t tx = this->tx_pin_.has_value() ? *this->tx_pin_ : -1; int8_t tx = this->tx_pin_.has_value() ? *this->tx_pin_ : -1;
int8_t rx = this->rx_pin_.has_value() ? *this->rx_pin_ : -1; int8_t rx = this->rx_pin_.has_value() ? *this->rx_pin_ : -1;
this->sw_serial_->setup(tx, rx, this->baud_rate_); this->sw_serial_->setup(tx, rx, this->baud_rate_, this->stop_bits_);
} }
} }
@ -129,6 +139,7 @@ void UARTComponent::dump_config() {
ESP_LOGCONFIG(TAG, " RX Pin: GPIO%d", *this->rx_pin_); ESP_LOGCONFIG(TAG, " RX Pin: GPIO%d", *this->rx_pin_);
} }
ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_); ESP_LOGCONFIG(TAG, " Baud Rate: %u baud", this->baud_rate_);
ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_);
if (this->hw_serial_ != nullptr) { if (this->hw_serial_ != nullptr) {
ESP_LOGCONFIG(TAG, " Using hardware serial interface."); ESP_LOGCONFIG(TAG, " Using hardware serial interface.");
} else { } else {
@ -231,7 +242,7 @@ void UARTComponent::flush() {
} }
} }
void ESP8266SoftwareSerial::setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_rate) { void ESP8266SoftwareSerial::setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_rate, uint8_t stop_bits) {
this->bit_time_ = F_CPU / baud_rate; this->bit_time_ = F_CPU / baud_rate;
if (tx_pin != -1) { if (tx_pin != -1) {
auto pin = GPIOPin(tx_pin, OUTPUT); auto pin = GPIOPin(tx_pin, OUTPUT);
@ -246,6 +257,7 @@ void ESP8266SoftwareSerial::setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_ra
this->rx_buffer_ = new uint8_t[this->rx_buffer_size_]; this->rx_buffer_ = new uint8_t[this->rx_buffer_size_];
pin.attach_interrupt(ESP8266SoftwareSerial::gpio_intr, this, FALLING); pin.attach_interrupt(ESP8266SoftwareSerial::gpio_intr, this, FALLING);
} }
this->stop_bits_ = stop_bits;
} }
void ICACHE_RAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg) { void ICACHE_RAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg) {
uint32_t wait = arg->bit_time_ + arg->bit_time_ / 3 - 500; uint32_t wait = arg->bit_time_ + arg->bit_time_ / 3 - 500;
@ -262,6 +274,8 @@ void ICACHE_RAM_ATTR ESP8266SoftwareSerial::gpio_intr(ESP8266SoftwareSerial *arg
rec |= arg->read_bit_(&wait, start) << 7; rec |= arg->read_bit_(&wait, start) << 7;
// Stop bit // Stop bit
arg->wait_(&wait, start); arg->wait_(&wait, start);
if (arg->stop_bits_ == 2)
arg->wait_(&wait, start);
arg->rx_buffer_[arg->rx_in_pos_] = rec; arg->rx_buffer_[arg->rx_in_pos_] = rec;
arg->rx_in_pos_ = (arg->rx_in_pos_ + 1) % arg->rx_buffer_size_; arg->rx_in_pos_ = (arg->rx_in_pos_ + 1) % arg->rx_buffer_size_;
@ -289,6 +303,8 @@ void ICACHE_RAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) {
this->write_bit_(data & (1 << 7), &wait, start); this->write_bit_(data & (1 << 7), &wait, start);
// Stop bit // Stop bit
this->write_bit_(true, &wait, start); this->write_bit_(true, &wait, start);
if (this->stop_bits_ == 2)
this->wait_(&wait, start);
enable_interrupts(); enable_interrupts();
} }
void ICACHE_RAM_ATTR ESP8266SoftwareSerial::wait_(uint32_t *wait, const uint32_t &start) { void ICACHE_RAM_ATTR ESP8266SoftwareSerial::wait_(uint32_t *wait, const uint32_t &start) {
@ -344,5 +360,16 @@ int UARTComponent::peek() {
return data; return data;
} }
void UARTDevice::check_uart_settings(uint32_t baud_rate, uint8_t stop_bits) {
if (this->parent_->baud_rate_ != baud_rate) {
ESP_LOGE(TAG, " Invalid baud_rate: Integration requested baud_rate %u but you have %u!", baud_rate,
this->parent_->baud_rate_);
}
if (this->parent_->stop_bits_ != stop_bits) {
ESP_LOGE(TAG, " Invalid stop bits: Integration requested stop_bits %u but you have %u!", stop_bits,
this->parent_->stop_bits_);
}
}
} // namespace uart } // namespace uart
} // namespace esphome } // namespace esphome

View file

@ -10,7 +10,7 @@ namespace uart {
#ifdef ARDUINO_ARCH_ESP8266 #ifdef ARDUINO_ARCH_ESP8266
class ESP8266SoftwareSerial { class ESP8266SoftwareSerial {
public: public:
void setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_rate); void setup(int8_t tx_pin, int8_t rx_pin, uint32_t baud_rate, uint8_t stop_bits);
uint8_t read_byte(); uint8_t read_byte();
uint8_t peek_byte(); uint8_t peek_byte();
@ -33,6 +33,7 @@ class ESP8266SoftwareSerial {
size_t rx_buffer_size_{512}; size_t rx_buffer_size_{512};
volatile size_t rx_in_pos_{0}; volatile size_t rx_in_pos_{0};
size_t rx_out_pos_{0}; size_t rx_out_pos_{0};
uint8_t stop_bits_;
ISRInternalGPIOPin *tx_pin_{nullptr}; ISRInternalGPIOPin *tx_pin_{nullptr};
ISRInternalGPIOPin *rx_pin_{nullptr}; ISRInternalGPIOPin *rx_pin_{nullptr};
}; };
@ -72,9 +73,11 @@ class UARTComponent : public Component, public Stream {
void set_tx_pin(uint8_t tx_pin) { this->tx_pin_ = tx_pin; } void set_tx_pin(uint8_t tx_pin) { this->tx_pin_ = tx_pin; }
void set_rx_pin(uint8_t rx_pin) { this->rx_pin_ = rx_pin; } void set_rx_pin(uint8_t rx_pin) { this->rx_pin_ = rx_pin; }
void set_stop_bits(uint8_t stop_bits) { this->stop_bits_ = stop_bits; }
protected: protected:
bool check_read_timeout_(size_t len = 1); bool check_read_timeout_(size_t len = 1);
friend class UARTDevice;
HardwareSerial *hw_serial_{nullptr}; HardwareSerial *hw_serial_{nullptr};
#ifdef ARDUINO_ARCH_ESP8266 #ifdef ARDUINO_ARCH_ESP8266
@ -83,6 +86,7 @@ class UARTComponent : public Component, public Stream {
optional<uint8_t> tx_pin_; optional<uint8_t> tx_pin_;
optional<uint8_t> rx_pin_; optional<uint8_t> rx_pin_;
uint32_t baud_rate_; uint32_t baud_rate_;
uint8_t stop_bits_;
}; };
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
@ -100,6 +104,9 @@ class UARTDevice : public Stream {
void write_array(const uint8_t *data, size_t len) { this->parent_->write_array(data, len); } void write_array(const uint8_t *data, size_t len) { this->parent_->write_array(data, len); }
void write_array(const std::vector<uint8_t> &data) { this->parent_->write_array(data); } void write_array(const std::vector<uint8_t> &data) { this->parent_->write_array(data); }
template<size_t N> void write_array(const std::array<uint8_t, N> &data) {
this->parent_->write_array(data.data(), data.size());
}
void write_str(const char *str) { this->parent_->write_str(str); } void write_str(const char *str) { this->parent_->write_str(str); }
@ -107,6 +114,13 @@ class UARTDevice : public Stream {
bool peek_byte(uint8_t *data) { return this->parent_->peek_byte(data); } bool peek_byte(uint8_t *data) { return this->parent_->peek_byte(data); }
bool read_array(uint8_t *data, size_t len) { return this->parent_->read_array(data, len); } bool read_array(uint8_t *data, size_t len) { return this->parent_->read_array(data, len); }
template<size_t N> optional<std::array<uint8_t, N>> read_array() { // NOLINT
std::array<uint8_t, N> res;
if (!this->read_array(res.data(), N)) {
return {};
}
return res;
}
int available() override { return this->parent_->available(); } int available() override { return this->parent_->available(); }
@ -116,6 +130,9 @@ class UARTDevice : public Stream {
int read() override { return this->parent_->read(); } int read() override { return this->parent_->read(); }
int peek() override { return this->parent_->peek(); } int peek() override { return this->parent_->peek(); }
/// Check that the configuration of the UART bus matches the provided values and otherwise print a warning
void check_uart_settings(uint32_t baud_rate, uint8_t stop_bits = 1);
protected: protected:
UARTComponent *parent_{nullptr}; UARTComponent *parent_{nullptr};
}; };

View file

@ -323,6 +323,7 @@ CONF_PM_2_5 = 'pm_2_5'
CONF_PORT = 'port' CONF_PORT = 'port'
CONF_POSITION = 'position' CONF_POSITION = 'position'
CONF_POWER = 'power' CONF_POWER = 'power'
CONF_POWER_FACTOR = 'power_factor'
CONF_POWER_ON_VALUE = 'power_on_value' CONF_POWER_ON_VALUE = 'power_on_value'
CONF_POWER_SAVE_MODE = 'power_save_mode' CONF_POWER_SAVE_MODE = 'power_save_mode'
CONF_POWER_SUPPLY = 'power_supply' CONF_POWER_SUPPLY = 'power_supply'
@ -479,6 +480,7 @@ ICON_BRIEFCASE_DOWNLOAD = 'mdi:briefcase-download'
ICON_BRIGHTNESS_5 = 'mdi:brightness-5' ICON_BRIGHTNESS_5 = 'mdi:brightness-5'
ICON_CHEMICAL_WEAPON = 'mdi:chemical-weapon' ICON_CHEMICAL_WEAPON = 'mdi:chemical-weapon'
ICON_CHECK_CIRCLE_OUTLINE = 'mdi:check-circle-outline' ICON_CHECK_CIRCLE_OUTLINE = 'mdi:check-circle-outline'
ICON_CURRENT_AC = 'mdi:current-ac'
ICON_EMPTY = '' ICON_EMPTY = ''
ICON_FLASH = 'mdi:flash' ICON_FLASH = 'mdi:flash'
ICON_FLOWER = 'mdi:flower' ICON_FLOWER = 'mdi:flower'

View file

@ -291,6 +291,31 @@ sensor:
name: ADE7953 Active Power A name: ADE7953 Active Power A
active_power_b: active_power_b:
name: ADE7953 Active Power B name: ADE7953 Active Power B
- platform: pzem004t
voltage:
name: "PZEM00T Voltage"
current:
name: "PZEM004T Current"
power:
name: "PZEM004T Power"
- platform: pzemac
voltage:
name: "PZEMAC Voltage"
current:
name: "PZEMAC Current"
power:
name: "PZEMAC Power"
frequency:
name: "PZEMAC Frequency"
power_factor:
name: "PZEMAC Power Factor"
- platform: pzemdc
voltage:
name: "PZEMDC Voltage"
current:
name: "PZEMDC Current"
power:
name: "PZEMDC Power"
time: time:
- platform: homeassistant - platform: homeassistant