Added support for Selec Energy Meter (#1993)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
Sourabh Jaiswal 2021-07-22 09:01:28 +05:30 committed by GitHub
parent 90394a50df
commit acbb8e9fd0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 399 additions and 0 deletions

View file

@ -97,6 +97,7 @@ esphome/components/rf_bridge/* @jesserockz
esphome/components/rtttl/* @glmnet
esphome/components/script/* @esphome/core
esphome/components/sdm_meter/* @jesserockz @polyfaces
esphome/components/selec_meter/* @sourabhjaiswal
esphome/components/sensor/* @esphome/core
esphome/components/sgp40/* @SenexCrenshaw
esphome/components/sht4x/* @sjtrny

View file

@ -0,0 +1,108 @@
#include "selec_meter.h"
#include "selec_meter_registers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace selec_meter {
static const char *const TAG = "selec_meter";
static const uint8_t MODBUS_CMD_READ_IN_REGISTERS = 0x04;
static const uint8_t MODBUS_REGISTER_COUNT = 34; // 34 x 16-bit registers
void SelecMeter::on_modbus_data(const std::vector<uint8_t> &data) {
if (data.size() < MODBUS_REGISTER_COUNT * 2) {
ESP_LOGW(TAG, "Invalid size for SelecMeter!");
return;
}
auto selec_meter_get_float = [&](size_t i, float unit) -> float {
uint32_t temp = encode_uint32(data[i + 2], data[i + 3], data[i], data[i + 1]);
float f;
memcpy(&f, &temp, sizeof(f));
return (f * unit);
};
float total_active_energy = selec_meter_get_float(SELEC_TOTAL_ACTIVE_ENERGY * 2, NO_DEC_UNIT);
float import_active_energy = selec_meter_get_float(SELEC_IMPORT_ACTIVE_ENERGY * 2, NO_DEC_UNIT);
float export_active_energy = selec_meter_get_float(SELEC_EXPORT_ACTIVE_ENERGY * 2, NO_DEC_UNIT);
float total_reactive_energy = selec_meter_get_float(SELEC_TOTAL_REACTIVE_ENERGY * 2, NO_DEC_UNIT);
float import_reactive_energy = selec_meter_get_float(SELEC_IMPORT_REACTIVE_ENERGY * 2, NO_DEC_UNIT);
float export_reactive_energy = selec_meter_get_float(SELEC_EXPORT_REACTIVE_ENERGY * 2, NO_DEC_UNIT);
float apparent_energy = selec_meter_get_float(SELEC_APPARENT_ENERGY * 2, NO_DEC_UNIT);
float active_power = selec_meter_get_float(SELEC_ACTIVE_POWER * 2, MULTIPLY_THOUSAND_UNIT);
float reactive_power = selec_meter_get_float(SELEC_REACTIVE_POWER * 2, MULTIPLY_THOUSAND_UNIT);
float apparent_power = selec_meter_get_float(SELEC_APPARENT_POWER * 2, MULTIPLY_THOUSAND_UNIT);
float voltage = selec_meter_get_float(SELEC_VOLTAGE * 2, NO_DEC_UNIT);
float current = selec_meter_get_float(SELEC_CURRENT * 2, NO_DEC_UNIT);
float power_factor = selec_meter_get_float(SELEC_POWER_FACTOR * 2, NO_DEC_UNIT);
float frequency = selec_meter_get_float(SELEC_FREQUENCY * 2, NO_DEC_UNIT);
float maximum_demand_active_power =
selec_meter_get_float(SELEC_MAXIMUM_DEMAND_ACTIVE_POWER * 2, MULTIPLY_THOUSAND_UNIT);
float maximum_demand_reactive_power =
selec_meter_get_float(SELEC_MAXIMUM_DEMAND_REACTIVE_POWER * 2, MULTIPLY_THOUSAND_UNIT);
float maximum_demand_apparent_power =
selec_meter_get_float(SELEC_MAXIMUM_DEMAND_APPARENT_POWER * 2, MULTIPLY_THOUSAND_UNIT);
if (this->total_active_energy_sensor_ != nullptr)
this->total_active_energy_sensor_->publish_state(total_active_energy);
if (this->import_active_energy_sensor_ != nullptr)
this->import_active_energy_sensor_->publish_state(import_active_energy);
if (this->export_active_energy_sensor_ != nullptr)
this->export_active_energy_sensor_->publish_state(export_active_energy);
if (this->total_reactive_energy_sensor_ != nullptr)
this->total_reactive_energy_sensor_->publish_state(total_reactive_energy);
if (this->import_reactive_energy_sensor_ != nullptr)
this->import_reactive_energy_sensor_->publish_state(import_reactive_energy);
if (this->export_reactive_energy_sensor_ != nullptr)
this->export_reactive_energy_sensor_->publish_state(export_reactive_energy);
if (this->apparent_energy_sensor_ != nullptr)
this->apparent_energy_sensor_->publish_state(apparent_energy);
if (this->active_power_sensor_ != nullptr)
this->active_power_sensor_->publish_state(active_power);
if (this->reactive_power_sensor_ != nullptr)
this->reactive_power_sensor_->publish_state(reactive_power);
if (this->apparent_power_sensor_ != nullptr)
this->apparent_power_sensor_->publish_state(apparent_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_factor_sensor_ != nullptr)
this->power_factor_sensor_->publish_state(power_factor);
if (this->frequency_sensor_ != nullptr)
this->frequency_sensor_->publish_state(frequency);
if (this->maximum_demand_active_power_sensor_ != nullptr)
this->maximum_demand_active_power_sensor_->publish_state(maximum_demand_active_power);
if (this->maximum_demand_reactive_power_sensor_ != nullptr)
this->maximum_demand_reactive_power_sensor_->publish_state(maximum_demand_reactive_power);
if (this->maximum_demand_apparent_power_sensor_ != nullptr)
this->maximum_demand_apparent_power_sensor_->publish_state(maximum_demand_apparent_power);
}
void SelecMeter::update() { this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT); }
void SelecMeter::dump_config() {
ESP_LOGCONFIG(TAG, "SELEC Meter:");
ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
LOG_SENSOR(" ", "Total Active Energy", this->total_active_energy_sensor_);
LOG_SENSOR(" ", "Import Active Energy", this->import_active_energy_sensor_);
LOG_SENSOR(" ", "Export Active Energy", this->export_active_energy_sensor_);
LOG_SENSOR(" ", "Total Reactive Energy", this->total_reactive_energy_sensor_);
LOG_SENSOR(" ", "Import Reactive Energy", this->import_reactive_energy_sensor_);
LOG_SENSOR(" ", "Export Reactive Energy", this->export_reactive_energy_sensor_);
LOG_SENSOR(" ", "Apparent Energy", this->apparent_energy_sensor_);
LOG_SENSOR(" ", "Active Power", this->active_power_sensor_);
LOG_SENSOR(" ", "Reactive Power", this->reactive_power_sensor_);
LOG_SENSOR(" ", "Apparent Power", this->apparent_power_sensor_);
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
LOG_SENSOR(" ", "Current", this->current_sensor_);
LOG_SENSOR(" ", "Power Factor", this->power_factor_sensor_);
LOG_SENSOR(" ", "Frequency", this->frequency_sensor_);
LOG_SENSOR(" ", "Maximum Demand Active Power", this->maximum_demand_active_power_sensor_);
LOG_SENSOR(" ", "Maximum Demand Reactive Power", this->maximum_demand_reactive_power_sensor_);
LOG_SENSOR(" ", "Maximum Demand Apparent Power", this->maximum_demand_apparent_power_sensor_);
}
} // namespace selec_meter
} // namespace esphome

View file

@ -0,0 +1,45 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/modbus/modbus.h"
namespace esphome {
namespace selec_meter {
#define SELEC_METER_SENSOR(name) \
protected: \
sensor::Sensor *name##_sensor_{nullptr}; \
\
public: \
void set_##name##_sensor(sensor::Sensor *(name)) { this->name##_sensor_ = name; }
class SelecMeter : public PollingComponent, public modbus::ModbusDevice {
public:
SELEC_METER_SENSOR(total_active_energy)
SELEC_METER_SENSOR(import_active_energy)
SELEC_METER_SENSOR(export_active_energy)
SELEC_METER_SENSOR(total_reactive_energy)
SELEC_METER_SENSOR(import_reactive_energy)
SELEC_METER_SENSOR(export_reactive_energy)
SELEC_METER_SENSOR(apparent_energy)
SELEC_METER_SENSOR(active_power)
SELEC_METER_SENSOR(reactive_power)
SELEC_METER_SENSOR(apparent_power)
SELEC_METER_SENSOR(voltage)
SELEC_METER_SENSOR(current)
SELEC_METER_SENSOR(power_factor)
SELEC_METER_SENSOR(frequency)
SELEC_METER_SENSOR(maximum_demand_active_power)
SELEC_METER_SENSOR(maximum_demand_reactive_power)
SELEC_METER_SENSOR(maximum_demand_apparent_power)
void update() override;
void on_modbus_data(const std::vector<uint8_t> &data) override;
void dump_config() override;
};
} // namespace selec_meter
} // namespace esphome

View file

@ -0,0 +1,32 @@
#pragma once
namespace esphome {
namespace selec_meter {
static const float TWO_DEC_UNIT = 0.01;
static const float ONE_DEC_UNIT = 0.1;
static const float NO_DEC_UNIT = 1;
static const float MULTIPLY_TEN_UNIT = 10;
static const float MULTIPLY_THOUSAND_UNIT = 1000;
/* PHASE STATUS REGISTERS */
static const uint16_t SELEC_TOTAL_ACTIVE_ENERGY = 0x0000;
static const uint16_t SELEC_IMPORT_ACTIVE_ENERGY = 0x0002;
static const uint16_t SELEC_EXPORT_ACTIVE_ENERGY = 0x0004;
static const uint16_t SELEC_TOTAL_REACTIVE_ENERGY = 0x0006;
static const uint16_t SELEC_IMPORT_REACTIVE_ENERGY = 0x0008;
static const uint16_t SELEC_EXPORT_REACTIVE_ENERGY = 0x000A;
static const uint16_t SELEC_APPARENT_ENERGY = 0x000C;
static const uint16_t SELEC_ACTIVE_POWER = 0x000E;
static const uint16_t SELEC_REACTIVE_POWER = 0x0010;
static const uint16_t SELEC_APPARENT_POWER = 0x0012;
static const uint16_t SELEC_VOLTAGE = 0x0014;
static const uint16_t SELEC_CURRENT = 0x0016;
static const uint16_t SELEC_POWER_FACTOR = 0x0018;
static const uint16_t SELEC_FREQUENCY = 0x001A;
static const uint16_t SELEC_MAXIMUM_DEMAND_ACTIVE_POWER = 0x001C;
static const uint16_t SELEC_MAXIMUM_DEMAND_REACTIVE_POWER = 0x001E;
static const uint16_t SELEC_MAXIMUM_DEMAND_APPARENT_POWER = 0x0020;
} // namespace selec_meter
} // namespace esphome

