new BL0937

This commit is contained in:
Mit4el
2024-04-16 23:31:23 +03:00
parent 7a1fad9855
commit d95907a839
4 changed files with 782 additions and 0 deletions

View File

@@ -0,0 +1,269 @@
#include "Global.h"
#include "classes/IoTItem.h"
#include "BL0937lib.h"
// #include "classes/IoTUart.h"
// #include <map>
BL0937 *bl0937 = nullptr;
class BL0937v : public IoTItem
{
private:
public:
BL0937v(String parameters) : IoTItem(parameters)
{
}
void doByInterval()
{
if (bl0937)
regEvent(bl0937->getVoltage(), "BL0937 V");
else
{
regEvent(NAN, "BL0937v");
SerialPrint("E", "BL0937cmd", "initialization error", _id);
}
}
~BL0937v(){};
};
class BL0937a : public IoTItem
{
private:
public:
BL0937a(String parameters) : IoTItem(parameters)
{
}
void doByInterval()
{
if (bl0937)
regEvent(bl0937->getCurrent(), "BL0937 A");
else
{
regEvent(NAN, "BL0937a");
SerialPrint("E", "BL0937cmd", "initialization error", _id);
}
}
~BL0937a(){};
};
class BL0937w : public IoTItem
{
private:
public:
BL0937w(String parameters) : IoTItem(parameters)
{
}
void doByInterval()
{
if (bl0937)
regEvent(bl0937->getApparentPower(), "BL0937 W");
else
{
regEvent(NAN, "BL0937w");
SerialPrint("E", "BL0937cmd", "initialization error", _id);
}
}
~BL0937w(){};
};
class BL0937reactw : public IoTItem
{
private:
public:
BL0937reactw(String parameters) : IoTItem(parameters)
{
}
void doByInterval()
{
if (bl0937)
regEvent(bl0937->getReactivePower(), "BL0937 reactW");
else
{
regEvent(NAN, "BL0937reactw");
SerialPrint("E", "BL0937cmd", "initialization error", _id);
}
}
~BL0937reactw(){};
};
class BL0937actw : public IoTItem
{
private:
public:
BL0937actw(String parameters) : IoTItem(parameters)
{
}
void doByInterval()
{
if (bl0937)
regEvent(bl0937->getActivePower(), "BL0937 actW");
else
{
regEvent(NAN, "BL0937actw");
SerialPrint("E", "BL0937cmd", "initialization error", _id);
}
}
~BL0937actw(){};
};
class BL0937wh : public IoTItem
{
private:
public:
BL0937wh(String parameters) : IoTItem(parameters)
{
}
void doByInterval()
{
if (bl0937)
regEvent(bl0937->getEnergy() / 3600.0 / 1000.0, "BL0937 Wh");
else
{
regEvent(NAN, "BL0937wh");
SerialPrint("E", "BL0937cmd", "initialization error", _id);
}
}
~BL0937wh(){};
};
void ICACHE_RAM_ATTR bl0937_cf1_interrupt()
{
bl0937->cf1_interrupt();
}
void ICACHE_RAM_ATTR bl0937_cf_interrupt()
{
bl0937->cf_interrupt();
}
class BL0937cmd : public IoTItem
{
private:
float CURRENT_RESISTOR = 0.001; // Нужна возможность задавать из веб, это по умолчанию
int VOLTAGE_RESISTOR_UPSTREAM = 1000000; // Нужна возможность задавать из веб, это по умолчанию
int VOLTAGE_RESISTOR_DOWNSTREAM = 1000; // Нужна возможность задавать из веб, это по умолчанию
int BL0937_CF_GPIO = 4; // 8266 12 //Нужна возможность задавать пин из веб, это по умолчанию
int BL0937_CF1_GPIO = 5; // 8266 13 //Нужна возможность задавать пин из веб, это по умолчанию
int BL0937_SEL_GPIO_INV = 12; // 8266 15 // inverted //Нужна возможность задавать пин из веб, это по умолчанию
float _expV = 0;
float _expA = 0;
float _expW = 0;
public:
BL0937cmd(String parameters) : IoTItem(parameters)
{
jsonRead(parameters, "R_current", CURRENT_RESISTOR);
jsonRead(parameters, "R_upstream", VOLTAGE_RESISTOR_UPSTREAM);
jsonRead(parameters, "R_downstream", VOLTAGE_RESISTOR_DOWNSTREAM);
jsonRead(parameters, "CF_GPIO", BL0937_CF_GPIO);
jsonRead(parameters, "CF1_GPIO", BL0937_CF1_GPIO);
jsonRead(parameters, "SEL_GPIO", BL0937_SEL_GPIO_INV);
jsonRead(parameters, "expV", _expV);
jsonRead(parameters, "expA", _expA);
jsonRead(parameters, "expW", _expW);
bl0937 = new BL0937;
bl0937->begin(BL0937_CF_GPIO, BL0937_CF1_GPIO, BL0937_SEL_GPIO_INV, LOW, true);
bl0937->setResistors(CURRENT_RESISTOR, VOLTAGE_RESISTOR_UPSTREAM, VOLTAGE_RESISTOR_DOWNSTREAM);
attachInterrupt(BL0937_CF1_GPIO, bl0937_cf1_interrupt, FALLING);
attachInterrupt(BL0937_CF_GPIO, bl0937_cf_interrupt, FALLING);
if (_expV)
bl0937->expectedVoltage(_expV); // для калибровки вольтаж нужно вводить из веб интерфейса
if (_expV)
bl0937->expectedCurrent(_expA); // для калибровки можно так, а лучше ток вводить из веб интерфейса
if (_expV)
bl0937->expectedActivePower(_expW); // для калибровки потребляемую мощность нужно вводить из веб интерфейса
}
void doByInterval()
{
}
void onModuleOrder(String &key, String &value)
{
if (bl0937)
{
if (key == "reset")
{
bl0937->resetEnergy();
SerialPrint("i", "BL0937", "reset energy done");
}
}
}
/*
IoTValue execute(String command, std::vector<IoTValue> &param)
{
if (!bl0937)
return {};
if (command == "calibration")
{
if (param.size() == 3)
{
float v = param[0].valD;
float a = param[1].valD;
float p = param[2].valD;
bl0937->expectedVoltage(v); // для калибровки вольтаж нужно вводить из веб интерфейса
bl0937->expectedCurrent(a); // для калибровки можно так, а лучше ток вводить из веб интерфейса
bl0937->expectedActivePower(p); // для калибровки потребляемую мощность нужно вводить из веб интерфейса
return {};
}
}
return {};
}
*/
~BL0937cmd()
{
if (bl0937)
{
delete bl0937;
bl0937 = nullptr;
}
};
};
void *getAPI_BL0937(String subtype, String param)
{
if (subtype == F("BL0937v"))
{
return new BL0937v(param);
}
else if (subtype == F("BL0937a"))
{
return new BL0937a(param);
}
else if (subtype == F("BL0937w"))
{
return new BL0937w(param);
}
else if (subtype == F("BL0937wh"))
{
return new BL0937wh(param);
}
else if (subtype == F("BL0937reactw"))
{
return new BL0937reactw(param);
}
else if (subtype == F("BL0937actw"))
{
return new BL0937actw(param);
}
else if (subtype == F("BL0937cmd"))
{
return new BL0937cmd(param);
}
else
{
return nullptr;
}
}

