Added V9240 energy metering chip driver (#23127)

* Add V9240 driver

* Addendum to previous commit

* Add driver code similar to the prototype

* they are talking to each other

* Added implementation of calibration commands

* continued work of calibration

* Maybe it works.

* Post-merger control

* Change driver number 34 to 25

* Correction of other comments

* Removed  duplicate code

* Adjusting the calibration procedure according to the behavior stated here. To the extent possible.
https://tasmota.github.io/docs/Power-Monitoring-Calibration/#calibration-procedure

* Removed added trailing whitespaces

* Fixing several small issues.
This commit is contained in:
Ivan Chopa 2025-03-11 15:28:07 +02:00 committed by GitHub
parent 08fae4bcd6
commit 758ba17dde
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 850 additions and 3 deletions

View File

@ -333,6 +333,8 @@ const be_const_member_t lv_gpio_constants[] = {
{ "TUYA_TX", (int32_t) GPIO_TUYA_TX },
{ "TX2X_TXD_BLACK", (int32_t) GPIO_TX2X_TXD_BLACK },
{ "TXD", (int32_t) GPIO_TXD },
{ "V9240_RX", (int32_t) GPIO_V9240_RX },
{ "V9240_TX", (int32_t) GPIO_V9240_TX },
{ "VINDRIKTNING_RX", (int32_t) GPIO_VINDRIKTNING_RX },
{ "VL53LXX_XSHUT1", (int32_t) GPIO_VL53LXX_XSHUT1 },
{ "WE517_RX", (int32_t) GPIO_WE517_RX },

View File

@ -230,6 +230,7 @@ enum UserSelectablePins {
GPIO_TM1640CLK, GPIO_TM1640DIN, // TM1640 (16 x seven-segment LED controler)
GPIO_TWAI_TX, GPIO_TWAI_RX, GPIO_TWAI_BO, GPIO_TWAI_CLK, // ESP32 TWAI serial interface
GPIO_C8_CO2_5K_TX, GPIO_C8_CO2_5K_RX, // C8-CO2-5K CO2 Sensor
GPIO_V9240_TX, GPIO_V9240_RX, // V9240 serial interface
GPIO_SENSOR_END };
// Error as warning to rethink GPIO usage with max 2045
@ -506,7 +507,8 @@ const char kSensorNames[] PROGMEM =
D_SENSOR_I2C_SER_TX "|" D_SENSOR_I2C_SER_RX "|"
D_SENSOR_TM1640_CLK "|" D_SENSOR_TM1640_DIN "|"
D_SENSOR_TWAI_TX "|" D_SENSOR_TWAI_RX "|" D_SENSOR_TWAI_BO "|" D_SENSOR_TWAI_CLK "|"
D_SENSOR_C8_CO2_5K_TX "|" D_SENSOR_C8_CO2_5K_RX
D_SENSOR_C8_CO2_5K_TX "|" D_SENSOR_C8_CO2_5K_RX "|"
D_SENSOR_V9240_TX "|" D_SENSOR_V9240_RX
;
const char kSensorNamesFixed[] PROGMEM =
@ -1007,6 +1009,10 @@ const uint16_t kGpioNiceList[] PROGMEM = {
AGPIO(GPIO_BL6523_TX), // BL6523 based Watt meter Serial interface
AGPIO(GPIO_BL6523_RX), // BL6523 based Watt meter Serial interface
#endif
#ifdef USE_V9240
AGPIO(GPIO_V9240_RX), // Serial V9240 interface
AGPIO(GPIO_V9240_TX), // Serial V9240 interface
#endif
#endif // USE_ENERGY_SENSOR
/*-------------------------------------------------------------------------------------------*\

View File

@ -1314,4 +1314,8 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_AF_AF_H_

View File

@ -1314,4 +1314,10 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_BG_BG_H_

View File

@ -1314,4 +1314,8 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_CA_AD_H_

View File

@ -1314,4 +1314,8 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_CS_CZ_H_

View File

@ -1314,4 +1314,8 @@
#define D_CHARGING "Aufladen"
#define D_CAPACITY "Kapazität"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_DE_DE_H_

View File

@ -1314,4 +1314,8 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_EL_GR_H_

View File

@ -1315,4 +1315,8 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_EN_GB_H_

View File

@ -1314,4 +1314,8 @@
#define D_CHARGING "Cargando"
#define D_CAPACITY "Capacidad"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_ES_ES_H_

View File

@ -1315,4 +1315,8 @@
#define D_CHARGING "En charge"
#define D_CAPACITY "Capacité"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_FR_FR_H_

View File

@ -1314,4 +1314,8 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_FY_NL_H_

View File

@ -1314,4 +1314,8 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_HE_HE_H_

View File

@ -1321,4 +1321,8 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_HU_HU_H_

View File

@ -1315,4 +1315,8 @@
#define D_CHARGING "In carica"
#define D_CAPACITY "Capacità"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_IT_IT_H_

View File

@ -1314,4 +1314,8 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_KO_KO_H_

View File

@ -1314,4 +1314,8 @@
#define D_CHARGING "Kraunasi"
#define D_CAPACITY "Talpa"
// xnrg_34_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_LT_LT_H_

View File

@ -1314,4 +1314,8 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_NL_NL_H_

View File

@ -1314,4 +1314,8 @@
#define D_CHARGING "Ładowanie"
#define D_CAPACITY "Pojemność"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_PL_PL_D_H_

View File

@ -1314,4 +1314,8 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_PT_BR_H_

View File

@ -1314,4 +1314,8 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_PT_PT_H_

View File

@ -1314,4 +1314,8 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_RO_RO_H_

View File

@ -1315,4 +1315,8 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_RU_RU_H_

View File

@ -1314,4 +1314,8 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_SK_SK_H_

View File

@ -1314,4 +1314,8 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_SV_SE_H_

View File

@ -1314,4 +1314,8 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_TR_TR_H_

View File

@ -1314,4 +1314,9 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_UK_UA_H_

View File

@ -1314,4 +1314,8 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_VI_VN_H_

View File

@ -1314,4 +1314,8 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_ZH_CN_H_

View File

@ -1314,4 +1314,8 @@
#define D_CHARGING "Charging"
#define D_CAPACITY "Capacity"
// xnrg_25_v9240.ino
#define D_SENSOR_V9240_TX "V9240 TX"
#define D_SENSOR_V9240_RX "V9240 RX"
#endif // _LANGUAGE_ZH_TW_H_

View File

@ -950,6 +950,7 @@
// #define IEM3000_IEM3155 // Compatibility fix for Iem3155 (changes Power and Energy total readout)
//#define USE_WE517 // Add support for Orno WE517-Modbus energy monitor (+1k code)
//#define USE_MODBUS_ENERGY // Add support for generic modbus energy monitor using a user file in rule space (+5k)
//#define USE_V9240 // Add support for v9240
// -- Low level interface devices -----------------
#define USE_DHT // Add support for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321) and SI7021 Temperature and Humidity sensor (1k6 code)

View File

@ -943,7 +943,9 @@ constexpr uint32_t feature[] = {
#ifdef USE_WIZMOTE
0x00002000 | // xdrv_77_wizmote.ino
#endif
// 0x00004000 | //
#if defined(USE_ENERGY_SENSOR) && defined(USE_V9240)
0x00004000 | // xnrg_25_v9240.ino
#endif
// 0x00008000 | //
// 0x00010000 | //
// 0x00020000 | //

View File

@ -924,6 +924,7 @@ uint32_t EnergyGetCalibration(uint32_t cal_type, uint32_t chan = 0) {
case ENERGY_POWER_CALIBRATION: return Settings->energy_power_calibration;
case ENERGY_VOLTAGE_CALIBRATION: return Settings->energy_voltage_calibration;
case ENERGY_CURRENT_CALIBRATION: return Settings->energy_current_calibration;
}
}
return Settings->energy_frequency_calibration;
@ -1300,7 +1301,6 @@ void EnergyShow(bool json) {
else if (0 == Energy->current[i]) {
reactive_power[i] = 0;
}
}
}
}

View File

@ -0,0 +1,721 @@
/*
xnrg_25_v9240.ino - v9240 energy sensor support for Tasmota
*/
#ifdef USE_ENERGY_SENSOR
#ifdef USE_V9240
#define XNRG_25 25
#include <TasmotaSerial.h>
#include <algorithm>
#define V9240_SERIAL_BAUDRATE 19200
namespace Address
{
static const uint16_t SysCtrl = 0x0180;
static const uint16_t AnaCtrl0 = 0x0182;
static const uint16_t AnaCtrl1 = 0x0183;
static const uint16_t PAC = 0x00F6;
static const uint16_t PHC = 0x00F7;
static const uint16_t PADCC = 0x00F8;
static const uint16_t QAC = 0x00F9;
static const uint16_t QADCC = 0x00FB;
static const uint16_t IAC = 0x00FD;
static const uint16_t IADCC = 0x00FE;
static const uint16_t UC = 0x00FF;
static const uint16_t IAADCC = 0x0104;
static const uint16_t UADCC = 0x0106;
static const uint16_t BPFPARA = 0x0107;
static const uint16_t UDCC = 0x0108;
static const uint16_t CKSUM = 0x0109;
static const uint16_t RW_START = PAC;
static const uint16_t SysStsClr = 0x019D;
static const uint16_t SFTRST = 0x01BF;
static const uint16_t SysSts = 0x00CA;
static const uint16_t FREQINST = 0x00CB;
static const uint16_t PAINST = 0x00CC;
static const uint16_t QINST = 0x00CD;
static const uint16_t IAINST = 0x00CE;
static const uint16_t UINST = 0x00CF;
static const uint16_t PAAVG = 0x00D0;
static const uint16_t QAVG = 0x00D1;
static const uint16_t FREQAVG = 0x00D2;
static const uint16_t IAAVG = 0x00D3;
static const uint16_t UAVG = 0x00D4;
static const uint16_t UDCINST = 0x00D9;
static const uint16_t IADCINST = 0x00DA;
static const uint16_t ZXDATREG = 0x00DC;
static const uint16_t ZXDAT = 0x00DD;
static const uint16_t PHDAT = 0x00DE;
static const uint16_t T8BAUD = 0x00E0;
static const uint16_t RO_START = SysSts;
} ;
// register structures
union SysSts // 0x0CA
{
int32_t reg;
struct __attribute__ ((packed))
{
uint32_t ref:1; // 0
uint32_t phsdone:1; // 1
uint32_t chkerr:1; // 2
uint32_t rstsrc:3; // 3:5
uint32_t pdn_r:1; // 6
uint32_t pdn:1; // 7
uint32_t bisterr:1; // 8
uint32_t phsdone_r:1; // 9
uint32_t Resered1:1; // 10
uint32_t usign:1; // 11
};
} ;
union SysCtrl // 0x0180
{
int32_t reg;
struct __attribute__ ((packed))
{
uint32_t Reserved0:1; // 0
uint32_t pgau:1; // 1
uint32_t bphpf:1; // 2
uint32_t iesul:1; // 3
uint32_t iepdn:1; // 4
uint32_t iehse:1; // 5
uint32_t rcx12:1; // 6
uint32_t rctrim:5; // 7:11
uint32_t shorti:1; // 12
uint32_t shortu:1; // 13
uint32_t restl:2; // 14:15
uint32_t rest:3; // 16:18
uint32_t meaclksel:1; // 19
uint32_t adcclksel:2; // 20:21
uint32_t gai:2; // 22:23
uint32_t Reserved1:2; // 24:25
uint32_t gu:1; // 26
uint32_t adciapdn:1; // 27
uint32_t Reserved2:1; // 28
uint32_t adcupdn:1; // 29
uint32_t Reserved3:2; // 30:31
};
};
union AnaCtrl0 // 0x0182
{
int32_t reg;
struct __attribute__ ((packed))
{
uint32_t Reserved1:8; // 0:7
uint32_t IT:2; // 8:9
uint32_t Reserved2:22; // 10:31
};
};
union AnaCtrl1 // 0x0183
{
int32_t reg;
struct __attribute__ ((packed))
{
uint32_t Reserved1:28; // 0:27
uint32_t CSEL:2; // 28:29
uint32_t Reserved2:2; // 30:31
};
};
union SysStsClr // 0x019D
{
int32_t reg;
struct __attribute__ ((packed))
{
uint32_t Reserved1:6; // 0:5
uint32_t pdn_clr:1; // 6
uint32_t Reserved2:2; // 7:8
uint32_t phsdone_clr:1;// 9
uint32_t Reserved3:22; // 10:31
};
};
// communication structures
union packet
{
struct __attribute__ ((packed)) {
uint8_t header;
uint16_t ctrl:4;
uint16_t addr_h:4;
uint16_t addr_l:8;
uint32_t data;
uint8_t check;
} ;
char buff[8];
} ;
class V9240
{
private:
static constexpr size_t rw_len = 23;
static constexpr size_t ro_len = 22;
static constexpr size_t buffer_size = 128; // max response + max query
static constexpr int32_t header = 0x7d;
static constexpr int32_t ctrl_read = 0x1;
static constexpr int32_t ctrl_write = 0x2;
explicit V9240();
~V9240();
public:
enum parameter{
Voltage = 0,
Amperage,
Frequency,
Power,
Reactive
};
static V9240& instance();
bool open(char const*);
void close();
void start();
bool reset();
inline void v9240_loop() {read_data_from_port();};
bool process_command();
float value(const parameter p) const;
inline float operator [] (const parameter p) const {return value(p);};
private:
bool read(int32_t *ptr,uint16_t address, size_t n);
bool write(int16_t address, int32_t data);
void set_checksum();
inline char calc_check(char *buff,size_t len);
private :
void read_data_from_port();
void send_next();
private:
TasmotaSerial *port;
size_t step = 0;
size_t next_read_len = 0;
char serial_buff[buffer_size];
int32_t *ptr_read = nullptr;
uint32_t start_pause_timer = 0;
uint32_t timeout = 0 ;
// chip memory
int32_t rw_mem[rw_len];
int32_t ro_mem[ro_len];
// RW Control & Calibration registers
volatile SysCtrl &SYSCTRL; // rw_mem[0];
volatile AnaCtrl0 &ANACTRL0;
volatile AnaCtrl1 &ANACTRL1;
volatile int32_t &UADCC;
volatile int32_t &IAADCC ;
volatile int32_t &PAC ; // = 1;
volatile int32_t &PADCC; // = 0;
volatile int32_t &PHC; // = 0;
volatile int32_t &QAC; // = 1;
volatile int32_t &QADCC; // = 0;
volatile int32_t &IAC; // = 0;
volatile int32_t &IADCC; // = 0;
volatile int32_t &UC; // = -1103500000;
volatile int32_t &UDCC; // = 1196289;
volatile int32_t &BPFPARA; // = 0x806764B6;
volatile int32_t &CKSUM; // = 0;
// RO Meterin control register
volatile const SysSts &SYSSTS;
volatile const int32_t &FREQINST;
volatile const int32_t &PAINST;
volatile const int32_t &QINST;
volatile const int32_t &IAINST;
volatile const int32_t &UINST;
volatile const int32_t &PAAVG;
volatile const int32_t &QAVG;
volatile const int32_t &FREQAVG;
volatile const int32_t &IAAVG;
volatile const int32_t &UAVG;
volatile const int32_t &UDCINST;
volatile const int32_t &IADCINST;
volatile const int32_t &ZXDATREG;
volatile const int32_t &ZXDAT;
volatile const int32_t &PHDAT;
volatile const int32_t &T8BAUD;
};
V9240& V9240::instance()
{
static V9240 chip;
return chip;
}
V9240::V9240() : port(nullptr)
// mapping registers to arrays
, SYSCTRL (*reinterpret_cast<SysCtrl*>(rw_mem + 0))
, ANACTRL0(*reinterpret_cast<AnaCtrl0*>(rw_mem + 1))
, ANACTRL1(*reinterpret_cast<AnaCtrl1*>(rw_mem + 2))
, UADCC (*(rw_mem+3+ (Address::UADCC - Address::RW_START )))
, IAADCC (*(rw_mem+3+ (Address::IAADCC - Address::RW_START )))
, PAC (*(rw_mem+3+ (Address::PAC - Address::RW_START )))
, PADCC (*(rw_mem+3+ (Address::PADCC - Address::RW_START )))
, PHC (*(rw_mem+3+ (Address::PHC - Address::RW_START )))
, QAC (*(rw_mem+3+ (Address::QAC - Address::RW_START )))
, QADCC (*(rw_mem+3+ (Address::QADCC - Address::RW_START )))
, IAC (*(rw_mem+3+ (Address::IAC - Address::RW_START )))
, IADCC (*(rw_mem+3+ (Address::IADCC - Address::RW_START )))
, UC (*(rw_mem+3+ (Address::UC - Address::RW_START )))
, UDCC (*(rw_mem+3+ (Address::UDCC - Address::RW_START )))
, BPFPARA(*(rw_mem+3+ (Address::BPFPARA - Address::RW_START )))
, CKSUM (*(rw_mem+3+ (Address::CKSUM - Address::RW_START )))
, SYSSTS (*reinterpret_cast<SysSts*>(ro_mem + (Address::SysSts - Address::RO_START)))
, FREQINST(*(ro_mem + (Address::FREQINST - Address::RO_START )))
, PAINST (*(ro_mem + (Address::PAINST - Address::RO_START )))
, QINST (*(ro_mem + (Address::QINST - Address::RO_START )))
, IAINST (*(ro_mem + (Address::IAINST - Address::RO_START )))
, UINST (*(ro_mem + (Address::UINST - Address::RO_START )))
, PAAVG (*(ro_mem + (Address::PAAVG - Address::RO_START )))
, QAVG (*(ro_mem + (Address::QAVG - Address::RO_START )))
, FREQAVG (*(ro_mem + (Address::FREQAVG - Address::RO_START )))
, IAAVG (*(ro_mem + (Address::IAAVG - Address::RO_START )))
, UAVG (*(ro_mem + (Address::UAVG - Address::RO_START )))
, UDCINST (*(ro_mem + (Address::UDCINST - Address::RO_START )))
, IADCINST(*(ro_mem + (Address::IADCINST - Address::RO_START )))
, ZXDATREG(*(ro_mem + (Address::ZXDATREG - Address::RO_START )))
, ZXDAT (*(ro_mem + (Address::ZXDAT - Address::RO_START )))
, PHDAT (*(ro_mem + (Address::PHDAT - Address::RO_START )))
, T8BAUD (*(ro_mem + (Address::T8BAUD - Address::RO_START - 1 )))
{
memset(rw_mem,0,sizeof (rw_mem));
memset(ro_mem,0,sizeof (ro_mem));
// its calibration coeficients for my chip
PAC = EnergyGetCalibration(ENERGY_POWER_CALIBRATION, 0);
PADCC = 0;
PHC = EnergyGetCalibration(ENERGY_FREQUENCY_CALIBRATION, 0);
QAC = -1;
QADCC = 0;
IAC = EnergyGetCalibration(ENERGY_CURRENT_CALIBRATION, 0);
IADCC = 75969; // 16758789;
UC = EnergyGetCalibration(ENERGY_VOLTAGE_CALIBRATION);
UDCC = 3596949; // 1196289;
BPFPARA = 0x806764B6;
}
V9240::~V9240()
{
close();
}
void V9240::start()
{
if(port != nullptr)
{
step = 0;
port->flush();
read(ro_mem,Address::SysSts,1);
timeout = millis();
}
}
float V9240::value(const V9240::parameter p) const
{
float v = 0;
switch (p) {
case Voltage:
v = float(UAVG)*1e-6;
break;
case Amperage:
v = float(IAAVG)*1e-6;
break;
case Frequency:
v = 0.00390625*V9240_SERIAL_BAUDRATE*T8BAUD/float(FREQAVG);
break;
case Power:
v = float(PAAVG)*1e-3;
break;
case Reactive:
v = float(QAVG)*1e-3;
break;
default:
break;
}
return v;
}
void V9240::set_checksum()
{
CKSUM = UINT32_MAX - std::accumulate(rw_mem,rw_mem+rw_len-1 ,0);
write(Address::CKSUM,CKSUM);
}
char V9240::calc_check(char *buff, size_t len)
{
return ~std::accumulate(buff,buff+len,0)+0x33;
}
void V9240::send_next()
{
port->flush();
switch (step++) {
case 0:
read(rw_mem,Address::SysCtrl,1);
break;
case 1:
write(Address::AnaCtrl0,ANACTRL0.reg);
break;
case 2:
// Configure CSEL (
ANACTRL1.CSEL = 1;
write(Address::AnaCtrl1,ANACTRL1.reg);
break;
case 3:
// Disable internal PGA
SYSCTRL.gu = 1;
SYSCTRL.gai = 3;
// Enable ADC
SYSCTRL.adcupdn = 1;
SYSCTRL.adciapdn = 1;
write(Address::SysCtrl,SYSCTRL.reg);
break;
case 4:
// DC calibratio. Paragraph 7.4 page 33
SYSCTRL.shortu = 1 ;
SYSCTRL.shorti = 1 ;
write(Address::SysCtrl,SYSCTRL.reg);
break;
case 5:
read(ro_mem, Address::RO_START,ro_len);
break;
case 6:
UADCC = UDCINST;
IAADCC = IADCINST;
SYSCTRL.shortu = 0 ;
SYSCTRL.shorti = 0 ;
write(Address::SysCtrl,SYSCTRL.reg);
break;
case 7:
// Write calibration parameter
write(Address::UADCC ,UADCC);
break;
case 8:
write(Address::IAADCC ,IAADCC);
break;
case 9:
write(Address::PAC ,PAC);
break;
case 10:
write(Address::PADCC ,PADCC);
break;
case 11:
write(Address::QAC ,QAC);
break;
case 12:
write(Address::QADCC ,QADCC);
break;
case 13:
write(Address::PHC ,PHC);
break;
case 14:
write(Address::IAC ,IAC);
break;
case 15:
write(Address::IADCC ,IADCC);
break;
case 16:
write(Address::UC ,UC);
break;
case 17:
write(Address::UDCC ,UDCC);
break;
case 18:
write(Address::BPFPARA ,BPFPARA);
break;
case 19:
set_checksum();
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_DEBUG "*** V9240 started"));
break;
case 20:
read(&ro_mem[ro_len-1],Address::T8BAUD,1);
break;
default:
read(ro_mem,Address::RO_START,ro_len-1);
step = 20;
}
timeout = millis();
}
// next methods is platfrm-specific (serial port & Qt signals)
void V9240::read_data_from_port()
{
if(next_read_len != 0 )
{
auto bytes = port->available();
if(bytes >= next_read_len)
{
port->read(serial_buff,next_read_len);
char checksum = calc_check(serial_buff+sizeof(packet),next_read_len-sizeof(packet)-1);
if(checksum == serial_buff[next_read_len-1])
{
packet &recv = *reinterpret_cast<packet*>( serial_buff + sizeof (packet) );
if(recv.ctrl == ctrl_read)
{
int32_t *data = (int32_t*)(serial_buff + sizeof (packet) + 3);
memcpy(ptr_read,data,sizeof (int32_t) * (recv.addr_l==0 ? 1 : recv.addr_l));
}
}
else {
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_DEBUG "*** V9240 checksum error in: %d calc: %d")
,uint32_t(serial_buff[next_read_len-1]),uint32_t(checksum));
}
next_read_len = 0;
ptr_read = nullptr;
start_pause_timer = millis();
timeout = 0;
}
else if(timeout > 0 && millis() - timeout > 1000)
{
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_DEBUG "*** V9240 timeout"));
start_pause_timer = 0;
start();
}
}
else {
if(start_pause_timer != 0 && millis()-start_pause_timer > 10)
{
send_next();
start_pause_timer = 0;
}
}
}
bool V9240::open(char const *portName)
{
if(port != nullptr)
return false;
port = new TasmotaSerial(Pin(GPIO_V9240_RX),Pin(GPIO_V9240_TX),1,0,256,false);
if (port->hardwareSerial()) {
ClaimSerial();
port->begin(V9240_SERIAL_BAUDRATE,SERIAL_8O1);
}
return true;
}
void V9240::close()
{
if(port != nullptr)
{
port->end();
delete port;
port = nullptr;
}
}
bool V9240::reset()
{
if(port != nullptr)
{
port->begin(100,SERIAL_8O1);
port->write(uint8_t(0));
char zero;
port->read(&zero,1);
port->begin(V9240_SERIAL_BAUDRATE,SERIAL_8O1);
}
return true;
}
bool V9240::read(int32_t *ptr, uint16_t address, size_t n)
{
packet p;
p.header = header;
p.ctrl = ctrl_read;
p.addr_h = (address&0x0F00)>>8;
p.addr_l = address&0x00FF;
p.data = n;
p.check = calc_check(p.buff,sizeof(p)-1);
next_read_len = sizeof (p) + 4 + sizeof (int32_t)*n;
ptr_read = ptr;
port->write(p.buff,sizeof (p));
return true;
}
bool V9240::write(int16_t address, int32_t data)
{
packet p;
p.header = header;
p.ctrl = ctrl_write;
p.addr_h = (address&0x0F00)>>8;
p.addr_l = address&0x00FF;
p.data = data;
p.check = calc_check(p.buff,sizeof(p)-1);
next_read_len = sizeof (p) + 4;
port->write(p.buff,sizeof (p));
return true;
}
bool V9240::process_command()
{
float value = atof(XdrvMailbox.data);
XdrvMailbox.payload = 0;
auto calc_cor = [] (float new_value,float old_value,int32_t k) -> int32_t
{ return int32_t( (float(INT_MAX) *( new_value - old_value ) + float(k) * new_value ) / old_value ); };
switch (Energy->command_code) {
case CMND_POWERCAL:
// PADCC = calc_cor (value,(*this)[Power],P);
break;
case CMND_VOLTAGECAL:
// UDCC = float(INT_MAX)*value;
break;
case CMND_CURRENTCAL:
// IADCC = float(INT_MAX)*value;
break;
case CMND_FREQUENCYCAL:
PHC = value ;
EnergySetCalibration(ENERGY_FREQUENCY_CALIBRATION, PHC, 0);
break;
case CMND_POWERSET:
PAC = calc_cor (value,(*this)[Power],PAC);
EnergySetCalibration(ENERGY_POWER_CALIBRATION, PAC, 0);
break;
case CMND_VOLTAGESET:
UC = calc_cor (value,(*this)[Voltage],UC);
EnergySetCalibration(ENERGY_VOLTAGE_CALIBRATION, UC, 0);
break;
case CMND_CURRENTSET:
IAC = calc_cor (value,(*this)[Amperage],IAC);
EnergySetCalibration(ENERGY_CURRENT_CALIBRATION, IAC, 0);
break;
case CMND_FREQUENCYSET:
break;
case CMND_MODULEADDRESS:
break;
case CMND_ENERGYCONFIG:
break;
default:
break;
}
step = 0;
return true;
}
void Xnrg34Init()
{
Energy->phase_count = 1;
Energy->voltage_common = true; // Phase voltage = false, Common voltage = true
Energy->frequency_common = true; // Phase frequency = false, Common frequency = true
Energy->type_dc = false; // AC = false, DC = true;
Energy->use_overtemp = true; // Use global temperature for overtemp detection
V9240::instance().start();
}
void Xnrg34Second()
{
static int32_t last_power = 0;
if (Energy->power_on) {
Energy->data_valid[0] = 0;
Energy->frequency[0] = V9240::instance()[V9240::Frequency];
Energy->voltage[0] = V9240::instance()[V9240::Voltage];
Energy->current[0] = V9240::instance()[V9240::Amperage];
Energy->active_power[0] = V9240::instance()[V9240::Power];
// Energy->reactive_power[0] = V9240::instance()[V9240::Reactive];
Energy->kWhtoday_delta[0] += (Energy->active_power[0]+last_power) * 500.0 / 36.0; // trapezoid integration
last_power = Energy->active_power[0];
EnergyUpdateTotal();
}
}
void Xnrg34PreInit()
{
if (PinUsed(GPIO_V9240_RX) && PinUsed(GPIO_V9240_TX))
{
TasmotaGlobal.energy_driver = XNRG_25;
V9240::instance().open("");
V9240::instance().reset();
}
}
bool Xnrg25(uint32_t function) {
bool result = false;
switch (function) {
case FUNC_LOOP:
V9240::instance().v9240_loop();
break;
case FUNC_ENERGY_EVERY_SECOND:
Xnrg34Second();
break;
case FUNC_EVERY_250_MSECOND:
break;
case FUNC_COMMAND:
result = V9240::instance().process_command();
break;
case FUNC_INIT:
Xnrg34Init();
break;
case FUNC_PRE_INIT:
Xnrg34PreInit();
break;
}
return result;
}
#endif // USE_ENERGY_V9240
#endif // USE_ENERGY_SENSOR