Merge pull request #13195 from barbudor/pr_bl039

bl09xx driver for both bl0939 and bl0940
This commit is contained in:
Theo Arends 2021-09-22 11:54:28 +02:00 committed by GitHub
commit 81102283da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 426 additions and 335 deletions

View File

@ -80,7 +80,7 @@ m = minimal, l = lite, t = tasmota, k = knx, s = sensors, i = ir, d = display
| USE_DDSU666 | - | - | - / x | - | x | - | - |
| USE_SOLAX_X1 | - | - | - / - | - | - | - | - |
| USE_LE01MR | - | - | - / - | - | - | - | - |
| USE_BL0940 | - | x | x / - | x | x | - | - |
| USE_BL09XX | - | x | x / x | x | x | - | - |
| USE_TELEINFO | - | - | - / - | - | - | - | - |
| USE_IEM3000 | - | - | - / - | - | - | - | - |
| USE_WE517 | - | - | - / - | - | - | - | - |

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -709,6 +709,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -709,7 +709,8 @@
#define D_SENSOR_CSE7761_TX "CSE7761 TX"
#define D_SENSOR_CSE7761_RX "CSE7761 RX"
#define D_SENSOR_CSE7766_TX "CSE7766 TX"
#define D_SENSOR_CSE7766_RX "CSE7766 RX"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 TX"
#define D_SENSOR_PN532_RX "PN532 RX"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -764,6 +764,7 @@
#define D_SENSOR_LE01MR_RX "LE-01MR - RX"
#define D_SENSOR_LE01MR_TX "LE-01MR - TX"
#define D_SENSOR_BL0940_RX "BL0940 - RX"
#define D_SENSOR_BL0939_RX "BL0939 - RX"
#define D_SENSOR_CC1101_GDO0 "CC1101 - GDO0"
#define D_SENSOR_CC1101_GDO2 "CC1101 - GDO2"
#define D_SENSOR_HRXL_RX "HRXL - RX"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -710,6 +710,7 @@
#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
#define D_SENSOR_CSE7766_TX "CSE7766 Tx"
#define D_SENSOR_CSE7766_RX "CSE7766 Rx"
#define D_SENSOR_BL0939_RX "BL0939 Rx"
#define D_SENSOR_PN532_TX "PN532 Tx"
#define D_SENSOR_PN532_RX "PN532 Rx"
#define D_SENSOR_SM16716_CLK "SM16716 CLK"

View File

@ -780,7 +780,7 @@
//#define USE_LE01MR // Add support for F&F LE-01MR Modbus energy monitor (+1k code)
#define LE01MR_SPEED 9600 // LE-01MR modbus baudrate (default: 9600)
#define LE01MR_ADDR 1 // LE-01MR modbus address (default: 0x01)
#define USE_BL0940 // Add support for BL0940 Energy monitor as used in Blitzwolf SHP-10 (+1k6 code)
#define USE_BL09XX // Add support for various BL09XX Energy monitor as used in Blitzwolf SHP-10 or Sonoff Dual R3 v2 (+1k6 code)
//#define USE_TELEINFO // Add support for Teleinfo via serial RX interface (+5k2 code, +168 RAM + SmartMeter LinkedList Values RAM)
//#define USE_IEM3000 // Add support for Schneider Electric iEM3000-Modbus series energy monitor (+0k8 code)
#define IEM3000_SPEED 19200 // iEM3000-Modbus RS485 serial speed (default: 19200 baud)

View File

@ -567,8 +567,8 @@ void ResponseAppendFeatures(void)
#if defined(USE_I2C) && defined(USE_MCP9808)
feature6 |= 0x00002000; // xsns_72_mcp9808.ino
#endif
#if defined(USE_ENERGY_SENSOR) && defined(USE_BL0940)
feature6 |= 0x00004000; // xnrg_14_bl0940.ino
#if defined(USE_ENERGY_SENSOR) && (defined(USE_BL0940) || defined(USE_BL09XX))
feature6 |= 0x00004000; // xnrg_14_bl09xx.ino
#endif
#ifdef USE_TELEGRAM
feature6 |= 0x00008000; // xdrv_40_telegram.ino

View File

@ -876,7 +876,7 @@
#undef USE_DDSU666 // Disable support for Chint DDSU666 Modbus energy monitor (+0k6 code)
#undef USE_SOLAX_X1 // Disable support for Solax X1 series Modbus log info (+3k1 code)
#undef USE_LE01MR // Disable support for F&F LE-01MR Modbus energy meter (+2k code)
#undef USE_BL0940 // Disable support for BL0940 Energy monitor as used in Blitzwolf SHP-10 (+1k6 code)
#undef USE_BL09XX // Add support for various BL09XX Energy monitor as used in Blitzwolf SHP-10 or Sonoff Dual R3 v2 (+1k6 code)
#undef USE_TELEINFO // Disable support for French Energy Provider metering telemetry
#undef USE_IEM3000 // Disable support for Schneider Electric iEM3000-Modbus series energy monitor (+0k8 code)
#undef USE_WE517 // Disable support for Orno WE517-Modbus energy monitor (+1k code)

View File

