2022-10-06 22:17:04 +01:00
/*
xnrg_29_modbus . ino - Generic Modbus energy meter support for Tasmota
Copyright ( C ) 2022 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_MODBUS_ENERGY
/*********************************************************************************************\
2022-10-08 15:14:11 +01:00
* Generic Modbus energy meter
2022-10-06 22:17:04 +01:00
*
2022-10-08 15:14:11 +01:00
* Using a rule file called modbus allows to easy configure modbus energy monitor devices up to three phases .
2022-10-06 22:17:04 +01:00
*
2022-10-08 15:14:11 +01:00
* Value pair description :
* { " Name " : " SDM230 " , " Baud " : 2400 , " Config " : 8 N1 " , " Address " :1, " Function " :4, " Voltage " :0, " Current " :6, " Power " :12, " ApparentPower " :18, " ReactivePower " :24, " Factor " :30, " Frequency " :70, " Total " :342, " ExportActive " :0x004A}
* Modbus config parameters :
* Name - Name of energy monitoring device
2022-10-11 17:39:48 +01:00
* Baud - Baudrate of device modbus interface - optional . default is 9600
2022-10-08 15:14:11 +01:00
* Config - Serial config parameters like 8 N1 - 8 databits , No parity , 1 stop bit
2022-10-11 17:39:48 +01:00
* Address - Modbus device address entered as decimal ( 1 ) or hexadecimal ( 0x01 ) ) - optional default = 1
* Function - Modbus function code to access two registers - optional . default = 4
2022-10-08 15:14:11 +01:00
* Tasmota default embedded register names :
2022-10-11 17:39:48 +01:00
* Voltage - Voltage register entered as decimal or hexadecimal for one phase ( 0x0000 ) or up to three phases ( [ 0x0000 , 0x0002 , 0x0004 ] ) or
* Additional defined parameters
* Value pair description :
* { " R " : 0 , " T " : 0 , " M " : 1 }
* R - Modbus register entered as decimal or hexadecimal for one phase ( 0x0160 ) or up to three phases ( [ 0x0160 , 0x0162 , 0x0164 ] )
* T - Datatype - optional . default is 0 - float :
* 0 - float
* 1 = 2 - byte signed
* 2 = 4 - byte signed
* 3 = 2 - byte unsigned
* 4 = 4 - byte unsigned
2022-12-21 17:11:35 +00:00
* 5 = not used
* 6 = 4 - byte signed with swapped words
* 7 = not used
* 8 = 4 - byte unsigned with swapped words
2022-10-11 17:39:48 +01:00
* M - Divider allowing to devide the read register by 1 , 10 , 100 , 1000 etc . - optional . default = 1
* Current - Current register entered as decimal or hexadecimal for one phase ( 0x0006 ) or up to three phases ( [ 0x0006 , 0x0008 , 0x000A ] ) or
* See additional defines like voltage .
* Power - Active power register entered as decimal or hexadecimal for one phase ( 0x000C ) or up to three phases ( [ 0x000C , 0x000E , 0x0010 ] ) or
* See additional defines like voltage .
* ApparentPower - Apparent power register entered as decimal or hexadecimal for one phase ( 0x000C ) or up to three phases ( [ 0x000C , 0x000E , 0x0010 ] ) or
* See additional defines like voltage .
* ReactivePower - Reactive power register entered as decimal or hexadecimal for one phase ( 0x0018 ) or up to three phases ( [ 0x0018 , 0x001A , 0x001C ] ) or
* See additional defines like voltage .
* Factor - Power factor register entered as decimal or hexadecimal for one phase ( 0x001E ) or up to three phases ( [ 0x001E , 0x0020 , 0x0022 ] ) or
* See additional defines like voltage .
* Frequency - Frequency register entered as decimal or hexadecimal for one phase ( 0x0046 ) or up to three phases ( [ 0x0046 , 0x0048 , 0x004A ] ) or
* See additional defines like voltage .
* Total - Total active energy register entered as decimal or hexadecimal for one phase ( 0x0156 ) or up to three phases ( [ 0x015A , 0x015C , 0x015E ] ) or
* See additional defines like voltage .
* ExportActive - Export active energy register entered as decimal or hexadecimal for one phase ( 0x0160 ) or up to three phases ( [ 0x0160 , 0x0162 , 0x0164 ] ) or
* See additional defines like voltage .
2022-10-08 15:14:11 +01:00
* Optional user defined registers :
* User - Additional user defined registers
2022-10-08 16:17:15 +01:00
* Value pair description :
2022-10-11 17:39:48 +01:00
* " User " : { " R " : 0x0024 , " T " : 0 , " M " : 1 , " J " : " PhaseAngle " , " G " : " Phase Angle " , " U " : " Deg " , " D " : 2 }
2022-10-08 16:17:15 +01:00
* R - Modbus register entered as decimal or hexadecimal for one phase ( 0x0160 ) or up to three phases ( [ 0x0160 , 0x0162 , 0x0164 ] )
2022-10-11 17:39:48 +01:00
* T - Datatype - optional . default is 0 - float :
* 0 - float
* 1 = 2 - byte signed
* 2 = 4 - byte signed
* 3 = 2 - byte unsigned
* 4 = 4 - byte unsigned
2022-12-21 17:11:35 +00:00
* 5 = not used
* 6 = 4 - byte signed with swapped words
* 7 = not used
* 8 = 4 - byte unsigned with swapped words
2022-10-11 17:39:48 +01:00
* M - Divider allowing to devide the read register by 1 , 10 , 100 , 1000 etc . - optional . default = 1
2022-10-08 16:17:15 +01:00
* J - JSON register name ( preferrably without spaces like " PhaseAngle " )
* G - GUI register name
* U - GUI unit name
2022-10-11 17:39:48 +01:00
* D - Number of decimals for floating point presentation ( 0 to 20 ) or a code correspondig to Tasmota resolution command settings :
2022-10-08 16:17:15 +01:00
* 21 - VoltRes ( V )
* 22 - AmpRes ( A )
* 23 - WattRes ( W , VA , VAr )
* 24 - EnergyRes ( kWh , kVAh , kVArh )
* 25 - FreqRes ( Hz )
* 26 - TempRes ( C , F )
* 27 - HumRes ( % )
* 28 - PressRes ( hPa , mmHg )
* 29 - WeightRes ( Kg )
2022-10-07 09:46:25 +01:00
*
2022-10-08 15:14:11 +01:00
* Example using default Energy registers :
* rule3 on file # modbus do { " Name " : " SDM230 " , " Baud " : 2400 , " Config " : 8 N1 " , " Address " :1, " Function " :4, " Voltage " :0, " Current " :6, " Power " :12, " ApparentPower " :18, " ReactivePower " :24, " Factor " :30, " Frequency " :70, " Total " :342, " ExportActive " :0x004A} endon
* rule3 on file # modbus do { " Name " : " SDM230 with hex registers " , " Baud " : 2400 , " Config " : 8 N1 " , " Address " :1, " Function " :4, " Voltage " :0x0000, " Current " :0x0006, " Power " :0x000C, " ApparentPower " :0x0012, " ReactivePower " :0x0018, " Factor " :0x001E, " Frequency " :0x0046, " Total " :0x0156, " ExportActive " :0x004A} endon
2022-10-09 17:38:30 +01:00
* rule3 on file # modbus do { " Name " : " DDSU666 " , " Baud " : 9600 , " Config " : 8 N1 " , " Address " :1, " Function " :4, " Voltage " :0x2000, " Current " :0x2002, " Power " :0x2004, " ReactivePower " :0x2006, " Factor " :0x200A, " Frequency " :0x200E, " Total " :0x4000, " ExportActive " :0x400A} endon
2022-10-08 15:14:11 +01:00
*
* Example using default Energy registers and some user defined registers :
2022-10-09 17:38:30 +01:00
* rule3 on file # modbus do { " Name " : " SDM72 " , " Baud " : 9600 , " Config " : 8 N1 " , " Address " :0x01, " Function " :0x04, " Power " :0x0034, " Total " :0x0156, " ExportActive " :0x004A, " User " :[{ " R " :0x0502, " J " : " ImportActive " , " G " : " Import Active " , " U " : " kWh " , " D " :24},{ " R " :0x0502, " J " : " ExportPower " , " G " : " Export Power " , " U " : " W " , " D " :23},{ " R " :0x0500, " J " : " ImportPower " , " G " : " Import Power " , " U " : " W " , " D " :23}]} endon
* rule3 on file # modbus do { " Name " : " SDM120 " , " Baud " : 2400 , " Config " : 8 N1 " , " Address " :1, " Function " :4, " Voltage " :0, " Current " :6, " Power " :12, " ApparentPower " :18, " ReactivePower " :24, " Factor " :30, " Frequency " :70, " Total " :342, " ExportActive " :0x004A, " User " :[{ " R " :0x0048, " J " : " ImportActive " , " G " : " Import Active " , " U " : " kWh " , " D " :24},{ " R " :0x004E, " J " : " ExportReactive " , " G " : " Export Reactive " , " U " : " kVArh " , " D " :24},{ " R " :0x004C, " J " : " ImportReactive " , " G " : " Import Reactive " , " U " : " kVArh " , " D " :24},{ " R " :0x0024, " J " : " PhaseAngle " , " G " : " Phase Angle " , " U " : " Deg " , " D " :2}]} endon
2022-10-08 15:14:11 +01:00
* rule3 on file # modbus do { " Name " : " SDM230 with two user registers " , " Baud " : 2400 , " Config " : 8 N1 " , " Address " :1, " Function " :4, " Voltage " :0, " Current " :6, " Power " :12, " ApparentPower " :18, " ReactivePower " :24, " Factor " :30, " Frequency " :70, " Total " :342, " ExportActive " :0x004A, " User " :[{ " R " :0x004E, " J " : " ExportReactive " , " G " : " Export Reactive " , " U " : " kVArh " , " D " :3},{ " R " :0x0024, " J " : " PhaseAngle " , " G " : " Phase Angle " , " U " : " Deg " , " D " :2}]} endon
2022-10-09 17:38:30 +01:00
* rule3 on file # modbus do { " Name " : " SDM630 " , " Baud " : 9600 , " Config " : 8 N1 " , " Address " :1, " Function " :4, " Voltage " :[0,2,4], " Current " :[6,8,10], " Power " :[12,14,16], " ApparentPower " :[18,20,22], " ReactivePower " :[24,26,28], " Factor " :[30,32,34], " Frequency " :70, " Total " :342, " ExportActive " :[352,354,356], " User " :{ " R " :[346,348,350], " J " : " ImportActive " , " G " : " Import Active " , " U " : " kWh " , " D " :24}} endon
2022-10-08 15:14:11 +01:00
*
* Note :
2022-10-09 17:38:30 +01:00
* - To enter long rules using the serial console and solve error " Serial buffer overrun " you might need to enlarge the serial input buffer with command serialbuffer 800
2022-10-08 15:14:11 +01:00
* - Changes to rule file are only executed on restart
*
* Restrictions :
* - Supports Modbus floating point registers
2022-10-23 17:21:12 +01:00
* - Max number of user defined registers is defined by one rule buffer ( 511 characters uncompressed , around 800 characters compressed )
2022-10-08 15:14:11 +01:00
*
* To do :
* - Support all three rule slots
* - Support other modbus register like integers
2022-10-06 22:17:04 +01:00
*
* Test set :
2022-10-08 15:14:11 +01:00
* rule3 on file # modbus do { " Name " : " SDM230 test1 " , " Baud " : 2400 , " Config " : 8 N1 " , " Address " :1, " Function " :4, " Voltage " :[0,0,0], " Current " :[6,6,6], " Power " :[12,12,12], " ApparentPower " :[18,18,18], " ReactivePower " :[24,24,24], " Factor " :[30,30,30], " Frequency " :[70,70,70], " Total " :[342,342,342]} endon
* rule3 on file # modbus do { " Name " : " SDM230 test2 " , " Baud " : 2400 , " Config " : 8 N1 " , " Address " :1, " Function " :4, " Voltage " :[0,0,0], " Current " :[6,6,6], " Power " :[12,12,12], " ApparentPower " :[18,18,18], " ReactivePower " :[24,24,24], " Factor " :[30,30,30], " Frequency " :70, " Total " :[342,342,342]} endon
* rule3 on file # modbus do { " Name " : " SDM230 test3 " , " Baud " : 2400 , " Config " : 8 N1 " , " Address " :1, " Function " :4, " Voltage " :0, " Current " :[6,6,6], " Power " :[12,12,12], " ApparentPower " :[18,18,18], " ReactivePower " :[24,24,24], " Factor " :[30,30,30], " Frequency " :70, " Total " :[342,342,342]} endon
2022-10-08 16:17:15 +01:00
* rule3 on file # modbus do { " Name " : " SDM230 test4 " , " Baud " : 2400 , " Config " : 8 N1 " , " Address " :1, " Function " :4, " Voltage " :0, " Current " :6, " Power " :12, " ApparentPower " :18, " ReactivePower " :24, " Factor " :30, " Frequency " :70, " Total " :342, " ExportActive " :0x004A, " User " :[{ " R " :0x004E, " J " : " ExportReactive " , " G " : " Export Reactive " , " U " : " kVArh " , " D " :24},{ " R " :0x0024, " J " : " PhaseAngle " , " G " : " Phase Angle " , " U " : " Deg " , " D " :2}]} endon
2022-10-08 15:14:11 +01:00
* rule3 on file # modbus do { " Name " : " SDM230 test5 " , " Baud " : 2400 , " Config " : 8 N1 " , " Address " :1, " Function " :4, " Voltage " :[0,0,0], " Current " :6, " Power " :12, " ApparentPower " :18, " ReactivePower " :24, " Factor " :30, " Frequency " :70, " Total " :342, " ExportActive " :0x004A, " User " :[{ " R " :[0x004E,0x004E,0x004E], " J " : " ExportReactive " , " G " : " Export Reactive " , " U " : " kVArh " , " D " :3},{ " R " :0x0024, " J " : " PhaseAngle " , " G " : " Phase Angle " , " U " : " Deg " , " D " :2}]} endon
2022-10-09 17:38:30 +01:00
* rule3 on file # modbus do { " Name " : " SDM120 test1 " , " Baud " : 2400 , " Config " : 8 N1 " , " Address " :1, " Function " :4, " Voltage " :0, " Current " :6, " Power " :12, " ApparentPower " :18, " ReactivePower " :24, " Factor " :30, " Frequency " :70, " Total " :342, " ExportActive " :0x004A, " User " :[{ " R " :0x0048, " J " : " ImportActive " , " G " : " Import Active " , " U " : " kWh " , " D " :24},{ " R " :0x004E, " J " : " ExportReactive " , " G " : " Export Reactive " , " U " : " kVArh " , " D " :24},{ " R " :0x004C, " J " : " ImportReactive " , " G " : " Import Reactive " , " U " : " kVArh " , " D " :24},{ " R " :0x0024, " J " : " PhaseAngle " , " G " : " Phase Angle " , " U " : " Deg " , " D " :2}]} endon
2022-10-11 10:10:47 +01:00
*
* rule3 on file # modbus do { " Name " : " SDM230 test6 " , " Baud " : 2400 , " Config " : 8 N1 " , " Address " :1, " Function " :4, " Voltage " :{ " R " :0, " T " :0, " M " :1}, " Current " :{ " R " :6, " T " :0, " M " :1}, " Power " :{ " R " :12, " T " :0, " M " :1}, " Frequency " :70, " Total " :342} endon
* rule3 on file # modbus do { " Name " : " SDM230 test6 " , " Baud " : 2400 , " Config " : 8 N1 " , " Address " :1, " Function " :4, " Voltage " :{ " R " :0, " T " :0, " M " :1}, " Current " :{ " R " :6, " T " :0, " M " :1}, " Power " :{ " R " :12, " T " :0, " M " :1}, " Frequency " :70, " Total " :342, " User " :{ " R " :0x0048, " T " :0, " M " :10, " J " : " ImportActive " , " G " : " Import Active " , " U " : " kWh " , " D " :24}} endon
2022-10-06 22:17:04 +01:00
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2022-10-11 10:10:47 +01:00
# define XNRG_29 29
2022-10-06 22:17:04 +01:00
2022-10-11 10:10:47 +01:00
# define ENERGY_MODBUS_SPEED 9600 // Default Modbus baudrate
# define ENERGY_MODBUS_CONFIG TS_SERIAL_8N1 // Default Modbus serial configuration
# define ENERGY_MODBUS_ADDR 1 // Default Modbus device_address
# define ENERGY_MODBUS_FUNC 0x04 // Default Modbus function code
2022-10-09 17:38:30 +01:00
2022-10-11 10:10:47 +01:00
# define ENERGY_MODBUS_DATATYPE 0 // Default Modbus datatype is 4-byte float
# define ENERGY_MODBUS_DIVIDER 1 // Default Modbus data divider
2022-10-07 09:46:25 +01:00
2022-10-11 10:10:47 +01:00
# define ENERGY_MODBUS_DECIMALS 0 // Default user decimal resolution
2022-10-07 09:46:25 +01:00
2022-10-11 17:39:48 +01:00
# define ENERGY_MODBUS_TICKER // Enable for ESP8266 when using softwareserial solving most modbus serial retries
2022-10-11 10:15:25 +01:00
//#define ENERGY_MODBUS_DEBUG
2022-10-11 10:10:47 +01:00
//#define ENERGY_MODBUS_DEBUG_SHOW
2022-10-06 22:17:04 +01:00
2022-10-11 10:10:47 +01:00
const uint16_t nrg_mbs_reg_not_used = 1 ; // Odd number 1 is unused register
enum EnergyModbusDataType { NRG_DT_FLOAT , // 4-byte float
NRG_DT_S16 , // 2-byte signed
NRG_DT_S32 , // 4-byte signed
NRG_DT_U16 , // 2-byte unsigned
NRG_DT_U32 , // 4-byte unsigned
2022-12-21 17:11:35 +00:00
NRG_DT_x16_nu1 ,
NRG_DT_S32_SW , // 4-byte signed with swapped words
NRG_DT_x16_nu2 ,
NRG_DT_U32_SW , // 4-byte unsigned with swapped words
2022-10-11 10:10:47 +01:00
NRG_DT_MAX } ;
enum EnergyModbusResolutions { NRG_RES_VOLTAGE = 21 , // V
NRG_RES_CURRENT , // A
NRG_RES_POWER , // W, VA, VAr
NRG_RES_ENERGY , // kWh, kVAh, kVArh
NRG_RES_FREQUENCY , // Hz
NRG_RES_TEMPERATURE , // C, F
NRG_RES_HUMIDITY , // %
NRG_RES_PRESSURE , // hPa, mmHg
NRG_RES_WEIGHT } ; // Kg
2022-10-08 16:17:15 +01:00
2022-10-06 22:17:04 +01:00
enum EnergyModbusRegisters { NRG_MBS_VOLTAGE ,
NRG_MBS_CURRENT ,
NRG_MBS_ACTIVE_POWER ,
NRG_MBS_APPARENT_POWER ,
NRG_MBS_REACTIVE_POWER ,
NRG_MBS_POWER_FACTOR ,
NRG_MBS_FREQUENCY ,
2022-10-08 15:14:11 +01:00
NRG_MBS_TOTAL_ENERGY ,
2022-10-06 22:17:04 +01:00
NRG_MBS_EXPORT_ACTIVE_ENERGY ,
NRG_MBS_MAX_REGS } ;
2022-10-07 09:46:25 +01:00
const char kEnergyModbusValues [ ] PROGMEM = D_JSON_VOLTAGE " | " // Voltage
D_JSON_CURRENT " | " // Current
D_JSON_POWERUSAGE " | " // Power
D_JSON_APPARENT_POWERUSAGE " | " // ApparentPower
D_JSON_REACTIVE_POWERUSAGE " | " // ReactivePower
D_JSON_POWERFACTOR " | " // Factor
D_JSON_FREQUENCY " | " // Frequency
2022-10-08 15:14:11 +01:00
D_JSON_TOTAL " | " // Total
2022-10-07 09:46:25 +01:00
D_JSON_EXPORT_ACTIVE " | " // ExportActive
;
2022-10-06 22:17:04 +01:00
# include <TasmotaModbus.h>
TasmotaModbus * EnergyModbus ;
2022-10-11 17:39:48 +01:00
# ifdef ENERGY_MODBUS_TICKER
2022-10-09 17:38:30 +01:00
# include <Ticker.h>
Ticker ticker_energy_modbus ;
2022-10-11 17:39:48 +01:00
# endif // ENERGY_MODBUS_TICKER
2022-10-11 10:10:47 +01:00
struct NRGMBSPARAM {
uint32_t serial_bps ;
uint32_t serial_config ;
uint8_t device_address ;
uint8_t function ;
uint8_t total_regs ;
uint8_t user_adds ;
uint8_t phase ;
uint8_t state ;
uint8_t retry ;
bool mutex ;
} NrgMbsParam ;
typedef struct NRGMBSREGISTER {
uint16_t address [ ENERGY_MAX_PHASES ] ;
uint16_t divider ;
uint32_t datatype ;
} NrgMbsRegister_t ;
2022-10-11 17:39:48 +01:00
NrgMbsRegister_t * NrgMbsReg = nullptr ;
2022-10-11 10:10:47 +01:00
typedef struct NRGMBSUSER {
float data [ ENERGY_MAX_PHASES ] ;
char * json_name ;
char * gui_name ;
char * gui_unit ;
uint32_t resolution ;
} NrgMbsUser_t ;
2022-10-11 17:39:48 +01:00
NrgMbsUser_t * NrgMbsUser = nullptr ;
2022-10-11 10:10:47 +01:00
/*********************************************************************************************/
2022-10-06 22:17:04 +01:00
void EnergyModbusLoop ( void ) {
2022-10-11 17:39:48 +01:00
# ifdef ENERGY_MODBUS_TICKER
if ( NrgMbsParam . mutex | | TasmotaGlobal . ota_state_flag ) { return ; }
# else
2022-10-11 10:10:47 +01:00
if ( NrgMbsParam . mutex ) { return ; }
2022-10-11 17:39:48 +01:00
# endif // ENERGY_MODBUS_TICKER
2022-10-11 10:10:47 +01:00
NrgMbsParam . mutex = 1 ;
2022-10-09 17:38:30 +01:00
2022-10-11 10:10:47 +01:00
uint32_t register_count ;
2022-10-08 15:14:11 +01:00
2022-10-06 22:17:04 +01:00
bool data_ready = EnergyModbus - > ReceiveReady ( ) ;
if ( data_ready ) {
2022-10-11 10:10:47 +01:00
uint8_t buffer [ 15 ] ; // At least 5 + (2 * 2) = 9
register_count = 2 - ( NrgMbsReg [ NrgMbsParam . state ] . datatype & 1 ) ;
uint32_t error = EnergyModbus - > ReceiveBuffer ( buffer , register_count ) ;
2022-10-06 22:17:04 +01:00
AddLog ( LOG_LEVEL_DEBUG_MORE , PSTR ( " NRG: Modbus register %d, phase %d, rcvd %*_H " ) ,
2022-10-11 10:10:47 +01:00
NrgMbsParam . state , NrgMbsParam . phase , EnergyModbus - > ReceiveCount ( ) , buffer ) ;
2022-10-06 22:17:04 +01:00
if ( error ) {
/* Return codes from TasmotaModbus.h:
* 0 = No error
* 1 = Illegal Function ,
* 2 = Illegal Data Address ,
* 3 = Illegal Data Value ,
* 4 = Slave Error
* 5 = Acknowledge but not finished ( no error )
* 6 = Slave Busy
* 7 = Not enough minimal data received
* 8 = Memory Parity error
* 9 = Crc error
* 10 = Gateway Path Unavailable
* 11 = Gateway Target device failed to respond
* 12 = Wrong number of registers
* 13 = Register data not specified
* 14 = To many registers
*/
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " NRG: Modbus error %d " ) , error ) ;
} else {
2022-10-11 10:10:47 +01:00
Energy . data_valid [ NrgMbsParam . phase ] = 0 ;
2022-10-06 22:17:04 +01:00
// 0 1 2 3 4 5 6 7 8
// SA FC BC Fh Fl Sh Sl Cl Ch
// 01 04 04 43 66 33 34 1B 38 = 230.2 Volt
float value ;
2022-10-11 10:10:47 +01:00
switch ( NrgMbsReg [ NrgMbsParam . state ] . datatype ) {
case NRG_DT_FLOAT : {
( ( uint8_t * ) & value ) [ 3 ] = buffer [ 3 ] ; // Get float values
( ( uint8_t * ) & value ) [ 2 ] = buffer [ 4 ] ;
( ( uint8_t * ) & value ) [ 1 ] = buffer [ 5 ] ;
( ( uint8_t * ) & value ) [ 0 ] = buffer [ 6 ] ;
break ;
}
case NRG_DT_S16 : {
int16_t value_buff = ( ( int16_t ) buffer [ 3 ] ) < < 8 | buffer [ 4 ] ;
value = ( float ) value_buff ;
break ;
}
case NRG_DT_U16 : {
uint16_t value_buff = ( ( uint16_t ) buffer [ 3 ] ) < < 8 | buffer [ 4 ] ;
value = ( float ) value_buff ;
break ;
}
case NRG_DT_S32 : {
int32_t value_buff = ( ( int32_t ) buffer [ 3 ] ) < < 24 | ( ( uint32_t ) buffer [ 4 ] ) < < 16 | ( ( uint32_t ) buffer [ 5 ] ) < < 8 | buffer [ 6 ] ;
value = ( float ) value_buff ;
break ;
}
2022-12-21 17:11:35 +00:00
case NRG_DT_S32_SW : {
int32_t value_buff = ( ( int32_t ) buffer [ 5 ] ) < < 24 | ( ( uint32_t ) buffer [ 6 ] ) < < 16 | ( ( uint32_t ) buffer [ 3 ] ) < < 8 | buffer [ 4 ] ;
value = ( float ) value_buff ;
break ;
}
2022-10-11 10:10:47 +01:00
case NRG_DT_U32 : {
uint32_t value_buff = ( ( uint32_t ) buffer [ 3 ] ) < < 24 | ( ( uint32_t ) buffer [ 4 ] ) < < 16 | ( ( uint32_t ) buffer [ 5 ] ) < < 8 | buffer [ 6 ] ;
value = ( float ) value_buff ;
break ;
}
2022-12-21 17:11:35 +00:00
case NRG_DT_U32_SW : {
uint32_t value_buff = ( ( uint32_t ) buffer [ 5 ] ) < < 24 | ( ( uint32_t ) buffer [ 6 ] ) < < 16 | ( ( uint32_t ) buffer [ 3 ] ) < < 8 | buffer [ 4 ] ;
value = ( float ) value_buff ;
break ;
}
2022-10-11 10:10:47 +01:00
}
value / = NrgMbsReg [ NrgMbsParam . state ] . divider ;
2022-10-06 22:17:04 +01:00
2022-10-11 10:10:47 +01:00
switch ( NrgMbsParam . state ) {
2022-10-06 22:17:04 +01:00
case NRG_MBS_VOLTAGE :
2022-10-11 10:10:47 +01:00
Energy . voltage [ NrgMbsParam . phase ] = value ; // 230.2 V
2022-10-06 22:17:04 +01:00
break ;
case NRG_MBS_CURRENT :
2022-10-11 10:10:47 +01:00
Energy . current [ NrgMbsParam . phase ] = value ; // 1.260 A
2022-10-06 22:17:04 +01:00
break ;
case NRG_MBS_ACTIVE_POWER :
2022-10-11 10:10:47 +01:00
Energy . active_power [ NrgMbsParam . phase ] = value ; // -196.3 W
2022-10-06 22:17:04 +01:00
break ;
case NRG_MBS_APPARENT_POWER :
2022-10-11 10:10:47 +01:00
Energy . apparent_power [ NrgMbsParam . phase ] = value ; // 223.4 VA
2022-10-06 22:17:04 +01:00
break ;
case NRG_MBS_REACTIVE_POWER :
2022-10-11 10:10:47 +01:00
Energy . reactive_power [ NrgMbsParam . phase ] = value ; // 92.2
2022-10-06 22:17:04 +01:00
break ;
case NRG_MBS_POWER_FACTOR :
2022-10-11 10:10:47 +01:00
Energy . power_factor [ NrgMbsParam . phase ] = value ; // -0.91
2022-10-06 22:17:04 +01:00
break ;
case NRG_MBS_FREQUENCY :
2022-10-11 10:10:47 +01:00
Energy . frequency [ NrgMbsParam . phase ] = value ; // 50.0 Hz
2022-10-06 22:17:04 +01:00
break ;
2022-10-08 15:14:11 +01:00
case NRG_MBS_TOTAL_ENERGY :
2022-10-11 10:10:47 +01:00
Energy . import_active [ NrgMbsParam . phase ] = value ; // 6.216 kWh => used in EnergyUpdateTotal()
2022-10-06 22:17:04 +01:00
break ;
case NRG_MBS_EXPORT_ACTIVE_ENERGY :
2022-10-11 10:10:47 +01:00
Energy . export_active [ NrgMbsParam . phase ] = value ; // 478.492 kWh
2022-10-06 22:17:04 +01:00
break ;
2022-10-08 15:14:11 +01:00
default :
2022-10-11 10:10:47 +01:00
if ( NrgMbsUser ) {
NrgMbsUser [ NrgMbsParam . state - NRG_MBS_MAX_REGS ] . data [ NrgMbsParam . phase ] = value ;
2022-10-08 15:14:11 +01:00
}
2022-10-06 22:17:04 +01:00
}
do {
2022-10-11 10:10:47 +01:00
NrgMbsParam . phase + + ;
if ( NrgMbsParam . phase > = Energy . phase_count ) {
NrgMbsParam . phase = 0 ;
NrgMbsParam . state + + ;
if ( NrgMbsParam . state > = NrgMbsParam . total_regs ) {
NrgMbsParam . state = 0 ;
NrgMbsParam . phase = 0 ;
2022-10-06 22:17:04 +01:00
EnergyUpdateTotal ( ) ; // update every cycle after all registers have been read
break ;
}
}
2022-10-08 15:14:11 +01:00
delay ( 0 ) ;
2022-10-11 10:10:47 +01:00
} while ( NrgMbsReg [ NrgMbsParam . state ] . address [ NrgMbsParam . phase ] = = nrg_mbs_reg_not_used ) ;
2022-10-06 22:17:04 +01:00
}
} // end data ready
2022-10-11 10:10:47 +01:00
if ( 0 = = NrgMbsParam . retry | | data_ready ) {
NrgMbsParam . retry = 1 ;
register_count = 2 - ( NrgMbsReg [ NrgMbsParam . state ] . datatype & 1 ) ;
EnergyModbus - > Send ( NrgMbsParam . device_address , NrgMbsParam . function , NrgMbsReg [ NrgMbsParam . state ] . address [ NrgMbsParam . phase ] , register_count ) ;
2022-10-06 22:17:04 +01:00
} else {
2022-10-11 10:10:47 +01:00
NrgMbsParam . retry - - ;
2022-10-09 17:38:30 +01:00
# ifdef ENERGY_MODBUS_DEBUG
2022-10-11 10:10:47 +01:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " NRG: Modbus state %d retry %d " ) , NrgMbsParam . state , NrgMbsParam . retry ) ;
2022-10-09 17:38:30 +01:00
# endif
2022-10-06 22:17:04 +01:00
}
2022-10-09 17:38:30 +01:00
delay ( 0 ) ;
2022-10-11 10:10:47 +01:00
NrgMbsParam . mutex = 0 ;
2022-10-06 22:17:04 +01:00
}
2022-10-08 15:14:11 +01:00
# ifdef USE_RULES
bool EnergyModbusReadUserRegisters ( JsonParserObject user_add_value , uint32_t add_index ) {
2022-10-11 10:10:47 +01:00
// {"R":0x004E,"T":0,"M":1,"J":"ExportReactive","G":"Export Reactive","U":"kVArh","D":3}
uint32_t reg_index = NRG_MBS_MAX_REGS + add_index ;
2022-10-08 15:14:11 +01:00
JsonParserToken val ;
val = user_add_value [ PSTR ( " R " ) ] ; // Register address
uint32_t phase = 0 ;
if ( val . isArray ( ) ) {
JsonParserArray address_arr = val . getArray ( ) ;
for ( auto value : address_arr ) {
2022-10-11 10:10:47 +01:00
NrgMbsReg [ reg_index ] . address [ phase ] = value . getUInt ( ) ;
2022-10-08 15:14:11 +01:00
phase + + ;
2022-10-09 17:38:30 +01:00
if ( phase > = ENERGY_MAX_PHASES ) { break ; }
2022-10-08 15:14:11 +01:00
}
} else if ( val ) {
2022-10-11 10:10:47 +01:00
NrgMbsReg [ reg_index ] . address [ 0 ] = val . getUInt ( ) ;
2022-10-08 15:14:11 +01:00
phase + + ;
} else {
return false ;
}
if ( phase > Energy . phase_count ) {
Energy . phase_count = phase ;
}
2022-10-11 10:10:47 +01:00
val = user_add_value [ PSTR ( " T " ) ] ; // Register data type
if ( val ) {
// "T":0
NrgMbsReg [ reg_index ] . datatype = val . getUInt ( ) ;
}
val = user_add_value [ PSTR ( " M " ) ] ; // Register divider
if ( val ) {
// "M":1
NrgMbsReg [ reg_index ] . divider = val . getUInt ( ) ;
}
2022-10-08 15:14:11 +01:00
val = user_add_value [ PSTR ( " J " ) ] ; // JSON value name
if ( val ) {
2022-10-11 10:10:47 +01:00
NrgMbsUser [ add_index ] . json_name = SetStr ( val . getStr ( ) ) ;
2022-10-08 15:14:11 +01:00
} else {
return false ;
}
val = user_add_value [ PSTR ( " G " ) ] ; // GUI value name
if ( val ) {
2022-10-11 10:10:47 +01:00
NrgMbsUser [ add_index ] . gui_name = SetStr ( val . getStr ( ) ) ;
2022-10-08 15:14:11 +01:00
} else {
return false ;
}
2022-10-11 10:10:47 +01:00
NrgMbsUser [ add_index ] . gui_unit = EmptyStr ;
2022-10-08 15:14:11 +01:00
val = user_add_value [ PSTR ( " U " ) ] ; // GUI value Unit
if ( val ) {
2022-10-11 10:10:47 +01:00
NrgMbsUser [ add_index ] . gui_unit = SetStr ( val . getStr ( ) ) ;
2022-10-08 15:14:11 +01:00
}
2022-10-11 10:10:47 +01:00
NrgMbsUser [ add_index ] . resolution = ENERGY_MODBUS_DECIMALS ;
2022-10-08 15:14:11 +01:00
val = user_add_value [ PSTR ( " D " ) ] ; // Decimal resolution
if ( val ) {
2022-10-11 10:10:47 +01:00
NrgMbsUser [ add_index ] . resolution = val . getUInt ( ) ;
2022-10-08 15:14:11 +01:00
}
# ifdef ENERGY_MODBUS_DEBUG
2022-10-11 10:10:47 +01:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " NRG: Idx %d, R [%04X,%04X,%04X], T %d, M %d, J '%s', G '%s', U '%s', D %d " ) ,
2022-10-08 15:14:11 +01:00
add_index ,
2022-10-11 10:10:47 +01:00
NrgMbsReg [ reg_index ] . address [ 0 ] ,
NrgMbsReg [ reg_index ] . address [ 1 ] ,
NrgMbsReg [ reg_index ] . address [ 2 ] ,
NrgMbsReg [ reg_index ] . datatype ,
NrgMbsReg [ reg_index ] . divider ,
NrgMbsUser [ add_index ] . json_name ,
NrgMbsUser [ add_index ] . gui_name ,
NrgMbsUser [ add_index ] . gui_unit ,
NrgMbsUser [ add_index ] . resolution ) ;
2022-10-08 15:14:11 +01:00
# endif
return true ;
}
# endif // USE_RULES
2022-10-06 22:17:04 +01:00
bool EnergyModbusReadRegisters ( void ) {
# ifdef USE_RULES
String modbus = RuleLoadFile ( " MODBUS " ) ;
2022-10-07 09:46:25 +01:00
if ( ! modbus . length ( ) ) { return false ; } // File not found
2022-10-06 22:17:04 +01:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("NRG: File '%s'"), modbus.c_str());
const char * json = modbus . c_str ( ) ;
uint32_t len = strlen ( json ) + 1 ;
2022-10-07 09:46:25 +01:00
if ( len < 7 ) { return false ; } // Invalid JSON
2022-10-06 22:17:04 +01:00
char json_buffer [ len ] ;
memcpy ( json_buffer , json , len ) ; // Keep original safe
JsonParser parser ( json_buffer ) ;
JsonParserObject root = parser . getRootObject ( ) ;
2022-10-07 09:46:25 +01:00
if ( ! root ) { return false ; } // Invalid JSON
2022-10-06 22:17:04 +01:00
2022-10-11 10:10:47 +01:00
// Init defaults
NrgMbsParam . serial_bps = ENERGY_MODBUS_SPEED ;
NrgMbsParam . serial_config = ENERGY_MODBUS_CONFIG ;
NrgMbsParam . device_address = ENERGY_MODBUS_ADDR ;
NrgMbsParam . function = ENERGY_MODBUS_FUNC ;
NrgMbsParam . user_adds = 0 ;
2022-10-06 22:17:04 +01:00
2022-10-11 10:10:47 +01:00
JsonParserToken val ;
val = root [ PSTR ( " User " ) ] ;
if ( val ) {
if ( val . isArray ( ) ) {
// "User":[{"R":0x004E,"J":"ExportReactive","G":"Export Reactive","U":"kVArh","D":3},{"R":0x0024,"J":"PhaseAngle","G":"Phase Angle","U":"Deg","D":2}]
NrgMbsParam . user_adds = val . size ( ) ;
} else {
// "User":{"R":0x004E,"J":"ExportReactive","G":"Export Reactive","U":"kVArh","D":3}
NrgMbsParam . user_adds = 1 ;
}
}
NrgMbsParam . total_regs = NRG_MBS_MAX_REGS + NrgMbsParam . user_adds ;
NrgMbsReg = ( NrgMbsRegister_t * ) calloc ( NrgMbsParam . total_regs , sizeof ( NrgMbsRegister_t ) ) ;
if ( NrgMbsReg = = nullptr ) { return false ; } // Unable to allocate variables on heap
2022-10-06 22:17:04 +01:00
// Init defaults
2022-10-11 10:10:47 +01:00
for ( uint32_t i = 0 ; i < NrgMbsParam . total_regs ; i + + ) {
NrgMbsReg [ i ] . datatype = ENERGY_MODBUS_DATATYPE ;
NrgMbsReg [ i ] . divider = ENERGY_MODBUS_DIVIDER ;
2022-10-06 22:17:04 +01:00
for ( uint32_t j = 0 ; j < ENERGY_MAX_PHASES ; j + + ) {
2022-10-11 10:10:47 +01:00
NrgMbsReg [ i ] . address [ j ] = nrg_mbs_reg_not_used ;
}
}
if ( NrgMbsParam . user_adds ) {
NrgMbsUser = ( NrgMbsUser_t * ) calloc ( NrgMbsParam . user_adds + 1 , sizeof ( NrgMbsUser_t ) ) ;
if ( NrgMbsUser = = nullptr ) {
NrgMbsParam . user_adds = 0 ;
NrgMbsParam . total_regs = NRG_MBS_MAX_REGS ;
} else {
// Init defaults
for ( uint32_t i = 0 ; i < NrgMbsParam . user_adds ; i + + ) {
NrgMbsUser [ i ] . resolution = ENERGY_MODBUS_DECIMALS ;
for ( uint32_t j = 0 ; j < ENERGY_MAX_PHASES ; j + + ) {
NrgMbsUser [ i ] . data [ j ] = NAN ;
}
}
2022-10-06 22:17:04 +01:00
}
}
2022-10-07 09:46:25 +01:00
val = root [ PSTR ( " Baud " ) ] ;
2022-10-06 22:17:04 +01:00
if ( val ) {
2022-10-11 10:10:47 +01:00
NrgMbsParam . serial_bps = val . getInt ( ) ; // 2400
2022-10-06 22:17:04 +01:00
}
2022-10-07 09:46:25 +01:00
val = root [ PSTR ( " Config " ) ] ;
2022-10-06 22:17:04 +01:00
if ( val ) {
const char * serial_config = val . getStr ( ) ; // 8N1
2022-10-11 10:10:47 +01:00
NrgMbsParam . serial_config = ConvertSerialConfig ( ParseSerialConfig ( serial_config ) ) ;
2022-10-06 22:17:04 +01:00
}
2022-10-07 09:46:25 +01:00
val = root [ PSTR ( " Address " ) ] ;
2022-10-06 22:17:04 +01:00
if ( val ) {
2022-10-11 10:10:47 +01:00
NrgMbsParam . device_address = val . getUInt ( ) ; // 1
2022-10-06 22:17:04 +01:00
}
2022-10-07 09:46:25 +01:00
val = root [ PSTR ( " Function " ) ] ;
2022-10-06 22:17:04 +01:00
if ( val ) {
2022-10-11 10:10:47 +01:00
NrgMbsParam . function = val . getUInt ( ) ; // 4
2022-10-06 22:17:04 +01:00
}
char register_name [ 32 ] ;
Energy . voltage_available = false ; // Disable voltage is measured
Energy . current_available = false ; // Disable current is measured
for ( uint32_t names = 0 ; names < NRG_MBS_MAX_REGS ; names + + ) {
val = root [ GetTextIndexed ( register_name , sizeof ( register_name ) , names , kEnergyModbusValues ) ] ;
2022-10-08 15:14:11 +01:00
if ( val ) {
// "Voltage":0
// "Voltage":[0,0,0]
2022-10-11 10:10:47 +01:00
// "Voltage":{"R":0,"T":0,"M":1}
// "Voltage":{"R":[0,0,0],"T":0,"M":1}
2022-10-08 15:14:11 +01:00
uint32_t phase = 0 ;
2022-10-11 10:10:47 +01:00
if ( val . isObject ( ) ) {
// "Voltage":{"R":0,"T":0,"M":1}
// "Voltage":{"R":[0,0,0],"T":0,"M":1}
JsonParserObject register_add_values = val . getObject ( ) ;
val = register_add_values [ PSTR ( " R " ) ] ; // Register address
if ( val . isArray ( ) ) {
// "R":[0,0,0]
JsonParserArray address_arr = val . getArray ( ) ;
for ( auto value : address_arr ) {
NrgMbsReg [ names ] . address [ phase ] = value . getUInt ( ) ;
phase + + ;
if ( phase > = ENERGY_MAX_PHASES ) { break ; }
}
} else if ( val ) {
// "R":0
NrgMbsReg [ names ] . address [ 0 ] = val . getUInt ( ) ;
phase + + ;
}
val = register_add_values [ PSTR ( " T " ) ] ; // Register data type
if ( val ) {
// "T":0
NrgMbsReg [ names ] . datatype = val . getUInt ( ) ;
}
val = register_add_values [ PSTR ( " M " ) ] ; // Register divider
if ( val ) {
// "M":1
NrgMbsReg [ names ] . divider = val . getUInt ( ) ;
}
} else if ( val . isArray ( ) ) {
// "Voltage":[0,0,0]
2022-10-08 15:14:11 +01:00
JsonParserArray arr = val . getArray ( ) ;
for ( auto value : arr ) {
2022-10-11 10:10:47 +01:00
NrgMbsReg [ names ] . address [ phase ] = value . getUInt ( ) ;
2022-10-08 15:14:11 +01:00
phase + + ;
2022-10-09 17:38:30 +01:00
if ( phase > = ENERGY_MAX_PHASES ) { break ; }
2022-10-08 15:14:11 +01:00
}
} else if ( val ) {
2022-10-11 10:10:47 +01:00
// "Voltage":0
NrgMbsReg [ names ] . address [ 0 ] = val . getUInt ( ) ;
2022-10-06 22:17:04 +01:00
phase + + ;
}
2022-10-06 22:26:54 +01:00
if ( phase > Energy . phase_count ) {
Energy . phase_count = phase ;
}
2022-10-06 22:17:04 +01:00
switch ( names ) {
case NRG_MBS_VOLTAGE :
Energy . voltage_available = true ; // Enable if voltage is measured
if ( 1 = = phase ) {
Energy . voltage_common = true ; // Use common voltage
}
break ;
case NRG_MBS_CURRENT :
Energy . current_available = true ; // Enable if current is measured
break ;
case NRG_MBS_FREQUENCY :
if ( 1 = = phase ) {
Energy . frequency_common = true ; // Use common frequency
}
break ;
2022-10-08 15:14:11 +01:00
case NRG_MBS_TOTAL_ENERGY :
2022-10-07 09:46:25 +01:00
Settings - > flag3 . hardware_energy_total = 1 ; // SetOption72 - Enable hardware energy total counter as reference (#6561)
break ;
2022-10-06 22:17:04 +01:00
}
2022-10-08 15:14:11 +01:00
# ifdef ENERGY_MODBUS_DEBUG
2022-10-11 10:10:47 +01:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " NRG: Idx %d, R [%04X,%04X,%04X], T %d, M %d " ) ,
2022-10-08 15:14:11 +01:00
names ,
2022-10-11 10:10:47 +01:00
NrgMbsReg [ names ] . address [ 0 ] ,
NrgMbsReg [ names ] . address [ 1 ] ,
NrgMbsReg [ names ] . address [ 2 ] ,
NrgMbsReg [ names ] . datatype ,
NrgMbsReg [ names ] . divider ) ;
2022-10-08 15:14:11 +01:00
# endif
}
}
// "User":{"R":0x004E,"J":"ExportReactive","G":"Export Reactive","U":"kVArh","D":3}
// "User":[{"R":0x004E,"J":"ExportReactive","G":"Export Reactive","U":"kVArh","D":3},{"R":0x0024,"J":"PhaseAngle","G":"Phase Angle","U":"Deg","D":2}]
val = root [ PSTR ( " User " ) ] ;
if ( val ) {
if ( val . isArray ( ) ) {
2022-10-11 10:10:47 +01:00
JsonParserArray user_adds_arr = val . getArray ( ) ;
uint32_t add_index = 0 ;
for ( auto user_add_values : user_adds_arr ) {
if ( ! user_add_values . isObject ( ) ) { break ; }
if ( EnergyModbusReadUserRegisters ( user_add_values . getObject ( ) , add_index ) ) {
add_index + + ;
} else {
AddLog ( LOG_LEVEL_INFO , PSTR ( " NRG: Dropped JSON user input %d " ) , add_index + 1 ) ;
NrgMbsParam . user_adds - - ;
2022-10-08 15:14:11 +01:00
}
}
2022-10-11 10:10:47 +01:00
} else if ( val ) {
if ( val . isObject ( ) ) {
if ( ! EnergyModbusReadUserRegisters ( val . getObject ( ) , 0 ) ) {
AddLog ( LOG_LEVEL_INFO , PSTR ( " NRG: Dropped JSON user input " ) ) ;
NrgMbsParam . user_adds - - ;
2022-10-08 15:14:11 +01:00
}
}
2022-10-11 10:10:47 +01:00
}
NrgMbsParam . total_regs = NRG_MBS_MAX_REGS + NrgMbsParam . user_adds ;
}
for ( uint32_t i = 0 ; i < NrgMbsParam . total_regs ; i + + ) {
if ( NrgMbsReg [ i ] . datatype > = NRG_DT_MAX ) {
NrgMbsReg [ i ] . datatype = ENERGY_MODBUS_DATATYPE ;
}
if ( NrgMbsReg [ i ] . divider < 1 ) {
NrgMbsReg [ i ] . divider = ENERGY_MODBUS_DIVIDER ;
2022-10-06 22:17:04 +01:00
}
}
2022-10-08 15:14:11 +01:00
2022-10-07 09:46:25 +01:00
# ifdef ENERGY_MODBUS_DEBUG
2022-10-11 10:10:47 +01:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " NRG: RAM usage %d + %d + %d " ) , sizeof ( NrgMbsParam ) , NrgMbsParam . total_regs * sizeof ( NrgMbsRegister_t ) , NrgMbsParam . user_adds * sizeof ( NrgMbsUser_t ) ) ;
2022-10-07 09:46:25 +01:00
# endif
2022-10-06 22:17:04 +01:00
2022-10-11 10:10:47 +01:00
// NrgMbsParam.state = 0; // Set by calloc()
// NrgMbsParam.phase = 0;
2022-10-06 22:17:04 +01:00
return true ;
# endif // USE_RULES
return false ;
}
bool EnergyModbusRegisters ( void ) {
if ( EnergyModbusReadRegisters ( ) ) {
return true ;
}
AddLog ( LOG_LEVEL_INFO , PSTR ( " NRG: No valid modbus data " ) ) ;
return false ;
}
void EnergyModbusSnsInit ( void ) {
if ( EnergyModbusRegisters ( ) ) {
2022-12-03 11:33:42 +00:00
EnergyModbus = new TasmotaModbus ( Pin ( GPIO_NRG_MBS_RX ) , Pin ( GPIO_NRG_MBS_TX ) , Pin ( GPIO_NRG_MBS_TX_ENA ) ) ;
2022-10-11 10:10:47 +01:00
uint8_t result = EnergyModbus - > Begin ( NrgMbsParam . serial_bps , NrgMbsParam . serial_config ) ;
2022-10-06 22:17:04 +01:00
if ( result ) {
if ( 2 = = result ) { ClaimSerial ( ) ; }
2022-10-11 17:39:48 +01:00
# ifdef ENERGY_MODBUS_TICKER
2022-10-11 10:10:47 +01:00
ticker_energy_modbus . attach_ms ( 200 , EnergyModbusLoop ) ;
2022-10-11 17:39:48 +01:00
# endif // ENERGY_MODBUS_TICKER
2022-10-06 22:17:04 +01:00
return ;
}
}
TasmotaGlobal . energy_driver = ENERGY_NONE ;
}
void EnergyModbusDrvInit ( void ) {
if ( PinUsed ( GPIO_NRG_MBS_RX ) & & PinUsed ( GPIO_NRG_MBS_TX ) ) {
TasmotaGlobal . energy_driver = XNRG_29 ;
}
}
2022-10-08 15:14:11 +01:00
/*********************************************************************************************\
* Additional presentation
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void EnergyModbusReset ( void ) {
2022-10-11 10:10:47 +01:00
for ( uint32_t i = 0 ; i < NrgMbsParam . user_adds ; i + + ) {
2022-10-08 15:14:11 +01:00
for ( uint32_t j = 0 ; j < ENERGY_MAX_PHASES ; j + + ) {
2022-10-11 10:10:47 +01:00
if ( NrgMbsReg [ NRG_MBS_MAX_REGS + i ] . address [ 0 ] ! = nrg_mbs_reg_not_used ) {
NrgMbsUser [ i ] . data [ j ] = 0 ;
2022-10-08 15:14:11 +01:00
}
}
}
}
2022-10-08 16:17:15 +01:00
uint32_t EnergyModbusResolution ( uint32_t resolution ) {
if ( resolution > = NRG_RES_VOLTAGE ) {
switch ( resolution ) {
case NRG_RES_VOLTAGE :
return Settings - > flag2 . voltage_resolution ;
case NRG_RES_CURRENT :
return Settings - > flag2 . current_resolution ;
case NRG_RES_POWER :
return Settings - > flag2 . wattage_resolution ;
case NRG_RES_ENERGY :
return Settings - > flag2 . energy_resolution ;
case NRG_RES_FREQUENCY :
return Settings - > flag2 . frequency_resolution ;
case NRG_RES_TEMPERATURE :
return Settings - > flag2 . temperature_resolution ;
case NRG_RES_HUMIDITY :
return Settings - > flag2 . humidity_resolution ;
case NRG_RES_PRESSURE :
return Settings - > flag2 . pressure_resolution ;
case NRG_RES_WEIGHT :
return Settings - > flag2 . weight_resolution ;
}
}
return resolution ;
}
2022-10-08 15:14:11 +01:00
void EnergyModbusShow ( bool json ) {
2022-10-11 10:10:47 +01:00
char value_chr [ GUISZ ] ;
float values [ ENERGY_MAX_PHASES ] ;
for ( uint32_t i = 0 ; i < NrgMbsParam . user_adds ; i + + ) {
uint32_t reg_index = NRG_MBS_MAX_REGS + i ;
2022-10-08 16:17:15 +01:00
# ifdef ENERGY_MODBUS_DEBUG_SHOW
2022-10-08 15:14:11 +01:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " NRG: Idx %d, R [%04X,%04X,%04X], J '%s', G '%s', U '%s', D %d, V [%3_f,%3_f,%3_f] " ) ,
i ,
2022-10-11 10:10:47 +01:00
NrgMbsReg [ reg_index ] . address [ 0 ] ,
NrgMbsReg [ reg_index ] . address [ 1 ] ,
NrgMbsReg [ reg_index ] . address [ 2 ] ,
NrgMbsUser [ i ] . json_name ,
NrgMbsUser [ i ] . gui_name ,
NrgMbsUser [ i ] . gui_unit ,
NrgMbsUser [ i ] . resolution ,
& NrgMbsUser [ i ] . data [ 0 ] ,
& NrgMbsUser [ i ] . data [ 1 ] ,
& NrgMbsUser [ i ] . data [ 2 ] ) ;
2022-10-08 15:14:11 +01:00
# endif
2022-10-08 16:17:15 +01:00
2022-10-11 10:10:47 +01:00
if ( ( NrgMbsReg [ reg_index ] . address [ 0 ] ! = nrg_mbs_reg_not_used ) & & ! isnan ( NrgMbsUser [ i ] . data [ 0 ] ) ) {
2022-10-08 15:14:11 +01:00
for ( uint32_t j = 0 ; j < ENERGY_MAX_PHASES ; j + + ) {
2022-10-11 10:10:47 +01:00
values [ j ] = NrgMbsUser [ i ] . data [ j ] ;
2022-10-08 15:14:11 +01:00
}
2022-10-11 10:10:47 +01:00
uint32_t resolution = EnergyModbusResolution ( NrgMbsUser [ i ] . resolution ) ;
2022-11-17 14:14:28 +00:00
uint32_t single = ( ! isnan ( NrgMbsUser [ i ] . data [ 1 ] ) & & ! isnan ( NrgMbsUser [ i ] . data [ 2 ] ) ) ? 0 : 1 ;
2022-10-08 16:17:15 +01:00
# ifdef ENERGY_MODBUS_DEBUG_SHOW
2022-10-11 10:10:47 +01:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " NRG: resolution %d -> %d " ) , NrgMbsUser [ i ] . resolution , resolution ) ;
2022-10-08 16:17:15 +01:00
# endif
2022-10-08 15:14:11 +01:00
if ( json ) {
2022-11-17 14:14:28 +00:00
ResponseAppend_P ( PSTR ( " , \" %s \" :%s " ) , NrgMbsUser [ i ] . json_name , EnergyFormat ( value_chr , values , resolution , single ) ) ;
2022-10-08 15:14:11 +01:00
# ifdef USE_WEBSERVER
} else {
WSContentSend_PD ( PSTR ( " {s}%s{m}%s %s{e} " ) ,
2022-10-11 10:10:47 +01:00
NrgMbsUser [ i ] . gui_name ,
2022-11-17 14:14:28 +00:00
WebEnergyFormat ( value_chr , values , resolution , single ) ,
2022-10-11 10:10:47 +01:00
NrgMbsUser [ i ] . gui_unit ) ;
2022-10-08 15:14:11 +01:00
# endif // USE_WEBSERVER
}
}
}
}
2022-10-06 22:17:04 +01:00
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2022-11-11 09:44:56 +00:00
bool Xnrg29 ( uint32_t function ) {
2022-10-06 22:17:04 +01:00
bool result = false ;
switch ( function ) {
2022-10-11 17:39:48 +01:00
# ifndef ENERGY_MODBUS_TICKER
// case FUNC_EVERY_200_MSECOND: // Energy ticker interrupt
case FUNC_EVERY_250_MSECOND : // Tasmota dispatcher
2022-10-06 22:17:04 +01:00
EnergyModbusLoop ( ) ;
break ;
2022-10-11 17:39:48 +01:00
# endif // No ENERGY_MODBUS_TICKER
2022-10-08 15:14:11 +01:00
case FUNC_JSON_APPEND :
EnergyModbusShow ( 1 ) ;
break ;
# ifdef USE_WEBSERVER
# ifdef USE_ENERGY_COLUMN_GUI
case FUNC_WEB_COL_SENSOR :
# else // not USE_ENERGY_COLUMN_GUI
case FUNC_WEB_SENSOR :
# endif // USE_ENERGY_COLUMN_GUI
EnergyModbusShow ( 0 ) ;
break ;
# endif // USE_WEBSERVER
2022-10-06 22:17:04 +01:00
case FUNC_ENERGY_RESET :
2022-10-08 15:14:11 +01:00
EnergyModbusReset ( ) ;
2022-10-06 22:17:04 +01:00
break ;
case FUNC_INIT :
EnergyModbusSnsInit ( ) ;
break ;
case FUNC_PRE_INIT :
EnergyModbusDrvInit ( ) ;
break ;
}
return result ;
}
# endif // USE_MODBUS_ENERGY
# endif // USE_ENERGY_SENSOR