View file

@ -0,0 +1,169 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, modbus
from esphome.const import (
CONF_ACTIVE_POWER,
CONF_APPARENT_POWER,
CONF_CURRENT,
CONF_EXPORT_ACTIVE_ENERGY,
CONF_EXPORT_REACTIVE_ENERGY,
CONF_FREQUENCY,
CONF_ID,
CONF_IMPORT_ACTIVE_ENERGY,
CONF_IMPORT_REACTIVE_ENERGY,
CONF_POWER_FACTOR,
CONF_REACTIVE_POWER,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_VOLTAGE,
ICON_CURRENT_AC,
ICON_EMPTY,
LAST_RESET_TYPE_AUTO,
STATE_CLASS_MEASUREMENT,
UNIT_AMPERE,
UNIT_EMPTY,
UNIT_HERTZ,
UNIT_VOLT,
UNIT_VOLT_AMPS,
UNIT_VOLT_AMPS_REACTIVE,
UNIT_WATT,
)
AUTO_LOAD = ["modbus"]
CODEOWNERS = ["@sourabhjaiswal"]
CONF_TOTAL_ACTIVE_ENERGY = "total_active_energy"
CONF_TOTAL_REACTIVE_ENERGY = "total_reactive_energy"
CONF_APPARENT_ENERGY = "apparent_energy"
CONF_MAXIMUM_DEMAND_ACTIVE_POWER = "maximum_demand_active_power"
CONF_MAXIMUM_DEMAND_REACTIVE_POWER = "maximum_demand_reactive_power"
CONF_MAXIMUM_DEMAND_APPARENT_POWER = "maximum_demand_apparent_power"
UNIT_KILOWATT_HOURS = "kWh"
UNIT_KILOVOLT_AMPS_HOURS = "kVAh"
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kVARh"
selec_meter_ns = cg.esphome_ns.namespace("selec_meter")
SelecMeter = selec_meter_ns.class_(
"SelecMeter", cg.PollingComponent, modbus.ModbusDevice
)
SENSORS = {
CONF_TOTAL_ACTIVE_ENERGY: sensor.sensor_schema(
UNIT_KILOWATT_HOURS,
ICON_EMPTY,
2,
DEVICE_CLASS_ENERGY,
STATE_CLASS_MEASUREMENT,
LAST_RESET_TYPE_AUTO,
),
CONF_IMPORT_ACTIVE_ENERGY: sensor.sensor_schema(
UNIT_KILOWATT_HOURS,
ICON_EMPTY,
2,
DEVICE_CLASS_ENERGY,
STATE_CLASS_MEASUREMENT,
LAST_RESET_TYPE_AUTO,
),
CONF_EXPORT_ACTIVE_ENERGY: sensor.sensor_schema(
UNIT_KILOWATT_HOURS,
ICON_EMPTY,
2,
DEVICE_CLASS_ENERGY,
STATE_CLASS_MEASUREMENT,
LAST_RESET_TYPE_AUTO,
),
CONF_TOTAL_REACTIVE_ENERGY: sensor.sensor_schema(
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
ICON_EMPTY,
2,
DEVICE_CLASS_ENERGY,
STATE_CLASS_MEASUREMENT,
LAST_RESET_TYPE_AUTO,
),
CONF_IMPORT_REACTIVE_ENERGY: sensor.sensor_schema(
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
ICON_EMPTY,
2,
DEVICE_CLASS_ENERGY,
STATE_CLASS_MEASUREMENT,
LAST_RESET_TYPE_AUTO,
),
CONF_EXPORT_REACTIVE_ENERGY: sensor.sensor_schema(
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS,
ICON_EMPTY,
2,
DEVICE_CLASS_ENERGY,
STATE_CLASS_MEASUREMENT,
LAST_RESET_TYPE_AUTO,
),
CONF_APPARENT_ENERGY: sensor.sensor_schema(
UNIT_KILOVOLT_AMPS_HOURS,
ICON_EMPTY,
2,
DEVICE_CLASS_ENERGY,
STATE_CLASS_MEASUREMENT,
LAST_RESET_TYPE_AUTO,
),
CONF_ACTIVE_POWER: sensor.sensor_schema(
UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
),
CONF_REACTIVE_POWER: sensor.sensor_schema(
UNIT_VOLT_AMPS_REACTIVE,
ICON_EMPTY,
3,
DEVICE_CLASS_POWER,
STATE_CLASS_MEASUREMENT,
),
CONF_APPARENT_POWER: sensor.sensor_schema(
UNIT_VOLT_AMPS, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
),
CONF_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT
),
CONF_CURRENT: sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 3, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT
),
CONF_POWER_FACTOR: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 3, DEVICE_CLASS_POWER_FACTOR, STATE_CLASS_MEASUREMENT
),
CONF_FREQUENCY: sensor.sensor_schema(
UNIT_HERTZ, ICON_CURRENT_AC, 2, DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT
),
CONF_MAXIMUM_DEMAND_ACTIVE_POWER: sensor.sensor_schema(
UNIT_WATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
),
CONF_MAXIMUM_DEMAND_REACTIVE_POWER: sensor.sensor_schema(
UNIT_VOLT_AMPS_REACTIVE,
ICON_EMPTY,
3,
DEVICE_CLASS_POWER,
STATE_CLASS_MEASUREMENT,
),
CONF_MAXIMUM_DEMAND_APPARENT_POWER: sensor.sensor_schema(
UNIT_VOLT_AMPS, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
),
}
CONFIG_SCHEMA = (
cv.Schema({cv.GenerateID(): cv.declare_id(SelecMeter)})
.extend(
{cv.Optional(sensor_name): schema for sensor_name, schema in SENSORS.items()}
)
.extend(cv.polling_component_schema("10s"))
.extend(modbus.modbus_device_schema(0x01))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await modbus.register_modbus_device(var, config)
for name in SENSORS:
if name in config:
sens = await sensor.new_sensor(config[name])
cg.add(getattr(var, f"set_{name}_sensor")(sens))

View file

@ -18,6 +18,13 @@ ota:
logger:
uart:
tx_pin: 1
rx_pin: 3
baud_rate: 9600
modbus:
binary_sensor:
- platform: gpio
pin: GPIO0
@ -55,3 +62,40 @@ number:
max_value: 100
min_value: 0
step: 5
sensor:
- platform: selec_meter
total_active_energy:
name: "SelecEM2M Total Active Energy"
import_active_energy:
name: "SelecEM2M Import Active Energy"
export_active_energy:
name: "SelecEM2M Export Active Energy"
total_reactive_energy:
name: "SelecEM2M Total Reactive Energy"
import_reactive_energy:
name: "SelecEM2M Import Reactive Energy"
export_reactive_energy:
name: "SelecEM2M Export Reactive Energy"
apparent_energy:
name: "SelecEM2M Apparent Energy"
active_power:
name: "SelecEM2M Active Power"
reactive_power:
name: "SelecEM2M Reactive Power"
apparent_power:
name: "SelecEM2M Apparent Power"
voltage:
name: "SelecEM2M Voltage"
current:
name: "SelecEM2M Current"
power_factor:
name: "SelecEM2M Power Factor"
frequency:
name: "SelecEM2M Frequency"
maximum_demand_active_power:
name: "SelecEM2M Maximum Demand Active Power"
maximum_demand_reactive_power:
name: "SelecEM2M Maximum Demand Reactive Power"
maximum_demand_apparent_power:
name: "SelecEM2M Maximum Demand Apparent Power"