From 5f87e3ced372eda19ef07b00ab3da2a862703c5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xose=20P=C3=A9rez?= Date: Mon, 18 Sep 2017 22:11:51 +0200 Subject: [PATCH] Initial support for V9261F-based multimeters --- code/espurna/config/hardware.h | 28 ++- code/espurna/config/sensors.h | 19 ++ code/espurna/espurna.ino | 6 + code/espurna/hardware.ino | 4 + code/espurna/v9261f.ino | 315 +++++++++++++++++++++++++++++++++ code/platformio.ini | 46 +++++ 6 files changed, 417 insertions(+), 1 deletion(-) create mode 100644 code/espurna/v9261f.ino diff --git a/code/espurna/config/hardware.h b/code/espurna/config/hardware.h index 0d65134f..434af84e 100644 --- a/code/espurna/config/hardware.h +++ b/code/espurna/config/hardware.h @@ -71,9 +71,20 @@ // Buttons #define BUTTON1_PIN 4 - #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH #define BUTTON1_RELAY 1 + // Normal pushbutton + //#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + + // Touch button + #define BUTTON1_MODE BUTTON_PUSHBUTTON + #define BUTTON1_PRESS BUTTON_MODE_TOGGLE + #define BUTTON1_CLICK BUTTON_MODE_NONE + #define BUTTON1_DBLCLICK BUTTON_MODE_NONE + #define BUTTON1_LNGCLICK BUTTON_MODE_NONE + #define BUTTON1_LNGLNGCLICK BUTTON_MODE_NONE + + // Relays #define RELAY1_PIN 12 #define RELAY1_TYPE RELAY_TYPE_INVERSE @@ -833,6 +844,21 @@ #define LIGHT_CH3_INVERSE 0 #define LIGHT_CH4_INVERSE 0 +// ----------------------------------------------------------------------------- +// V9261F +// ----------------------------------------------------------------------------- + +#elif defined(GENERIC_V9261F) + + // Info + #define MANUFACTURER "GENERIC" + #define DEVICE "V9261F" + + // V9261F + #define V9261F_SUPPORT 1 + #define V9261F_PIN 2 + #define V9261F_PIN_INVERSE 1 + // ----------------------------------------------------------------------------- // Unknown hardware // ----------------------------------------------------------------------------- diff --git a/code/espurna/config/sensors.h b/code/espurna/config/sensors.h index 33ad37f4..8fb95a80 100644 --- a/code/espurna/config/sensors.h +++ b/code/espurna/config/sensors.h @@ -155,6 +155,25 @@ #define HLW8012_MIN_CURRENT 0.05 #define HLW8012_MAX_CURRENT 10 +//-------------------------------------------------------------------------------- +// V9261F power sensor (Intertek) +// Enable support by passing V9261F_SUPPORT=1 build flag +//-------------------------------------------------------------------------------- + +#ifndef V9261F_SUPPORT +#define V9261F_SUPPORT 0 +#endif + +#define V9261F_SYNC_INTERVAL 600 +#define V9261F_REPORT_INTERVAL 60000 + +#define V9261F_BAUDRATE 4800 + +#define V9261F_CURRENT_FACTOR 81156358 +#define V9261F_VOLTAGE_FACTOR 4178508 +#define V9261F_POWER_FACTOR 157859 +#define V9261F_RPOWER_FACTOR V9261F_CURRENT_FACTOR + //-------------------------------------------------------------------------------- // Internal power montior // Enable support by passing ADC_VCC_ENABLED=1 build flag diff --git a/code/espurna/espurna.ino b/code/espurna/espurna.ino index 5b6fa25e..7215177f 100644 --- a/code/espurna/espurna.ino +++ b/code/espurna/espurna.ino @@ -271,6 +271,9 @@ void setup() { #if HLW8012_SUPPORT hlw8012Setup(); #endif + #if V9261F_SUPPORT + v9261fSetup(); + #endif #if DS18B20_SUPPORT dsSetup(); #endif @@ -329,6 +332,9 @@ void loop() { #if HLW8012_SUPPORT hlw8012Loop(); #endif + #if V9261F_SUPPORT + v9261fLoop(); + #endif #if DS18B20_SUPPORT dsLoop(); #endif diff --git a/code/espurna/hardware.ino b/code/espurna/hardware.ino index e38ae2df..d870f514 100644 --- a/code/espurna/hardware.ino +++ b/code/espurna/hardware.ino @@ -434,6 +434,10 @@ void hwUpwardsCompatibility() { setSetting("chLogic", 4, 0); setSetting("relays", 1); + #elif defined(GENERIC_V9261F) + + setSetting("board", 37); + #else #error "UNSUPPORTED HARDWARE!" diff --git a/code/espurna/v9261f.ino b/code/espurna/v9261f.ino new file mode 100644 index 00000000..5bc7334f --- /dev/null +++ b/code/espurna/v9261f.ino @@ -0,0 +1,315 @@ +/* + +V9261F MODULE +Support for V9261D-based power monitors + +Copyright (C) 2016-2017 by Xose PĂ©rez + +*/ + +#if V9261F_SUPPORT + +#include +#include + +SoftwareSerial * _v9261f_uart; + +bool _v9261f_enabled = false; +bool _v9261f_ready = false; +bool _v9261f_newdata = false; +int _v9261f_power = 0; +int _v9261f_rpower = 0; +int _v9261f_voltage = 0; +double _v9261f_current = 0; + +unsigned char _v9261f_data[24]; + +// ----------------------------------------------------------------------------- +// PRIVATE +// ----------------------------------------------------------------------------- + +void v9261fRead() { + + static unsigned char state = 0; + static unsigned long last = 0; + static bool found = false; + static unsigned char index = 0; + + if (state == 0) { + + while (_v9261f_uart->available()) { + _v9261f_uart->flush(); + found = true; + last = millis(); + } + + if (found && (millis() - last > V9261F_SYNC_INTERVAL)) { + _v9261f_uart->flush(); + index = 0; + state = 1; + } + + } else if (state == 1) { + + while (_v9261f_uart->available()) { + _v9261f_uart->read(); + if (index++ >= 7) { + _v9261f_uart->flush(); + index = 0; + state = 2; + } + } + + } else if (state == 2) { + + while (_v9261f_uart->available()) { + _v9261f_data[index] = _v9261f_uart->read(); + if (index++ >= 19) { + _v9261f_uart->flush(); + last = millis(); + state = 3; + } + } + + } else if (state == 3) { + + /* + for (unsigned char i=0; iavailable()) { + _v9261f_uart->flush(); + last = millis(); + } + + if (millis() - last > V9261F_SYNC_INTERVAL) { + state = 1; + } + + } + +} + +boolean checksumOK() { + unsigned char checksum = 0; + for (unsigned char i = 0; i < 19; i++) { + checksum = checksum + _v9261f_data[i]; + } + checksum = ~checksum + 0x33; + return checksum == _v9261f_data[19]; +} + +// ----------------------------------------------------------------------------- +// HAL +// ----------------------------------------------------------------------------- + +unsigned int getActivePower() { + return _v9261f_power; +} + +unsigned int getReactivePower() { + return _v9261f_rpower; +} + +unsigned int getApparentPower() { + return sqrt(_v9261f_rpower * _v9261f_rpower + _v9261f_power * _v9261f_power); +} + +unsigned int getVoltage() { + return _v9261f_voltage; +} + +double getCurrent() { + return _v9261f_current; +} + +double getPowerFactor() { + unsigned int apparent = getApparentPower(); + if (apparent > 0) return getActivePower() / getApparentPower(); + return 1; +} + +// ----------------------------------------------------------------------------- + +void v9261fSetup() { + + _v9261f_uart = new SoftwareSerial(V9261F_PIN, SW_SERIAL_UNUSED_PIN, V9261F_PIN_INVERSE, 256); + _v9261f_uart->begin(V9261F_BAUDRATE); + + // API definitions + #if WEB_SUPPORT + + apiRegister(HLW8012_POWER_TOPIC, HLW8012_POWER_TOPIC, [](char * buffer, size_t len) { + snprintf_P(buffer, len, PSTR("%d"), _v9261f_power); + }); + apiRegister(HLW8012_CURRENT_TOPIC, HLW8012_CURRENT_TOPIC, [](char * buffer, size_t len) { + dtostrf(_v9261f_current, len-1, 3, buffer); + }); + apiRegister(HLW8012_VOLTAGE_TOPIC, HLW8012_VOLTAGE_TOPIC, [](char * buffer, size_t len) { + snprintf_P(buffer, len, PSTR("%d"), _v9261f_voltage); + }); + + #endif // WEB_SUPPORT + +} + + +void v9261fLoop() { + + static int sum_power = 0; + static int sum_rpower = 0; + static int sum_voltage = 0; + static double sum_current = 0; + static int count = 0; + + // Sniff data in the UART interface + v9261fRead(); + + // Do we have new data? + if (_v9261f_newdata) { + + _v9261f_newdata = false; + + sum_power += getActivePower(); + sum_rpower += getReactivePower(); + sum_voltage += getVoltage(); + sum_current += getCurrent(); + count++; + + #if WEB_SUPPORT + { + DynamicJsonBuffer jsonBuffer; + JsonObject& root = jsonBuffer.createObject(); + + char buf_current[10]; + dtostrf(getCurrent(), 6, 3, buf_current); + + root["powVisible"] = 1; + root["powActivePower"] = getActivePower(); + root["powCurrent"] = String(ltrim(buf_current)); + root["powVoltage"] = getVoltage(); + root["powApparentPower"] = getApparentPower(); + root["powReactivePower"] = getReactivePower(); + root["powPowerFactor"] = 100 * getPowerFactor(); + + String output; + root.printTo(output); + wsSend(output.c_str()); + } + #endif + + } + + // Do we have to report? + static unsigned long last = 0; + if ((count == 0) || (millis() - last < V9261F_REPORT_INTERVAL)) return; + last = millis(); + + { + + unsigned int power = sum_power / count; + unsigned int reactive = sum_rpower / count; + unsigned int voltage = sum_voltage / count; + double current = sum_current / count; + char buf_current[10]; + dtostrf(current, 6, 3, buf_current); + unsigned int apparent = sqrt(power * power + reactive * reactive); + double energy_delta = (double) power * V9261F_REPORT_INTERVAL / 1000.0 / 3600.0; + char buf_energy[10]; + dtostrf(energy_delta, 6, 3, buf_energy); + unsigned int factor = 100 * ((double) power / apparent); + + // Report values to MQTT broker + mqttSend(HLW8012_POWER_TOPIC, String(power).c_str()); + mqttSend(HLW8012_CURRENT_TOPIC, buf_current); + mqttSend(HLW8012_VOLTAGE_TOPIC, String(voltage).c_str()); + mqttSend(HLW8012_ENERGY_TOPIC, buf_energy); + mqttSend(HLW8012_APOWER_TOPIC, String(apparent).c_str()); + mqttSend(HLW8012_RPOWER_TOPIC, String(reactive).c_str()); + mqttSend(HLW8012_PFACTOR_TOPIC, String(factor).c_str()); + + // Report values to Domoticz + #if DOMOTICZ_SUPPORT + { + char buffer[20]; + snprintf_P(buffer, sizeof(buffer), PSTR("%d;%s"), power, buf_energy); + domoticzSend("dczPowIdx", 0, buffer); + snprintf_P(buffer, sizeof(buffer), PSTR("%s"), buf_energy); + domoticzSend("dczEnergyIdx", 0, buffer); + snprintf_P(buffer, sizeof(buffer), PSTR("%d"), voltage); + domoticzSend("dczVoltIdx", 0, buffer); + snprintf_P(buffer, sizeof(buffer), PSTR("%s"), buf_current); + domoticzSend("dczCurrentIdx", 0, buffer); + } + #endif + + #if INFLUXDB_SUPPORT + { + influxDBSend(HLW8012_POWER_TOPIC, String(power).c_str()); + influxDBSend(HLW8012_CURRENT_TOPIC, buf_current); + influxDBSend(HLW8012_VOLTAGE_TOPIC, String(voltage).c_str()); + influxDBSend(HLW8012_ENERGY_TOPIC, buf_energy); + influxDBSend(HLW8012_APOWER_TOPIC, String(apparent).c_str()); + influxDBSend(HLW8012_RPOWER_TOPIC, String(reactive).c_str()); + influxDBSend(HLW8012_PFACTOR_TOPIC, String(factor).c_str()); + } + #endif + + // Reset counters + sum_power = sum_rpower = sum_voltage = sum_current = count = 0; + + } + +} + +#endif diff --git a/code/platformio.ini b/code/platformio.ini index 15cf222a..10596d85 100644 --- a/code/platformio.ini +++ b/code/platformio.ini @@ -22,6 +22,7 @@ lib_deps = OneWire DallasTemperature Brzo I2C + EspSoftwareSerial https://bitbucket.org/xoseperez/justwifi.git#1.1.4 https://bitbucket.org/xoseperez/hlw8012.git#1.0.1 https://bitbucket.org/xoseperez/fauxmoesp.git#2.2.0 @@ -815,6 +816,51 @@ upload_port = "192.168.4.1" upload_flags = --auth=fibonacci --port 8266 monitor_baud = 115200 +[env:wemos-v9261f] +platform = espressif8266 +framework = arduino +board = d1_mini +lib_deps = ${common.lib_deps} +lib_ignore = ${common.lib_ignore} +build_flags = ${common.build_flags} -DGENERIC_V9261F +upload_speed = 460800 +monitor_baud = 115200 + +[env:wemos-v9261f-ota] +platform = espressif8266 +framework = arduino +board = d1_mini +lib_deps = ${common.lib_deps} +lib_ignore = ${common.lib_ignore} +build_flags = ${common.build_flags} -DGENERIC_V9261F +upload_speed = 115200 +upload_port = "192.168.4.1" +upload_flags = --auth=fibonacci --port 8266 +monitor_baud = 115200 + +[env:esp01-v9261f] +platform = espressif8266 +framework = arduino +board = esp01_1m +board_flash_mode = dout +lib_deps = ${common.lib_deps} +lib_ignore = ${common.lib_ignore} +build_flags = ${common.build_flags_1m} -DGENERIC_V9261F +monitor_baud = 115200 + +[env:esp01-v9261f-ota] +platform = espressif8266 +framework = arduino +board = esp01_1m +board_flash_mode = dout +lib_deps = ${common.lib_deps} +lib_ignore = ${common.lib_ignore} +build_flags = ${common.build_flags_1m} -DGENERIC_V9261F +upload_speed = 115200 +upload_port = "192.168.4.1" +upload_flags = --auth=fibonacci --port 8266 +monitor_baud = 115200 + # ------------------------------------------------------------------------------ # GENERIC OTA ENVIRONMENTS # ------------------------------------------------------------------------------