View File

@@ -0,0 +1,238 @@
/*
BL0937
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#include "BL0937lib.h"
void BL0937::begin(
unsigned char cf_pin,
unsigned char cf1_pin,
unsigned char sel_pin,
unsigned char currentWhen,
bool use_interrupts,
unsigned long pulse_timeout
) {
_cf_pin = cf_pin;
_cf1_pin = cf1_pin;
_sel_pin = sel_pin;
_current_mode = currentWhen;
_use_interrupts = use_interrupts;
_pulse_timeout = pulse_timeout;
pinMode(_cf_pin, INPUT_PULLUP);
pinMode(_cf1_pin, INPUT_PULLUP);
pinMode(_sel_pin, OUTPUT);
_calculateDefaultMultipliers();
_mode = _current_mode;
digitalWrite(_sel_pin, _mode);
}
void BL0937::setMode(bl0937_mode_t mode) {
_mode = (mode == MODE_CURRENT) ? _current_mode : 1 - _current_mode;
digitalWrite(_sel_pin, _mode);
if (_use_interrupts) {
_last_cf1_interrupt = _first_cf1_interrupt = micros();
}
}
bl0937_mode_t BL0937::getMode() {
return (_mode == _current_mode) ? MODE_CURRENT : MODE_VOLTAGE;
}
bl0937_mode_t BL0937::toggleMode() {
bl0937_mode_t new_mode = getMode() == MODE_CURRENT ? MODE_VOLTAGE : MODE_CURRENT;
setMode(new_mode);
return new_mode;
}
double BL0937::getCurrent() {
// Power measurements are more sensitive to switch offs,
// so we first check if power is 0 to set _current to 0 too
if (_power == 0) {
_current_pulse_width = 0;
} else if (_use_interrupts) {
_checkCF1Signal();
} else if (_mode == _current_mode) {
_current_pulse_width = pulseIn(_cf1_pin, HIGH, _pulse_timeout);
}
_current = (_current_pulse_width > 0) ? _current_multiplier / _current_pulse_width : 0;
return _current;
}
unsigned int BL0937::getVoltage() {
if (_use_interrupts) {
_checkCF1Signal();
} else if (_mode != _current_mode) {
_voltage_pulse_width = pulseIn(_cf1_pin, HIGH, _pulse_timeout);
}
_voltage = (_voltage_pulse_width > 0) ? _voltage_multiplier / _voltage_pulse_width : 0;
return _voltage;
}
unsigned int BL0937::getActivePower() {
if (_use_interrupts) {
_checkCFSignal();
} else {
_power_pulse_width = pulseIn(_cf_pin, HIGH, _pulse_timeout);
}
_power = (_power_pulse_width > 0) ? _power_multiplier / _power_pulse_width : 0;
return _power;
}
unsigned int BL0937::getApparentPower() {
double current = getCurrent();
unsigned int voltage = getVoltage();
return voltage * current;
}
unsigned int BL0937::getReactivePower() {
unsigned int active = getActivePower();
unsigned int apparent = getApparentPower();
if (apparent > active) {
return sqrt(apparent * apparent - active * active);
} else {
return 0;
}
}
double BL0937::getPowerFactor() {
unsigned int active = getActivePower();
unsigned int apparent = getApparentPower();
if (active > apparent) return 1;
if (apparent == 0) return 0;
return (double) active / apparent;
}
unsigned long BL0937::getEnergy() {
// Counting pulses only works in IRQ mode
if (!_use_interrupts) return 0;
/*
Pulse count is directly proportional to energy:
P = m*f (m=power multiplier, f = Frequency)
f = N/t (N=pulse count, t = time)
E = P*t = m*N (E=energy)
*/
return _pulse_count * _power_multiplier / 1000000.0;
}
void BL0937::resetEnergy() {
_pulse_count = 0;
}
void BL0937::expectedCurrent(double value) {
if (_current == 0) getCurrent();
if (_current > 0) _current_multiplier *= (value / _current);
}
void BL0937::expectedVoltage(unsigned int value) {
if (_voltage == 0) getVoltage();
if (_voltage > 0) _voltage_multiplier *= ((double) value / _voltage);
}
void BL0937::expectedActivePower(unsigned int value) {
if (_power == 0) getActivePower();
if (_power > 0) _power_multiplier *= ((double) value / _power);
}
void BL0937::resetMultipliers() {
_calculateDefaultMultipliers();
}
void BL0937::setResistors(double current, double voltage_upstream, double voltage_downstream) {
if (voltage_downstream > 0) {
_current_resistor = current;
_voltage_resistor = (voltage_upstream + voltage_downstream) / voltage_downstream;
_calculateDefaultMultipliers();
}
}
void ICACHE_RAM_ATTR BL0937::cf_interrupt() {
unsigned long now = micros();
_power_pulse_width = now - _last_cf_interrupt;
_last_cf_interrupt = now;
_pulse_count++;
}
void ICACHE_RAM_ATTR BL0937::cf1_interrupt() {
unsigned long now = micros();
if ((now - _first_cf1_interrupt) > _pulse_timeout) {
unsigned long pulse_width;
if (_last_cf1_interrupt == _first_cf1_interrupt) {
pulse_width = 0;
} else {
pulse_width = now - _last_cf1_interrupt;
}
if (_mode == _current_mode) {
_current_pulse_width = pulse_width;
} else {
_voltage_pulse_width = pulse_width;
}
_mode = 1 - _mode;
digitalWrite(_sel_pin, _mode);
_first_cf1_interrupt = now;
}
_last_cf1_interrupt = now;
}
void BL0937::_checkCFSignal() {
if ((micros() - _last_cf_interrupt) > _pulse_timeout) _power_pulse_width = 0;
}
void BL0937::_checkCF1Signal() {
if ((micros() - _last_cf1_interrupt) > _pulse_timeout) {
if (_mode == _current_mode) {
_current_pulse_width = 0;
} else {
_voltage_pulse_width = 0;
}
toggleMode();
}
}
// These are the multipliers for current, voltage and power as per datasheet
// These values divided by output period (in useconds) give the actual value
void BL0937::_calculateDefaultMultipliers() {
_current_multiplier = ( 1000000.0 * 512 * V_REF / _current_resistor / 24.0 / F_OSC ) * 0.850112464f;
_voltage_multiplier = ( 1000000.0 * 512 * V_REF * _voltage_resistor / 2.0 / F_OSC) * 0.863158011f;
_power_multiplier = ( 1000000.0 * 128 * V_REF * V_REF * _voltage_resistor / _current_resistor / 48.0 / F_OSC) * 0.713465334f;
}