@ -172,6 +172,7 @@ enum UserSelectablePins {
GPIO_MCP2515_CS, // MCP2515 Chip Select
GPIO_HRG15_TX, GPIO_HRG15_RX, // Hydreon RG-15 rain sensor serial interface
GPIO_VINDRIKTNING_RX, // IKEA VINDRIKTNING Serial interface
GPIO_BL0939_RX, // BL0939 Serial interface (Dual R3 v2)
GPIO_SENSOR_END };
enum ProgramSelectablePins {
@ -363,7 +364,8 @@ const char kSensorNames[] PROGMEM =
D_SENSOR_INTERRUPT "|"
D_SENSOR_MCP2515_CS "|"
D_SENSOR_HRG15_TX "|" D_SENSOR_HRG15_RX "|"
D_SENSOR_VINDRIKTNING_RX
D_SENSOR_VINDRIKTNING_RX "|"
D_SENSOR_BL0939_RX
;
const char kSensorNamesFixed[] PROGMEM =
@ -694,7 +696,8 @@ const uint16_t kGpioNiceList[] PROGMEM = {
AGPIO(GPIO_LE01MR_TX), // F7F LE-01MR energy meter tx pin
AGPIO(GPIO_LE01MR_RX), // F7F LE-01MR energy meter rx pin
#endif // IFDEF:USE_LE01MR
#ifdef USE_BL0940
#if defined(USE_BL0940) || defined(USE_BL09XX)
AGPIO(GPIO_BL0939_RX), // BL0939 Serial interface (Dual R3 v2)
AGPIO(GPIO_BL0940_RX), // BL0940 Serial interface
#endif
#ifdef USE_IEM3000

View File

@ -1,326 +0,0 @@
/*
xnrg_14_bl0940.ino - BL0940 energy sensor support for Tasmota
Copyright (C) 2021 Theo Arends
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/>.
*/
#ifdef USE_ENERGY_SENSOR
#ifdef USE_BL0940
/*********************************************************************************************\
* BL0940 - Energy (Blitzwolf SHP10)
*
* Template {"NAME":"BW-SHP10","GPIO":[0,148,0,207,158,21,0,0,0,17,0,0,0],"FLAG":0,"BASE":18}
*
* Based on datasheet from http://www.belling.com.cn/media/file_object/bel_product/BL0940/datasheet/BL0940_V1.1_en.pdf
\*********************************************************************************************/
#define XNRG_14 14
#define BL0940_PREF 1430
#define BL0940_UREF 33000
#define BL0940_IREF 2750
#define BL0940_PULSES_NOT_INITIALIZED -1
#define BL0940_BUFFER_SIZE 36
#define BL0940_WRITE_COMMAND 0xA0 // 0xA8 according to documentation
#define BL0940_REG_I_FAST_RMS_CTRL 0x10
#define BL0940_REG_MODE 0x18
#define BL0940_REG_SOFT_RESET 0x19
#define BL0940_REG_USR_WRPROT 0x1A
#define BL0940_REG_TPS_CTRL 0x1B
#define BL0940_READ_COMMAND 0x50 // 0x58 according to documentation
#define BL0940_FULL_PACKET 0xAA
#define BL0940_PACKET_HEADER 0x55 // 0x58 according to documentation
#include <TasmotaSerial.h>
TasmotaSerial *Bl0940Serial = nullptr;
struct BL0940 {
long voltage = 0;
long current = 0;
long power = 0;
long power_cycle_first = 0;
long cf_pulses = 0;
long cf_pulses_last_time = BL0940_PULSES_NOT_INITIALIZED;
float temperature;
int byte_counter = 0;
uint16_t tps1 = 0;
uint8_t *rx_buffer = nullptr;
bool received = false;
} Bl0940;
const uint8_t bl0940_init[5][6] = {
{ BL0940_WRITE_COMMAND, BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38 }, // Reset to default
{ BL0940_WRITE_COMMAND, BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0 }, // Enable User Operation Write
{ BL0940_WRITE_COMMAND, BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37 }, // 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS
{ BL0940_WRITE_COMMAND, BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE }, // 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS
{ BL0940_WRITE_COMMAND, BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B }}; // 0x181C = Half cycle, Fast RMS threshold 6172
void Bl0940Received(void) {
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
// 55 F2 03 00 00 00 00 7E 02 00 D4 B0 72 AC 01 00 00 00 00 02 01 00 00 00 00 00 00 00 BA 01 00 FE 03 00 83
// 55 88 02 00 49 00 00 FE 02 00 AF EF 71 D2 01 00 EB FF FF 49 01 00 00 00 00 02 00 00 CF 01 00 FE 03 00 9F
// 55 B9 33 00 DE 45 00 94 02 00 CF E4 70 63 02 00 6C 4C 00 13 01 00 09 00 00 00 00 00 E4 01 00 FE 03 00 72
// Hd IFRms--- Current- Reserved Voltage- Reserved Power--- Reserved CF------ Reserved TPS1---- TPS2---- Ck
uint16_t tps1 = Bl0940.rx_buffer[29] << 8 | Bl0940.rx_buffer[28]; // TPS1 unsigned
if ((Bl0940.rx_buffer[0] != BL0940_PACKET_HEADER) || // Bad header
(Bl0940.tps1 && ((tps1 < (Bl0940.tps1 -10)) || (tps1 > (Bl0940.tps1 +10)))) // Invalid temperature change
) {
AddLog(LOG_LEVEL_DEBUG, PSTR("BL9: Invalid data"));
return;
}
Bl0940.tps1 = tps1;
float t = ((170.0f/448.0f)*(((float)Bl0940.tps1/2.0f)-32.0f))-45.0f;
Bl0940.temperature = ConvertTemp(t);
Bl0940.voltage = Bl0940.rx_buffer[12] << 16 | Bl0940.rx_buffer[11] << 8 | Bl0940.rx_buffer[10]; // V_RMS unsigned
Bl0940.current = Bl0940.rx_buffer[6] << 16 | Bl0940.rx_buffer[5] << 8 | Bl0940.rx_buffer[4]; // I_RMS unsigned
int32_t power = Bl0940.rx_buffer[18] << 24 | Bl0940.rx_buffer[17] << 16 | Bl0940.rx_buffer[16] << 8; // WATT signed
Bl0940.power = abs(power) >> 8; // WATT unsigned
int32_t cf_cnt = Bl0940.rx_buffer[24] << 24 | Bl0940.rx_buffer[23] << 16 | Bl0940.rx_buffer[22] << 8; // CF_CNT signed
Bl0940.cf_pulses = abs(cf_cnt) >> 8;
AddLog(LOG_LEVEL_DEBUG, PSTR("BL9: U %d, I %d, P %d, C %d, T %d"),
Bl0940.voltage, Bl0940.current, Bl0940.power, Bl0940.cf_pulses, Bl0940.tps1);
if (Energy.power_on) { // Powered on
Energy.voltage[0] = (float)Bl0940.voltage / Settings->energy_voltage_calibration;
if (power && (Bl0940.power > Settings->energy_power_calibration)) { // We need at least 1W
Energy.active_power[0] = (float)Bl0940.power / Settings->energy_power_calibration;
Energy.current[0] = (float)Bl0940.current / (Settings->energy_current_calibration * 100);
} else {
Energy.active_power[0] = 0;
Energy.current[0] = 0;
}
} else { // Powered off
// Bl0940.power_cycle_first = 0;
Energy.voltage[0] = 0;
Energy.active_power[0] = 0;
Energy.current[0] = 0;
}
}
void Bl0940SerialInput(void) {
while (Bl0940Serial->available()) {
yield();
uint8_t serial_in_byte = Bl0940Serial->read();
if (!Bl0940.received && (BL0940_PACKET_HEADER == serial_in_byte)) {
Bl0940.received = true;
Bl0940.byte_counter = 0;
}
if (Bl0940.received) {
Bl0940.rx_buffer[Bl0940.byte_counter++] = serial_in_byte;
if (BL0940_BUFFER_SIZE == Bl0940.byte_counter) {
AddLogBuffer(LOG_LEVEL_DEBUG_MORE, Bl0940.rx_buffer, BL0940_BUFFER_SIZE -1);
uint8_t checksum = BL0940_READ_COMMAND;
for (uint32_t i = 0; i < BL0940_BUFFER_SIZE -2; i++) { checksum += Bl0940.rx_buffer[i]; }
checksum ^= 0xFF;
if (checksum == Bl0940.rx_buffer[34]) {
Energy.data_valid[0] = 0;
Bl0940Received();
Bl0940.received = false;
return;
} else {
do { // Sync buffer with data (issue #1907 and #3425)
memmove(Bl0940.rx_buffer, Bl0940.rx_buffer +1, BL0940_BUFFER_SIZE -1);
Bl0940.byte_counter--;
} while ((Bl0940.byte_counter > 1) && (BL0940_PACKET_HEADER != Bl0940.rx_buffer[0]));
if (BL0940_PACKET_HEADER != Bl0940.rx_buffer[0]) {
AddLog(LOG_LEVEL_DEBUG, PSTR("BL9: " D_CHECKSUM_FAILURE));
Bl0940.received = false;
Bl0940.byte_counter = 0;
}
}
}
}
}
}
/********************************************************************************************/
void Bl0940EverySecond(void) {
if (Energy.data_valid[0] > ENERGY_WATCHDOG) {
Bl0940.voltage = 0;
Bl0940.current = 0;
Bl0940.power = 0;
} else {
/*
// Calculate energy by using active power
if (Energy.active_power[0]) {
Energy.kWhtoday_delta += (Energy.active_power[0] * 1000) / 36;
EnergyUpdateToday();
}
*/
// Calculate energy by using active energy pulse count
if (BL0940_PULSES_NOT_INITIALIZED == Bl0940.cf_pulses_last_time) {
Bl0940.cf_pulses_last_time = Bl0940.cf_pulses; // Init after restart
} else {
uint32_t cf_pulses = 0;
if (Bl0940.cf_pulses < Bl0940.cf_pulses_last_time) { // Rolled over after 0xFFFFFF (16777215) pulses
cf_pulses = (0x1000000 - Bl0940.cf_pulses_last_time) + Bl0940.cf_pulses;
} else {
cf_pulses = Bl0940.cf_pulses - Bl0940.cf_pulses_last_time;
}
if (cf_pulses && Energy.active_power[0]) {
uint32_t watt256 = (1638400 * 256) / Settings->energy_power_calibration;
uint32_t delta = (cf_pulses * watt256) / 36;
if (delta <= (4000 * 1000 / 36)) { // max load for SHP10: 4.00kW (3.68kW)
Bl0940.cf_pulses_last_time = Bl0940.cf_pulses;
Energy.kWhtoday_delta += delta;
} else {
AddLog(LOG_LEVEL_DEBUG, PSTR("BL9: Overload"));
Bl0940.cf_pulses_last_time = BL0940_PULSES_NOT_INITIALIZED;
}
EnergyUpdateToday();
}
}
}
// AddLog(LOG_LEVEL_DEBUG, PSTR("BL9: Poll"));
Bl0940Serial->flush();
Bl0940Serial->write(BL0940_READ_COMMAND);
Bl0940Serial->write(BL0940_FULL_PACKET);
}
void Bl0940SnsInit(void) {
// Software serial init needs to be done here as earlier (serial) interrupts may lead to Exceptions
Bl0940Serial = new TasmotaSerial(Pin(GPIO_BL0940_RX), Pin(GPIO_TXD), 1);
if (Bl0940Serial->begin(4800, 1)) {
if (Bl0940Serial->hardwareSerial()) {
ClaimSerial();
}
if (HLW_UREF_PULSE == Settings->energy_voltage_calibration) {
Settings->energy_voltage_calibration = BL0940_UREF;
Settings->energy_current_calibration = BL0940_IREF;
Settings->energy_power_calibration = BL0940_PREF;
}
Energy.use_overtemp = true; // Use global temperature for overtemp detection
for (uint32_t i = 0; i < 5; i++) {
for (uint32_t j = 0; j < 6; j++) {
Bl0940Serial->write(bl0940_init[i][j]);
// Bl0940Serial->write(pgm_read_byte(bl0940_init + (6 * i) + j)); // Wrong byte order!
}
delay(1);
}
} else {
TasmotaGlobal.energy_driver = ENERGY_NONE;
}
}
void Bl0940DrvInit(void) {
if (PinUsed(GPIO_BL0940_RX) && PinUsed(GPIO_TXD)) {
Bl0940.rx_buffer = (uint8_t*)(malloc(BL0940_BUFFER_SIZE));
if (Bl0940.rx_buffer != nullptr) {
TasmotaGlobal.energy_driver = XNRG_14;
}
}
}
bool Bl0940Command(void) {
bool serviced = true;
uint32_t value = (uint32_t)(CharToFloat(XdrvMailbox.data) * 100); // 1.23 = 123
if (CMND_POWERSET == Energy.command_code) {
if (XdrvMailbox.data_len && Bl0940.power) {
Settings->energy_power_calibration = (Bl0940.power * 100) / value;
}
}
else if (CMND_VOLTAGESET == Energy.command_code) {
if (XdrvMailbox.data_len && Bl0940.voltage) {
Settings->energy_voltage_calibration = (Bl0940.voltage * 100) / value;
}
}
else if (CMND_CURRENTSET == Energy.command_code) {
if (XdrvMailbox.data_len && Bl0940.current) {
Settings->energy_current_calibration = Bl0940.current / value;
}
}
else serviced = false; // Unknown command
return serviced;
}
void Bl0940Show(bool json) {
if (json) {
ResponseAppend_P(JSON_SNS_F_TEMP, "BL0940", Settings->flag2.temperature_resolution, &Bl0940.temperature);
if (0 == TasmotaGlobal.tele_period) {
#ifdef USE_DOMOTICZ
DomoticzFloatSensor(DZ_TEMP, Bl0940.temperature);
#endif // USE_DOMOTICZ
#ifdef USE_KNX
KnxSensor(KNX_TEMPERATURE, Bl0940.temperature);
#endif // USE_KNX
}
#ifdef USE_WEBSERVER
} else {
WSContentSend_Temp("", Bl0940.temperature);
#endif // USE_WEBSERVER
}
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xnrg14(uint8_t function) {
bool result = false;
switch (function) {
case FUNC_LOOP:
if (Bl0940Serial) { Bl0940SerialInput(); }
break;
case FUNC_EVERY_SECOND:
Bl0940EverySecond();
break;
case FUNC_JSON_APPEND:
Bl0940Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
Bl0940Show(0);
break;
#endif // USE_WEBSERVER
case FUNC_COMMAND:
result = Bl0940Command();
break;
case FUNC_INIT:
Bl0940SnsInit();
break;
case FUNC_PRE_INIT:
Bl0940DrvInit();
break;
}
return result;
}
#endif // USE_BL0940
#endif // USE_ENERGY_SENSOR

388
tasmota/xnrg_14_bl09xx.ino Normal file
View File

@ -0,0 +1,388 @@
/*
xnrg_14_bl09xx.ino - BL09XX energy sensor support for Tasmota
Copyright (C) 2021 Theo Arends
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/>.
*/
#ifdef USE_ENERGY_SENSOR
#if defined(USE_BL0940) || defined(USE_BL09XX)
#ifdef USE_BL0940
#warning **** USE_BL0940 is obsolete. Please replace with USE_BLE09XX ****
#endif
/*********************************************************************************************\
* Support the following Shangai Belling energy sensors:
*
* BL09XX - Energy (as in Blitzwolf SHP10
* Template {"NAME":"BW-SHP10","GPIO":[0,148,0,207,158,21,0,0,0,17,0,0,0],"FLAG":0,"BASE":18}
* Based on datasheet from http://www.belling.com.cn/media/file_object/bel_product/BL09XX/datasheet/BL09XX_V1.1_en.pdf
*
* BL0939 - Energy (as in Sonoff Dual R3 v2)
* {"NAME":"Sonoff Dual R3 v2","GPIO":[32,0,0,0,0,0,0,0,0,576,225,0,0,0,0,0,0,0,0,0,0,3200,8128,224,0,0,0,0,160,161,0,0,0,0,0,0],"FLAG":0,"BASE":1}
* Based on datasheet from https://www.belling.com.cn/product_info.html?id=368
* See https://github.com/arendst/Tasmota/discussions/10793
\*********************************************************************************************/
#define XNRG_14 14
#define BL09XX_PREF 713 // =(4046*1*0,51*1000)/(1,218*1,218*(390*5+0,51)) = 713,105
#define BL09XX_UREF 17159 // =(79931*0,51*1000)/(1,218*(390*5+0,51)) = 17158,92
#define BL09XX_IREF 266013 // =(324004*1)/1,218 = 266013,14
#define BL09XX_PULSES_NOT_INITIALIZED -1
#define BL09XX_BUFFER_SIZE 36
#define BL0939_MODEL 39
#define BL0940_MODEL 40
#define BL0939_ADDRESS 0x05
#define BL0940_ADDRESS 0x00
#define BL09XX_WRITE_COMMAND 0xA0 // 0xA8 according to documentation
#define BL09XX_REG_I_FAST_RMS_CTRL 0x10
#define BL09XX_REG_MODE 0x18
#define BL09XX_REG_SOFT_RESET 0x19
#define BL09XX_REG_USR_WRPROT 0x1A
#define BL09XX_REG_TPS_CTRL 0x1B
#define BL09XX_READ_COMMAND 0x50 // 0x58 according to documentation
#define BL09XX_FULL_PACKET 0xAA
#define BL09XX_PACKET_HEADER 0x55 // 0x58 according to documentation
#include <TasmotaSerial.h>
TasmotaSerial *Bl09XXSerial = nullptr;
struct BL09XX {
long voltage = 0;
long current[2] = { 0, };
long power[2] = { 0, };
long power_cycle_first = 0;
long cf_pulses[2] = { 0, };
long cf_pulses_last_time[2] = { BL09XX_PULSES_NOT_INITIALIZED, BL09XX_PULSES_NOT_INITIALIZED};
float temperature;
uint8_t address;
uint8_t model;
int byte_counter = 0;
uint16_t tps1 = 0;
uint8_t *rx_buffer = nullptr;
bool received = false;
} Bl09XX;
const uint8_t bl09xx_init[5][6] = {
{ BL09XX_WRITE_COMMAND, BL09XX_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38 }, // Reset to default
{ BL09XX_WRITE_COMMAND, BL09XX_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0 }, // Enable User Operation Write
{ BL09XX_WRITE_COMMAND, BL09XX_REG_MODE, 0x00, 0x10, 0x00, 0x37 }, // 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS
{ BL09XX_WRITE_COMMAND, BL09XX_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE }, // 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS
{ BL09XX_WRITE_COMMAND, BL09XX_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B }}; // 0x181C = Half cycle, Fast RMS threshold 6172
void Bl09XXReceived(void) {
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
// Sample from BL0940 (single channel)
// 55 F2 03 00 00 00 00 7E 02 00 D4 B0 72 AC 01 00 00 00 00 02 01 00 00 00 00 00 00 00 BA 01 00 FE 03 00 83
// 55 88 02 00 49 00 00 FE 02 00 AF EF 71 D2 01 00 EB FF FF 49 01 00 00 00 00 02 00 00 CF 01 00 FE 03 00 9F
// 55 B9 33 00 DE 45 00 94 02 00 CF E4 70 63 02 00 6C 4C 00 13 01 00 09 00 00 00 00 00 E4 01 00 FE 03 00 72
// Hd IFRms--- Current- Reserved Voltage- Reserved Power--- Reserved CF------ Reserved TPS1---- TPS2---- Ck
// Sample from BL0939 (dual channel)
// 55 82 03 00 00 00 00 1E 15 01 65 80 3E E5 C6 00 00 00 00 50 B1 00 00 00 00 00 00 00 F9 01 00 FE 03 00 D2 = U 4096101, I 0/70942, P 0/45392, C 0/0, T 505
// 55 E6 02 00 00 00 00 37 15 01 0F 83 3E F4 C7 00 00 00 00 69 B1 00 00 00 00 01 00 00 FA 01 00 FE 03 00 7E = U 4096783, I 0/70967, P 0/45417, C 0/1, T 506
// 55 29 03 00 00 00 00 27 15 01 3A 86 3E AF C8 00 00 00 00 67 B1 00 00 00 00 01 00 00 FA 01 00 FE 03 00 62 = U 4097594, I 0/70951, P 0/45415, C 0/1, T 506
// 55 04 03 00 00 00 00 D6 14 01 7D 8E 3E 25 C7 00 00 00 00 53 B1 00 00 00 00 01 00 00 F9 01 00 FE 03 00 2E = U 4099709, I 0/70870, P 0/45395, C 0/1, T 505
// Hd IFRms-A- CurrentA CurrentB Voltage- IFRms-B- PowerA-- PowerB-- CF-A---- CF-B---- TPS1---- TPS2---- Ck
uint16_t tps1 = Bl09XX.rx_buffer[29] << 8 | Bl09XX.rx_buffer[28]; // TPS1 unsigned
if ((Bl09XX.rx_buffer[0] != BL09XX_PACKET_HEADER) || // Bad header
(Bl09XX.tps1 && ((tps1 < (Bl09XX.tps1 -10)) || (tps1 > (Bl09XX.tps1 +10)))) // Invalid temperature change
) {
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("BL9: Invalid data"));
return;
}
Bl09XX.tps1 = tps1;
float t = ((170.0f/448.0f)*(((float)Bl09XX.tps1/2.0f)-32.0f))-45.0f;
Bl09XX.temperature = ConvertTemp(t);
Bl09XX.voltage = Bl09XX.rx_buffer[12] << 16 | Bl09XX.rx_buffer[11] << 8 | Bl09XX.rx_buffer[10]; // V_RMS unsigned
int32_t tmp;
Bl09XX.current[0] = Bl09XX.rx_buffer[6] << 16 | Bl09XX.rx_buffer[5] << 8 | Bl09XX.rx_buffer[4]; // IA_RMS unsigned
tmp = Bl09XX.rx_buffer[18] << 24 | Bl09XX.rx_buffer[17] << 16 | Bl09XX.rx_buffer[16] << 8; // WATT_A signed
Bl09XX.power[0] = abs(tmp >> 8); // WATT_A unsigned
tmp = Bl09XX.rx_buffer[24] << 24 | Bl09XX.rx_buffer[23] << 16 | Bl09XX.rx_buffer[22] << 8; // CFA_CNT signed
Bl09XX.cf_pulses[0] = abs(tmp >> 8); // CFA_CNT unsigned
if (Energy.phase_count > 1) {
Bl09XX.current[1] = Bl09XX.rx_buffer[9] << 16 | Bl09XX.rx_buffer[8] << 8 | Bl09XX.rx_buffer[7]; // IB_RMS unsigned
tmp = Bl09XX.rx_buffer[21] << 24 | Bl09XX.rx_buffer[20] << 16 | Bl09XX.rx_buffer[19] << 8; // WATT_B signed
Bl09XX.power[1] = abs(tmp >> 8); // WATT_B unsigned
tmp = Bl09XX.rx_buffer[27] << 24 | Bl09XX.rx_buffer[26] << 16 | Bl09XX.rx_buffer[25] << 8; // CFB_CNT signed
Bl09XX.cf_pulses[1] = abs(tmp >> 8); // CFB_CNT unsigned
}
//AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("BL9: U %d, I %d/%d, P %d/%d, C %d/%d, T %d"),
// Bl09XX.voltage, Bl09XX.current[0], Bl09XX.current[1], Bl09XX.power[0], Bl09XX.power[1], Bl09XX.cf_pulses[0], Bl09XX.cf_pulses[1], Bl09XX.tps1);
if (Energy.power_on) { // Powered on
Energy.voltage[0] = (float)Bl09XX.voltage / Settings->energy_voltage_calibration;
//AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("BL9: Voltage %f, Temp %f"), Energy.voltage[0], Bl09XX.temperature);
for ( int chan = 0 ; chan < Energy.phase_count ; chan++ ) {
if (Bl09XX.power[chan] > Settings->energy_power_calibration) { // We need at least 1W
Energy.active_power[chan] = (float)Bl09XX.power[chan] / Settings->energy_power_calibration;
Energy.current[chan] = (float)Bl09XX.current[chan] / Settings->energy_current_calibration;
//AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("BL9: Chan[%d] I %f, P %f"), chan, Energy.current[chan], Energy.active_power[chan]);
} else {
Energy.active_power[chan] = 0;
Energy.current[chan] = 0;
//AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("BL9: Chan[%d] I zero, P zero"), chan);
}
}
} else { // Powered off
// Bl09XX.power_cycle_first = 0;
Energy.voltage[0] = 0;
Energy.active_power[0] = Energy.active_power[1] = 0;
Energy.current[0] = Energy.current[1] = 0;
}
}
void Bl09XXSerialInput(void) {
while (Bl09XXSerial->available()) {
yield();
uint8_t serial_in_byte = Bl09XXSerial->read();
if (!Bl09XX.received && (BL09XX_PACKET_HEADER == serial_in_byte)) {
Bl09XX.received = true;
Bl09XX.byte_counter = 0;
}
if (Bl09XX.received) {
Bl09XX.rx_buffer[Bl09XX.byte_counter++] = serial_in_byte;
if (BL09XX_BUFFER_SIZE == Bl09XX.byte_counter) {
AddLogBuffer(LOG_LEVEL_DEBUG_MORE, Bl09XX.rx_buffer, BL09XX_BUFFER_SIZE -1);
uint8_t checksum = BL09XX_READ_COMMAND | Bl09XX.address;
for (uint32_t i = 0; i < BL09XX_BUFFER_SIZE -2; i++) { checksum += Bl09XX.rx_buffer[i]; }
checksum ^= 0xFF;
if (checksum == Bl09XX.rx_buffer[34]) {
Energy.data_valid[0] = 0;
Bl09XXReceived();
Bl09XX.received = false;
return;
} else {
//AddLog(LOG_LEVEL_DEBUG, PSTR("BL9: " D_CHECKSUM_FAILURE "received 0x%02X instead of 0x%02X"), Bl09XX.rx_buffer[34], checksum);
do { // Sync buffer with data (issue #1907 and #3425)
memmove(Bl09XX.rx_buffer, Bl09XX.rx_buffer +1, BL09XX_BUFFER_SIZE -1);
Bl09XX.byte_counter--;
} while ((Bl09XX.byte_counter > 1) && (BL09XX_PACKET_HEADER != Bl09XX.rx_buffer[0]));
if (BL09XX_PACKET_HEADER != Bl09XX.rx_buffer[0]) {
//AddLog(LOG_LEVEL_DEBUG, PSTR("BL9: " D_CHECKSUM_FAILURE));
Bl09XX.received = false;
Bl09XX.byte_counter = 0;
}
}
}
}
}
}
/********************************************************************************************/
void Bl09XXEverySecond(void) {
if (Energy.data_valid[0] > ENERGY_WATCHDOG) {
Bl09XX.voltage = 0;
memset(Bl09XX.current,0,sizeof(Bl09XX.current));
memset(Bl09XX.power,0,sizeof(Bl09XX.power));
} else {
/*
// Calculate energy by using active power
if (Energy.active_power[0]) {
Energy.kWhtoday_delta += (Energy.active_power[0] * 1000) / 36;
EnergyUpdateToday();
}
*/
// Calculate energy by using active energy pulse count
bool update_today = false;
for (int chan = 0 ; chan < Energy.phase_count ; chan++ ) {
if (BL09XX_PULSES_NOT_INITIALIZED == Bl09XX.cf_pulses_last_time[chan]) {
Bl09XX.cf_pulses_last_time[chan] = Bl09XX.cf_pulses[chan]; // Init after restart
} else {
uint32_t cf_pulses = 0;
if (Bl09XX.cf_pulses[chan] < Bl09XX.cf_pulses_last_time[chan]) { // Rolled over after 0xFFFFFF (16777215) pulses
cf_pulses = (0x1000000 - Bl09XX.cf_pulses_last_time[chan]) + Bl09XX.cf_pulses[chan];
} else {
cf_pulses = Bl09XX.cf_pulses[chan] - Bl09XX.cf_pulses_last_time[chan];
}
if (cf_pulses && Energy.active_power[chan]) {
uint32_t watt256 = (1638400 * 256) / Settings->energy_power_calibration;
uint32_t delta = (cf_pulses * watt256) / 36;
if (delta <= (4000 * 1000 / 36)) { // max load for SHP10: 4.00kW (3.68kW)
Bl09XX.cf_pulses_last_time[chan] = Bl09XX.cf_pulses[chan];
} else {
AddLog(LOG_LEVEL_DEBUG, PSTR("BL9: Overload [%d] %d"), chan, delta);
Bl09XX.cf_pulses_last_time[chan] = BL09XX_PULSES_NOT_INITIALIZED;
}
update_today = true;
}
}
}
if (update_today)
EnergyUpdateToday();
}
// AddLog(LOG_LEVEL_DEBUG, PSTR("BL9: Poll"));
Bl09XXSerial->flush();
Bl09XXSerial->write(BL09XX_READ_COMMAND | Bl09XX.address);
Bl09XXSerial->write(BL09XX_FULL_PACKET);
}
void Bl09XXSnsInit(void) {
// Software serial init needs to be done here as earlier (serial) interrupts may lead to Exceptions
if (XNRG_14 != TasmotaGlobal.energy_driver) return;
int rx_pin = Pin((BL0939_MODEL == Bl09XX.model) ? GPIO_BL0939_RX : GPIO_BL0940_RX);
Bl09XXSerial = new TasmotaSerial(rx_pin, Pin(GPIO_TXD), 1);
if (Bl09XXSerial->begin(4800, 1)) {
if (Bl09XXSerial->hardwareSerial()) {
ClaimSerial();
}
if (HLW_UREF_PULSE == Settings->energy_voltage_calibration) {
Settings->energy_voltage_calibration = BL09XX_UREF;
Settings->energy_current_calibration = BL09XX_IREF;
Settings->energy_power_calibration = BL09XX_PREF;
}
Energy.use_overtemp = true; // Use global temperature for overtemp detection
for (uint32_t i = 0; i < 5; i++) {
Bl09XXSerial->write(bl09xx_init[i][0] | Bl09XX.address);
for (uint32_t j = 1; j < 6; j++) {
Bl09XXSerial->write(bl09xx_init[i][j]);
// Bl09XXSerial->write(pgm_read_byte(bl09xx_init + (6 * i) + j)); // Wrong byte order!
}
delay(1);
}
} else {
TasmotaGlobal.energy_driver = ENERGY_NONE;
}
}
void Bl09XXDrvInit(void) {
if (PinUsed(GPIO_BL0939_RX) && PinUsed(GPIO_TXD)) {
Bl09XX.model = BL0939_MODEL;
Bl09XX.address = BL0939_ADDRESS;
} else if (PinUsed(GPIO_BL0940_RX) && PinUsed(GPIO_TXD)) {
Bl09XX.model = BL0940_MODEL;
Bl09XX.address = BL0940_ADDRESS;
}
if (Bl09XX.model) {
Bl09XX.rx_buffer = (uint8_t*)(malloc(BL09XX_BUFFER_SIZE));
if (Bl09XX.rx_buffer != nullptr) {
TasmotaGlobal.energy_driver = XNRG_14;
Energy.voltage_common = true; // Use common voltage
Energy.frequency_common = true; // Use common frequency
Energy.use_overtemp = true; // Use global temperature for overtemp detection
if (BL0939_MODEL == Bl09XX.model) {
Energy.phase_count = 2; // Handle two channels as two phases
AddLog(LOG_LEVEL_DEBUG, PSTR("BL9: BL0939 driver enabled, TX:%d, RX:%d"), Pin(GPIO_TXD), Pin(GPIO_BL0939_RX));
} else {
Energy.phase_count = 1; // Handle 1 channel
AddLog(LOG_LEVEL_DEBUG, PSTR("BL9: BL0940 driver enabled, TX:%d, RX:%d"), Pin(GPIO_TXD), Pin(GPIO_BL0940_RX));
}
}
}
}
bool Bl09XXCommand(void) {
bool serviced = true;
uint32_t channel = (2 == XdrvMailbox.index) && (Energy.phase_count > 1) ? 1 : 0;
uint32_t value = (uint32_t)(CharToFloat(XdrvMailbox.data) * 100); // 1.23 = 123
if (CMND_POWERSET == Energy.command_code) {
if (XdrvMailbox.data_len && Bl09XX.power[channel]) {
Settings->energy_power_calibration = (Bl09XX.power[channel] * 100) / value;
}
}
else if (CMND_VOLTAGESET == Energy.command_code) {
if (XdrvMailbox.data_len && Bl09XX.voltage) {
Settings->energy_voltage_calibration = (Bl09XX.voltage * 100) / value;
}
}
else if (CMND_CURRENTSET == Energy.command_code) {
if (XdrvMailbox.data_len && Bl09XX.current[channel]) {
Settings->energy_current_calibration = Bl09XX.current[channel] / value;
}
}
else serviced = false; // Unknown command
return serviced;
}
void Bl09XXShow(bool json) {
if (json) {
ResponseAppend_P(JSON_SNS_F_TEMP, "BL09XX", Settings->flag2.temperature_resolution, &Bl09XX.temperature);
if (0 == TasmotaGlobal.tele_period) {
#ifdef USE_DOMOTICZ
DomoticzFloatSensor(DZ_TEMP, Bl09XX.temperature);
#endif // USE_DOMOTICZ
#ifdef USE_KNX
KnxSensor(KNX_TEMPERATURE, Bl09XX.temperature);
#endif // USE_KNX
}
#ifdef USE_WEBSERVER
} else {
WSContentSend_Temp("", Bl09XX.temperature);
#endif // USE_WEBSERVER
}
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xnrg14(uint8_t function) {
bool result = false;
switch (function) {
case FUNC_LOOP:
if (Bl09XXSerial) { Bl09XXSerialInput(); }
break;
case FUNC_EVERY_SECOND:
Bl09XXEverySecond();
break;
case FUNC_JSON_APPEND:
Bl09XXShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
Bl09XXShow(0);
break;
#endif // USE_WEBSERVER
case FUNC_COMMAND:
result = Bl09XXCommand();
break;
case FUNC_INIT:
Bl09XXSnsInit();
break;
case FUNC_PRE_INIT:
Bl09XXDrvInit();
break;
}
return result;
}
#endif // USE_BL09XX
#endif // USE_ENERGY_SENSOR

View File

@ -238,7 +238,7 @@ a_features = [[
"USE_KEELOQ","USE_HRXL","USE_SONOFF_D1","USE_HDC1080",
"USE_IAQ","USE_DISPLAY_SEVENSEG","USE_AS3935","USE_PING",
"USE_WINDMETER","USE_OPENTHERM","USE_THERMOSTAT","USE_VEML6075",
"USE_VEML7700","USE_MCP9808","USE_BL0940","USE_TELEGRAM",
"USE_VEML7700","USE_MCP9808","USE_BL09XX","USE_TELEGRAM",
"USE_HP303B","USE_TCP_BRIDGE","USE_TELEINFO","USE_LMT01",
"USE_PROMETHEUS","USE_IEM3000","USE_DYP","USE_I2S_AUDIO",
"USE_MLX90640","USE_VL53L1X","USE_MIEL_HVAC","USE_WE517",