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-12-23 15:56:18 +00:00
* - Supports single three phase device or three single phase devices of same model on bus .
2023-01-03 14:10:05 +00:00
* - For easy configuration of modbus energy monitor device ( s ) use :
* - a rule file called modbus
2023-01-03 16:50:39 +00:00
* - a script using > y
2023-01-03 14:10:05 +00:00
* - a filesystem file called modbus . json
2022-10-06 22:17:04 +01:00
*
2023-01-03 16:05:18 +00:00
* See files configurations . md and value_pair_description . md in folder energy_modbus_configs
2022-10-08 15:14:11 +01:00
*
* Restrictions :
2022-12-23 13:26:25 +00:00
* - Supports Modbus single and double integer registers in addition to 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 :
2023-01-02 16:48:51 +00:00
* rule3 on file # modbus do { " Name " : " GROWATT " , " Baud " : 9600 , " Config " : " 8N1 " , " Address " : 1 , " Function " : 4 , " Voltage " : { " R " : [ 4110 , 4114 , 4118 ] , " T " : 3 , " F " : - 1 } , " Current " : { " R " : [ 4111 , 4115 , 4119 ] , " T " : 3 , " F " : - 1 } , " Power " : { " R " : [ 4112 , 4116 , 4120 ] , " T " : 8 , " F " : - 1 } , " Frequency " : { " R " : 4109 , " T " : 3 , " F " : - 2 } , " Total " : { " R " : 4124 , " T " : 8 , " F " : - 1 } , " User " : [ { " R " : [ 4099 , 4103 ] , " J " : " VoltagePV " , " G " : " Voltage PV " , " U " : " V " , " D " : 21 , " T " : 3 , " F " : - 1 } , { " R " : [ 4100 , 4104 ] , " J " : " CurrentPV " , " G " : " Current PV " , " U " : " A " , " D " : 22 , " T " : 3 , " F " : - 1 } , { " R " : [ 4101 , 4105 ] , " J " : " PowerPV " , " G " : " Power PV " , " U " : " W " , " D " : 23 , " T " : 8 , " F " : - 1 } ] } endon
* rule3 on file # modbus do { " Name " : " 2 x PZEM014 " , " Baud " : 9600 , " Config " : " 8N1 " , " Address " : [ 1 , 1 ] , " Function " : 4 , " Voltage " : { " R " : 0 , " T " : 3 , " F " : - 1 } , " Current " : { " R " : 1 , " T " : 8 , " F " : - 3 } , " Power " : { " R " : 3 , " T " : 8 , " F " : - 1 } , " Factor " : { " R " : 8 , " T " : 3 , " F " : - 2 } , " Frequency " : { " R " : 7 , " T " : 3 , " F " : - 1 } , " Total " : { " R " : 5 , " T " : 8 , " F " : - 3 } } endon
* rule3 on file # modbus do { " Name " : " SDM230 test1 " , " Baud " : 2400 , " Config " : " 8N1 " , " 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 " : " 8N1 " , " 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 " : " 8N1 " , " 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
* rule3 on file # modbus do { " Name " : " SDM230 test4 " , " Baud " : 2400 , " Config " : " 8N1 " , " 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
* rule3 on file # modbus do { " Name " : " SDM230 test5 " , " Baud " : 2400 , " Config " : " 8N1 " , " 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
* rule3 on file # modbus do { " Name " : " SDM230 test6 " , " Baud " : 2400 , " Config " : " 8N1 " , " 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 ] , " ExportActive " : 0x004A } endon
* rule3 on file # modbus do { " Name " : " SDM230 test7 " , " Baud " : 2400 , " Config " : " 8N1 " , " 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 ] , " ExportActive " : 0x004A , " User " : { " J " : " PhaseAngle " , " G " : " Phase Angle " , " R " : 0x0024 , " U " : " Deg " , " D " : 2 } } endon
* rule3 on file # modbus do { " Name " : " SDM230 test8 " , " Baud " : 2400 , " Config " : " 8N1 " , " 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 ] , " ExportActive " : 0x004A , " User " : { " J " : " PhaseAngle " , " G " : " Phase Angle " , " R " : [ 0x24 , 0x24 , 0x24 ] , " U " : " Deg " , " D " : 2 } } endon
2022-10-11 10:10:47 +01:00
*
2023-01-02 16:48:51 +00:00
* rule3 on file # modbus do { " Name " : " SDM120 test1 " , " Baud " : 2400 , " Config " : " 8N1 " , " 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
* rule3 on file # modbus do { " Name " : " PZEM014 test1 " , " Baud " : 9600 , " Config " : " 8N1 " , " Address " : 1 , " Function " : 4 , " Voltage " : { " R " : 0 , " T " : 3 , " F " : - 1 } , " Current " : { " R " : 1 , " T " : 8 , " F " : - 3 } , " Power " : { " R " : 3 , " T " : 8 , " F " : - 1 } , " Factor " : { " R " : 8 , " T " : 3 , " F " : - 2 } , " Frequency " : { " R " : 7 , " T " : 3 , " F " : - 1 } , " Total " : { " R " : 5 , " T " : 8 , " F " : - 3 } , " User " : { " R " : 0 , " J " : " VoltageTest " , " G " : " Voltage test " , " U " : " V " , " D " : 21 , " T " : 3 , " F " : - 1 } } endon
*
* rule3 on file # modbus do { " Name " : " SDM230 test6 " , " Baud " : 2400 , " Config " : " 8N1 " , " 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 " : " 8N1 " , " Address " : 1 , " Function " : 4 , " Voltage " : { " R " : 0 , " T " : 0 , " F " : 0 } , " Current " : { " R " : 6 , " T " : 0 , " F " : 0 } , " Power " : { " R " : 12 , " T " : 0 , " F " : 0 } , " Frequency " : 70 , " Total " : 342 , " User " : { " R " : 0x0048 , " T " : 0 , " F " : - 1 , " 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-12-23 15:56:18 +00:00
# define ENERGY_MODBUS_MAX_DEVICES ENERGY_MAX_PHASES // Support up to three single phase devices as three phases
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_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-12-24 14:59:29 +00:00
# define ENERGY_MODBUS_TICKER_POLL 200 // Modbus poll time in ms between read register requests
2022-10-11 17:39:48 +01:00
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
2023-01-03 14:10:05 +00:00
# define ENERGY_MODBUS_FILE " / modbus.json" // Modbus parameter file name used by filesystem
2022-12-23 13:26:25 +00:00
const uint16_t nrg_mbs_reg_not_used = 0xFFFF ; // Odd number 65535 is unused register
2022-10-11 10:10:47 +01:00
2022-12-23 15:56:18 +00:00
// Even data type is single (2-byte) register, Odd data type is double (4-byte) registers
2022-12-23 13:26:25 +00:00
enum EnergyModbusDataType { NRG_DT_FLOAT , // 0 = 4-byte float
NRG_DT_S16 , // 1 = 2-byte signed
NRG_DT_S32 , // 2 = 4-byte signed
NRG_DT_U16 , // 3 = 2-byte unsigned
NRG_DT_U32 , // 4 = 4-byte unsigned
2022-12-23 15:56:18 +00:00
NRG_DT_x16_nu1 , // 5 = 2-byte
2022-12-23 13:26:25 +00:00
NRG_DT_S32_SW , // 6 = 4-byte signed with swapped words
2022-12-23 15:56:18 +00:00
NRG_DT_x16_nu2 , // 7 = 2-byte
2022-12-23 13:26:25 +00:00
NRG_DT_U32_SW , // 8 = 4-byte unsigned with swapped words
2022-10-11 10:10:47 +01:00
NRG_DT_MAX } ;
2022-12-24 14:59:29 +00:00
enum EnergyModbusResolutions { NRG_RES_VOLTAGE = 21 , // 21 = V
NRG_RES_CURRENT , // 22 = A
NRG_RES_POWER , // 23 = W, VA, VAr
NRG_RES_ENERGY , // 24 = kWh, kVAh, kVArh
NRG_RES_FREQUENCY , // 25 = Hz
NRG_RES_TEMPERATURE , // 26 = C, F
NRG_RES_HUMIDITY , // 27 = %
NRG_RES_PRESSURE , // 28 = hPa, mmHg
NRG_RES_WEIGHT } ; // 29 = 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 ;
2022-12-24 14:59:29 +00:00
uint16_t ticker_poll ;
2022-12-23 15:56:18 +00:00
uint8_t device_address [ ENERGY_MODBUS_MAX_DEVICES ] ;
uint8_t devices ;
2022-10-11 10:10:47 +01:00
uint8_t function ;
uint8_t total_regs ;
uint8_t user_adds ;
uint8_t state ;
uint8_t retry ;
2022-12-28 16:06:54 +00:00
int8_t phase ;
2022-10-11 10:10:47 +01:00
bool mutex ;
} NrgMbsParam ;
typedef struct NRGMBSREGISTER {
uint16_t address [ ENERGY_MAX_PHASES ] ;
2022-12-23 10:39:13 +00:00
int16_t factor ;
2022-10-11 10:10:47 +01:00
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
2022-12-23 15:56:18 +00:00
// Even data type is single register, Odd data type is double registers
2022-10-11 10:10:47 +01:00
register_count = 2 - ( NrgMbsReg [ NrgMbsParam . state ] . datatype & 1 ) ;
uint32_t error = EnergyModbus - > ReceiveBuffer ( buffer , register_count ) ;
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
*/
2022-12-30 15:56:36 +00:00
# ifdef ENERGY_MODBUS_DEBUG
AddLog ( LOG_LEVEL_DEBUG_MORE , PSTR ( " NRG: Modbus register %d, phase %d, rcvd %*_H " ) ,
NrgMbsParam . state , NrgMbsParam . phase , EnergyModbus - > ReceiveCount ( ) , buffer ) ;
# endif
2022-10-06 22:17:04 +01:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " NRG: Modbus error %d " ) , error ) ;
} else {
2022-12-22 16:54:54 +00:00
/* Modbus protocol format:
* SA = Device Address
* FC = Function Code
* BC = Byte count
* Fh = First or High word MSB
* Fl = First or High word LSB
* Sh = Second or Low word MSB
* Sl = Second or Low word LSB
* Cl = CRC lsb
* Ch = CRC msb
*/
2023-01-24 15:54:03 +00:00
Energy - > data_valid [ NrgMbsParam . phase ] = 0 ;
2022-10-06 22:17:04 +01:00
float value ;
2022-10-11 10:10:47 +01:00
switch ( NrgMbsReg [ NrgMbsParam . state ] . datatype ) {
2022-12-22 16:54:54 +00:00
case NRG_DT_FLOAT : { // 0
// 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
2022-10-11 10:10:47 +01:00
( ( 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 ;
}
2022-12-22 16:54:54 +00:00
case NRG_DT_S16 : { // 1
// 0 1 2 3 4 5 6
// SA FC BC Fh Fl Cl Ch
2022-10-11 10:10:47 +01:00
int16_t value_buff = ( ( int16_t ) buffer [ 3 ] ) < < 8 | buffer [ 4 ] ;
value = ( float ) value_buff ;
break ;
}
2022-12-22 16:54:54 +00:00
case NRG_DT_U16 : { // 3
// 0 1 2 3 4 5 6
// SA FC BC Fh Fl Cl Ch
2022-10-11 10:10:47 +01:00
uint16_t value_buff = ( ( uint16_t ) buffer [ 3 ] ) < < 8 | buffer [ 4 ] ;
value = ( float ) value_buff ;
break ;
}
2022-12-22 16:54:54 +00:00
case NRG_DT_S32 : { // 2
// 0 1 2 3 4 5 6 7 8
// SA FC BC Fh Fl Sh Sl Cl Ch
2022-10-11 10:10:47 +01:00
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-22 16:54:54 +00:00
case NRG_DT_S32_SW : { // 6
// 0 1 2 3 4 5 6 7 8
// SA FC BC Sh Sl Fh Fl Cl Ch
2022-12-21 17:11:35 +00:00
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-12-22 16:54:54 +00:00
case NRG_DT_U32 : { // 4
// 0 1 2 3 4 5 6 7 8
// SA FC BC Fh Fl Sh Sl Cl Ch
2022-10-11 10:10:47 +01:00
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-22 16:54:54 +00:00
case NRG_DT_U32_SW : { // 8
// 0 1 2 3 4 5 6 7 8
// SA FC BC Sh Sl Fh Fl Cl Ch
// 01 04 04 EB EC 00 0E 8E 51 = 977.9000 (Solax protocol X1&X3)
2022-12-21 17:11:35 +00:00
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
}
2022-12-23 10:39:13 +00:00
uint32_t factor = 1 ;
// 1 = 10, 2 = 100, 3 = 1000, 4 = 10000
uint32_t scaler = abs ( NrgMbsReg [ NrgMbsParam . state ] . factor ) ;
while ( scaler ) {
factor * = 10 ;
scaler - - ;
}
if ( NrgMbsReg [ NrgMbsParam . state ] . factor < 0 ) {
value / = factor ;
2022-12-22 16:54:54 +00:00
} else {
2022-12-23 10:39:13 +00:00
value * = factor ;
2022-12-22 16:54:54 +00:00
}
2022-10-06 22:17:04 +01:00
2022-12-30 15:56:36 +00:00
AddLog ( LOG_LEVEL_DEBUG_MORE , PSTR ( " NRG: Modbus register %d, phase %d, rcvd %*_H, T %d, F %d, value %4_f " ) ,
NrgMbsParam . state , NrgMbsParam . phase , EnergyModbus - > ReceiveCount ( ) , buffer ,
NrgMbsReg [ NrgMbsParam . state ] . datatype , NrgMbsReg [ NrgMbsParam . state ] . factor , & value ) ;
2022-10-11 10:10:47 +01:00
switch ( NrgMbsParam . state ) {
2022-10-06 22:17:04 +01:00
case NRG_MBS_VOLTAGE :
2023-01-24 15:54:03 +00:00
Energy - > voltage [ NrgMbsParam . phase ] = value ; // 230.2 V
2022-10-06 22:17:04 +01:00
break ;
case NRG_MBS_CURRENT :
2023-01-24 15:54:03 +00:00
Energy - > current [ NrgMbsParam . phase ] = value ; // 1.260 A
2022-10-06 22:17:04 +01:00
break ;
case NRG_MBS_ACTIVE_POWER :
2023-01-24 15:54:03 +00:00
Energy - > active_power [ NrgMbsParam . phase ] = value ; // -196.3 W
2022-10-06 22:17:04 +01:00
break ;
case NRG_MBS_APPARENT_POWER :
2023-01-24 15:54:03 +00:00
Energy - > apparent_power [ NrgMbsParam . phase ] = value ; // 223.4 VA
2022-10-06 22:17:04 +01:00
break ;
case NRG_MBS_REACTIVE_POWER :
2023-01-24 15:54:03 +00:00
Energy - > reactive_power [ NrgMbsParam . phase ] = value ; // 92.2
2022-10-06 22:17:04 +01:00
break ;
case NRG_MBS_POWER_FACTOR :
2023-01-24 15:54:03 +00:00
Energy - > power_factor [ NrgMbsParam . phase ] = value ; // -0.91
2022-10-06 22:17:04 +01:00
break ;
case NRG_MBS_FREQUENCY :
2023-01-24 15:54:03 +00: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 :
2023-01-24 15:54:03 +00: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 :
2023-01-24 15:54:03 +00: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
}
}
} // end data ready
2022-10-11 10:10:47 +01:00
if ( 0 = = NrgMbsParam . retry | | data_ready ) {
NrgMbsParam . retry = 1 ;
2022-12-28 16:06:54 +00:00
uint32_t address = 0 ;
uint32_t phase = 0 ;
do {
NrgMbsParam . phase + + ;
2023-01-24 15:54:03 +00:00
if ( NrgMbsParam . phase > = Energy - > phase_count ) {
2022-12-28 16:06:54 +00:00
NrgMbsParam . phase = 0 ;
NrgMbsParam . state + + ;
if ( NrgMbsParam . state > = NrgMbsParam . total_regs ) {
NrgMbsParam . state = 0 ;
NrgMbsParam . phase = 0 ;
EnergyUpdateTotal ( ) ; // update every cycle after all registers have been read
}
}
delay ( 0 ) ;
if ( NrgMbsParam . devices = = 1 ) {
phase = NrgMbsParam . phase ;
} else {
address = NrgMbsParam . phase ;
}
} while ( NrgMbsReg [ NrgMbsParam . state ] . address [ phase ] = = nrg_mbs_reg_not_used ) ;
2022-12-23 15:56:18 +00:00
// Even data type is single register, Odd data type is double registers
2022-10-11 10:10:47 +01:00
register_count = 2 - ( NrgMbsReg [ NrgMbsParam . state ] . datatype & 1 ) ;
2022-12-28 16:06:54 +00:00
# ifdef ENERGY_MODBUS_DEBUG
AddLog ( LOG_LEVEL_DEBUG_MORE , PSTR ( " NRG: Modbus send Device %d, Function %d, Register %04X (%d/%d), Size %d " ) ,
NrgMbsParam . device_address [ address ] , NrgMbsParam . function ,
NrgMbsReg [ NrgMbsParam . state ] . address [ phase ] , NrgMbsParam . state , phase ,
register_count ) ;
# endif
2022-12-23 15:56:18 +00:00
EnergyModbus - > Send ( NrgMbsParam . device_address [ address ] , NrgMbsParam . function , NrgMbsReg [ NrgMbsParam . state ] . address [ 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-12-23 15:56:18 +00:00
if ( NrgMbsParam . devices > 1 ) {
2022-12-28 16:06:54 +00:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " NRG: Modbus retry device %d state %d " ) , NrgMbsParam . device_address [ NrgMbsParam . phase ] , NrgMbsParam . state ) ;
2022-12-23 15:56:18 +00:00
} else {
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " NRG: Modbus retry state %d phase %d " ) , NrgMbsParam . state , NrgMbsParam . phase ) ;
}
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-12-24 14:59:29 +00:00
uint32_t EnergyModbusReadRegisterInfo ( JsonParserObject add_value , uint32_t reg_index ) {
// {"R":0,"T":0,"F":0}
// {"R":[0,2,4],"T":0,"F":0}
// {"R":[0,2,4],"T":0,"M":10} - [LEGACY]
2022-10-08 15:14:11 +01:00
uint32_t phase = 0 ;
2022-12-24 14:59:29 +00:00
JsonParserToken val ;
val = add_value [ PSTR ( " R " ) ] ; // Register address
2022-10-08 15:14:11 +01:00
if ( val . isArray ( ) ) {
2022-12-24 14:59:29 +00:00
// [0,2,4]
2022-10-08 15:14:11 +01:00
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-12-24 14:59:29 +00:00
// 0
2022-10-11 10:10:47 +01:00
NrgMbsReg [ reg_index ] . address [ 0 ] = val . getUInt ( ) ;
2022-10-08 15:14:11 +01:00
phase + + ;
}
2022-12-24 14:59:29 +00:00
val = add_value [ PSTR ( " T " ) ] ; // Register data type
2022-10-11 10:10:47 +01:00
if ( val ) {
2022-12-24 14:59:29 +00:00
// 0
2022-10-11 10:10:47 +01:00
NrgMbsReg [ reg_index ] . datatype = val . getUInt ( ) ;
}
2022-12-24 14:59:29 +00:00
val = add_value [ PSTR ( " F " ) ] ; // Register factor
2022-10-11 10:10:47 +01:00
if ( val ) {
2022-12-24 14:59:29 +00:00
// 1 or -2
2022-12-23 10:39:13 +00:00
NrgMbsReg [ reg_index ] . factor = val . getInt ( ) ;
}
2022-12-24 14:59:29 +00:00
val = add_value [ PSTR ( " M " ) ] ; // [LEGACY] Register divider
2022-12-23 10:39:13 +00:00
if ( val ) {
2022-12-24 14:59:29 +00:00
// 1
2022-12-23 10:39:13 +00:00
int32_t divider = val . getUInt ( ) ;
int factor = 0 ;
while ( divider > 1 ) {
divider / = 10 ;
factor - - ;
}
NrgMbsReg [ reg_index ] . factor = factor ;
2022-10-11 10:10:47 +01:00
}
2022-12-24 14:59:29 +00:00
return phase ;
}
bool EnergyModbusReadUserRegisters ( JsonParserObject user_add_value , uint32_t add_index ) {
// {"R":0x004E,"T":0,"F":0,"J":"ExportReactive","G":"Export Reactive","U":"kVArh","D":3,"T":0,"F":0}
// {"R":[0,2,4],"T":0,"F":0,"J":"ExportReactive","G":"Export Reactive","U":"kVArh","D":3,"T":0,"F":0}
uint32_t reg_index = NRG_MBS_MAX_REGS + add_index ;
// {"R":0,"T":0,"F":0}
// {"R":[0,2,4],"T":0,"F":0}
// {"R":[0,2,4],"T":0,"M":10} - [LEGACY]
uint32_t phase = EnergyModbusReadRegisterInfo ( user_add_value , reg_index ) ;
if ( ! phase ) {
return false ; // No register entered so skip
}
2023-01-24 15:54:03 +00:00
if ( phase > Energy - > phase_count ) {
Energy - > phase_count = phase ;
2022-12-24 14:59:29 +00:00
NrgMbsParam . devices = 1 ; // Only one device allowed with multiple phases
}
JsonParserToken val ;
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-12-28 16:45:13 +00:00
char json_name [ 32 ] ;
if ( GetCommandCode ( json_name , sizeof ( json_name ) , NrgMbsUser [ add_index ] . json_name , kEnergyModbusValues ) > - 1 ) {
return false ; // Duplicate JSON name
}
2022-10-08 15:14:11 +01:00
} else {
2022-12-28 16:45:13 +00:00
return false ; // No mandatory JSON name
2022-10-08 15:14:11 +01:00
}
val = user_add_value [ PSTR ( " G " ) ] ; // GUI value name
2022-12-28 16:45:13 +00:00
NrgMbsUser [ add_index ] . gui_name = ( val ) ? SetStr ( val . getStr ( ) ) : EmptyStr ;
2022-10-08 15:14:11 +01:00
val = user_add_value [ PSTR ( " U " ) ] ; // GUI value Unit
2022-12-28 16:45:13 +00:00
NrgMbsUser [ add_index ] . gui_unit = ( val ) ? SetStr ( val . getStr ( ) ) : EmptyStr ;
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-12-24 14:59:29 +00:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " NRG: Idx %d (%s), R [%04X,%04X,%04X], T %d, F %d, J '%s', G '%s', U '%s', D %d " ) ,
2022-12-28 16:06:54 +00:00
reg_index , NrgMbsUser [ add_index ] . json_name ,
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 ,
2022-12-23 10:39:13 +00:00
NrgMbsReg [ reg_index ] . factor ,
2022-10-11 10:10:47 +01:00
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 ;
}
2022-10-06 22:17:04 +01:00
bool EnergyModbusReadRegisters ( void ) {
2023-01-03 14:10:05 +00:00
String modbus = " " ;
# ifdef USE_UFILESYS
2023-01-04 11:00:09 +00:00
modbus = TfsLoadString ( ENERGY_MODBUS_FILE ) ;
if ( modbus . length ( ) ) {
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " NRG: Loaded from File " ) ) ;
2023-01-03 14:10:05 +00:00
}
# endif // USE_UFILESYS
2022-10-06 22:17:04 +01:00
# ifdef USE_RULES
2023-01-03 14:10:05 +00:00
if ( ! modbus . length ( ) ) {
modbus = RuleLoadFile ( " MODBUS " ) ;
2023-01-03 16:05:18 +00:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " NRG: Loaded from Rule " ) ) ;
2023-01-03 14:10:05 +00:00
}
# endif // USE_RULES
2023-01-03 16:41:37 +00:00
# ifdef USE_SCRIPT
if ( ! modbus . length ( ) ) {
modbus = ScriptLoadSection ( " >y " ) ;
2023-01-03 16:50:39 +00:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " NRG: Loaded from Script " ) ) ;
2023-01-03 16:41:37 +00:00
}
# endif // USE_SCRIPT
2023-01-04 11:00:09 +00:00
if ( modbus . length ( ) < 7 ) { return false ; } // File not found or Invalid JSON
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 ;
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
2023-01-24 15:54:03 +00:00
Energy - > phase_count = 1 ;
2022-10-11 10:10:47 +01:00
NrgMbsParam . serial_bps = ENERGY_MODBUS_SPEED ;
NrgMbsParam . serial_config = ENERGY_MODBUS_CONFIG ;
2022-12-24 14:59:29 +00:00
NrgMbsParam . ticker_poll = ENERGY_MODBUS_TICKER_POLL ;
2022-12-23 15:56:18 +00:00
NrgMbsParam . device_address [ 0 ] = ENERGY_MODBUS_ADDR ;
NrgMbsParam . devices = 1 ;
2022-10-11 10:10:47 +01:00
NrgMbsParam . function = ENERGY_MODBUS_FUNC ;
NrgMbsParam . user_adds = 0 ;
2022-10-06 22:17:04 +01:00
2022-12-24 14:59:29 +00:00
// Detect buffer allocation
2022-10-11 10:10:47 +01:00
JsonParserToken val ;
val = root [ PSTR ( " User " ) ] ;
if ( val ) {
if ( val . isArray ( ) ) {
2022-12-24 14:59:29 +00:00
// [{"R":0x004E,"J":"ExportReactive","G":"Export Reactive","U":"kVArh","D":3},{"R":0x0024,"J":"PhaseAngle","G":"Phase Angle","U":"Deg","D":2}]
2022-10-11 10:10:47 +01:00
NrgMbsParam . user_adds = val . size ( ) ;
} else {
2022-12-24 14:59:29 +00:00
// {"R":0x004E,"J":"ExportReactive","G":"Export Reactive","U":"kVArh","D":3}
2022-10-11 10:10:47 +01:00
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-12-24 14:59:29 +00:00
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 ;
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-12-24 14:59:29 +00:00
// Get global parameters
2022-10-07 09:46:25 +01:00
val = root [ PSTR ( " Baud " ) ] ;
2022-10-06 22:17:04 +01:00
if ( val ) {
2022-12-24 14:59:29 +00: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-12-24 14:59:29 +00:00
val = root [ PSTR ( " Poll " ) ] ;
if ( val ) {
NrgMbsParam . ticker_poll = val . getUInt ( ) ; // 200
if ( NrgMbsParam . ticker_poll < 100 ) { // Below 100 ms makes no sense as the comms usually is 9600bps
NrgMbsParam . ticker_poll = ENERGY_MODBUS_TICKER_POLL ;
}
}
2022-10-07 09:46:25 +01:00
val = root [ PSTR ( " Address " ) ] ;
2022-10-06 22:17:04 +01:00
if ( val ) {
2022-12-23 15:56:18 +00:00
NrgMbsParam . devices = 0 ;
if ( val . isArray ( ) ) {
2022-12-24 14:59:29 +00:00
// [1,2,3]
2022-12-23 15:56:18 +00:00
JsonParserArray arr = val . getArray ( ) ;
for ( auto value : arr ) {
NrgMbsParam . device_address [ NrgMbsParam . devices ] = value . getUInt ( ) ; // 1
NrgMbsParam . devices + + ;
if ( NrgMbsParam . devices > = ENERGY_MODBUS_MAX_DEVICES ) { break ; }
}
} else if ( val ) {
2022-12-24 14:59:29 +00:00
// 1
NrgMbsParam . device_address [ 0 ] = val . getUInt ( ) ; // 1
2022-12-23 15:56:18 +00:00
NrgMbsParam . devices + + ;
}
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-12-24 14:59:29 +00:00
NrgMbsParam . function = val . getUInt ( ) ; // 4
2022-10-06 22:17:04 +01:00
}
2022-12-24 14:59:29 +00:00
// Get default energy registers
2022-10-06 22:17:04 +01:00
char register_name [ 32 ] ;
2023-01-24 15:54:03 +00:00
Energy - > voltage_available = false ; // Disable voltage is measured
Energy - > current_available = false ; // Disable current is measured
2022-10-06 22:17:04 +01:00
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
2022-12-24 14:59:29 +00:00
// "Voltage":[0,2,4]
2022-12-23 10:39:13 +00:00
// "Voltage":{"R":0,"T":0,"F":0}
2022-12-24 14:59:29 +00:00
// "Voltage":{"R":[0,2,4],"T":0,"F":0}
2022-10-08 15:14:11 +01:00
uint32_t phase = 0 ;
2022-10-11 10:10:47 +01:00
if ( val . isObject ( ) ) {
2022-12-24 14:59:29 +00:00
// {"R":0,"T":0,"F":0}
// {"R":[0,2,4],"T":0,"F":0}
// {"R":[0,2,4],"T":0,"M":10} - [LEGACY]
phase = EnergyModbusReadRegisterInfo ( val . getObject ( ) , names ) ;
2022-10-11 10:10:47 +01:00
} else if ( val . isArray ( ) ) {
2022-12-24 14:59:29 +00:00
// [0,2,4]
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-12-24 14:59:29 +00:00
// 0
2022-10-11 10:10:47 +01:00
NrgMbsReg [ names ] . address [ 0 ] = val . getUInt ( ) ;
2022-10-06 22:17:04 +01:00
phase + + ;
}
2023-01-24 15:54:03 +00:00
if ( phase > Energy - > phase_count ) {
Energy - > phase_count = phase ;
2022-12-24 14:59:29 +00:00
NrgMbsParam . devices = 1 ; // Only one device allowed with multiple phases
2022-10-06 22:26:54 +01:00
}
2022-12-24 14:59:29 +00:00
2022-10-06 22:17:04 +01:00
switch ( names ) {
case NRG_MBS_VOLTAGE :
2023-01-24 15:54:03 +00:00
Energy - > voltage_available = true ; // Enable if voltage is measured
2022-10-06 22:17:04 +01:00
if ( 1 = = phase ) {
2023-01-24 15:54:03 +00:00
Energy - > voltage_common = true ; // Use common voltage
2022-10-06 22:17:04 +01:00
}
break ;
case NRG_MBS_CURRENT :
2023-01-24 15:54:03 +00:00
Energy - > current_available = true ; // Enable if current is measured
2022-10-06 22:17:04 +01:00
break ;
case NRG_MBS_FREQUENCY :
if ( 1 = = phase ) {
2023-01-24 15:54:03 +00:00
Energy - > frequency_common = true ; // Use common frequency
2022-10-06 22:17:04 +01:00
}
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-12-24 14:59:29 +00:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " NRG: Idx %d (%s), R [%04X,%04X,%04X], T %d, F %d " ) ,
names , register_name ,
2022-10-11 10:10:47 +01:00
NrgMbsReg [ names ] . address [ 0 ] ,
NrgMbsReg [ names ] . address [ 1 ] ,
NrgMbsReg [ names ] . address [ 2 ] ,
NrgMbsReg [ names ] . datatype ,
2022-12-23 10:39:13 +00:00
NrgMbsReg [ names ] . factor ) ;
2022-10-08 15:14:11 +01:00
# endif
}
}
2022-12-24 14:59:29 +00:00
// Get user defined registers
// "User":{"R":0x004E,"J":"ExportReactive","G":"Export Reactive","U":"kVArh","D":3,"T":0,"F":0}
// "User":[{"R":0x004E,"J":"ExportReactive","G":"Export Reactive","U":"kVArh","D":3,"T":0,"F":0},{"R":0x0024,"J":"PhaseAngle","G":"Phase Angle","U":"Deg","D":2,"T":0,"F":0}]
2022-10-08 15:14:11 +01:00
val = root [ PSTR ( " User " ) ] ;
if ( val ) {
if ( val . isArray ( ) ) {
2022-12-24 14:59:29 +00:00
// [{"R":0x004E,"J":"ExportReactive","G":"Export Reactive","U":"kVArh","D":3,"T":0,"F":0},{"R":0x0024,"J":"PhaseAngle","G":"Phase Angle","U":"Deg","D":2,"T":0,"F":0}]
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 ) {
2022-12-24 14:59:29 +00:00
// {"R":0x004E,"J":"ExportReactive","G":"Export Reactive","U":"kVArh","D":3,"T":0,"F":0}
2022-10-11 10:10:47 +01:00
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 ;
}
2022-12-24 14:59:29 +00:00
// Fix variable boundaries
2022-10-11 10:10:47 +01:00
for ( uint32_t i = 0 ; i < NrgMbsParam . total_regs ; i + + ) {
if ( NrgMbsReg [ i ] . datatype > = NRG_DT_MAX ) {
NrgMbsReg [ i ] . datatype = ENERGY_MODBUS_DATATYPE ;
}
2022-10-06 22:17:04 +01:00
}
2022-12-23 15:56:18 +00:00
if ( NrgMbsParam . devices > 1 ) {
2022-12-24 14:59:29 +00:00
// Multiple devices have no common values
2023-01-24 15:54:03 +00:00
Energy - > phase_count = NrgMbsParam . devices ;
Energy - > voltage_common = false ; // Use no common voltage
Energy - > frequency_common = false ; // Use no common frequency
2022-12-23 15:56:18 +00:00
Settings - > flag5 . energy_phase = 1 ; // SetOption129 - (Energy) Show phase information
}
2022-10-08 15:14:11 +01:00
2022-10-07 09:46:25 +01:00
# ifdef ENERGY_MODBUS_DEBUG
2022-12-24 14:59:29 +00:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " NRG: Devices %d, RAM usage %d + %d + %d " ) ,
NrgMbsParam . devices ,
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()
2022-12-28 16:06:54 +00:00
NrgMbsParam . phase = - 1 ;
2022-10-06 22:17:04 +01:00
return true ;
}
bool EnergyModbusRegisters ( void ) {
if ( EnergyModbusReadRegisters ( ) ) {
return true ;
}
2023-01-03 14:10:05 +00:00
AddLog ( LOG_LEVEL_INFO , PSTR ( " NRG: No valid modbus data " ) ) ;
2022-10-06 22:17:04 +01:00
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-12-24 14:59:29 +00:00
ticker_energy_modbus . attach_ms ( NrgMbsParam . ticker_poll , 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
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 ) {
2023-03-17 20:05:51 +00:00
ResponseAppend_P ( PSTR ( " , \" %s \" :%s " ) , NrgMbsUser [ i ] . json_name , EnergyFmt ( values , resolution , single ) ) ;
2022-10-08 15:14:11 +01:00
# ifdef USE_WEBSERVER
} else {
2022-12-28 16:45:13 +00:00
if ( strlen ( NrgMbsUser [ i ] . gui_name ) ) { // Skip empty GUI names
2023-03-18 17:26:43 +00:00
WSContentSend_PD ( PSTR ( " {s}%s{m}%s %s{e} " ) , NrgMbsUser [ i ] . gui_name , WebEnergyFmt ( values , resolution , single ) , NrgMbsUser [ i ] . gui_unit ) ;
2022-12-28 16:45:13 +00:00
}
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
case FUNC_WEB_COL_SENSOR :
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