View File

@@ -0,0 +1,146 @@
/*
BL0937
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef BL0937_h
#define BL0937_h
#include <Arduino.h>
// Internal voltage reference value
#define V_REF 1.218
// The factor of a 1mOhm resistor
// as per recomended circuit in datasheet
// A 1mOhm resistor allows a ~30A max measurement
#define R_CURRENT 0.001
// This is the factor of a voltage divider of 1MOhm upstream and 1kOhm downstream
// as per recomended circuit in datasheet
#define R_VOLTAGE 1000
// Frequency of the BL0937 internal clock
#define F_OSC 2000000
// Minimum delay between selecting a mode and reading a sample
#define READING_INTERVAL 3000
// Maximum pulse with in microseconds
// If longer than this pulse width is reset to 0
// This value is purely experimental.
// Higher values allow for a better precission but reduce sampling rate
// and response speed to change
// Lower values increase sampling rate but reduce precission
// Values below 0.5s are not recommended since current and voltage output
// will have no time to stabilise
#define PULSE_TIMEOUT 5000000
// Define ICACHE_RAM_ATTR for AVR platforms
#if defined(ARDUINO_ARCH_AVR)
#define ICACHE_RAM_ATTR
#endif
// CF1 mode
typedef enum {
MODE_CURRENT,
MODE_VOLTAGE
} bl0937_mode_t;
class BL0937 {
public:
void cf_interrupt();
void cf1_interrupt();
void begin(
unsigned char cf_pin,
unsigned char cf1_pin,
unsigned char sel_pin,
unsigned char currentWhen = HIGH,
bool use_interrupts = true,
unsigned long pulse_timeout = PULSE_TIMEOUT);
void setMode(bl0937_mode_t mode);
bl0937_mode_t getMode();
bl0937_mode_t toggleMode();
double getCurrent();
unsigned int getVoltage();
unsigned int getActivePower();
unsigned int getApparentPower();
double getPowerFactor();
unsigned int getReactivePower();
unsigned long getEnergy(); //in Ws
void resetEnergy();
void setResistors(double current, double voltage_upstream, double voltage_downstream);
void expectedCurrent(double current);
void expectedVoltage(unsigned int current);
void expectedActivePower(unsigned int power);
double getCurrentMultiplier() { return _current_multiplier; };
double getVoltageMultiplier() { return _voltage_multiplier; };
double getPowerMultiplier() { return _power_multiplier; };
void setCurrentMultiplier(double current_multiplier) { _current_multiplier = current_multiplier; };
void setVoltageMultiplier(double voltage_multiplier) { _voltage_multiplier = voltage_multiplier; };
void setPowerMultiplier(double power_multiplier) { _power_multiplier = power_multiplier; };
void resetMultipliers();
private:
unsigned char _cf_pin;
unsigned char _cf1_pin;
unsigned char _sel_pin;
double _current_resistor = R_CURRENT;
double _voltage_resistor = R_VOLTAGE;
double _current_multiplier; // Unit: us/A
double _voltage_multiplier; // Unit: us/V
double _power_multiplier; // Unit: us/W
unsigned long _pulse_timeout = PULSE_TIMEOUT; //Unit: us
volatile unsigned long _voltage_pulse_width = 0; //Unit: us
volatile unsigned long _current_pulse_width = 0; //Unit: us
volatile unsigned long _power_pulse_width = 0; //Unit: us
volatile unsigned long _pulse_count = 0;
double _current = 0;
unsigned int _voltage = 0;
unsigned int _power = 0;
unsigned char _current_mode = HIGH;
volatile unsigned char _mode;
bool _use_interrupts = true;
volatile unsigned long _last_cf_interrupt = 0;
volatile unsigned long _last_cf1_interrupt = 0;
volatile unsigned long _first_cf1_interrupt = 0;
void _checkCFSignal();
void _checkCF1Signal();
void _calculateDefaultMultipliers();
};
#endif

View File

@@ -0,0 +1,129 @@
{
"menuSection": "sensors",
"configItem": [
{
"global": 0,
"name": "BL0937 Напряжение",
"type": "Reading",
"subtype": "BL0937v",
"id": "bl_v",
"widget": "anydataVlt",
"page": "BL0937",
"descr": "Напряжение",
"int": 15,
"round": 1
},
{
"global": 0,
"name": "BL0937 Сила тока",
"type": "Reading",
"subtype": "BL0937a",
"id": "bl_a",
"widget": "anydataAmp",
"page": "BL0937",
"descr": "Сила тока",
"int": 15,
"round": 1
},
{
"global": 0,
"name": "BL0937 Мощность",
"type": "Reading",
"subtype": "BL0937w",
"id": "bl_w",
"widget": "anydataWt",
"page": "BL0937",
"descr": "Мощность",
"int": 15,
"round": 1
},
{
"global": 0,
"name": "BL0937 Реакт.Мощность",
"type": "Reading",
"subtype": "BL0937reactw",
"id": "bl_w",
"widget": "anydataWt",
"page": "BL0937",
"descr": "Реакт.Мощность",
"int": 15,
"round": 1
},
{
"global": 0,
"name": "BL0937 Активн.Мощность",
"type": "Reading",
"subtype": "BL0937actw",
"id": "bl_actw",
"widget": "anydataWt",
"page": "BL0937",
"descr": "Актив.Мощность",
"int": 15,
"round": 1
},
{
"global": 0,
"name": "BL0937 Энергия",
"type": "Reading",
"subtype": "BL0937wh",
"id": "bl_wh",
"widget": "anydataWth",
"page": "BL0937",
"descr": "Энергия",
"int": 15,
"round": 1
},
{
"global": 0,
"name": "BL0937 настройка",
"type": "Reading",
"subtype": "BL0937cmd",
"id": "bl_set",
"widget": "nil",
"page": "",
"descr": "",
"btn-reset": "",
"R_current": 0.001,
"R_upstream": 1000000,
"R_downstream": 1000,
"CF_GPIO": 4,
"CF1_GPIO": 5,
"SEL_GPIO": 12,
"expV": 0,
"expA": 0,
"expW": 0
}
],
"about": {
"authorName": "Bubnov Mikhail",
"authorContact": "https://t.me/Mit4bmw",
"authorGit": "https://github.com/Mit4el",
"specialThanks": "",
"moduleName": "BL0937",
"moduleVersion": "1.0",
"usedRam": {
"esp32_4mb": 15,
"esp8266_4mb": 15
},
"title": "Счетчик электроэнергии BL0937",
"moduleDesc": "Считает потраченную электроэнергию, измеряет напряжение, силу тока и прочие параметры.",
"propInfo": {
"int": "Количество секунд между опросами датчика.",
"btn-reset": "Энергия BL0937 будет сброшена к нулю.",
"R_current": "Резистор подключенный последовательно к основной линии",
"R_upstream": "это 5 резисторов по 470 Ком в делителе напряжения, который питает вывод V2P",
"R_downstream": "это резистор емкостью 1 Ком в делителе напряжения, который питает вывод V2P",
"CF_GPIO": "пин CF",
"CF1_GPIO": "пин CF1",
"SEL_GPIO": "пин SEL",
"expV": "реальное напряжение, указать для калибровки",
"expA": "реальный ток, указать для калибровки",
"expW": "реальная мощность, указать для калибровки"
}
},
"defActive": true,
"usedLibs": {
"esp32*": [],
"esp82*": []
}
}