mirror of
https://github.com/esphome/esphome.git
synced 2024-12-21 16:27:44 +01:00
Add CS5460A power-meter component (#1474)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
bb759d52c8
commit
4d586b1446
@ -29,6 +29,7 @@ esphome/components/climate/* @esphome/core
|
|||||||
esphome/components/climate_ir/* @glmnet
|
esphome/components/climate_ir/* @glmnet
|
||||||
esphome/components/coolix/* @glmnet
|
esphome/components/coolix/* @glmnet
|
||||||
esphome/components/cover/* @esphome/core
|
esphome/components/cover/* @esphome/core
|
||||||
|
esphome/components/cs5460a/* @balrog-kun
|
||||||
esphome/components/ct_clamp/* @jesserockz
|
esphome/components/ct_clamp/* @jesserockz
|
||||||
esphome/components/debug/* @OttoWinter
|
esphome/components/debug/* @OttoWinter
|
||||||
esphome/components/dfplayer/* @glmnet
|
esphome/components/dfplayer/* @glmnet
|
||||||
|
0
esphome/components/cs5460a/__init__.py
Normal file
0
esphome/components/cs5460a/__init__.py
Normal file
342
esphome/components/cs5460a/cs5460a.cpp
Normal file
342
esphome/components/cs5460a/cs5460a.cpp
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
#include "cs5460a.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace cs5460a {
|
||||||
|
|
||||||
|
static const char *TAG = "cs5460a";
|
||||||
|
|
||||||
|
void CS5460AComponent::write_register_(enum CS5460ARegister addr, uint32_t value) {
|
||||||
|
this->write_byte(CMD_WRITE | (addr << 1));
|
||||||
|
this->write_byte(value >> 16);
|
||||||
|
this->write_byte(value >> 8);
|
||||||
|
this->write_byte(value >> 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t CS5460AComponent::read_register_(uint8_t addr) {
|
||||||
|
uint32_t value;
|
||||||
|
|
||||||
|
this->write_byte(CMD_READ | (addr << 1));
|
||||||
|
value = (uint32_t) this->transfer_byte(CMD_SYNC0) << 16;
|
||||||
|
value |= (uint32_t) this->transfer_byte(CMD_SYNC0) << 8;
|
||||||
|
value |= this->transfer_byte(CMD_SYNC0) << 0;
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CS5460AComponent::softreset_() {
|
||||||
|
uint32_t pc = ((uint8_t) phase_offset_ & 0x3f) | (phase_offset_ < 0 ? 0x40 : 0);
|
||||||
|
uint32_t config = (1 << 0) | /* K = 0b0001 */
|
||||||
|
(current_hpf_ ? 1 << 5 : 0) | /* IHPF */
|
||||||
|
(voltage_hpf_ ? 1 << 6 : 0) | /* VHPF */
|
||||||
|
(pga_gain_ << 16) | /* Gi */
|
||||||
|
(pc << 17); /* PC */
|
||||||
|
int cnt = 0;
|
||||||
|
|
||||||
|
/* Serial resynchronization */
|
||||||
|
this->write_byte(CMD_SYNC1);
|
||||||
|
this->write_byte(CMD_SYNC1);
|
||||||
|
this->write_byte(CMD_SYNC1);
|
||||||
|
this->write_byte(CMD_SYNC0);
|
||||||
|
|
||||||
|
/* Reset */
|
||||||
|
this->write_register_(REG_CONFIG, 1 << 7);
|
||||||
|
delay(10);
|
||||||
|
while (cnt++ < 50 && (this->read_register_(REG_CONFIG) & 0x81) != 0x000001)
|
||||||
|
;
|
||||||
|
if (cnt > 50)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
this->write_register_(REG_CONFIG, config);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CS5460AComponent::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up CS5460A...");
|
||||||
|
|
||||||
|
float current_full_scale = (pga_gain_ == CS5460A_PGA_GAIN_10X) ? 0.25 : 0.10;
|
||||||
|
float voltage_full_scale = 0.25;
|
||||||
|
current_multiplier_ = current_full_scale / (fabsf(current_gain_) * 0x1000000);
|
||||||
|
voltage_multiplier_ = voltage_full_scale / (voltage_gain_ * 0x1000000);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculate power from the Energy register because the Power register
|
||||||
|
* stores instantaneous power which varies a lot in each AC cycle,
|
||||||
|
* while the Energy value is accumulated over the "computation cycle"
|
||||||
|
* which should be an integer number of AC cycles.
|
||||||
|
*/
|
||||||
|
power_multiplier_ =
|
||||||
|
(current_full_scale * voltage_full_scale * 4096) / (current_gain_ * voltage_gain_ * samples_ * 0x800000);
|
||||||
|
|
||||||
|
pulse_freq_ =
|
||||||
|
(current_full_scale * voltage_full_scale) / (fabsf(current_gain_) * voltage_gain_ * pulse_energy_wh_ * 3600);
|
||||||
|
|
||||||
|
hw_init_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CS5460AComponent::hw_init_() {
|
||||||
|
this->spi_setup();
|
||||||
|
this->enable();
|
||||||
|
|
||||||
|
if (!this->softreset_()) {
|
||||||
|
this->disable();
|
||||||
|
ESP_LOGE(TAG, "CS5460A reset failed!");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t status = this->read_register_(REG_STATUS);
|
||||||
|
ESP_LOGCONFIG(TAG, " Version: %x", (status >> 6) & 7);
|
||||||
|
|
||||||
|
this->write_register_(REG_CYCLE_COUNT, samples_);
|
||||||
|
this->write_register_(REG_PULSE_RATE, lroundf(pulse_freq_ * 32.0f));
|
||||||
|
|
||||||
|
/* Use one of the power saving features (assuming external oscillator), reset other CONTROL bits,
|
||||||
|
* sometimes softreset_() is not enough */
|
||||||
|
this->write_register_(REG_CONTROL, 0x000004);
|
||||||
|
|
||||||
|
this->restart_();
|
||||||
|
this->disable();
|
||||||
|
ESP_LOGCONFIG(TAG, " Init ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Doesn't reset the register values etc., just restarts the "computation cycle" */
|
||||||
|
void CS5460AComponent::restart_() {
|
||||||
|
int cnt;
|
||||||
|
|
||||||
|
this->enable();
|
||||||
|
/* Stop running conversion, wake up if needed */
|
||||||
|
this->write_byte(CMD_POWER_UP);
|
||||||
|
/* Start continuous conversion */
|
||||||
|
this->write_byte(CMD_START_CONT);
|
||||||
|
this->disable();
|
||||||
|
|
||||||
|
this->started_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CS5460AComponent::started_() {
|
||||||
|
/*
|
||||||
|
* Try to guess when the next batch of results is going to be ready and
|
||||||
|
* schedule next STATUS check some time before that moment. This assumes
|
||||||
|
* two things:
|
||||||
|
* * a new "computation cycle" started just now. If it started some
|
||||||
|
* time ago we may be a late next time, but hopefully less late in each
|
||||||
|
* iteration -- that's why we schedule the next check in some 0.8 of
|
||||||
|
* the time we actually expect the next reading ready.
|
||||||
|
* * MCLK rate is 4.096MHz and K == 1. If there's a CS5460A module in
|
||||||
|
* use with a different clock this will need to be parametrised.
|
||||||
|
*/
|
||||||
|
expect_data_ts_ = millis() + samples_ * 1024 / 4096;
|
||||||
|
|
||||||
|
schedule_next_check_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CS5460AComponent::schedule_next_check_() {
|
||||||
|
int32_t time_left = expect_data_ts_ - millis();
|
||||||
|
|
||||||
|
/* First try at 0.8 of the actual expected time (if it's in the future) */
|
||||||
|
if (time_left > 0)
|
||||||
|
time_left -= time_left / 5;
|
||||||
|
|
||||||
|
if (time_left > -500) {
|
||||||
|
/* But not sooner than in 30ms from now */
|
||||||
|
if (time_left < 30)
|
||||||
|
time_left = 30;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* If the measurement is more than 0.5s overdue start worrying. The
|
||||||
|
* device may be stuck because of an overcurrent error or similar,
|
||||||
|
* from now on just retry every 1s. After 15s try a reset, if it
|
||||||
|
* fails we give up and mark the component "failed".
|
||||||
|
*/
|
||||||
|
if (time_left > -15000) {
|
||||||
|
time_left = 1000;
|
||||||
|
this->status_momentary_warning("warning", 1000);
|
||||||
|
} else {
|
||||||
|
ESP_LOGCONFIG(TAG, "Device officially stuck, resetting");
|
||||||
|
this->cancel_timeout("status-check");
|
||||||
|
this->hw_init_();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->set_timeout("status-check", time_left, [this]() {
|
||||||
|
if (!this->check_status_())
|
||||||
|
this->schedule_next_check_();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CS5460AComponent::check_status_() {
|
||||||
|
this->enable();
|
||||||
|
uint32_t status = this->read_register_(REG_STATUS);
|
||||||
|
|
||||||
|
if (!(status & 0xcbf83c)) {
|
||||||
|
this->disable();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t clear = 1 << 20;
|
||||||
|
|
||||||
|
/* TODO: Report if IC=0 but only once as it can't be cleared */
|
||||||
|
|
||||||
|
if (status & (1 << 2)) {
|
||||||
|
clear |= 1 << 2;
|
||||||
|
ESP_LOGE(TAG, "Low supply detected");
|
||||||
|
this->status_momentary_warning("warning", 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status & (1 << 3)) {
|
||||||
|
clear |= 1 << 3;
|
||||||
|
ESP_LOGE(TAG, "Modulator oscillation on current channel");
|
||||||
|
this->status_momentary_warning("warning", 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status & (1 << 4)) {
|
||||||
|
clear |= 1 << 4;
|
||||||
|
ESP_LOGE(TAG, "Modulator oscillation on voltage channel");
|
||||||
|
this->status_momentary_warning("warning", 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status & (1 << 5)) {
|
||||||
|
clear |= 1 << 5;
|
||||||
|
ESP_LOGE(TAG, "Watch-dog timeout");
|
||||||
|
this->status_momentary_warning("warning", 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status & (1 << 11)) {
|
||||||
|
clear |= 1 << 11;
|
||||||
|
ESP_LOGE(TAG, "EOUT Energy Accumulation Register out of range");
|
||||||
|
this->status_momentary_warning("warning", 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status & (1 << 12)) {
|
||||||
|
clear |= 1 << 12;
|
||||||
|
ESP_LOGE(TAG, "Energy out of range");
|
||||||
|
this->status_momentary_warning("warning", 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status & (1 << 13)) {
|
||||||
|
clear |= 1 << 13;
|
||||||
|
ESP_LOGE(TAG, "RMS voltage out of range");
|
||||||
|
this->status_momentary_warning("warning", 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status & (1 << 14)) {
|
||||||
|
clear |= 1 << 14;
|
||||||
|
ESP_LOGE(TAG, "RMS current out of range");
|
||||||
|
this->status_momentary_warning("warning", 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status & (1 << 15)) {
|
||||||
|
clear |= 1 << 15;
|
||||||
|
ESP_LOGE(TAG, "Power calculation out of range");
|
||||||
|
this->status_momentary_warning("warning", 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status & (1 << 16)) {
|
||||||
|
clear |= 1 << 16;
|
||||||
|
ESP_LOGE(TAG, "Voltage out of range");
|
||||||
|
this->status_momentary_warning("warning", 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status & (1 << 17)) {
|
||||||
|
clear |= 1 << 17;
|
||||||
|
ESP_LOGE(TAG, "Current out of range");
|
||||||
|
this->status_momentary_warning("warning", 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status & (1 << 19)) {
|
||||||
|
clear |= 1 << 19;
|
||||||
|
ESP_LOGE(TAG, "Divide overflowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status & (1 << 22)) {
|
||||||
|
bool dir = status & (1 << 21);
|
||||||
|
if (current_gain_ < 0)
|
||||||
|
dir = !dir;
|
||||||
|
ESP_LOGI(TAG, "Energy counter %s pulse", dir ? "negative" : "positive");
|
||||||
|
clear |= 1 << 22;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t raw_current = 0; /* Calm the validators */
|
||||||
|
uint32_t raw_voltage = 0;
|
||||||
|
uint32_t raw_energy = 0;
|
||||||
|
|
||||||
|
if (status & (1 << 23)) {
|
||||||
|
clear |= 1 << 23;
|
||||||
|
|
||||||
|
if (current_sensor_ != nullptr)
|
||||||
|
raw_current = this->read_register_(REG_IRMS);
|
||||||
|
|
||||||
|
if (voltage_sensor_ != nullptr)
|
||||||
|
raw_voltage = this->read_register_(REG_VRMS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status & ((1 << 23) | (1 << 5))) {
|
||||||
|
/* Read to clear the WDT bit */
|
||||||
|
raw_energy = this->read_register_(REG_E);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->write_register_(REG_STATUS, clear);
|
||||||
|
this->disable();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Schedule the next STATUS check assuming that DRDY was asserted very
|
||||||
|
* recently, then publish the new values. Do this last for reentrancy in
|
||||||
|
* case the publish triggers a restart() or for whatever reason needs to
|
||||||
|
* cancel the timeout set in schedule_next_check_(), or needs to use SPI.
|
||||||
|
* If the current or power values haven't changed one bit it may be that
|
||||||
|
* the chip somehow forgot to update the registers -- seen happening very
|
||||||
|
* rarely. In that case don't publish them because the user may have
|
||||||
|
* the input connected to a multiplexer and may have switched channels
|
||||||
|
* since the previous reading and we'd be publishing the stale value for
|
||||||
|
* the new channel. If the value *was* updated it's very unlikely that
|
||||||
|
* it wouldn't have changed, especially power/energy which are affected
|
||||||
|
* by the noise on both the current and value channels (in case of energy,
|
||||||
|
* accumulated over many conversion cycles.)
|
||||||
|
*/
|
||||||
|
if (status & (1 << 23)) {
|
||||||
|
this->started_();
|
||||||
|
|
||||||
|
if (current_sensor_ != nullptr && raw_current != prev_raw_current_) {
|
||||||
|
current_sensor_->publish_state(raw_current * current_multiplier_);
|
||||||
|
prev_raw_current_ = raw_current;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (voltage_sensor_ != nullptr)
|
||||||
|
voltage_sensor_->publish_state(raw_voltage * voltage_multiplier_);
|
||||||
|
|
||||||
|
if (power_sensor_ != nullptr && raw_energy != prev_raw_energy_) {
|
||||||
|
int32_t raw = (int32_t)(raw_energy << 8) >> 8; /* Sign-extend */
|
||||||
|
power_sensor_->publish_state(raw * power_multiplier_);
|
||||||
|
prev_raw_energy_ = raw_energy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CS5460AComponent::dump_config() {
|
||||||
|
uint32_t state = this->get_component_state();
|
||||||
|
|
||||||
|
ESP_LOGCONFIG(TAG, "CS5460A:");
|
||||||
|
ESP_LOGCONFIG(TAG, " Init status: %s",
|
||||||
|
state == COMPONENT_STATE_LOOP ? "OK" : (state == COMPONENT_STATE_FAILED ? "failed" : "other"));
|
||||||
|
LOG_PIN(" CS Pin: ", cs_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Samples / cycle: %u", samples_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Phase offset: %i", phase_offset_);
|
||||||
|
ESP_LOGCONFIG(TAG, " PGA Gain: %s", pga_gain_ == CS5460A_PGA_GAIN_50X ? "50x" : "10x");
|
||||||
|
ESP_LOGCONFIG(TAG, " Current gain: %.5f", current_gain_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Voltage gain: %.5f", voltage_gain_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Current HPF: %s", current_hpf_ ? "enabled" : "disabled");
|
||||||
|
ESP_LOGCONFIG(TAG, " Voltage HPF: %s", voltage_hpf_ ? "enabled" : "disabled");
|
||||||
|
ESP_LOGCONFIG(TAG, " Pulse energy: %.2f Wh", pulse_energy_wh_);
|
||||||
|
LOG_SENSOR(" ", "Voltage", voltage_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Current", current_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Power", power_sensor_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cs5460a
|
||||||
|
} // namespace esphome
|
123
esphome/components/cs5460a/cs5460a.h
Normal file
123
esphome/components/cs5460a/cs5460a.h
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/spi/spi.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace cs5460a {
|
||||||
|
|
||||||
|
enum CS5460ACommand {
|
||||||
|
CMD_SYNC0 = 0xfe,
|
||||||
|
CMD_SYNC1 = 0xff,
|
||||||
|
CMD_START_SINGLE = 0xe0,
|
||||||
|
CMD_START_CONT = 0xe8,
|
||||||
|
CMD_POWER_UP = 0xa0,
|
||||||
|
CMD_POWER_STANDBY = 0x88,
|
||||||
|
CMD_POWER_SLEEP = 0x90,
|
||||||
|
CMD_CALIBRATION = 0xc0,
|
||||||
|
CMD_READ = 0x00,
|
||||||
|
CMD_WRITE = 0x40,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum CS5460ARegister {
|
||||||
|
REG_CONFIG = 0x00,
|
||||||
|
REG_IDCOFF = 0x01,
|
||||||
|
REG_IGN = 0x02,
|
||||||
|
REG_VDCOFF = 0x03,
|
||||||
|
REG_VGN = 0x04,
|
||||||
|
REG_CYCLE_COUNT = 0x05,
|
||||||
|
REG_PULSE_RATE = 0x06,
|
||||||
|
REG_I = 0x07,
|
||||||
|
REG_V = 0x08,
|
||||||
|
REG_P = 0x09,
|
||||||
|
REG_E = 0x0a,
|
||||||
|
REG_IRMS = 0x0b,
|
||||||
|
REG_VRMS = 0x0c,
|
||||||
|
REG_TBC = 0x0d,
|
||||||
|
REG_POFF = 0x0e,
|
||||||
|
REG_STATUS = 0x0f,
|
||||||
|
REG_IACOFF = 0x10,
|
||||||
|
REG_VACOFF = 0x11,
|
||||||
|
REG_MASK = 0x1a,
|
||||||
|
REG_CONTROL = 0x1c,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Enum listing the current channel aplifiergain settings for the CS5460A.
|
||||||
|
*/
|
||||||
|
enum CS5460APGAGain {
|
||||||
|
CS5460A_PGA_GAIN_10X = 0b0,
|
||||||
|
CS5460A_PGA_GAIN_50X = 0b1,
|
||||||
|
};
|
||||||
|
|
||||||
|
class CS5460AComponent : public Component,
|
||||||
|
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||||
|
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> {
|
||||||
|
public:
|
||||||
|
void set_samples(uint32_t samples) { samples_ = samples; }
|
||||||
|
void set_phase_offset(int8_t phase_offset) { phase_offset_ = phase_offset; }
|
||||||
|
void set_pga_gain(CS5460APGAGain pga_gain) { pga_gain_ = pga_gain; }
|
||||||
|
void set_gains(float current_gain, float voltage_gain) {
|
||||||
|
current_gain_ = current_gain;
|
||||||
|
voltage_gain_ = voltage_gain;
|
||||||
|
}
|
||||||
|
void set_hpf_enable(bool current_hpf, bool voltage_hpf) {
|
||||||
|
current_hpf_ = current_hpf;
|
||||||
|
voltage_hpf_ = voltage_hpf;
|
||||||
|
}
|
||||||
|
void set_pulse_energy_wh(float pulse_energy_wh) { pulse_energy_wh_ = pulse_energy_wh; }
|
||||||
|
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
|
||||||
|
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||||
|
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
|
||||||
|
|
||||||
|
void restart() { restart_(); }
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
|
void loop() override {}
|
||||||
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint32_t samples_;
|
||||||
|
int8_t phase_offset_;
|
||||||
|
CS5460APGAGain pga_gain_;
|
||||||
|
float current_gain_;
|
||||||
|
float voltage_gain_;
|
||||||
|
bool current_hpf_;
|
||||||
|
bool voltage_hpf_;
|
||||||
|
float pulse_energy_wh_;
|
||||||
|
sensor::Sensor *current_sensor_{nullptr};
|
||||||
|
sensor::Sensor *voltage_sensor_{nullptr};
|
||||||
|
sensor::Sensor *power_sensor_{nullptr};
|
||||||
|
|
||||||
|
void write_register_(enum CS5460ARegister addr, uint32_t value);
|
||||||
|
uint32_t read_register_(uint8_t addr);
|
||||||
|
bool softreset_();
|
||||||
|
void hw_init_();
|
||||||
|
void restart_();
|
||||||
|
void started_();
|
||||||
|
void schedule_next_check_();
|
||||||
|
bool check_status_();
|
||||||
|
|
||||||
|
float current_multiplier_;
|
||||||
|
float voltage_multiplier_;
|
||||||
|
float power_multiplier_;
|
||||||
|
float pulse_freq_;
|
||||||
|
uint32_t expect_data_ts_;
|
||||||
|
uint32_t prev_raw_current_{0};
|
||||||
|
uint32_t prev_raw_energy_{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class CS5460ARestartAction : public Action<Ts...> {
|
||||||
|
public:
|
||||||
|
CS5460ARestartAction(CS5460AComponent *cs5460a) : cs5460a_(cs5460a) {}
|
||||||
|
|
||||||
|
void play(Ts... x) override { cs5460a_->restart(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
CS5460AComponent *cs5460a_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace cs5460a
|
||||||
|
} // namespace esphome
|
136
esphome/components/cs5460a/sensor.py
Normal file
136
esphome/components/cs5460a/sensor.py
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import spi, sensor
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_CURRENT,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_POWER,
|
||||||
|
CONF_VOLTAGE,
|
||||||
|
UNIT_VOLT,
|
||||||
|
UNIT_AMPERE,
|
||||||
|
UNIT_WATT,
|
||||||
|
ICON_EMPTY,
|
||||||
|
DEVICE_CLASS_POWER,
|
||||||
|
DEVICE_CLASS_CURRENT,
|
||||||
|
DEVICE_CLASS_VOLTAGE,
|
||||||
|
)
|
||||||
|
from esphome import automation
|
||||||
|
from esphome.automation import maybe_simple_id
|
||||||
|
|
||||||
|
CODEOWNERS = ["@balrog-kun"]
|
||||||
|
DEPENDENCIES = ["spi"]
|
||||||
|
|
||||||
|
cs5460a_ns = cg.esphome_ns.namespace("cs5460a")
|
||||||
|
CS5460APGAGain = cs5460a_ns.enum("CS5460APGAGain")
|
||||||
|
PGA_GAIN_OPTIONS = {
|
||||||
|
"10X": CS5460APGAGain.CS5460A_PGA_GAIN_10X,
|
||||||
|
"50X": CS5460APGAGain.CS5460A_PGA_GAIN_50X,
|
||||||
|
}
|
||||||
|
|
||||||
|
CS5460AComponent = cs5460a_ns.class_("CS5460AComponent", spi.SPIDevice, cg.Component)
|
||||||
|
CS5460ARestartAction = cs5460a_ns.class_("CS5460ARestartAction", automation.Action)
|
||||||
|
|
||||||
|
CONF_SAMPLES = "samples"
|
||||||
|
CONF_PHASE_OFFSET = "phase_offset"
|
||||||
|
CONF_PGA_GAIN = "pga_gain"
|
||||||
|
CONF_CURRENT_GAIN = "current_gain"
|
||||||
|
CONF_VOLTAGE_GAIN = "voltage_gain"
|
||||||
|
CONF_CURRENT_HPF = "current_hpf"
|
||||||
|
CONF_VOLTAGE_HPF = "voltage_hpf"
|
||||||
|
CONF_PULSE_ENERGY = "pulse_energy"
|
||||||
|
|
||||||
|
|
||||||
|
def validate_config(config):
|
||||||
|
current_gain = abs(config[CONF_CURRENT_GAIN]) * (
|
||||||
|
1.0 if config[CONF_PGA_GAIN] == "10X" else 5.0
|
||||||
|
)
|
||||||
|
voltage_gain = config[CONF_VOLTAGE_GAIN]
|
||||||
|
pulse_energy = config[CONF_PULSE_ENERGY]
|
||||||
|
|
||||||
|
if current_gain == 0.0 or voltage_gain == 0.0:
|
||||||
|
raise cv.Invalid("The gains can't be zero")
|
||||||
|
|
||||||
|
max_energy = (0.25 * 0.25 / 3600 / (2 ** -4)) / (voltage_gain * current_gain)
|
||||||
|
min_energy = (0.25 * 0.25 / 3600 / (2 ** 18)) / (voltage_gain * current_gain)
|
||||||
|
mech_min_energy = (0.25 * 0.25 / 3600 / 7.8) / (voltage_gain * current_gain)
|
||||||
|
if pulse_energy < min_energy or pulse_energy > max_energy:
|
||||||
|
raise cv.Invalid(
|
||||||
|
"For given current&voltage gains, the pulse energy must be between "
|
||||||
|
f"{min_energy} Wh and {max_energy} Wh and in mechanical counter mode "
|
||||||
|
f"between {mech_min_energy} Wh and {max_energy} Wh"
|
||||||
|
)
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
validate_energy = cv.float_with_unit("energy", "(Wh|WH|wh)?", optional_unit=True)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(CS5460AComponent),
|
||||||
|
cv.Optional(CONF_SAMPLES, default=4000): cv.int_range(min=1, max=0xFFFFFF),
|
||||||
|
cv.Optional(CONF_PHASE_OFFSET, default=0): cv.int_range(min=-64, max=63),
|
||||||
|
cv.Optional(CONF_PGA_GAIN, default="10X"): cv.enum(
|
||||||
|
PGA_GAIN_OPTIONS, upper=True
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_CURRENT_GAIN, default=0.001): cv.negative_one_to_one_float,
|
||||||
|
cv.Optional(CONF_VOLTAGE_GAIN, default=0.001): cv.zero_to_one_float,
|
||||||
|
cv.Optional(CONF_CURRENT_HPF, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_VOLTAGE_HPF, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_PULSE_ENERGY, default=10.0): validate_energy,
|
||||||
|
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||||
|
UNIT_VOLT, ICON_EMPTY, 0, DEVICE_CLASS_VOLTAGE
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
|
||||||
|
UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_POWER): sensor.sensor_schema(
|
||||||
|
UNIT_WATT, ICON_EMPTY, 0, DEVICE_CLASS_POWER
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend(spi.spi_device_schema(cs_pin_required=False)),
|
||||||
|
validate_config,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
cg.add(var.set_samples(config[CONF_SAMPLES]))
|
||||||
|
cg.add(var.set_phase_offset(config[CONF_PHASE_OFFSET]))
|
||||||
|
cg.add(var.set_pga_gain(config[CONF_PGA_GAIN]))
|
||||||
|
cg.add(var.set_gains(config[CONF_CURRENT_GAIN], config[CONF_VOLTAGE_GAIN]))
|
||||||
|
cg.add(var.set_hpf_enable(config[CONF_CURRENT_HPF], config[CONF_VOLTAGE_HPF]))
|
||||||
|
cg.add(var.set_pulse_energy_wh(config[CONF_PULSE_ENERGY]))
|
||||||
|
|
||||||
|
if CONF_VOLTAGE in config:
|
||||||
|
conf = config[CONF_VOLTAGE]
|
||||||
|
sens = await sensor.new_sensor(conf)
|
||||||
|
cg.add(var.set_voltage_sensor(sens))
|
||||||
|
if CONF_CURRENT in config:
|
||||||
|
conf = config[CONF_CURRENT]
|
||||||
|
sens = await sensor.new_sensor(conf)
|
||||||
|
cg.add(var.set_current_sensor(sens))
|
||||||
|
if CONF_POWER in config:
|
||||||
|
conf = config[CONF_POWER]
|
||||||
|
sens = await sensor.new_sensor(conf)
|
||||||
|
cg.add(var.set_power_sensor(sens))
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"cs5460a.restart",
|
||||||
|
CS5460ARestartAction,
|
||||||
|
maybe_simple_id(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_ID): cv.use_id(CS5460AComponent),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async def restart_action_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)
|
@ -914,6 +914,28 @@ sensor:
|
|||||||
id: ph_ezo
|
id: ph_ezo
|
||||||
address: 99
|
address: 99
|
||||||
unit_of_measurement: 'pH'
|
unit_of_measurement: 'pH'
|
||||||
|
- platform: cs5460a
|
||||||
|
id: cs5460a1
|
||||||
|
current:
|
||||||
|
name: "Socket current"
|
||||||
|
voltage:
|
||||||
|
name: "Mains voltage"
|
||||||
|
power:
|
||||||
|
name: "Socket power"
|
||||||
|
on_value:
|
||||||
|
then:
|
||||||
|
cs5460a.restart: cs5460a1
|
||||||
|
samples: 1600
|
||||||
|
pga_gain: 10X
|
||||||
|
current_gain: 0.01
|
||||||
|
voltage_gain: 0.000573
|
||||||
|
current_hpf: on
|
||||||
|
voltage_hpf: on
|
||||||
|
phase_offset: 20
|
||||||
|
pulse_energy: 0.01 kWh
|
||||||
|
cs_pin:
|
||||||
|
mcp23xxx: mcp23017_hub
|
||||||
|
number: 14
|
||||||
|
|
||||||
esp32_touch:
|
esp32_touch:
|
||||||
setup_mode: False
|
setup_mode: False
|
||||||
|
Loading…
Reference in New Issue
Block a user