diff --git a/src/modules/sensors/BL0937/BL0937.cpp b/src/modules/sensors/BL0937/BL0937.cpp new file mode 100644 index 00000000..2e207813 --- /dev/null +++ b/src/modules/sensors/BL0937/BL0937.cpp @@ -0,0 +1,269 @@ + +#include "Global.h" +#include "classes/IoTItem.h" + +#include "BL0937lib.h" +// #include "classes/IoTUart.h" +// #include +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 ¶m) + { + 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; + } +} diff --git a/src/modules/sensors/BL0937/BL0937lib.cpp b/src/modules/sensors/BL0937/BL0937lib.cpp new file mode 100644 index 00000000..d4b46e55 --- /dev/null +++ b/src/modules/sensors/BL0937/BL0937lib.cpp @@ -0,0 +1,238 @@ +/* + +BL0937 + +Copyright (C) 2016-2018 by Xose Pérez + +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 . + +*/ + +#include +#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; +} diff --git a/src/modules/sensors/BL0937/BL0937lib.h b/src/modules/sensors/BL0937/BL0937lib.h new file mode 100644 index 00000000..f69295ae --- /dev/null +++ b/src/modules/sensors/BL0937/BL0937lib.h @@ -0,0 +1,146 @@ +/* + +BL0937 + +Copyright (C) 2016-2018 by Xose Pérez + +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 . + +*/ + +#ifndef BL0937_h +#define BL0937_h + +#include + +// 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 diff --git a/src/modules/sensors/BL0937/modinfo.json b/src/modules/sensors/BL0937/modinfo.json new file mode 100644 index 00000000..d9be43f7 --- /dev/null +++ b/src/modules/sensors/BL0937/modinfo.json @@ -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*": [] + } +} \ No newline at end of file