From 74a1ffe8cef4cfbe507cb12fbafd9d75eb1b1a68 Mon Sep 17 00:00:00 2001 From: Jimmy Wennlund Date: Tue, 3 Sep 2024 10:37:07 +0200 Subject: [PATCH] Support for BL0910 10 channel current sensor --- CODEOWNERS | 1 + esphome/components/bl0910/__init__.py | 1 + esphome/components/bl0910/bl0910.cpp | 455 ++++++++++++++++++++++++++ esphome/components/bl0910/bl0910.h | 79 +++++ esphome/components/bl0910/sensor.py | 135 ++++++++ 5 files changed, 671 insertions(+) create mode 100644 esphome/components/bl0910/__init__.py create mode 100644 esphome/components/bl0910/bl0910.cpp create mode 100644 esphome/components/bl0910/bl0910.h create mode 100644 esphome/components/bl0910/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 807829eafd..6db5b4c02e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -59,6 +59,7 @@ esphome/components/bh1750/* @OttoWinter esphome/components/binary_sensor/* @esphome/core esphome/components/bk72xx/* @kuba2k2 esphome/components/bl0906/* @athom-tech @jesserockz @tarontop +esphome/components/bl0910/* @jimmyw esphome/components/bl0939/* @ziceva esphome/components/bl0940/* @tobias- esphome/components/bl0942/* @dbuezas diff --git a/esphome/components/bl0910/__init__.py b/esphome/components/bl0910/__init__.py new file mode 100644 index 0000000000..b53bc9fa95 --- /dev/null +++ b/esphome/components/bl0910/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@jimmyw"] diff --git a/esphome/components/bl0910/bl0910.cpp b/esphome/components/bl0910/bl0910.cpp new file mode 100644 index 0000000000..93b9964cc0 --- /dev/null +++ b/esphome/components/bl0910/bl0910.cpp @@ -0,0 +1,455 @@ +#include "bl0910.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace bl0910 { + +static const char *const TAG = "bl0910"; + +// Credit to JustNoot for the following constants +// https://github.com/JustNoot/10CH-Energy-Meter/blob/main/bl0910_esp32_test.ino + +// https://www.compel.ru/item-pdf/5ba3245acc89f7213ea0cc7e9a0b8c0e/pn/cn-bell~bl0910.pdf +static const uint8_t BL0910_READ_COMMAND = 0x82; +static const uint8_t BL0910_WRITE_COMMAND = 0x81; + +// current and voltage waveform data, 24bit signed number, default: 0x000000 +static const uint8_t BL0910_REG_WAVE_1 = 0x01; +static const uint8_t BL0910_REG_WAVE_2 = 0x02; +static const uint8_t BL0910_REG_WAVE_3 = 0x03; +static const uint8_t BL0910_REG_WAVE_4 = 0x04; +static const uint8_t BL0910_REG_WAVE_5 = 0x05; +static const uint8_t BL0910_REG_WAVE_6 = 0x06; +static const uint8_t BL0910_REG_WAVE_7 = 0x07; +static const uint8_t BL0910_REG_WAVE_8 = 0x08; +static const uint8_t BL0910_REG_WAVE_9 = 0x09; +static const uint8_t BL0910_REG_WAVE_10 = 0x0A; +static const uint8_t BL0910_REG_WAVE_11 = 0x0B; + +// effective value calculation register, 24 bit unsigned number, default: 0x000000 +static const uint8_t BL0910_REG_RMS_1 = 0x0C; +static const uint8_t BL0910_REG_RMS_2 = 0x0D; +static const uint8_t BL0910_REG_RMS_3 = 0x0E; +static const uint8_t BL0910_REG_RMS_4 = 0x0F; +static const uint8_t BL0910_REG_RMS_5 = 0x10; +static const uint8_t BL0910_REG_RMS_6 = 0x11; +static const uint8_t BL0910_REG_RMS_7 = 0x12; +static const uint8_t BL0910_REG_RMS_8 = 0x13; +static const uint8_t BL0910_REG_RMS_9 = 0x14; +static const uint8_t BL0910_REG_RMS_10 = 0x15; +static const uint8_t BL0910_REG_RMS_11 = 0x16; +static const uint8_t BL0910_REG_RMS[] = {BL0910_REG_RMS_1, BL0910_REG_RMS_2, BL0910_REG_RMS_3, BL0910_REG_RMS_4, + BL0910_REG_RMS_5, BL0910_REG_RMS_6, BL0910_REG_RMS_7, BL0910_REG_RMS_8, + BL0910_REG_RMS_9, BL0910_REG_RMS_10, BL0910_REG_RMS_11}; + +// fast effective value register, 24 bit unsigned number, default: 0x000000 +static const uint8_t BL0910_REG_FAST_RMS_1 = 0x17; +static const uint8_t BL0910_REG_FAST_RMS_2 = 0x18; +static const uint8_t BL0910_REG_FAST_RMS_3 = 0x19; +static const uint8_t BL0910_REG_FAST_RMS_4 = 0x1A; +static const uint8_t BL0910_REG_FAST_RMS_5 = 0x1B; +static const uint8_t BL0910_REG_FAST_RMS_6 = 0x1C; +static const uint8_t BL0910_REG_FAST_RMS_7 = 0x1D; +static const uint8_t BL0910_REG_FAST_RMS_8 = 0x1E; +static const uint8_t BL0910_REG_FAST_RMS_9 = 0x1F; +static const uint8_t BL0910_REG_FAST_RMS_10 = 0x20; +static const uint8_t BL0910_REG_FAST_RMS_11 = 0x21; + +// active power, 24 bit signed number, default 0x000000 +static const uint8_t BL0910_REG_WATT_1 = 0x22; +static const uint8_t BL0910_REG_WATT_2 = 0x23; +static const uint8_t BL0910_REG_WATT_3 = 0x24; +static const uint8_t BL0910_REG_WATT_4 = 0x25; +static const uint8_t BL0910_REG_WATT_5 = 0x26; +static const uint8_t BL0910_REG_WATT_6 = 0x27; +static const uint8_t BL0910_REG_WATT_7 = 0x28; +static const uint8_t BL0910_REG_WATT_8 = 0x29; +static const uint8_t BL0910_REG_WATT_9 = 0x2A; +static const uint8_t BL0910_REG_WATT_10 = 0x2B; +static const uint8_t BL0910_REG_WATT[] = {BL0910_REG_WATT_1, BL0910_REG_WATT_2, BL0910_REG_WATT_3, BL0910_REG_WATT_4, + BL0910_REG_WATT_5, BL0910_REG_WATT_6, BL0910_REG_WATT_7, BL0910_REG_WATT_8, + BL0910_REG_WATT_9, BL0910_REG_WATT_10}; +static const uint8_t BL0910_REG_WATT_TOTAL = 0x2C; + +static const uint8_t BL0910_REG_FVAR = 0x2D; +static const uint8_t BL0910_REG_VA = 0x2E; + +// active energy pulse count register, 24 bit unsigned number, default: 0x000000 +static const uint8_t BL0910_REG_CF_CNT_1 = 0x2F; +static const uint8_t BL0910_REG_CF_CNT_2 = 0x30; +static const uint8_t BL0910_REG_CF_CNT_3 = 0x31; +static const uint8_t BL0910_REG_CF_CNT_4 = 0x32; +static const uint8_t BL0910_REG_CF_CNT_5 = 0x33; +static const uint8_t BL0910_REG_CF_CNT_6 = 0x34; +static const uint8_t BL0910_REG_CF_CNT_7 = 0x35; +static const uint8_t BL0910_REG_CF_CNT_8 = 0x36; +static const uint8_t BL0910_REG_CF_CNT_9 = 0x37; +static const uint8_t BL0910_REG_CF_CNT_10 = 0x38; +static const uint8_t BL0910_REG_CF_CNT[] = { + BL0910_REG_CF_CNT_1, BL0910_REG_CF_CNT_2, BL0910_REG_CF_CNT_3, BL0910_REG_CF_CNT_4, BL0910_REG_CF_CNT_5, + BL0910_REG_CF_CNT_6, BL0910_REG_CF_CNT_7, BL0910_REG_CF_CNT_8, BL0910_REG_CF_CNT_9, BL0910_REG_CF_CNT_10}; +static const uint8_t BL0910_REG_CF_CNT_TOTAL = 0x39; +static const uint8_t BL0910_REG_CFQ_CNT_TOTAL = 0x3A; +static const uint8_t BL0910_REG_CFS_CNT_TOTAL = 0x3B; + +// phase angle measurement register, 16 bit unsigned number, default: 0x0000 +static const uint8_t BL0910_REG_ANGLE_1 = 0x3C; +static const uint8_t BL0910_REG_ANGLE_2 = 0x3D; +static const uint8_t BL0910_REG_ANGLE_3 = 0x3E; +static const uint8_t BL0910_REG_ANGLE_4 = 0x3F; +static const uint8_t BL0910_REG_ANGLE_5 = 0x40; +static const uint8_t BL0910_REG_ANGLE_6 = 0x41; +static const uint8_t BL0910_REG_ANGLE_7 = 0x42; +static const uint8_t BL0910_REG_ANGLE_8 = 0x43; +static const uint8_t BL0910_REG_ANGLE_9 = 0x44; +static const uint8_t BL0910_REG_ANGLE_10 = 0x45; +static const uint8_t BL0910_REG_ANGLE[] = { + BL0910_REG_ANGLE_1, BL0910_REG_ANGLE_2, BL0910_REG_ANGLE_3, BL0910_REG_ANGLE_4, BL0910_REG_ANGLE_5, + BL0910_REG_ANGLE_6, BL0910_REG_ANGLE_7, BL0910_REG_ANGLE_8, BL0910_REG_ANGLE_9, BL0910_REG_ANGLE_10}; + +static const uint8_t BL0910_REG_FAST_RMS_H_1 = 0x46; +static const uint8_t BL0910_REG_FAST_RMS_H_2 = 0x47; +static const uint8_t BL0910_REG_FAST_RMS_H_3 = 0x48; +static const uint8_t BL0910_REG_FAST_RMS_H_4 = 0x49; +static const uint8_t BL0910_REG_FAST_RMS_H_5 = 0x57; +static const uint8_t BL0910_REG_FAST_RMS_H_6 = 0x58; +static const uint8_t BL0910_REG_FAST_RMS_H_7 = 0x59; +static const uint8_t BL0910_REG_FAST_RMS_H_8 = 0x5A; +static const uint8_t BL0910_REG_FAST_RMS_H_9 = 0x5B; +static const uint8_t BL0910_REG_FAST_RMS_H_10 = 0x5C; + +static const uint8_t BL0910_REG_PF = 0x4A; + +static const uint8_t BL0910_REG_LINE_WATTHR = 0x4B; +static const uint8_t BL0910_REG_LINE_VARH = 0x4C; +static const uint8_t BL0910_REG_SIGN = 0x4D; +static const uint8_t BL0910_REG_PERIOD = 0x4E; + +static const uint8_t BL0910_REG_STATUS1 = 0x54; +static const uint8_t BL0910_REG_STATUS3 = 0x56; + +// temperature measurement register, 24 bit unsigned numver, default: 0x000000 +static const uint8_t BL0910_REG_TPS1 = 0x5E; // internal temperature, (TPS1-64)*12.5/59-40 ˚C +static const uint8_t BL0910_REG_TPS2 = 0x5F; // VT pin ADC value + +// PGA gain adjustment register, 24 bit (4 bits per channel), default: 0x000000 +// 4 PGA settings: 0000 = 1; 0001 = 2; 0010 = 8; 0011 = 16 +// [3:0] voltage channel, current channel 6 +// [7:4] current channel 1, current channel 7 +// [11:8] current channel 2, current channel 8 +// [15:12] current channel 3, current channel 9 +// [19:16] current channel 4, current channel 10 +// [23:20] current channel 5 +static const uint8_t BL0910_REG_GAIN1 = 0x60; +static const uint8_t BL0910_REG_GAIN2 = 0x61; + +// channel phase compensation register, 16 bit (8 bit per channel), default: 0x0000 +static const uint8_t BL0910_REG_PHASE_1_2 = 0x64; +static const uint8_t BL0910_REG_PHASE_3_4 = 0x65; +static const uint8_t BL0910_REG_PHASE_5_6 = 0x66; +static const uint8_t BL0910_REG_PHASE_7_8 = 0x67; +static const uint8_t BL0910_REG_PHASE_9_10 = 0x68; +static const uint8_t BL0910_REG_PHASE_11 = 0x69; // 8 bit, default: 0x00 + +// phase compensation register, 5 bit number, default: 0000H +static const uint8_t BL0910_REG_VAR_PHCAL_I = 0x6A; +static const uint8_t BL0910_REG_VAR_PHCAL_V = 0x6B; + +static const uint8_t BL0910_REG_RMS_GAIN_1 = 0x6C; +static const uint8_t BL0910_REG_RMS_GAIN_2 = 0x6D; +static const uint8_t BL0910_REG_RMS_GAIN_3 = 0x6E; +static const uint8_t BL0910_REG_RMS_GAIN_4 = 0x6F; +static const uint8_t BL0910_REG_RMS_GAIN_5 = 0x70; +static const uint8_t BL0910_REG_RMS_GAIN_6 = 0x71; +static const uint8_t BL0910_REG_RMS_GAIN_7 = 0x72; +static const uint8_t BL0910_REG_RMS_GAIN_8 = 0x73; +static const uint8_t BL0910_REG_RMS_GAIN_9 = 0x74; +static const uint8_t BL0910_REG_RMS_GAIN_10 = 0x75; +static const uint8_t BL0910_REG_RMS_GAIN_11 = 0x76; + +static const uint8_t BL0910_REG_RMS_OFFSET_1 = 0x77; +static const uint8_t BL0910_REG_RMS_OFFSET_2 = 0x78; +static const uint8_t BL0910_REG_RMS_OFFSET_3 = 0x79; +static const uint8_t BL0910_REG_RMS_OFFSET_4 = 0x7A; +static const uint8_t BL0910_REG_RMS_OFFSET_5 = 0x7B; +static const uint8_t BL0910_REG_RMS_OFFSET_6 = 0x7C; +static const uint8_t BL0910_REG_RMS_OFFSET_7 = 0x7D; +static const uint8_t BL0910_REG_RMS_OFFSET_8 = 0x7E; +static const uint8_t BL0910_REG_RMS_OFFSET_9 = 0x7F; +static const uint8_t BL0910_REG_RMS_OFFSET_10 = 0x80; +static const uint8_t BL0910_REG_RMS_OFFSET_11 = 0x81; + +// active power small signal compensation register, 12 bit two's complement (register is 24 bit), default: 0x000 +static const uint8_t BL0910_REG_WA_LOW_OFFSET_1_2 = 0x82; +static const uint8_t BL0910_REG_WA_LOW_OFFSET_3_4 = 0x83; +static const uint8_t BL0910_REG_WA_LOW_OFFSET_5_6 = 0x84; +static const uint8_t BL0910_REG_WA_LOW_OFFSET_7_8 = 0x85; +static const uint8_t BL0910_REG_WA_LOW_OFFSET_9_10 = 0x86; + +static const uint8_t BL0910_REG_FVAR_LOW_OFFSET = 0x87; + +// [11:0] active power anti-creep threshold register, 12 bit unsigned number, default: 0x04C +// [23:12] reactive power anti-creep threshold register, 12 bit unsigned number, default: 0x04C +// 24 bit unsigned number, default: 0x04C04C +static const uint8_t BL0910_REG_VAR_CREEP_WA_CREEP = 0x88; + +// total power anti-creep threshold register, 12 bit unsigned number, default: 0x000 +static const uint8_t BL0910_REG_WA_CREEP2 = 0x89; + +// effective value anti-creep threshold register, 12 bit unsigned number, default: 0x200 +static const uint8_t BL0910_REG_RMS_CREEP = 0x8A; + +static const uint8_t BL0910_REG_FAST_RMS_CTRL = 0x8B; + +// peak threshold register, 24 bit number, deafult: 0xFFFFFF +// [11:0] voltage peak threshold, default: 0xFFF +// [23:12] current peak threshold, default: 0xFFF +static const uint8_t BL0910_REG_I_PKLVL_V_PKLVL = 0x8C; + +static const uint8_t BL0910_REG_SAGCYC_ZXTOUT = 0x8E; +static const uint8_t BL0910_REG_SAGLVL_LINECYC = 0x8F; + +static const uint8_t BL0910_REG_FLAG_CTRL = 0x90; +static const uint8_t BL0910_REG_FLAG_CTRL1 = 0x91; +static const uint8_t BL0910_REG_FLAG_CTRL2 = 0x92; + +// ADC shutdown register, 11 bit number, default: 0x000 +static const uint8_t BL0910_REG_ADC_PD = 0x93; + +// temperature control register, 16 bit number, default: 0x07FF +static const uint8_t BL0910_REG_TPS_CTRL = 0x94; + +// external temperature coefficient register, 16 bit number, default: 0x0000 +static const uint8_t BL0910_REG_TPS2_A_B = 0x95; + +static const uint8_t BL0910_REG_MODE1 = 0x96; +static const uint8_t BL0910_REG_MODE2 = 0x97; +static const uint8_t BL0910_REG_MODE = 0x98; + +static const uint8_t BL0910_REG_MASK1 = 0x9A; + +static const uint8_t BL0910_REG_RST_ENG = 0x9D; + +static const uint8_t BL0910_REG_USR_WRPROT = 0x9E; + +static const uint8_t BL0910_REG_SOFT_RESET = 0x9F; + +// channel gain calibration register, 16 bit two's complement, default: 0x0000 +static const uint8_t BL0910_REG_CHANNEL_GAIN_1 = 0xA0; +static const uint8_t BL0910_REG_CHANNEL_GAIN_2 = 0xA1; +static const uint8_t BL0910_REG_CHANNEL_GAIN_3 = 0xA2; +static const uint8_t BL0910_REG_CHANNEL_GAIN_4 = 0xA3; +static const uint8_t BL0910_REG_CHANNEL_GAIN_5 = 0xA4; +static const uint8_t BL0910_REG_CHANNEL_GAIN_6 = 0xA5; +static const uint8_t BL0910_REG_CHANNEL_GAIN_7 = 0xA6; +static const uint8_t BL0910_REG_CHANNEL_GAIN_8 = 0xA7; +static const uint8_t BL0910_REG_CHANNEL_GAIN_9 = 0xA8; +static const uint8_t BL0910_REG_CHANNEL_GAIN_10 = 0xA9; +static const uint8_t BL0910_REG_CHANNEL_GAIN_11 = 0xAA; + +// channel offset calibration register, 16 bit two's complement, default: 0x0000 +static const uint8_t BL0910_REG_CHANNEL_OFFSET_1 = 0xAB; +static const uint8_t BL0910_REG_CHANNEL_OFFSET_2 = 0xAC; +static const uint8_t BL0910_REG_CHANNEL_OFFSET_3 = 0xAD; +static const uint8_t BL0910_REG_CHANNEL_OFFSET_4 = 0xAE; +static const uint8_t BL0910_REG_CHANNEL_OFFSET_5 = 0xAF; +static const uint8_t BL0910_REG_CHANNEL_OFFSET_6 = 0xB0; +static const uint8_t BL0910_REG_CHANNEL_OFFSET_7 = 0xB1; +static const uint8_t BL0910_REG_CHANNEL_OFFSET_8 = 0xB2; +static const uint8_t BL0910_REG_CHANNEL_OFFSET_9 = 0xB3; +static const uint8_t BL0910_REG_CHANNEL_OFFSET_10 = 0xB4; +static const uint8_t BL0910_REG_CHANNEL_OFFSET_11 = 0xB5; + +// active power gain correction register, 16bit signed number, default: 0x0000 +static const uint8_t BL0910_REG_WATT_GAIN_1 = 0xB6; +static const uint8_t BL0910_REG_WATT_GAIN_2 = 0xB7; +static const uint8_t BL0910_REG_WATT_GAIN_3 = 0xB8; +static const uint8_t BL0910_REG_WATT_GAIN_4 = 0xB9; +static const uint8_t BL0910_REG_WATT_GAIN_5 = 0xBA; +static const uint8_t BL0910_REG_WATT_GAIN_6 = 0xBB; +static const uint8_t BL0910_REG_WATT_GAIN_7 = 0xBC; +static const uint8_t BL0910_REG_WATT_GAIN_8 = 0xBD; +static const uint8_t BL0910_REG_WATT_GAIN_9 = 0xBE; +static const uint8_t BL0910_REG_WATT_GAIN_10 = 0xBF; + +// active power offset correction register, 16bit signed number, default: 0x0000 +static const uint8_t BL0910_REG_WATT_OFFSET_1 = 0xC0; +static const uint8_t BL0910_REG_WATT_OFFSET_2 = 0xC1; +static const uint8_t BL0910_REG_WATT_OFFSET_3 = 0xC2; +static const uint8_t BL0910_REG_WATT_OFFSET_4 = 0xC3; +static const uint8_t BL0910_REG_WATT_OFFSET_5 = 0xC4; +static const uint8_t BL0910_REG_WATT_OFFSET_6 = 0xC5; +static const uint8_t BL0910_REG_WATT_OFFSET_7 = 0xC6; +static const uint8_t BL0910_REG_WATT_OFFSET_8 = 0xC7; +static const uint8_t BL0910_REG_WATT_OFFSET_9 = 0xC8; +static const uint8_t BL0910_REG_WATT_OFFSET_10 = 0xC9; + +static const uint8_t BL0910_REG_VAR_GAIN = 0xCA; +static const uint8_t BL0910_REG_VAR_OFFSET = 0xCB; + +static const uint8_t BL0910_REG_VA_GAIN = 0xCC; +static const uint8_t BL0910_REG_VA_OFFSET = 0xCD; + +static const uint8_t BL0910_REG_CFDIV = 0xCE; + +static const uint8_t BL0910_REG_OTP_CHECKSUM1 = 0xD0; + +int8_t BL0910::checksum_calc(uint8_t *data) { + uint8_t checksum = 0; + for (int i = 0; i < 5; i++) { + checksum += data[i]; + } + checksum &= 0xFF; + checksum ^= 0xFF; + + return checksum; +} + +void BL0910::write_register(uint8_t addr, uint8_t data_h, uint8_t data_m, uint8_t data_l) { + uint8_t packet[6] = {0}; + packet[0] = BL0910_WRITE_COMMAND; + packet[1] = addr; + packet[2] = data_h; + packet[3] = data_m; + packet[4] = data_l; + packet[5] = checksum_calc(packet); + this->enable(); + this->transfer_array(packet, sizeof(packet) / sizeof(packet[0])); + this->disable(); +} + +int32_t BL0910::read_register(uint8_t addr) { + int32_t data = 0; + uint8_t packet[6] = {0}; + + packet[0] = BL0910_READ_COMMAND; + packet[1] = addr; + this->enable(); + this->transfer_array(packet, sizeof(packet) / sizeof(packet[0])); + this->disable(); + + // Verify checksum + uint8_t checksum = BL0910_READ_COMMAND + addr; + for (int i = 2; i < 5; i++) { + checksum += packet[i]; + } + checksum &= 0xFF; + checksum ^= 0xFF; + if (checksum != packet[5]) // checksum is byte 6 + { + ESP_LOGE(TAG, "Checksum error calculated: %x != read: %x", checksum, packet[5]); + return 0xFF000000; + } + + return (((int8_t) packet[2]) << 16) | (packet[3] << 8) | packet[4]; +} + +float BL0910::getVoltage(uint8_t channel) { + return ((float) read_register(BL0910_REG_RMS[channel])) / this->uref[channel]; +} + +float BL0910::getCurrent(uint8_t channel) { + return ((float) read_register(BL0910_REG_RMS[channel])) / this->iref[channel]; +} + +float BL0910::getPower(uint8_t channel) { + return ((float) read_register(BL0910_REG_WATT[channel])) / this->pref[channel]; +} + +float BL0910::getEnergy(uint8_t channel) { + return ((float) read_register(BL0910_REG_CF_CNT[channel])) / this->eref[channel]; +} + +float BL0910::getFreq(void) { + const float freq = (float) read_register(BL0910_REG_PERIOD); + return 10000000.0 / freq; +} + +float BL0910::getTemperature(void) { + const float temp = (float) read_register(BL0910_REG_TPS1); + return (temp - 64.0) * 12.5 / 59.0 - 40.0; +} + +float BL0910::getPowerFactor(uint8_t channel, float freq) { + const float angle = (float) read_register(BL0910_REG_ANGLE[channel]); + return (360.0f * angle * freq) / 500000.0f; +} + +void BL0910::loop() {} + +void BL0910::update() { + static int i = 0; + static float freq = 50.0; + if (i < NUM_CHANNELS) { + if (voltage_sensor[i]) + voltage_sensor[i]->publish_state(getVoltage(i)); + if (current_sensor[i]) + current_sensor[i]->publish_state(getCurrent(i)); + if (power_sensor[i]) + power_sensor[i]->publish_state(getPower(i)); + if (energy_sensor[i]) + energy_sensor[i]->publish_state(getEnergy(i)); + if (power_factor_sensor[i]) + power_factor_sensor[i]->publish_state(getPowerFactor(i, freq)); + i++; + } else { + freq = getFreq(); + if (frequency_sensor) + frequency_sensor->publish_state(freq); + if (temperature_sensor) + temperature_sensor->publish_state(getTemperature()); + i = 0; + } +} + +void BL0910::setup() { + this->spi_setup(); + this->write_register(BL0910_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A); // Reset to default + this->write_register(BL0910_REG_USR_WRPROT, 0x00, 0x55, 0x55); // Enable User Operation Write + this->write_register(BL0910_REG_MODE, 0x00, 0x00, 0x00); + this->write_register(BL0910_REG_TPS_CTRL, 0x00, 0x07, 0xFF); + this->write_register(BL0910_REG_FAST_RMS_CTRL, 0x40, 0xFF, 0xFF); + this->write_register(BL0910_REG_GAIN1, 0x00, 0x00, 0x00); +} + +void BL0910::dump_config() { // NOLINT(readability-function-cognitive-complexity) + ESP_LOGCONFIG(TAG, "BL0910:"); + for (int i = 0; i < NUM_CHANNELS; i++) { + if (voltage_sensor[i]) { + char buf[20]; + snprintf(buf, sizeof(buf), "Voltage %d", i + 1); + LOG_SENSOR("", buf, voltage_sensor[i]); + } + + if (current_sensor[i]) { + char buf[20]; + snprintf(buf, sizeof(buf), "Current %d", i + 1); + LOG_SENSOR("", buf, current_sensor[i]); + } + if (power_sensor[i]) { + char buf[20]; + snprintf(buf, sizeof(buf), "Power %d", i + 1); + LOG_SENSOR("", buf, power_sensor[i]); + } + if (energy_sensor[i]) { + char buf[20]; + snprintf(buf, sizeof(buf), "Energy %d", i + 1); + LOG_SENSOR("", buf, energy_sensor[i]); + } + if (power_factor_sensor[i]) { + char buf[20]; + snprintf(buf, sizeof(buf), "Power Factor %d", i + 1); + LOG_SENSOR("", buf, power_factor_sensor[i]); + } + } + if (this->frequency_sensor) { + LOG_SENSOR("", "Frequency", this->frequency_sensor); + } + if (this->temperature_sensor) { + LOG_SENSOR("", "Temperature", this->temperature_sensor); + } +} + +} // namespace bl0910 +} // namespace esphome diff --git a/esphome/components/bl0910/bl0910.h b/esphome/components/bl0910/bl0910.h new file mode 100644 index 0000000000..ec5cdb8d60 --- /dev/null +++ b/esphome/components/bl0910/bl0910.h @@ -0,0 +1,79 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/spi/spi.h" + +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace bl0910 { + +// Mode 1 Trailing clock CPHA=1, Polarity low CPOL=0 + +#define NUM_CHANNELS 11 + +class BL0910 : public PollingComponent, + public spi::SPIDevice { + public: + void set_voltage_sensor(sensor::Sensor *voltage_sensor_, int index, float uref_) { + voltage_sensor[index - 1] = voltage_sensor_; + uref[index - 1] = uref_; + } + void set_current_sensor(sensor::Sensor *current_sensor_, int index, float iref_) { + current_sensor[index - 1] = current_sensor_; + iref[index - 1] = iref_; + } + void set_power_sensor(sensor::Sensor *power_sensor_, int index, float pref_) { + power_sensor[index - 1] = power_sensor_; + pref[index - 1] = pref_; + } + void set_energy_sensor(sensor::Sensor *energy_sensor_, int index, float eref_) { + energy_sensor[index - 1] = energy_sensor_; + eref[index - 1] = eref_; + } + void set_power_factor_sensor(sensor::Sensor *power_factor_sensor_, int index) { + power_factor_sensor[index - 1] = power_factor_sensor_; + } + void set_frequency_sensor(sensor::Sensor *frequency_sensor_) { frequency_sensor = frequency_sensor_; } + void set_temperature_sensor(sensor::Sensor *temperature_sensor_) { temperature_sensor = temperature_sensor_; } + + void loop() override; + + void update() override; + void setup() override; + void dump_config() override; + + protected: + sensor::Sensor *voltage_sensor[NUM_CHANNELS] = {}; + sensor::Sensor *current_sensor[NUM_CHANNELS] = {}; + // NB This may be negative as the circuits is seemingly able to measure + // power in both directions + sensor::Sensor *power_sensor[NUM_CHANNELS] = {}; + sensor::Sensor *energy_sensor[NUM_CHANNELS] = {}; + sensor::Sensor *power_factor_sensor[NUM_CHANNELS] = {}; + sensor::Sensor *frequency_sensor{nullptr}; + sensor::Sensor *temperature_sensor{nullptr}; + + float uref[NUM_CHANNELS] = {}; + float iref[NUM_CHANNELS] = {}; + float pref[NUM_CHANNELS] = {}; + float eref[NUM_CHANNELS] = {}; + + private: + int8_t checksum_calc(uint8_t *data); + void write_register(uint8_t addr, uint32_t data) { + return this->write_register(addr, (data >> 16) & 0xFF, (data >> 8) & 0xFF, data & 0xFF); + } + void write_register(uint8_t addr, uint8_t data_h, uint8_t data_m, uint8_t data_l); + int32_t read_register(uint8_t addr); + float getVoltage(uint8_t channel); + float getFreq(void); + float getCurrent(uint8_t channel); + float getPower(uint8_t channel); + float getEnergy(uint8_t channel); + float getTemperature(void); + float getPowerFactor(uint8_t channel, float freq); +}; +} // namespace bl0910 +} // namespace esphome diff --git a/esphome/components/bl0910/sensor.py b/esphome/components/bl0910/sensor.py new file mode 100644 index 0000000000..28f7b056f4 --- /dev/null +++ b/esphome/components/bl0910/sensor.py @@ -0,0 +1,135 @@ +import esphome.codegen as cg +from esphome.components import sensor, spi +import esphome.config_validation as cv +from esphome.const import ( + CONF_FREQUENCY, + CONF_ID, + CONF_TEMPERATURE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_FREQUENCY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_POWER_FACTOR, + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, + UNIT_AMPERE, + UNIT_DEGREES, + UNIT_KILOWATT_HOURS, + UNIT_VOLT, + UNIT_WATT, +) + +# 10 sensors in range 1-11 +SENSOR_RANGE = range(1, 12) + +DEPENDENCIES = ["spi"] + +bl0910_ns = cg.esphome_ns.namespace("bl0910") +BL0910 = bl0910_ns.class_("BL0910", cg.PollingComponent, spi.SPIDevice) + +VOLTAGE_CB = { + cv.Optional(f"voltage_{i}"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ).extend({cv.Optional("uref", default=1.0): cv.float_}) + for i in SENSOR_RANGE +} + +CURRENT_CV = { + cv.Optional(f"current_{i}"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ).extend({cv.Optional("iref", default=1.0): cv.float_}) + for i in SENSOR_RANGE +} + +ACTIVE_POWER_CV = { + cv.Optional(f"active_power_{i}"): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ).extend({cv.Optional("pref", default=1.0): cv.float_}) + for i in SENSOR_RANGE +} + +ENERGY_CV = { + cv.Optional(f"energy_{i}"): sensor.sensor_schema( + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ).extend({cv.Optional("eref", default=1.0): cv.float_}) + for i in SENSOR_RANGE +} + +POWER_FACTOR_CV = { + cv.Optional(f"power_factor_{i}"): sensor.sensor_schema( + unit_of_measurement=UNIT_DEGREES, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER_FACTOR, + state_class=STATE_CLASS_MEASUREMENT, + ) + for i in SENSOR_RANGE +} + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(BL0910), + cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( + unit_of_measurement="Hz", + accuracy_decimals=1, + device_class=DEVICE_CLASS_FREQUENCY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement="°C", + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(spi.spi_device_schema()) + .extend(VOLTAGE_CB) + .extend(CURRENT_CV) + .extend(ACTIVE_POWER_CV) + .extend(ENERGY_CV) + .extend(POWER_FACTOR_CV) +) + + +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) + + if frequency_config := config.get(CONF_FREQUENCY): + sens = await sensor.new_sensor(frequency_config) + cg.add(var.set_frequency_sensor(sens)) + if temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(var.set_temperature_sensor(sens)) + + for i in SENSOR_RANGE: + if voltage_config := config.get(f"voltage_{i}"): + sens = await sensor.new_sensor(voltage_config) + cg.add(var.set_voltage_sensor(sens, i, voltage_config.get("uref"))) + if current_config := config.get(f"current_{i}"): + sens = await sensor.new_sensor(current_config) + cg.add(var.set_current_sensor(sens, i, current_config.get("iref"))) + if active_power_config := config.get(f"active_power_{i}"): + sens = await sensor.new_sensor(active_power_config) + cg.add(var.set_power_sensor(sens, i, active_power_config.get("pref"))) + if energy_config := config.get(f"energy_{i}"): + sens = await sensor.new_sensor(energy_config) + cg.add(var.set_energy_sensor(sens, i, energy_config.get("eref"))) + if power_factor_config := config.get(f"power_factor_{i}"): + sens = await sensor.new_sensor(power_factor_config) + cg.add(var.set_power_factor_sensor(sens, i))