2020-06-07 16:21:29 +01:00
/*
xnrg_14_bl0940 . ino - BL0940 energy sensor support for Tasmota
Copyright ( C ) 2020 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
2020-06-08 09:55:12 +01:00
# define BL0940_PREF 1430
# define BL0940_UREF 33000
# define BL0940_IREF 2750
2020-06-07 16:21:29 +01:00
# 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
2020-06-08 09:55:12 +01:00
2020-06-07 16:21:29 +01:00
# 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 ;
2020-06-08 10:04:45 +01:00
// long power_cycle_first = 0;
2020-06-08 09:55:12 +01:00
// long cf_pulses = 0;
2020-06-07 16:21:29 +01:00
float temperature ;
int byte_counter = 0 ;
2020-06-12 16:42:04 +01:00
uint16_t tps1 = 0 ;
2020-06-07 16:21:29 +01:00
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
2020-06-12 16:42:04 +01:00
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
) {
2020-06-07 16:21:29 +01:00
AddLog_P ( LOG_LEVEL_DEBUG , PSTR ( " BL9: Invalid data " ) ) ;
return ;
}
2020-06-12 16:42:04 +01:00
Bl0940 . tps1 = tps1 ;
float t = ( ( 170.0f / 448.0f ) * ( ( ( float ) Bl0940 . tps1 / 2.0f ) - 32.0f ) ) - 45.0f ;
Bl0940 . temperature = ConvertTemp ( t ) ;
2020-06-12 15:51:21 +01:00
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
// Bl0940.cf_pulses = Bl0940.rx_buffer[24] << 16 | Bl0940.rx_buffer[23] << 8 | Bl0940.rx_buffer[22]; // CF_CNT unsigned
2020-06-07 16:21:29 +01:00
2020-06-12 16:42:04 +01:00
AddLog_P2 ( LOG_LEVEL_DEBUG , PSTR ( " BL9: U %d, I %d, P %d, T %d " ) , Bl0940 . voltage , Bl0940 . current , Bl0940 . power , Bl0940 . tps1 ) ;
2020-06-12 15:51:21 +01:00
2020-06-07 16:21:29 +01:00
if ( Energy . power_on ) { // Powered on
Energy . voltage [ 0 ] = ( float ) Bl0940 . voltage / Settings . energy_voltage_calibration ;
2020-06-12 15:51:21 +01:00
if ( power & & ( Bl0940 . power > Settings . energy_power_calibration ) ) { // We need at least 1W
2020-06-07 16:21:29 +01:00
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
2020-06-08 10:04:45 +01:00
// Bl0940.power_cycle_first = 0;
2020-06-07 16:21:29 +01:00
Energy . voltage [ 0 ] = 0 ;
Energy . active_power [ 0 ] = 0 ;
Energy . current [ 0 ] = 0 ;
}
}
bool Bl0940SerialInput ( void ) {
while ( Bl0940Serial - > available ( ) ) {
yield ( ) ;
uint8_t serial_in_byte = Bl0940Serial - > read ( ) ;
2020-06-08 10:04:45 +01:00
if ( ! Bl0940 . received & & ( BL0940_PACKET_HEADER = = serial_in_byte ) ) {
2020-06-07 16:21:29 +01:00
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 true ;
} else {
2020-06-08 10:13:57 +01:00
// AddLog_P(LOG_LEVEL_DEBUG, PSTR("BL9: " D_CHECKSUM_FAILURE));
2020-06-07 16:21:29 +01:00
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 ] ) {
2020-06-08 10:13:57 +01:00
AddLog_P ( LOG_LEVEL_DEBUG , PSTR ( " BL9: " D_CHECKSUM_FAILURE ) ) ;
2020-06-07 16:21:29 +01:00
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 {
2020-06-07 16:59:54 +01:00
if ( Energy . active_power [ 0 ] ) {
Energy . kWhtoday_delta + = ( Energy . active_power [ 0 ] * 1000 ) / 36 ;
EnergyUpdateToday ( ) ;
2020-06-07 16:21:29 +01:00
}
}
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 , 2 ) ) {
if ( Bl0940Serial - > hardwareSerial ( ) ) {
ClaimSerial ( ) ;
}
if ( HLW_UREF_PULSE = = Settings . energy_voltage_calibration ) {
2020-06-08 09:55:12 +01:00
Settings . energy_voltage_calibration = BL0940_UREF ;
Settings . energy_current_calibration = BL0940_IREF ;
Settings . energy_power_calibration = BL0940_PREF ;
2020-06-07 16:21:29 +01:00
}
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 {
energy_flg = 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 ) {
energy_flg = 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 )
{
char temperature [ 33 ] ;
dtostrfd ( Bl0940 . temperature , Settings . flag2 . temperature_resolution , temperature ) ;
if ( json ) {
ResponseAppend_P ( JSON_SNS_TEMP , " BL0940 " , temperature ) ;
if ( 0 = = tele_period ) {
# ifdef USE_DOMOTICZ
DomoticzSensor ( DZ_TEMP , temperature ) ;
# endif // USE_DOMOTICZ
# ifdef USE_KNX
KnxSensor ( KNX_TEMPERATURE , Bl0940 . temperature ) ;
# endif // USE_KNX
}
# ifdef USE_WEBSERVER
} else {
WSContentSend_PD ( HTTP_SNS_TEMP , " " , temperature , TempUnit ( ) ) ;
# endif // USE_WEBSERVER
}
}
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
bool Xnrg14 ( uint8_t function )
{
bool result = false ;
switch ( function ) {
case FUNC_LOOP :
if ( Bl0940Serial ) { Bl0940SerialInput ( ) ; }
break ;
case FUNC_ENERGY_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