2022-02-15 09:41:25 +00:00
/*
2022-02-16 14:41:37 +00:00
xnrg_22_bl6523 . ino - BL6523 based Watt hour meter support for Tasmota
2022-02-15 09:41:25 +00:00
2022-02-16 14:41:37 +00:00
Copyright ( C ) 2022 Jeevas Vasudevan
2022-02-15 09:41:25 +00:00
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/>.
*/
2022-02-16 14:41:37 +00:00
2022-02-15 09:41:25 +00:00
# ifdef USE_ENERGY_SENSOR
# ifdef USE_BL6523
/*********************************************************************************************\
* Chinese BL6523 based Watt hour meter
*
2022-10-29 18:08:06 +01:00
* This meter provides accurate Voltage , Frequency , Ampere , Wattage , Power Factor , KWh
2022-02-15 09:41:25 +00:00
* To use Tasmota the user needs to add an ESP8266 or ESP32
* Three lines need to be connected via 1 KOhh resistors to ESP from the main board ( RX , TX GND )
2022-10-29 18:08:06 +01:00
*
2022-02-15 09:41:25 +00:00
* Connection Eg ( ESP8266 ) - Non - Isolated :
* BL6523 RX - > 1 KOhm - > ESP IO4 ( D2 ) ( Should be Input Capable )
* BL6523 TX - > 1 KOhm - > ESP IO5 ( D1 ) ( Should be Input Capable )
* BL6523 GND - > ESP GND
2022-10-29 18:08:06 +01:00
*
2022-02-15 09:41:25 +00:00
* Connection Eg ( ESP32 ) - Non - Isolated :
* BL6523 RX - > 1 KOhm - > ESP IO4 ( Should be Input Capable )
* BL6523 TX - > 1 KOhm - > ESP IO5 ( Should be Input Capable )
* BL6523 GND - > ESP GND
2022-10-29 18:08:06 +01:00
*
2022-02-15 09:41:25 +00:00
* To build add the below to user_config_override . h
2022-02-15 15:32:29 +00:00
* # define USE_ENERGY_SENSOR // Enable Energy sensor framework
* # define USE_BL6523 // Add support for Chinese BL6523 based Watt hour meter (+1k code)¸
2022-10-29 18:08:06 +01:00
*
2022-02-15 09:41:25 +00:00
* After Installation use the below template sample :
* { " NAME " : " BL6523 Smart Meter " , " GPIO " : [ 0 , 0 , 0 , 0 , 7488 , 7520 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] , " FLAG " : 0 , " BASE " : 18 }
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# define XNRG_22 22
# include <TasmotaSerial.h>
# define BL6523_RX_DATASET_SIZE 2
# define BL6523_TX_DATASET_SIZE 4
# define BL6523_BAUD 4800
# define BL6523_REG_AMPS 0x05
# define BL6523_REG_VOLTS 0x07
# define BL6523_REG_FREQ 0x09
# define BL6523_REG_WATTS 0x0A
# define BL6523_REG_POWF 0x08
# define BL6523_REG_WATTHR 0x0C
2022-02-15 15:32:29 +00:00
# define SINGLE_PHASE 0
2022-02-16 13:33:11 +00:00
# define RX_WAIT 100
2022-02-16 13:01:27 +00:00
# define BL6523_IREF 297899
# define BL6523_UREF 13304
# define BL6523_FREF 3907
# define BL6523_PREF 707
# define BL6523_PWHRREF_D 33 // Substract this from BL6523_PREF to get WattHr Div.
2022-02-15 09:41:25 +00:00
TasmotaSerial * Bl6523RxSerial ;
TasmotaSerial * Bl6523TxSerial ;
struct BL6523
{
uint8_t type = 1 ;
uint8_t valid = 0 ;
uint8_t got_data_stone = 0 ;
bool discovery_triggered = false ;
} Bl6523 ;
bool Bl6523ReadData ( void )
{
2022-02-16 13:33:11 +00:00
uint32_t powf_word = 0 , powf_buf = 0 , i = 0 ;
2022-02-15 15:32:29 +00:00
float powf = 0.0f ;
2022-02-15 09:41:25 +00:00
if ( ! Bl6523RxSerial - > available ( ) )
{
2022-02-15 16:04:28 +00:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " BL6:No Rx Data available " ) ) ;
2022-02-15 09:41:25 +00:00
return false ;
}
while ( ( Bl6523RxSerial - > peek ( ) ! = 0x35 ) & & Bl6523RxSerial - > available ( ) )
{
Bl6523RxSerial - > read ( ) ;
}
if ( Bl6523RxSerial - > available ( ) < BL6523_RX_DATASET_SIZE )
{
2022-02-15 16:04:28 +00:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " BL6:Rx less than expected " ) ) ;
2022-02-15 09:41:25 +00:00
return false ;
}
uint8_t rx_buffer [ BL6523_RX_DATASET_SIZE ] ;
Bl6523RxSerial - > readBytes ( rx_buffer , BL6523_RX_DATASET_SIZE ) ;
Bl6523RxSerial - > flush ( ) ; // Make room for another burst
AddLogBuffer ( LOG_LEVEL_DEBUG_MORE , rx_buffer , BL6523_RX_DATASET_SIZE ) ;
2022-10-29 18:08:06 +01:00
2022-02-16 13:33:11 +00:00
i = 0 ;
2022-02-15 09:41:25 +00:00
while ( Bl6523TxSerial - > available ( ) < BL6523_TX_DATASET_SIZE )
{
// sleep till TX buffer is full
2022-02-16 13:33:11 +00:00
delay ( 10 ) ;
if ( i + + > RX_WAIT ) {
2022-02-16 13:42:22 +00:00
break ;
2022-02-16 13:33:11 +00:00
}
2022-02-15 09:41:25 +00:00
}
2022-10-29 18:08:06 +01:00
2022-02-15 09:41:25 +00:00
uint8_t tx_buffer [ BL6523_TX_DATASET_SIZE ] ;
Bl6523TxSerial - > readBytes ( tx_buffer , BL6523_TX_DATASET_SIZE ) ;
Bl6523TxSerial - > flush ( ) ; // Make room for another burst
2022-10-29 18:08:06 +01:00
2022-02-15 09:41:25 +00:00
AddLogBuffer ( LOG_LEVEL_DEBUG_MORE , tx_buffer , BL6523_TX_DATASET_SIZE ) ;
2022-10-29 18:08:06 +01:00
2022-02-15 09:41:25 +00:00
/* Checksum: ( Addr+Data_L+Data_M+Data_H) & 0xFF, then byte invert */
uint8_t crc = rx_buffer [ 1 ] ; //Addr
for ( uint32_t i = 0 ; i < ( BL6523_TX_DATASET_SIZE - 1 ) ; i + + )
{
crc + = tx_buffer [ i ] ; //Add Data_L,Data_M and Data_H to Addr
}
crc & = 0xff ; // Bitwise AND 0xFF
crc = ~ crc ; // Invert the byte
if ( crc ! = tx_buffer [ BL6523_TX_DATASET_SIZE - 1 ] )
{
2022-02-15 16:04:28 +00:00
AddLog ( LOG_LEVEL_DEBUG_MORE , PSTR ( " BL6: " D_CHECKSUM_FAILURE ) ) ;
2022-10-29 18:08:06 +01:00
Bl6523TxSerial - > flush ( ) ;
Bl6523RxSerial - > flush ( ) ;
2022-02-15 09:41:25 +00:00
return false ;
}
/* WRITE DATA (format: command(write->0xCA) address data_low data_mid data_high checksum )
WRITE Sample ( RX ) :
RX : CA 3 E 55 00 00 6 C ( WRPROT - allow )
2022-10-29 18:08:06 +01:00
RX : CA 14 00 00 10 DB ( MODE )
2022-02-15 09:41:25 +00:00
RX : CA 15 04 00 00 E6 ( GAIN - IB 16 x gain )
RX : CA 19 08 00 00 DE ( WA_CFDIV )
RX : CA 3 E AA 00 00 17 ( WRPROT - disable )
*/
/* READ DATA (format: command(read->0x35) address data_low data_mid data_high checksum )
READ Sample ( RX - TX ) Data :
RX : 35 05 TX : E4 00 00 16 ( IA rms )
RX : 35 07 TX : D5 A3 2 E 52 ( V rms )
2022-10-29 18:08:06 +01:00
RX : 35 09 TX : F0 FB 02 09 ( FREQ )
RX : 35 0 A TX : 00 00 00 F5 ( WATT )
2022-02-15 09:41:25 +00:00
RX : 35 08 TX : 00 00 00 F7 ( PF )
RX : 35 0 C TX : 00 00 00 F3 ( WATT_HR )
*/
switch ( rx_buffer [ 1 ] ) {
case BL6523_REG_AMPS :
2023-01-25 16:05:48 +00:00
Energy - > current [ SINGLE_PHASE ] = ( float ) ( ( tx_buffer [ 2 ] < < 16 ) | ( tx_buffer [ 1 ] < < 8 ) | tx_buffer [ 0 ] ) / EnergyGetCalibration ( ENERGY_CURRENT_CALIBRATION ) ; // 1.260 A
2022-02-15 09:41:25 +00:00
break ;
case BL6523_REG_VOLTS :
2023-01-25 16:05:48 +00:00
Energy - > voltage [ SINGLE_PHASE ] = ( float ) ( ( tx_buffer [ 2 ] < < 16 ) | ( tx_buffer [ 1 ] < < 8 ) | tx_buffer [ 0 ] ) / EnergyGetCalibration ( ENERGY_VOLTAGE_CALIBRATION ) ; // 230.2 V
2022-02-15 09:41:25 +00:00
break ;
case BL6523_REG_FREQ :
2023-01-25 16:05:48 +00:00
Energy - > frequency [ SINGLE_PHASE ] = ( float ) ( ( tx_buffer [ 2 ] < < 16 ) | ( tx_buffer [ 1 ] < < 8 ) | tx_buffer [ 0 ] ) / EnergyGetCalibration ( ENERGY_FREQUENCY_CALIBRATION ) ; // 50.0 Hz
2022-10-29 18:08:06 +01:00
break ;
2022-02-15 09:41:25 +00:00
case BL6523_REG_WATTS :
2023-01-25 16:05:48 +00:00
Energy - > active_power [ SINGLE_PHASE ] = ( float ) ( ( tx_buffer [ 2 ] < < 16 ) | ( tx_buffer [ 1 ] < < 8 ) | tx_buffer [ 0 ] ) / EnergyGetCalibration ( ENERGY_POWER_CALIBRATION ) ; // -196.3 W
2022-02-15 09:41:25 +00:00
break ;
case BL6523_REG_POWF :
2022-02-15 15:32:29 +00:00
/* Power factor =(sign bit)*((PF[22]× 2^- 1) + ( PF[21]× 2^- 2) + 。。。)
Eg . , reg value 0x7FFFFF ( HEX ) - > PF 1 , 0x800000 ( HEX ) - > - 1 , 0x400000 ( HEX ) - > 0.5
*/
powf = 0.0f ;
powf_buf = ( ( tx_buffer [ 2 ] < < 16 ) | ( tx_buffer [ 1 ] < < 8 ) | tx_buffer [ 0 ] ) ;
powf_word = ( powf_buf > > 23 ) ? ~ ( powf_buf & 0x7fffff ) : powf_buf & 0x7fffff ; //Extract the 23 bits and invert if sign bit(24) is set
for ( int i = 0 ; i < 23 ; i + + ) { // Accumulate powf from 23 bits
2022-10-29 18:08:06 +01:00
powf + = ( ( powf_word > > ( 22 - i ) ) * pow ( 2 , ( 0 - ( i + 1 ) ) ) ) ;
2022-02-15 15:32:29 +00:00
powf_word = powf_word & ( 0x7fffff > > ( 1 + i ) ) ;
}
powf = ( powf_buf > > 23 ) ? ( 0.0f - ( powf ) ) : powf ; // Negate if sign bit(24) is set
2023-01-24 15:54:03 +00:00
Energy - > power_factor [ SINGLE_PHASE ] = powf ;
2022-02-15 09:41:25 +00:00
break ;
case BL6523_REG_WATTHR :
2023-01-25 16:05:48 +00:00
Energy - > import_active [ SINGLE_PHASE ] = ( float ) ( ( tx_buffer [ 2 ] < < 16 ) | ( tx_buffer [ 1 ] < < 8 ) | tx_buffer [ 0 ] ) / ( EnergyGetCalibration ( ENERGY_POWER_CALIBRATION ) - BL6523_PWHRREF_D ) ; // 6.216 kWh => used in EnergyUpdateTotal()
2022-02-15 09:41:25 +00:00
break ;
2022-10-29 18:08:06 +01:00
default :
break ;
2022-02-15 09:41:25 +00:00
}
2023-01-24 15:54:03 +00:00
Energy - > data_valid [ SINGLE_PHASE ] = 0 ;
2022-02-15 15:32:29 +00:00
EnergyUpdateTotal ( ) ;
2022-02-15 09:41:25 +00:00
if ( ! Bl6523 . discovery_triggered )
{
TasmotaGlobal . discovery_counter = 1 ; // force TasDiscovery()
Bl6523 . discovery_triggered = true ;
}
return true ;
2022-10-29 18:08:06 +01:00
2022-02-15 09:41:25 +00:00
}
/*********************************************************************************************/
void Bl6523Update ( void )
{ // Every 250 millisecond
if ( Bl6523ReadData ( ) )
{
Bl6523 . valid = 60 ;
}
else
{
if ( Bl6523 . valid ) {
Bl6523 . valid - - ;
2022-10-29 18:08:06 +01:00
}
2022-02-15 09:41:25 +00:00
}
}
/*********************************************************************************************/
void Bl6523Init ( void )
{
2022-10-29 18:08:06 +01:00
2022-02-15 15:32:29 +00:00
Bl6523 . type = 0 ;
2022-02-15 09:41:25 +00:00
Bl6523RxSerial = new TasmotaSerial ( Pin ( GPIO_BL6523_RX ) , - 1 , 1 ) ;
Bl6523TxSerial = new TasmotaSerial ( Pin ( GPIO_BL6523_TX ) , - 1 , 1 ) ;
if ( ( Bl6523RxSerial - > begin ( BL6523_BAUD ) ) & & ( Bl6523TxSerial - > begin ( BL6523_BAUD ) ) )
{
if ( Bl6523RxSerial - > hardwareSerial ( ) )
{
ClaimSerial ( ) ;
}
if ( Bl6523TxSerial - > hardwareSerial ( ) )
{
ClaimSerial ( ) ;
}
2023-12-28 16:25:01 +00:00
# ifdef ESP32
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " BL6: Serial UART%d and UART%d " ) , Bl6523RxSerial - > getUart ( ) , Bl6523TxSerial - > getUart ( ) ) ;
# endif
2022-02-15 09:41:25 +00:00
Bl6523 . type = 1 ;
2023-01-24 15:54:03 +00:00
Energy - > phase_count = 1 ;
2022-02-15 16:04:28 +00:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " BL6:Init Success " ) ) ;
2022-02-15 09:41:25 +00:00
}
2022-02-15 15:32:29 +00:00
else
{
2022-02-15 16:04:28 +00:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " BL6:Init Failure! " ) ) ;
2022-02-15 15:32:29 +00:00
TasmotaGlobal . energy_driver = ENERGY_NONE ;
}
2022-10-29 18:08:06 +01:00
2022-02-15 09:41:25 +00:00
}
2022-02-16 13:01:27 +00:00
bool Bl6523Command ( void ) {
bool serviced = true ;
int32_t value = ( int32_t ) ( CharToFloat ( XdrvMailbox . data ) * 1000 ) ; // 1.234 = 1234, -1.234 = -1234
uint32_t abs_value = abs ( value ) / 10 ; // 1.23 = 123, -1.23 = 123
2024-08-21 15:27:43 +01:00
if ( ( CMND_POWERCAL = = Energy - > command_code ) | |
( CMND_VOLTAGECAL = = Energy - > command_code ) | |
( CMND_CURRENTCAL = = Energy - > command_code ) ) {
2022-02-16 13:01:27 +00:00
// Service in xdrv_03_energy.ino
}
2023-01-24 15:54:03 +00:00
else if ( CMND_POWERSET = = Energy - > command_code ) {
2022-02-16 13:01:27 +00:00
if ( XdrvMailbox . data_len ) {
2024-06-30 16:26:39 +01:00
if ( ( abs_value > 100 ) & & ( abs_value < 2000000 ) ) { // Between 1.00 and 20000.00 W
2022-10-29 18:08:06 +01:00
XdrvMailbox . payload = abs_value ;
2022-02-16 13:01:27 +00:00
}
}
}
2023-01-24 15:54:03 +00:00
else if ( CMND_VOLTAGESET = = Energy - > command_code ) {
2022-02-16 13:01:27 +00:00
if ( XdrvMailbox . data_len ) {
2024-06-30 16:26:39 +01:00
if ( ( abs_value > 10000 ) & & ( abs_value < 40000 ) ) { // Between 100.00 and 400.00 V
2022-10-29 18:08:06 +01:00
XdrvMailbox . payload = abs_value ;
2022-02-16 13:01:27 +00:00
}
}
}
2023-01-24 15:54:03 +00:00
else if ( CMND_CURRENTSET = = Energy - > command_code ) {
2022-02-16 13:01:27 +00:00
if ( XdrvMailbox . data_len ) {
2024-06-30 16:26:39 +01:00
if ( ( abs_value > 1000 ) & & ( abs_value < 10000000 ) ) { // Between 10.00 mA and 100.00000 A
2022-10-29 18:08:06 +01:00
XdrvMailbox . payload = abs_value ;
2022-02-16 13:01:27 +00:00
}
}
}
2023-01-24 15:54:03 +00:00
else if ( CMND_FREQUENCYSET = = Energy - > command_code ) {
2022-02-16 13:01:27 +00:00
if ( XdrvMailbox . data_len ) {
if ( ( abs_value > 4500 ) & & ( abs_value < 6500 ) ) { // Between 45.00 and 65.00 Hz
2022-10-29 18:08:06 +01:00
XdrvMailbox . payload = abs_value ;
2022-02-16 13:01:27 +00:00
}
}
}
2023-01-24 15:54:03 +00:00
else if ( CMND_ENERGYCONFIG = = Energy - > command_code ) {
2022-02-16 13:01:27 +00:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " NRG: Config index %d, payload %d, value %d, data '%s' " ) ,
XdrvMailbox . index , XdrvMailbox . payload , value , XdrvMailbox . data ? XdrvMailbox . data : " null " ) ;
2023-01-24 15:54:03 +00:00
// EnergyConfig1 to 3 = Set Energy->current[channel] in A like 0.417 for 417mA
2022-02-16 13:01:27 +00:00
if ( ( XdrvMailbox . index > 0 ) & & ( XdrvMailbox . index < 4 ) ) {
//Bl6523.current[XdrvMailbox.index -1] = value;
}
2023-01-24 15:54:03 +00:00
// EnergyConfig4 to 6 = Set Energy->active_power[channel] in W like 100 for 100W
2022-02-16 13:01:27 +00:00
if ( ( XdrvMailbox . index > 3 ) & & ( XdrvMailbox . index < 7 ) ) {
//Bl6523.power[XdrvMailbox.index -4] = value;
}
}
else serviced = false ; // Unknown command
return serviced ;
}
2022-02-15 15:32:29 +00:00
void Bl6523DrvInit ( void )
2022-02-15 09:41:25 +00:00
{
2022-02-15 15:32:29 +00:00
if ( PinUsed ( GPIO_BL6523_RX ) & & PinUsed ( GPIO_BL6523_TX ) ) {
2022-02-15 16:04:28 +00:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " BL6:PreInit Success " ) ) ;
2022-02-15 15:32:29 +00:00
TasmotaGlobal . energy_driver = XNRG_22 ;
2023-01-25 16:05:48 +00:00
if ( HLW_PREF_PULSE = = EnergyGetCalibration ( ENERGY_POWER_CALIBRATION ) ) {
EnergySetCalibration ( ENERGY_POWER_CALIBRATION , BL6523_PREF ) ;
EnergySetCalibration ( ENERGY_VOLTAGE_CALIBRATION , BL6523_UREF ) ;
EnergySetCalibration ( ENERGY_CURRENT_CALIBRATION , BL6523_IREF ) ;
EnergySetCalibration ( ENERGY_FREQUENCY_CALIBRATION , BL6523_FREF ) ;
2022-02-16 13:01:27 +00:00
}
2022-02-15 15:32:29 +00:00
}
else
2022-02-15 09:41:25 +00:00
{
2022-02-15 16:04:28 +00:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " BL6:PreInit Failure! " ) ) ;
2022-02-15 15:32:29 +00:00
TasmotaGlobal . energy_driver = ENERGY_NONE ;
2022-02-15 09:41:25 +00:00
}
2022-10-29 18:08:06 +01:00
2022-02-15 09:41:25 +00:00
}
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2022-11-11 09:44:56 +00:00
bool Xnrg22 ( uint32_t function )
2022-02-15 09:41:25 +00:00
{
bool result = false ;
2022-10-29 18:08:06 +01:00
2022-02-15 09:41:25 +00:00
switch ( function )
{
case FUNC_EVERY_250_MSECOND :
Bl6523Update ( ) ;
break ;
2022-02-16 13:01:27 +00:00
case FUNC_COMMAND :
result = Bl6523Command ( ) ;
2022-10-29 18:08:06 +01:00
break ;
2022-02-15 15:32:29 +00:00
case FUNC_INIT :
Bl6523Init ( ) ;
2022-02-15 09:41:25 +00:00
break ;
2022-02-15 15:32:29 +00:00
case FUNC_PRE_INIT :
Bl6523DrvInit ( ) ;
2022-02-15 09:41:25 +00:00
break ;
}
2022-10-29 18:08:06 +01:00
2022-02-15 09:41:25 +00:00
return result ;
}
# endif // USE_BL6523
# endif // USE_ENERGY_SENSOR