diff --git a/.gitignore b/.gitignore index 2abac8eb4e..4101796d82 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,5 @@ configMemory.bin # MSVC enc_temp_folder/** -*.su \ No newline at end of file +*.su +/.idea/** \ No newline at end of file diff --git a/docs/json/channelTypes.json b/docs/json/channelTypes.json index 898781e786..c7211d1e98 100644 --- a/docs/json/channelTypes.json +++ b/docs/json/channelTypes.json @@ -487,6 +487,14 @@ "file": "new_pins.h", "driver": "" }, + { + "name": "EnergyImport_kWh_div1000", + "title": "TODO", + "descr": "TODO", + "enum": "ChType_EnergyImport_kWh_div1000", + "file": "new_pins.h", + "driver": "" + }, { "name": "Max", "title": "TODO", diff --git a/docs/json/ioRoles.json b/docs/json/ioRoles.json index affb66eb4c..8d923379b5 100644 --- a/docs/json/ioRoles.json +++ b/docs/json/ioRoles.json @@ -654,5 +654,13 @@ "enum": "IOR_Total_Options", "file": "new_pins.h", "driver": "" + }, + { + "name": "HLW8112_SCSN", + "title": "HLW8112 SCSN Pin", + "descr": "SCSN pin for HLW8112 SPI energy measuring devices. ", + "enum": "IOR_BHLW8112_SCSN", + "file": "new_pins.h", + "driver": "HLW8112SPI" } ] \ No newline at end of file diff --git a/platforms/obk_main.mk b/platforms/obk_main.mk index 3677453f51..e169992194 100644 --- a/platforms/obk_main.mk +++ b/platforms/obk_main.mk @@ -103,6 +103,7 @@ OBKM_SRC += $(OBK_SRCS)driver/drv_freeze.c OBKM_SRC += $(OBK_SRCS)driver/drv_gn6932.c OBKM_SRC += $(OBK_SRCS)driver/drv_hd2015.c OBKM_SRC += $(OBK_SRCS)driver/drv_hgs02.c +OBKM_SRC += $(OBK_SRCS)driver/drv_hlw8112.c OBKM_SRC += $(OBK_SRCS)driver/drv_ht16k33.c OBKM_SRC += $(OBK_SRCS)driver/drv_httpButtons.c OBKM_SRC += $(OBK_SRCS)driver/drv_hue.c diff --git a/src/driver/drv_hlw8112.c b/src/driver/drv_hlw8112.c new file mode 100644 index 0000000000..6f0049f551 --- /dev/null +++ b/src/driver/drv_hlw8112.c @@ -0,0 +1,1213 @@ +// HLW8112 + +// workaround for code folding region pragma remove it when compiler are updated +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunknown-pragmas" + +#include "drv_hlw8112.h" +#include "../obk_config.h" + +#if ENABLE_DRIVER_HLW8112SPI + +#include +#include +#include + + +#include "../cmnds/cmd_public.h" +#include "../hal/hal_flashVars.h" +#include "../hal/hal_ota.h" +#include "../hal/hal_pins.h" +#include "../httpserver/hass.h" +#include "../logging/logging.h" +#include "../mqtt/new_mqtt.h" +#include "../new_cfg.h" +#include "../new_pins.h" + +#include "drv_public.h" +#include "drv_spi.h" + + +static const uint32_t RMS_I_RESOLUTION = 8388608 ; // 1 << 23; +static const uint32_t RMS_U_RESOLUTION = 4194304 ; //1 << 22; +static const uint32_t PF_RESOLUTION = 8388607 ; // +static const uint32_t PWR_RESOLUTION = 2147483648; // 1 << 31; +static const uint32_t E_RESOLUTION = 536870912; // 1 << 29; + +static HLW8112_Device_Conf_t device; // coeff reg k1 k2 pga etc +static HLW8112_Data_t last_data; // last read reg values +static HLW8112_UpdateData_t last_update_data; // last scaled values for ext systems + + +// energy stats saved/restored in/out flash +static ENERGY_DATA energy_acc_a = { + .Import =0 , .Export = 0 +}; +static ENERGY_DATA energy_acc_b= { + .Import =0 , .Export = 0 +};; + +static HLW8112_UpdateData_t last_update_data = { + .v_rms = 0, .freq = 0, .pf = 0 , .ap = 0, + .ia_rms = 0, .pa =0, .ea= &energy_acc_a, + .ib_rms = 0, .pb =0, .eb= &energy_acc_b +}; // last scaled values for ext systems + +static int stat_save_count_down = HLW8112_SAVE_COUNTER; +int GPIO_HLW_SCSN = 9; + +#pragma region HLW8112 utils + +#if HLW8112_SPI_RAWACCESS + +void HLW8112_Print_Array(uint8_t *data, int size) { + for (int i = 0; i <= size; i++) { + ADDLOG_DEBUG(LOG_FEATURE_ENERGYMETER, "HLW8112_Print_Array i = %d : v = %02hhX ", i, data[i]); + } +} +#else +void HLW8112_Print_Array(uint8_t *data, int size) {} +#endif + +static int32_t HLW8112_24BitTo32Bit(uint32_t value) { + int32_t result = 0; + value &= 0x00FFFFFF; + if (value & 0x00800000) + result = 0xFF000000; + result |= value; + return result; +} + +#pragma endregion + + +#pragma region HLW8112 register ops + +#pragma region HLW8112 register lowlevel ops + +void HLW8112_SPI_Txn_Begin(void) { + HAL_PIN_SetOutputValue(GPIO_HLW_SCSN, 0); +} + +void HLW8112_SPI_Txn_End(void) { + HAL_PIN_SetOutputValue(GPIO_HLW_SCSN, 1); +} + +int HLW8112_SPI_ReadBytes(uint8_t *buffer, uint32_t size) { + int Result = SPI_ReadBytes(buffer, size); + ADDLOG_DEBUG(LOG_FEATURE_ENERGYMETER, "HLW8112_SPI_Read result %x ", Result); + return Result; +} + +int HLW8112_SPI_WriteBytes(uint8_t *data, uint32_t size) { + int Result = SPI_WriteBytes(data, size); + ADDLOG_DEBUG(LOG_FEATURE_ENERGYMETER, "HLW8112_SPI_Write result %x ", Result); + return Result; +} + +int HLW8112_SPI_Transact(uint8_t *txBuffer, uint32_t txSize, uint8_t *rxBuffer, uint32_t rxSize) { + HLW8112_SPI_Txn_Begin(); + int Result = SPI_Transmit(txBuffer, txSize, rxBuffer, rxSize); + HLW8112_SPI_Txn_End(); + ADDLOG_DEBUG(LOG_FEATURE_ENERGYMETER, "HLW8112_SPI_Transact result %d", Result); + return Result; +} +#pragma endregion + +#pragma region read + +int HLW8112_ReadRegister(uint8_t reg, uint8_t size, uint32_t *valueResult) { + uint8_t tx[1] = {0xFF}; + uint8_t rx[5] = {0}; + tx[0] = reg & 0x7F; + + int result = HLW8112_SPI_Transact(tx, 1, rx, 5); + if (result < 0) { + ADDLOG_ERROR(LOG_FEATURE_ENERGYMETER, "HLW8112_ReadRegister non zero result %d", result); + return result; + } + HLW8112_Print_Array(rx, 5); + + uint32_t value = 0x0; + if (size == 4) { + value = ((uint32_t)rx[0] << 24) | ((uint32_t)rx[1] << 16) | ((uint32_t)rx[2] << 8) | ((uint32_t)rx[3]); + } else if (size == 3) { + value = ((uint32_t)rx[0] << 16) | ((uint32_t)rx[1] << 8) | ((uint32_t)rx[2]); + } else if (size == 2) { + value = ((uint32_t)rx[0] << 8) | ((uint32_t)rx[1]); + } else { + value = ((uint32_t)rx[0]); + } + *valueResult = value; + return result; +} + + +int HLW8112_ReadRegister8(uint8_t reg, uint8_t *valueResult) { + uint32_t tmpValue; + int result = HLW8112_ReadRegister(reg, 1, &tmpValue); + *valueResult = (uint8_t)tmpValue; + ADDLOG_DEBUG(LOG_FEATURE_ENERGYMETER, "HLW8112_ReadRegister8 reg= %02X : v = %02X", reg, *valueResult); + return result; +} + +int HLW8112_ReadRegister16(uint8_t reg, uint16_t *valueResult) { + uint32_t tmpValue; + int result = HLW8112_ReadRegister(reg, 2, &tmpValue); + *valueResult = (uint16_t)tmpValue; + ADDLOG_DEBUG(LOG_FEATURE_ENERGYMETER, "HLW8112_ReadRegister16 reg= %02X : v = %04X", reg, *valueResult); + return result; +} + +int HLW8112_ReadRegister24(uint8_t reg, uint32_t *valueResult) { + int result = HLW8112_ReadRegister(reg, 3, valueResult); + ADDLOG_DEBUG(LOG_FEATURE_ENERGYMETER, "HLW8112_ReadRegister24 reg= %02X : v = %06X", reg, *valueResult); + return result; +} + +int HLW8112_ReadRegister32(uint8_t reg, uint32_t *valueResult) { + int result = HLW8112_ReadRegister(reg, 4, valueResult); + ADDLOG_DEBUG(LOG_FEATURE_ENERGYMETER, "HLW8112_ReadRegister32 reg= %02X : v = %08X", reg, *valueResult); + return result; +} +#pragma endregion + +#pragma region write + +// needs to take care of spi txn by callee +int HLW8112_WriteRegister(uint8_t reg, uint8_t *data, uint8_t size) { + uint8_t tx[3] = {0}; + if (reg == HLW8112_REG_COMMAND) { + tx[0] = reg; + } else { + tx[0] = reg | 0x80; + } + //checks + if (size > 2) { + ADDLOG_ERROR(LOG_FEATURE_ENERGYMETER, "HLW8112_WriteRegister can only write 8 or 16 bit reg = %02X", reg); + return -2; + } + + // buffer prepare + for (uint8_t i = 0; i < size; i++) { + tx[1 + i] = data[i]; + } + + int result = HLW8112_SPI_WriteBytes(tx, size + 1); + //TODO: verify written bytes register + return result; +} + +uint8_t HLW8112_WriteRegisterValue(uint8_t reg, uint16_t value) { + uint8_t data[2] = {0}; + data[0] = (value >> 8) & 0xFF; + data[1] = value & 0xFF; + uint8_t result = HLW8112_WriteRegister(reg, data, 2); + return result; +} + +uint8_t HLW8112_WriteRegisterValue8(uint8_t reg, uint8_t value) { + uint8_t data[1] = {value}; + uint8_t result = HLW8112_WriteRegister(reg, data, 1); + return result; +} + +uint8_t HLW8112_SPI_WriteControl(uint8_t control) { + return HLW8112_WriteRegisterValue8(HLW8112_REG_COMMAND, control); +} + +int writeEnable() { + return HLW8112_SPI_WriteControl(HLW8112_COMMAND_WRITE_EN); +} + +int writeDisable() { + return HLW8112_SPI_WriteControl(HLW8112_COMMAND_WRITE_PROTECT); +} + +uint8_t HLW8112_WriteRegister16(uint8_t reg, uint16_t value) { + HLW8112_SPI_Txn_Begin(); + writeEnable(); + + uint8_t result = HLW8112_WriteRegisterValue(reg, value); + ADDLOG_DEBUG(LOG_FEATURE_ENERGYMETER, "HLW8112_WriteRegisterValue result %d", result); + + writeDisable(); + HLW8112_SPI_Txn_End(); + + //TODO verify reg this is big no for clearing regs will need to switch last read reg + uint16_t readValue; + int rresult = HLW8112_ReadRegister16(reg, &readValue); + if (rresult < 0) { + ADDLOG_ERROR(LOG_FEATURE_ENERGYMETER, "Write Verify read result %d", rresult); + return -2; + } + + if (value != readValue) { + ADDLOG_ERROR(LOG_FEATURE_ENERGYMETER, "Write Verify failed reg=%02X value=%04X rv=%04X", reg, value, readValue); + return -3; + } + + return result; +} +uint8_t HLW8112_WriteRegister8(uint8_t reg, uint8_t value) { + HLW8112_SPI_Txn_Begin(); + writeEnable(); + + uint8_t result = HLW8112_WriteRegisterValue8(reg, value); + ADDLOG_DEBUG(LOG_FEATURE_ENERGYMETER, "HLW8112_WriteRegisterValue8 result %d", result); + + writeDisable(); + HLW8112_SPI_Txn_End(); + + //TODO verify reg this is big no for clearing regs will need to switch last read reg + uint8_t readValue; + int rresult = HLW8112_ReadRegister8(reg, &readValue); + if (rresult < 0) { + ADDLOG_ERROR(LOG_FEATURE_ENERGYMETER, "Write Verify read result %d", rresult); + return -2; + } + + if (value != readValue) { + ADDLOG_ERROR(LOG_FEATURE_ENERGYMETER, "Write Verify failed reg=%02X value=%04X rv=%04X", reg, value, readValue); + return -3; + } + + return result; +} +#pragma endregion + +#pragma endregion + + +#pragma region flash ops +void HLW8112_Restore_Stats(void) { + energy_acc_a.Export = 0; + energy_acc_a.Import = 0; + energy_acc_b.Export = 0; + energy_acc_a.Import = 0; + HAL_FlashVars_GetEnergy(&energy_acc_a, ENERGY_CHANNEL_A); + HAL_FlashVars_GetEnergy(&energy_acc_b, ENERGY_CHANNEL_B); +} + +void HLW8112_save_stats(HLW8112_SaveFlags_t save) { + ADDLOG_DEBUG( LOG_FEATURE_ENERGYMETER , "HLW8112_SaveFlash_t value = %08X", save); + if(save > 0 ){ + //TODO write proper flash write debounce + int pg = OTA_GetProgress(); + // sometime this messup ota when ota is also writing to flash + if (pg != -1) + { + ADDLOG_WARN( LOG_FEATURE_ENERGYMETER , "OTA in progress skip write to flash pg=%d", pg); + return; + } + stat_save_count_down --; + if(stat_save_count_down <= 0 || save & HLW8112_SAVE_FORCE) { + stat_save_count_down = HLW8112_SAVE_COUNTER; + ENERGY_DATA* data[2] = {&energy_acc_a, &energy_acc_b}; + HAL_FlashVars_SaveEnergy(data, 2); + } + } +} +void HLW8112_Set_EnergyStat(HLW8112_Channel_t channel, float import, float export) { + ENERGY_DATA* data = &energy_acc_a; + uint16_t counter_reg = HLW8112_REG_PFCntPA; + if (channel == HLW8112_CHANNEL_B){ + data = &energy_acc_b; + counter_reg = HLW8112_REG_PFCntPB; + CHANNEL_Set(HLW8112_Channel_export_B, export * 1000, 0); + CHANNEL_Set(HLW8112_Channel_import_B, import * 1000, 0); + }else { + CHANNEL_Set(HLW8112_Channel_export_A, export * 1000, 0); + CHANNEL_Set(HLW8112_Channel_import_A, import * 1000, 0); + } + + HLW8112_WriteRegister16(counter_reg,0); + data->Import = import; + data->Export = export; + HLW8112_save_stats(HLW8112_SAVE_FORCE); +} +#pragma endregion + + +#pragma region commands + +static commandResult_t HLW8112_SetClock(const void *context, const char *cmd, const char *args, int cmdFlags) { + Tokenizer_TokenizeString(args, TOKENIZER_ALLOW_QUOTES); + if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 1)) { + return CMD_RES_NOT_ENOUGH_ARGUMENTS; + } + uint32_t value = Tokenizer_GetArgInteger(0); + device.CLKI = value; + //CHANNEL_Set(HLW8112_Channel_Clk, value, 0 ); + CFG_SetPowerMeasurementCalibrationInteger(CFG_OBK_CLK,value); + HLW8112_compute_scale_factor(); + return CMD_RES_OK; +} +static commandResult_t HLW8112_SetResistorGain(const void *context, const char *cmd, const char *args, int cmdFlags) { + Tokenizer_TokenizeString(args, TOKENIZER_ALLOW_QUOTES); + if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 3)) { + return CMD_RES_NOT_ENOUGH_ARGUMENTS; + } + + device.ResistorCoeff.KU = Tokenizer_GetArgFloat(0); + device.ResistorCoeff.KIA = Tokenizer_GetArgFloat(1); + device.ResistorCoeff.KIB = Tokenizer_GetArgFloat(2); + + //CHANNEL_Set(HLW8112_Channel_ResCof_Voltage, device.ResistorCoeff.KU, 0 ); + //CHANNEL_Set(HLW8112_Channel_ResCof_A, device.ResistorCoeff.KIA, 0 ); + //CHANNEL_Set(HLW8112_Channel_ResCof_B, device.ResistorCoeff.KIB, 0 ); + CFG_SetPowerMeasurementCalibrationFloat(CFG_OBK_RES_KU, device.ResistorCoeff.KU ); + CFG_SetPowerMeasurementCalibrationFloat(CFG_OBK_RES_KIA, device.ResistorCoeff.KIA ); + CFG_SetPowerMeasurementCalibrationFloat(CFG_OBK_RES_KIB, device.ResistorCoeff.KIB ); + HLW8112_compute_scale_factor(); + return CMD_RES_OK; +} +static commandResult_t HLW8112_SetEnergyStat(const void *context, const char *cmd, const char *args, int cmdFlags) { + Tokenizer_TokenizeString(args, TOKENIZER_ALLOW_QUOTES); + if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 4)) { + return CMD_RES_NOT_ENOUGH_ARGUMENTS; + } + + HLW8112_Set_EnergyStat(HLW8112_CHANNEL_A,Tokenizer_GetArgFloat(0),Tokenizer_GetArgFloat(1)); + HLW8112_Set_EnergyStat(HLW8112_CHANNEL_B,Tokenizer_GetArgFloat(2),Tokenizer_GetArgFloat(3)); + return CMD_RES_OK; +} + +static commandResult_t HLW8112_ClearEnergy(const void *context, const char *cmd, const char *args, int cmdFlags) { + Tokenizer_TokenizeString(args, TOKENIZER_ALLOW_QUOTES); + if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 1)) { + return CMD_RES_NOT_ENOUGH_ARGUMENTS; + } + char* channel = Tokenizer_GetArg(0); + if (!strcmp("channel_a" , channel)) { + HLW8112_Set_EnergyStat(HLW8112_CHANNEL_A,0,0); + } else if (!strcmp("channel_b" , channel)){ + HLW8112_Set_EnergyStat(HLW8112_CHANNEL_B,0,0); + } + return CMD_RES_OK; +} + +#if HLW8112_SPI_RAWACCESS +static commandResult_t HLW8112_write_reg(const void *context, const char *cmd, const char *args, int cmdFlags) { + + Tokenizer_TokenizeString(args, TOKENIZER_ALLOW_QUOTES); + if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 2)) { + return CMD_RES_NOT_ENOUGH_ARGUMENTS; + } + int reg = Tokenizer_GetArgInteger(0); + if (reg < 0 || reg > 255) { + return CMD_RES_BAD_ARGUMENT; + } + int val = Tokenizer_GetArgInteger(1); + if (val > 65535) { + return CMD_RES_BAD_ARGUMENT; + } + int result = HLW8112_WriteRegister16((uint8_t)reg, (uint16_t)val); + ADDLOG_INFO(LOG_FEATURE_CMD, "HLW8112_write_reg result %d", result); + + int cr = HLW8112_CheckCoeffs(); + if (cr > 0) { + HLW8112_UpdateCoeff(); + } + return CMD_RES_OK; +} +static commandResult_t HLW8112_read_reg(const void *context, const char *cmd, const char *args, int cmdFlags) { + + Tokenizer_TokenizeString(args, TOKENIZER_ALLOW_QUOTES); + if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 2)) { + return CMD_RES_NOT_ENOUGH_ARGUMENTS; + } + int reg = Tokenizer_GetArgInteger(0); + if (reg < 0 || reg > 255) { + return CMD_RES_BAD_ARGUMENT; + } + int width = Tokenizer_GetArgInteger(1); + if (width > 32) { + return CMD_RES_BAD_ARGUMENT; + } + uint32_t val; + int result; + if (width == 8) { + result = HLW8112_ReadRegister8((uint8_t)reg, (uint8_t *)&val); + } + else if (width == 16) { + result = HLW8112_ReadRegister16((uint8_t)reg, (uint16_t *)&val); + } + else if (width == 24) { + result = HLW8112_ReadRegister24((uint8_t)reg, &val); + } + else if (width == 32) { + result = HLW8112_ReadRegister32((uint8_t)reg, &val); + } + else { + return CMD_RES_BAD_ARGUMENT; + } + ADDLOG_INFO( LOG_FEATURE_CMD , "HLW8112_read_reg result %d reg = %02X value = %08X", result, reg , val); + + return CMD_RES_OK; +} +static commandResult_t HLW8112_print_coeff(const void *context, const char *cmd, const char *args, int cmdFlags) { + + Tokenizer_TokenizeString(args, TOKENIZER_ALLOW_QUOTES); + if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 0)) { + return CMD_RES_NOT_ENOUGH_ARGUMENTS; + } + HLW8112_UpdateCoeff(); + return CMD_RES_OK; +} + + +static commandResult_t HLW8112_c(const void *context, const char *cmd, const char *args, int cmdFlags) { + + Tokenizer_TokenizeString(args, TOKENIZER_ALLOW_QUOTES); + if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 1)) { + return CMD_RES_NOT_ENOUGH_ARGUMENTS; + } + energy_acc_a.Export = Tokenizer_GetArgFloat(0); + HLW8112_save_stats(HLW8112_SAVE_A_EXP); + return CMD_RES_OK; +} + +static commandResult_t HLW8112_a(const void *context, const char *cmd, const char *args, int cmdFlags) { + + Tokenizer_TokenizeString(args, TOKENIZER_ALLOW_QUOTES); + if (Tokenizer_CheckArgsCountAndPrintWarning(cmd, 0)) { + return CMD_RES_NOT_ENOUGH_ARGUMENTS; + } + HLW8112_Restore_Stats(); + ADDLOG_INFO( LOG_FEATURE_CMD , "HLW8112_a HLW8112_Restore_Stats exp = %.4f imp = %.4f", energy_acc_a.Export, energy_acc_a.Import); + return CMD_RES_OK; +} +#endif + +void HLW8112_addCommads(void){ + CMD_RegisterCommand("HLW8112_SetClock", HLW8112_SetClock, NULL); + CMD_RegisterCommand("HLW8112_SetResistorGain", HLW8112_SetResistorGain, NULL); + CMD_RegisterCommand("HLW8112_SetEnergyStat", HLW8112_SetEnergyStat, NULL); + CMD_RegisterCommand("clear_energy", HLW8112_ClearEnergy, NULL); +#if HLW8112_SPI_RAWACCESS + CMD_RegisterCommand("HLW8112_write_reg", HLW8112_write_reg, NULL); + CMD_RegisterCommand("HLW8112_read_reg", HLW8112_read_reg, NULL); + CMD_RegisterCommand("HLW8112_print_coeff", HLW8112_print_coeff, NULL); + CMD_RegisterCommand("HLW8112_c", HLW8112_c, NULL); + CMD_RegisterCommand("HLW8112_a", HLW8112_a, NULL); +#endif +} + +#pragma endregion + + + +#pragma region HLW8112 init + +void HLW8112_Shutdown_Pins() { + +} +void HLW8112_Init_Channels() { + CHANNEL_SetType(HLW8112_Channel_Voltage, ChType_Voltage_div100); + CHANNEL_SetType(HLW8112_Channel_Frequency, ChType_Frequency_div100); + CHANNEL_SetType(HLW8112_Channel_PowerFactor, ChType_PowerFactor_div1000); + CHANNEL_SetType(HLW8112_Channel_current_B, ChType_Current_div1000); + CHANNEL_SetType(HLW8112_Channel_current_A, ChType_Current_div1000); + CHANNEL_SetType(HLW8112_Channel_power_B, ChType_Power_div100); + CHANNEL_SetType(HLW8112_Channel_power_A, ChType_Power_div100); + CHANNEL_SetType(HLW8112_Channel_apparent_power_A, ChType_Power_div100); + CHANNEL_SetType(HLW8112_Channel_export_B, ChType_EnergyExport_kWh_div1000); + CHANNEL_SetType(HLW8112_Channel_export_A, ChType_EnergyExport_kWh_div1000); + CHANNEL_SetType(HLW8112_Channel_import_B, ChType_EnergyImport_kWh_div1000); + CHANNEL_SetType(HLW8112_Channel_import_A, ChType_EnergyImport_kWh_div1000); + //CHANNEL_SetType(HLW8112_Channel_ResCof_Voltage, ChType_Custom); + //CHANNEL_SetType(HLW8112_Channel_ResCof_A, ChType_Custom); + //CHANNEL_SetType(HLW8112_Channel_ResCof_B, ChType_Custom); + //CHANNEL_SetType(HLW8112_Channel_Clk, ChType_Custom); + + CHANNEL_SetLabel(HLW8112_Channel_PowerFactor , "Power Factor", 1); + CHANNEL_SetLabel(HLW8112_Channel_current_B , "Current B", 1); + CHANNEL_SetLabel(HLW8112_Channel_current_A , "Current A", 1); + CHANNEL_SetLabel(HLW8112_Channel_power_B , "Power B", 1); + CHANNEL_SetLabel(HLW8112_Channel_power_A , "Power A", 1); + CHANNEL_SetLabel(HLW8112_Channel_apparent_power_A , "Apparent Power", 1); + CHANNEL_SetLabel(HLW8112_Channel_export_B , "Energy Export B", 1); + CHANNEL_SetLabel(HLW8112_Channel_export_A , "Energy Export A", 1); + CHANNEL_SetLabel(HLW8112_Channel_import_A , "Energy Import A", 1); + CHANNEL_SetLabel(HLW8112_Channel_import_B , "Energy Import B", 1); + //CHANNEL_SetLabel(HLW8112_Channel_ResCof_Voltage, "Voltage Resistor Ratio",1 ); + //CHANNEL_SetLabel(HLW8112_Channel_ResCof_A, "Channel A Resistor Ratio", 1); + //CHANNEL_SetLabel(HLW8112_Channel_ResCof_B, "Channel b Resistor Ratio",1 ); + //CHANNEL_SetLabel(HLW8112_Channel_ResCof_B, "CLK",1 ); + +} + +void HLW8112_Init_Pins() { + + //TODO INT1/INT2 + int tmp; + tmp = PIN_FindPinIndexForRole(IOR_HLW8112_SCSN, -1); + if (tmp != -1) { + GPIO_HLW_SCSN = tmp; + } else { + GPIO_HLW_SCSN = PIN_FindPinIndexForRole(IOR_HLW8112_SCSN, GPIO_HLW_SCSN); + } + + HAL_PIN_Setup_Output(GPIO_HLW_SCSN); + HAL_PIN_SetOutputValue(GPIO_HLW_SCSN, 1); + +} + +#pragma region register init and ops + + +int HLW8112_UpdateCoeffFromRegister(uint16_t reg, uint16_t* sink, char* name) { + uint16_t regValue; + int cmdResult; + cmdResult = HLW8112_ReadRegister16(reg, ®Value); + ADDLOG_INFO(LOG_FEATURE_ENERGYMETER, "HLW8112_UpdateCoeff %s %i value = %04X",name, cmdResult, regValue); + if (cmdResult < 0) + return cmdResult; + *sink = regValue; + return 0; +} + +void HLW8112_compute_scale_factor() { + double k2 = device.ResistorCoeff.KU; + double k1a = device.ResistorCoeff.KIA; + double k1b = device.ResistorCoeff.KIB; + + double v = (double)device.DeviceRegisterCoeff.RmsUC *10 / (k2 * RMS_U_RESOLUTION); // mV + double frq = (double) device.CLKI * 100 / 8; + double pf = (double) 1000.0 / ( (double) PF_RESOLUTION ); + + + double ia = (double) device.DeviceRegisterCoeff.RmsIAC / (k1a * RMS_I_RESOLUTION); // ma + double ib = (double) device.DeviceRegisterCoeff.RmsIBC / (k1b * RMS_I_RESOLUTION); + + double pa = (double) device.DeviceRegisterCoeff.PowerPAC * 1000 / (k1a * k2 * PWR_RESOLUTION); + double pb = (double) device.DeviceRegisterCoeff.PowerPBC * 1000 / (k1b * k2 * PWR_RESOLUTION); + double apa = (double) device.DeviceRegisterCoeff.PowerSC * 1000 / (k1a * k2 * PWR_RESOLUTION); + double apb = (double) device.DeviceRegisterCoeff.PowerSC * 1000 / (k1b * k2 * PWR_RESOLUTION); + + double ea = (double) device.DeviceRegisterCoeff.EnergyAC * 10000000 / (k1a * k2 * E_RESOLUTION); + double eb = (double) device.DeviceRegisterCoeff.EnergyAC * 10000000 / (k1b * k2 * E_RESOLUTION); + + device.ScaleFactor.v_rms = v; + device.ScaleFactor.freq = frq; + device.ScaleFactor.pf = pf; + device.ScaleFactor.a.i = ia; + device.ScaleFactor.a.p = pa; + device.ScaleFactor.a.ap = apa; + device.ScaleFactor.a.e = ea; + device.ScaleFactor.b.i = ib; + device.ScaleFactor.b.p = pb; + device.ScaleFactor.b.ap = apb; + device.ScaleFactor.b.e = eb; + +} + +int HLW8112_UpdateCoeff() { + CHECK_AND_RETURN(HLW8112_UpdateCoeffFromRegister(HLW8112_REG_HFCONST, &device.HFconst, "HLW8112_REG_HFCONST")); + CHECK_AND_RETURN(HLW8112_UpdateCoeffFromRegister(HLW8112_REG_RMSIAC, &device.DeviceRegisterCoeff.RmsIAC, "HLW8112_REG_RMSIAC")); + CHECK_AND_RETURN(HLW8112_UpdateCoeffFromRegister(HLW8112_REG_RMSIBC, &device.DeviceRegisterCoeff.RmsIBC, "HLW8112_REG_RMSIBC")); + CHECK_AND_RETURN(HLW8112_UpdateCoeffFromRegister(HLW8112_REG_RMSUC, &device.DeviceRegisterCoeff.RmsUC, "HLW8112_REG_RMSUC")); + CHECK_AND_RETURN(HLW8112_UpdateCoeffFromRegister(HLW8112_REG_POWER_PAC, &device.DeviceRegisterCoeff.PowerPAC, "HLW8112_REG_POWER_PAC")); + CHECK_AND_RETURN(HLW8112_UpdateCoeffFromRegister(HLW8112_REG_POWER_PBC, &device.DeviceRegisterCoeff.PowerPBC, "HLW8112_REG_POWER_PBC")); + CHECK_AND_RETURN(HLW8112_UpdateCoeffFromRegister(HLW8112_REG_POWER_SC, &device.DeviceRegisterCoeff.PowerSC, "HLW8112_REG_POWER_SC")); + CHECK_AND_RETURN(HLW8112_UpdateCoeffFromRegister(HLW8112_REG_ENERGY_AC, &device.DeviceRegisterCoeff.EnergyAC, "HLW8112_REG_ENERGY_AC")); + CHECK_AND_RETURN(HLW8112_UpdateCoeffFromRegister(HLW8112_REG_ENERGY_BC, &device.DeviceRegisterCoeff.EnergyBC, "HLW8112_REG_ENERGY_BC")); + HLW8112_compute_scale_factor(); + return 0; +} + +int HLW8112_CheckCoeffs() { + uint16_t checksum = 0xFFFF + + device.DeviceRegisterCoeff.RmsIAC + + device.DeviceRegisterCoeff.RmsIBC + + device.DeviceRegisterCoeff.RmsUC + + device.DeviceRegisterCoeff.PowerPAC + + device.DeviceRegisterCoeff.PowerPBC + + device.DeviceRegisterCoeff.PowerSC + + device.DeviceRegisterCoeff.EnergyAC + + device.DeviceRegisterCoeff.EnergyBC; + checksum = ~checksum; + + uint16_t regValue; + int cmdResult; + cmdResult = HLW8112_ReadRegister16(HLW8112_REG_COF_CHECKSUM, ®Value); + ADDLOG_DEBUG(LOG_FEATURE_ENERGYMETER, "HLW8112_CheckCoeffs HLW8112_REG_EMUStatus_Chksum %i", cmdResult); + if (cmdResult < 0) { + ADDLOG_ERROR(LOG_FEATURE_ENERGYMETER, "HLW8112_CheckCoeffs HLW8112_REG_EMUStatus_Chksum no good %i", cmdResult); + return -1; + } + + if (checksum != regValue) { + ADDLOG_WARN( LOG_FEATURE_ENERGYMETER, "HLW8112_CheckCoeffs Chksum mismatch computed = %04X read = %04X", checksum, regValue); + return 1; + } + return 0; +} + +int HLW8112_SetMainChannel(HLW8112_Channel_t channel) { + + switch (channel) { + case HLW8112_CHANNEL_A: { + device.MainChannel = channel; + return HLW8112_SPI_WriteControl(HLW8112_COMMAND_SELECT_CH_A); + } + case HLW8112_CHANNEL_B: { + device.MainChannel = channel; + return HLW8112_SPI_WriteControl(HLW8112_COMMAND_SELECT_CH_B); + } + } + return -1; +} + + +int HLW8112_InitReg() { + + //HLW8112_SPI_WriteControl(HLW8112_COMMAND_RESET); + + // pga values to config ?? + uint16_t PGA = HLW8112_PGA_16; + uint16_t PGB = HLW8112_PGA_16; + uint16_t PGU = HLW8112_PGA_1; + + #pragma region init registers + { + uint16_t syscon = 0; + syscon |= (1 << HLW8112_REG_SYSCON_ADC3ON); + syscon |= (1 << HLW8112_REG_SYSCON_ADC2ON); + syscon |= (1 << HLW8112_REG_SYSCON_ADC1ON); + + syscon |= (PGA << HLW8112_REG_SYSCON_PGAIA); + syscon |= (PGB << HLW8112_REG_SYSCON_PGAIB); + syscon |= (PGU << HLW8112_REG_SYSCON_PGAU); + + ADDLOG_DEBUG(LOG_FEATURE_ENERGYMETER, "HLW8112_InitReg syscon %04X", syscon); + HLW8112_WriteRegister16(HLW8112_REG_SYSCON, syscon); + + uint16_t emucon = 0; + emucon |= (1 << HLW8112_REG_EMUCON_PBRUN); + emucon |= (1 << HLW8112_REG_EMUCON_PARUN); + emucon |= (1 << HLW8112_REG_EMUCON_COMP_OFF); + + emucon |= (HLW8112_ACTIVE_POW_CALC_METHOD_POS_NEG_ALGEBRAIC << HLW8112_REG_EMUCON_PMODE); + + ADDLOG_DEBUG(LOG_FEATURE_ENERGYMETER, "HLW8112_InitReg emucon %04X", emucon); + HLW8112_WriteRegister16(HLW8112_REG_EMUCON, emucon); + + uint16_t emucon2 = 0; + + emucon2 |= (0 << HLW8112_REG_EMUCON2_EPB_CB); + emucon2 |= (0 << HLW8112_REG_EMUCON2_EPB_CA); + emucon2 |= (1 << HLW8112_REG_EMUCON2_CHS_IB); + emucon2 |= (1 << HLW8112_REG_EMUCON2_PFACTOREN); + emucon2 |= (1 << HLW8112_REG_EMUCON2_WAVEEN); + emucon2 |= (1 << HLW8112_REG_EMUCON2_ZXEN); + emucon2 |= (1 << HLW8112_REG_EMUCON2_VREFSEL); + + ADDLOG_DEBUG(LOG_FEATURE_ENERGYMETER, "HLW8112_InitReg emucon2 %04X", emucon2); + HLW8112_WriteRegister16(HLW8112_REG_EMUCON2, emucon2); + } + #pragma endregion + + // device info + device.ResistorCoeff.KU = CFG_GetPowerMeasurementCalibrationFloat(CFG_OBK_RES_KU, DEFAULT_RES_KU); + device.ResistorCoeff.KIA = CFG_GetPowerMeasurementCalibrationFloat(CFG_OBK_RES_KIA, DEFAULT_RES_KIA); + device.ResistorCoeff.KIB = CFG_GetPowerMeasurementCalibrationFloat(CFG_OBK_RES_KIB, DEFAULT_RES_KIB); + + device.PGA.U = PGU; + device.PGA.IA = PGA; + device.PGA.IB = PGB; + + //internal clock + + device.CLKI = CFG_GetPowerMeasurementCalibrationInteger(CFG_OBK_CLK, DEFAULT_INTERNAL_CLK); + + device.ScaleFactor.v_rms = 1.0; + device.ScaleFactor.freq = 1.0; + device.ScaleFactor.a.i = 1.0; + device.ScaleFactor.a.p = 1.0; + device.ScaleFactor.a.e = 1.0; + device.ScaleFactor.a.ap = 1.0; + device.ScaleFactor.b.i = 1.0; + device.ScaleFactor.b.p = 1.0; + device.ScaleFactor.b.e = 1.0; + device.ScaleFactor.b.ap = 1.0; + + int cmdResult; + + //TODO: to config + cmdResult = HLW8112_SetMainChannel(HLW8112_CHANNEL_A); + if (cmdResult < 0) { + return cmdResult; + } + + HLW8112_UpdateCoeff(); + HLW8112_compute_scale_factor(); + + cmdResult = HLW8112_CheckCoeffs(); + if (cmdResult > 0) { + ADDLOG_ERROR(LOG_FEATURE_ENERGYMETER, "HLW8112_InitReg Chksum mismatch"); + return -2; + } + return 0; +} + +#pragma endregion +#pragma endregion + +void HLW8112SPI_Stop(void) { + HLW8112_Save_Statistics(); + SPI_Deinit(); + SPI_DriverDeinit(); +} + +void HLW8112_Init(void) { + HLW8112_Restore_Stats(); + HLW8112_Init_Channels(); + HLW8112_addCommads(); + HLW8112_Init_Pins(); + +} + +void HLW8112SPI_Init(void) { + HLW8112_Init(); + SPI_DriverInit(); + + spi_config_t cfg; + cfg.role = SPI_ROLE_MASTER; + cfg.bit_width = SPI_BIT_WIDTH_8BITS; + cfg.polarity = SPI_POLARITY_HIGH; + cfg.phase = SPI_PHASE_1ST_EDGE; + cfg.wire_mode = SPI_3WIRE_MODE; + cfg.baud_rate = HLW8112_SPI_BAUD_RATE; + cfg.bit_order = SPI_MSB_FIRST; + OBK_SPI_Init(&cfg); + + int result = HLW8112_InitReg(); + ADDLOG_DEBUG(LOG_FEATURE_ENERGYMETER, "HLW8112_InitReg result %i", result); + +} +#pragma endregion + +#pragma region scalers + +void HLW8112_ScaleVoltage(uint32_t regValue, int32_t* value){ + if (regValue == 0) { + *value = 0; + } + else if( regValue & HLW8112_INVALID_REGVALUE) { + *value = 0; + } + else { + double scale = device.ScaleFactor.v_rms; + int32_t rv = HLW8112_24BitTo32Bit(regValue); + double v = rv*scale; + + *value = (int32_t)v; + } + +} + +void HLW8112_ScaleFrequency(uint32_t regValue, int32_t* value){ + if (regValue == 0) { + *value = 0; + } else { + *value = device.ScaleFactor.freq / regValue; + } +} + +void HLW8112_ScaleCurrent(HLW8112_Channel_t channel, uint32_t regValue, int32_t* value){ +if (regValue == 0) { + *value = 0; + } + else if( regValue & HLW8112_INVALID_REGVALUE) { + *value = 0; + } + else { + double scale = channel == HLW8112_CHANNEL_B ? device.ScaleFactor.b.i : device.ScaleFactor.a.i; + int32_t rv = HLW8112_24BitTo32Bit(regValue); + double v = rv*scale; + + *value = (int32_t)v; + } +} + +void HLW8112_ScalePower(HLW8112_Channel_t channel, uint32_t regValue, int32_t* value){ + if (regValue == 0) { + *value = 0; + } + else { + int32_t rv = (int32_t)regValue; + double scale = channel == HLW8112_CHANNEL_B ? device.ScaleFactor.b.p : device.ScaleFactor.a.p; + double v = rv*scale; + *value = (int32_t)v; + } + +} + +void HLW8112_ScaleEnergy(HLW8112_Channel_t channel, uint32_t regValue, int32_t* value){ + if (regValue == 0) { + *value = 0; + } else { + int32_t rv = HLW8112_24BitTo32Bit(regValue); + double scale = channel == HLW8112_CHANNEL_B ? device.ScaleFactor.b.e : device.ScaleFactor.a.e; + double v = rv*scale; + *value = (int32_t)v; + } +} + +void HLW8112_ScaleAparentPower(HLW8112_Channel_t channel, uint32_t regValue, int32_t* value){ + + if (regValue == 0) { + *value = 0; + } + else { + int32_t rv = (int32_t)regValue; + double scale = channel == HLW8112_CHANNEL_B ? device.ScaleFactor.b.ap : device.ScaleFactor.a.ap; + double v = rv*scale; + *value = (int32_t)v; + } +} + + +void HLW8112_ScalePowerFactor(uint32_t regValue, int16_t* value){ + if (regValue == 0) { + *value = 0; + } else { + int32_t rv = HLW8112_24BitTo32Bit(regValue); + double scale = device.ScaleFactor.pf; + double v = rv*scale; + *value = (int32_t)v; + } +} + +static void HLW8112_ScaleAndUpdate(HLW8112_Data_t* data) { + + int16_t power_factor; + int32_t voltage, frequency, current_a, current_b, power_a, power_b, apparent_power ,energy_a, energy_b; + + HLW8112_ScaleVoltage(data->v_rms, &voltage); + HLW8112_ScaleFrequency(data->freq, &frequency); + + HLW8112_ScaleCurrent(HLW8112_CHANNEL_A, data->ia_rms, ¤t_a); + HLW8112_ScaleCurrent(HLW8112_CHANNEL_B, data->ib_rms, ¤t_b); + + HLW8112_ScalePower(HLW8112_CHANNEL_A, data->pa, &power_a); + HLW8112_ScalePower(HLW8112_CHANNEL_B, data->pb, &power_b); + + + HLW8112_ScaleEnergy(HLW8112_CHANNEL_A, data->ea, &energy_a); + HLW8112_ScaleEnergy(HLW8112_CHANNEL_B, data->eb, &energy_b); + + + HLW8112_ScalePowerFactor(data->pf, &power_factor); + + HLW8112_ScaleAparentPower(HLW8112_CHANNEL_A, data->ap, &apparent_power); + + //ADDLOG_ERROR(LOG_FEATURE_ENERGYMETER, + // "Values X v=%d f=%d pf=%d ca=%d pa=%d ap=%d ea=%d cb=%d pb=%d eb=%d", + // voltage, frequency, power_factor, current_a, power_a, apparent_power, energy_a, current_b, power_b, energy_b); + + last_update_data.v_rms = voltage; + last_update_data.freq = frequency; + last_update_data.pf = power_factor; + last_update_data.ap = apparent_power; + last_update_data.ia_rms = current_a; + last_update_data.ib_rms = current_b; + last_update_data.pa = power_a; + last_update_data.pb = power_b; + + HLW8112_SaveFlags_t save = HLW8112_SAVE_NONE; + if (energy_a !=0 ) { + ADDLOG_ERROR(LOG_FEATURE_ENERGYMETER, "EA val %08X", data->ea); + ADDLOG_ERROR(LOG_FEATURE_ENERGYMETER, "EA scaled val %08X", energy_a); + ADDLOG_ERROR(LOG_FEATURE_ENERGYMETER, "Import before %f", energy_acc_a.Import); + if (power_a > 0) { + energy_acc_a.Import += (double)energy_a / 10000000.0; + save |= HLW8112_SAVE_A_IMP; + }else { + energy_acc_a.Export += (double)energy_a / 10000000.0; + save |= HLW8112_SAVE_A_EXP; + } + + ADDLOG_ERROR(LOG_FEATURE_ENERGYMETER, "Import after %f", energy_acc_a.Import); + } + if (energy_b !=0.0f ) { + if (power_b > 0) { + energy_acc_b.Import += energy_b / 10000000.0; + save |= HLW8112_SAVE_B_IMP; + }else { + energy_acc_b.Export += energy_b/ 10000000.0; + save |= HLW8112_SAVE_B_EXP; + } + } + if (save != HLW8112_SAVE_NONE) { + HLW8112_save_stats(save); + } + // BL_ProcessUpdate(voltage, current_a, power_a, frequency, energy_a); + + // update + + CHANNEL_Set(HLW8112_Channel_Voltage, last_update_data.v_rms / 10.0, 0); + CHANNEL_Set(HLW8112_Channel_Frequency,last_update_data.freq , 0); + CHANNEL_Set(HLW8112_Channel_PowerFactor, last_update_data.pf, 0); + CHANNEL_Set(HLW8112_Channel_current_B, last_update_data.ib_rms, 0); + CHANNEL_Set(HLW8112_Channel_current_A,last_update_data.ia_rms , 0); + CHANNEL_Set(HLW8112_Channel_power_B, last_update_data.pb / 10.0, 0); + CHANNEL_Set(HLW8112_Channel_power_A, last_update_data.pa / 10.0, 0); + CHANNEL_Set(HLW8112_Channel_apparent_power_A, last_update_data.ap / 10.0, 0); + CHANNEL_Set(HLW8112_Channel_export_B, last_update_data.eb->Export * 1000, 0); + CHANNEL_Set(HLW8112_Channel_export_A, last_update_data.ea->Export * 1000, 0); + CHANNEL_Set(HLW8112_Channel_import_B, last_update_data.eb->Import * 1000, 0); + CHANNEL_Set(HLW8112_Channel_import_A, last_update_data.ea->Import * 1000, 0); +} + +#pragma endregion + + +void HLW8112_RunEverySecond(void) { + HLW8112_Data_t data; + + HLW8112_ReadRegister24(HLW8112_REG_RMSU, &data.v_rms); + HLW8112_ReadRegister16(HLW8112_REG_UFREQ, &data.freq); + + HLW8112_ReadRegister24(HLW8112_REG_RMSIA, &data.ia_rms); + HLW8112_ReadRegister32(HLW8112_REG_POWER_PA, &data.pa); + HLW8112_ReadRegister24(HLW8112_REG_ENERGY_PA, &data.ea); + + HLW8112_ReadRegister24(HLW8112_REG_RMSIB, &data.ib_rms); + + HLW8112_ReadRegister32(HLW8112_REG_POWER_PB, &data.pb); + HLW8112_ReadRegister24(HLW8112_REG_ENERGY_PB, &data.eb); + + HLW8112_ReadRegister24(HLW8112_REG_POWER_FACTOR, &data.pf); + HLW8112_ReadRegister32(HLW8112_REG_POWER_S, &data.ap); + + HLW8112_ReadRegister8(HLW8112_REG_SYSSTATUS, &data.sysstat); + HLW8112_ReadRegister24(HLW8112_REG_EMUSTATUS, &data.emustat); + HLW8112_ReadRegister16(HLW8112_REG_IF, &data.int_f); + + + + READ_REG(SYSCON,16); + READ_REG(EMUCON,16); + READ_REG(HFCONST,16); + READ_REG(PSTARTA,16); + READ_REG(PSTARTB,16); + READ_REG(PAGAIN,16); + READ_REG(PBGAIN,16); + READ_REG(PHASEA,8); + READ_REG(PHASEB,8); + READ_REG(PAOS,16); + READ_REG(PBOS,16); + READ_REG(RMSIAOS,16); + READ_REG(RMSIBOS,16); + READ_REG(IBGAIN,16); + READ_REG(PSGAIN,16); + READ_REG(PSOS,16); + READ_REG(EMUCON2,16); + + last_data = data; + + HLW8112_ScaleAndUpdate(&data); +} + + +#pragma region http ui + +void appendBitFlag(char *name, uint32_t regValue, uint8_t bitNum, http_request_t *request) { + hprintf255(request, "
%s%d
", name, ((regValue & ( 1 << bitNum)) > 0 ? 1 : 0) ); + +} + +void appendTableRow(http_request_t *request, char *name,char* unit, int32_t value, int precision, float factor) { + hprintf255(request, + "%s%.*f%s", + name, precision, value / factor, unit); +} + + + +void appendChannelTableRow(http_request_t *request, char *name,char* unit, float value_a, float value_b, int precision, float factor) { + hprintf255(request, + "%s%.*f%s%.*f%s", + name, precision, value_a/ factor, unit,precision, value_b/ factor, unit); +} + +void appendRegEdit(http_request_t *request, char *name,uint16_t reg, bool readonly, + int32_t currentVal, uint16_t width, bool comp) { + + poststr(request,"
"); + hprintf255(request,"",name); + hprintf255(request,"",currentVal); + hprintf255(request," ",reg); + hprintf255(request," ",width); + hprintf255(request," ",comp); + poststr(request," "); + poststr(request,""); + poststr(request,"
"); +} + +void HLW8112_AppendInformationToHTTPIndexPage(http_request_t *request, int bPreState) { + if (bPreState) { + if (http_getArgInteger(request->url, "clear_energy")) { + char channel[2]; + if (http_getArg(request->url, "channel", channel, sizeof(channel))) { + if (!strcmp("a" , channel)) { + HLW8112_Set_EnergyStat(HLW8112_CHANNEL_A,0,0); + } else { + HLW8112_Set_EnergyStat(HLW8112_CHANNEL_B,0,0); + } + } + } + else if (http_getArgInteger(request->url, "reg_edit")) { + //TODO fix this + u16_t reg = http_getArgInteger(request->url, "reg"); + uint32_t val = http_getArgInteger(request->url, "reg_value"); + uint16_t width = http_getArgInteger(request->url, "reg_width"); + bool comp = http_getArgInteger(request->url, "compliment") ==1; + switch (width){ + case 8: { + HLW8112_WriteRegister8(reg, val); + break; + } + case 16: { + HLW8112_WriteRegister16(reg, val); + break; + } + } + } + //?action=reg_edit®_value=®=100®_width=24&compliment=1 + return; + } + + poststr(request, "
"); + appendTableRow(request, "Voltage", "V", last_update_data.v_rms, 2,1000.0f ); + appendTableRow(request, "Frequency", "Hz", last_update_data.freq, 2, 100.0f ); + appendTableRow(request, "Active Power", "W", last_update_data.pa, 3,1000.0f ); + appendTableRow(request, "Apparent Power", "VA", last_update_data.ap, 3, 1000.0f ); + appendTableRow(request, "Power Factor", "", last_update_data.pf, 2, 1000.0f ); + poststr(request, "
"); + + poststr(request, "
"); + poststr(request, ""); + appendChannelTableRow(request, "Current", "mA", last_update_data.ia_rms, last_update_data.ib_rms, 0,1 ); + appendChannelTableRow(request, "Active Power", "W", last_update_data.pa, last_update_data.pb, 3,1000 ); + appendChannelTableRow(request, "Import Energy", "KWh", last_update_data.ea->Import, last_update_data.eb->Import, 4 , 1 ); + appendChannelTableRow(request, "Export Energy", "KWh", last_update_data.ea->Export, last_update_data.eb->Export, 4 , 1 ); + + poststr(request, + ""); + + poststr(request, "
Channel AChannel B
Actions \ + \ + \ + \ +
"); +#if HLW8112_SPI_RAWACCESS + poststr(request,"\ +
"); + + REG_EDIT(SYSCON, 16, false, false); + REG_EDIT(EMUCON, 16, false, false); + REG_EDIT(EMUCON2, 16, false, false); + REG_EDIT(HFCONST, 16, false, false); + REG_EDIT(PSTARTA, 16, false, false); + REG_EDIT(PSTARTB, 16, false, false); + REG_EDIT(PAGAIN, 16, false, false); + REG_EDIT(PBGAIN, 16, false, false); + REG_EDIT(PHASEA, 16, false, false); + REG_EDIT(PHASEB, 16, false, false); + REG_EDIT(PAOS, 16, false, false); + REG_EDIT(PBOS, 16, false, false); + REG_EDIT(RMSIAOS, 16, false, false); + REG_EDIT(RMSIBOS, 16, false, false); + REG_EDIT(IBGAIN, 16, false, false); + REG_EDIT(PSOS, 16, false, false); + + poststr(request, "
"); + poststr(request, ""); + + + poststr(request, "

System Status

"); + appendBitFlag("RST", last_data.sysstat, HLW8112_REG_SYSSTATUS_RST, request); + appendBitFlag("WREN", last_data.sysstat, HLW8112_REG_SYSSTATUS_WREN, request); + appendBitFlag("clksel", last_data.sysstat, HLW8112_REG_SYSSTATUS_CLKSEL, request); + poststr(request, "
"); + + hprintf255(request, "

Emu Status

"); + // TODO: check sum + appendBitFlag("ChksumBusy", last_data.emustat, HLW8112_REG_EMUSTATUS_CHKSUMBSY, request); + appendBitFlag("REVPA", last_data.emustat, HLW8112_REG_EMUSTATUS_REVPA, request); + appendBitFlag("REVPB", last_data.emustat, HLW8112_REG_EMUSTATUS_REVPB, request); + appendBitFlag("NopldA", last_data.emustat, HLW8112_REG_EMUSTATUS_NOPLDA, request); + appendBitFlag("NopldB", last_data.emustat, HLW8112_REG_EMUSTATUS_NOPLDB, request); + appendBitFlag("Channel_sel", last_data.emustat, HLW8112_REG_EMUSTATUS_CHA_SEL, request); + poststr(request, "
"); + + hprintf255(request, "

Interrupt status

"); + + appendBitFlag("DUPDIF", last_data.int_f, HLW8112_REG_IF_DUPDIF, request); + appendBitFlag("PFBIF", last_data.int_f, HLW8112_REG_IF_PFAIF, request); + appendBitFlag("PFBIF", last_data.int_f, HLW8112_REG_IF_PFBIF, request); + appendBitFlag("PEAOIF", last_data.int_f, HLW8112_REG_IF_PEAOIF, request); + appendBitFlag("PEBOIF", last_data.int_f, HLW8112_REG_IF_PEBOIF, request); + appendBitFlag("INSTANIF", last_data.int_f, HLW8112_REG_IF_INSTANIF, request); + appendBitFlag("OIAIF", last_data.int_f, HLW8112_REG_IF_OIAIF, request); + appendBitFlag("OIBIF", last_data.int_f, HLW8112_REG_IF_OIBIF, request); + appendBitFlag("OVIF", last_data.int_f, HLW8112_REG_IF_OVIF, request); + appendBitFlag("OPIF", last_data.int_f, HLW8112_REG_IF_OPIF, request); + appendBitFlag("SAGIF", last_data.int_f, HLW8112_REG_IF_SAGIF, request); + appendBitFlag("ZX_IAIF", last_data.int_f, HLW8112_REG_IF_ZX_IAIF, request); + appendBitFlag("ZX_IBIF", last_data.int_f, HLW8112_REG_IF_ZX_IBIF, request); + appendBitFlag("ZX_UIF ", last_data.int_f, HLW8112_REG_IF_ZX_UIF, request); + appendBitFlag("LeakageIF ", last_data.int_f, HLW8112_REG_IF_LEAKAGEIF, request); + poststr(request, "
"); + #endif +} + + +#pragma endregion + + +void HLW8112_OnHassDiscovery(const char *topic) { + //TODO clear energy buttons + + ADDLOG_ERROR(LOG_FEATURE_ENERGYMETER, "HLW8112_OnHassDiscovery"); + HassDeviceInfo* dev_info = NULL; + dev_info = hass_init_button_device_info("Clear Energy A", "clear_energy", "channel_a", HASS_CATEGORY_DIAGNOSTIC); + MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN); + dev_info = hass_init_button_device_info("Clear Energy B", "clear_energy", "channel_b", HASS_CATEGORY_DIAGNOSTIC); + MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN); + hass_free_device_info(dev_info); +} + +void HLW8112_Save_Statistics() { + if (DRV_IsRunning("HLW8112SPI")){ + HLW8112_save_stats(HLW8112_SAVE_FORCE); + } +} +#endif // ENABLE_DRIVER_HLW8112SPI + +#pragma GCC diagnostic pop \ No newline at end of file diff --git a/src/driver/drv_hlw8112.h b/src/driver/drv_hlw8112.h new file mode 100644 index 0000000000..3b31b3df52 --- /dev/null +++ b/src/driver/drv_hlw8112.h @@ -0,0 +1,447 @@ +#pragma once +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunknown-pragmas" + +#ifndef __DRV_HLW8112_H__ +#define __DRV_HLW8112_H__ + +#define HLW8112_SPI_RAWACCESS 0 //WIP +#include "../httpserver/new_http.h" +#include "../hal/hal_flashVars.h" +#include + +//driver funcs +void HLW8112SPI_Init(void); +void HLW8112SPI_Stop(void); +void HLW8112_RunEverySecond(void); +void HLW8112_OnHassDiscovery(const char *topic); +void HLW8112_AppendInformationToHTTPIndexPage(http_request_t *request, int bPreState); + +//for external use fill force write to flash if driver is running +void HLW8112_Save_Statistics(); + +#pragma region util macros + +#define CHECK_AND_RETURN(value) \ + if (value != 0) { \ + return value; \ + } + +#define READ_REG(REG,SIZE) HLW8112_ReadRegister##SIZE(HLW8112_REG_##REG, (uint##SIZE##_t *) &device.EX_REGiSTERS._##REG); + +#define REG_EDIT(REG,SIZE,RO,C) appendRegEdit(request, #REG , HLW8112_REG_##REG, RO, device.EX_REGiSTERS._##REG, SIZE, C) + +#pragma endregion + + +// constants +#define HLW8112_SPI_BAUD_RATE 1000000 // 1000000 // 900000 +#define DEFAULT_INTERNAL_CLK 3579545UL +#define HLW8112_INVALID_REGVALUE 1 << 23 +#define HLW8112_SAVE_COUNTER 3600 // 1 * 60 * 60; // maybe once in a hour + +#define DEFAULT_RES_KU 1.0f +#define DEFAULT_RES_KIA 0.2f +#define DEFAULT_RES_KIB 0.2f + + + + +#pragma region resistors + +// Register addresses +#define HLW8112_REG_SYSCON 0x00 // System Control 2 bytes 0x0a04 +#define HLW8112_REG_SYSCON_ADC3ON 11 +#define HLW8112_REG_SYSCON_ADC2ON 10 +#define HLW8112_REG_SYSCON_ADC1ON 9 +#define HLW8112_REG_SYSCON_PGAIB 6 +#define HLW8112_REG_SYSCON_PGAU 3 +#define HLW8112_REG_SYSCON_PGAIA 0 + +#define HLW8112_REG_EMUCON 0x01 // Energy Measure Control 2 bytes 0x0000 +#define HLW8112_REG_EMUCON_TEMP_STEP 14 +#define HLW8112_REG_EMUCON_TEMP_EN 13 +#define HLW8112_REG_EMUCON_COMP_OFF 12 +#define HLW8112_REG_EMUCON_PMODE 10 +#define HLW8112_REG_EMUCON_DC_MODE 9 +#define HLW8112_REG_EMUCON_ZXD1 8 +#define HLW8112_REG_EMUCON_ZXD0 7 +#define HLW8112_REG_EMUCON_HPFIBOFF 6 +#define HLW8112_REG_EMUCON_HPFIAOFF 5 +#define HLW8112_REG_EMUCON_HPFUOFF 4 +#define HLW8112_REG_EMUCON_PBRUN 1 +#define HLW8112_REG_EMUCON_PARUN 0 + +#define HLW8112_REG_HFCONST 0x02 // Pulse Frequency 2bytes 0x1000 +#define HLW8112_REG_PSTARTA 0x03 // Active Start Power A 2 bytes 0x0060 +#define HLW8112_REG_PSTARTB 0x04 // Active Start Power B +#define HLW8112_REG_PAGAIN 0x05 // Power Gain Calibration A 2 bytes 0x0000 +#define HLW8112_REG_PBGAIN 0x06 // Power Gain Calibration B +#define HLW8112_REG_PHASEA 0x07 // Phase Calibration A 1 bytes 0x00 +#define HLW8112_REG_PHASEB 0x08 // Phase Calibration B +#define HLW8112_REG_PAOS 0x0A // Active Power Offset Calibration A 2 bytes 0x0000 +#define HLW8112_REG_PBOS 0x0B // Active Power Offset Calibration B 2 bytes 0x0000 +#define HLW8112_REG_RMSIAOS 0x0E // RMS Offset Compensation A 2 bytes 0x0000 +#define HLW8112_REG_RMSIBOS 0x0F // RMS Offset Compensation B 2 bytes 0x0000 +#define HLW8112_REG_IBGAIN 0x10 // Channel B Current Gain 2 bytes 0x0000 +#define HLW8112_REG_PSGAIN 0x11 // Apparent Power Gain Calibration 2 bytes 0x0000 +#define HLW8112_REG_PSOS 0x12 // Apparent Power Offset Compensation 2 bytes 0x0000 + +#define HLW8112_REG_EMUCON2 0x13 // Meter Control2 2 bytes 0x0001 +#define HLW8112_REG_EMUCON2_SDOCMOS 12 +#define HLW8112_REG_EMUCON2_EPB_CB 11 +#define HLW8112_REG_EMUCON2_EPB_CA 10 +#define HLW8112_REG_EMUCON2_DUPSEL 8 +#define HLW8112_REG_EMUCON2_CHS_IB 7 +#define HLW8112_REG_EMUCON2_PFACTOREN 6 +#define HLW8112_REG_EMUCON2_WAVEEN 5 +#define HLW8112_REG_EMUCON2_SAGEN 4 +#define HLW8112_REG_EMUCON2_OVEREN 3 +#define HLW8112_REG_EMUCON2_ZXEN 2 +#define HLW8112_REG_EMUCON2_PEAKEN 1 +#define HLW8112_REG_EMUCON2_VREFSEL 0 + +#define HLW8112_REG_DCIA 0x14 // DC Offset Correction A 2 bytes 0x0000 +#define HLW8112_REG_DCIB 0x15 // DC Offset Correction B +#define HLW8112_REG_DCIC 0x16 // DC Offset Correction U +#define HLW8112_REG_SAGCYC 0x17 // Voltage Sag Period 2 bytes 0x0000 +#define HLW8112_REG_SAGLVL 0x18 // Voltage Sag Threshold 2 bytes 0x0000 +#define HLW8112_REG_OVLVL 0x19 // Overvoltage Threshold 2 bytes 0x0ffff +#define HLW8112_REG_OIALVL 0x1A // Overcurrent Threshold A 2 bytes 0x0ffff +#define HLW8112_REG_OIBLVL 0x1B // COvercurrent Threshold B 2 bytes 0x0ffff +#define HLW8112_REG_OPLVL 0x1C // Active Power Overload Threshold 2 bytes 0x0ffff +#define HLW8112_REG_INT 0x1D // INT1/INT2 Interrupt Setting 2 bytes 0x3210 + +// Meter Parameter and Status Registers +#define HLW8112_REG_PFCntPA 0x20 // Fast Combination Active Pulse Count A 2 bytes 0x0000 +#define HLW8112_REG_PFCntPB 0x21 // Fast Combination Active Pulse Count B 2 bytes 0x0000 + + +//readonly regss below +#define HLW8112_REG_ANGLE 0x22 // Angle between Current and Voltage 2 bytes 0x0000 +#define HLW8112_REG_UFREQ 0x23 // Frequency 2 bytes 0x0000 +#define HLW8112_REG_RMSIA 0x24 // RMS Current A 3 bytes 0x000000 +#define HLW8112_REG_RMSIB 0x25 // RMS Current B 3 bytes 0x000000 +#define HLW8112_REG_RMSU 0x26 // RMS Voltage 3 bytes 0x000000 +#define HLW8112_REG_POWER_FACTOR 0x27 // Power Factor 3 bytes 0x7fffff +#define HLW8112_REG_ENERGY_PA 0x28 // Active Power Energy A 3 bytes 0x000000 (reset on read ) +#define HLW8112_REG_ENERGY_PB 0x29 // Active Power Energy B +#define HLW8112_REG_POWER_PA 0x2C // Active Power A 4 bytes 0x00000000 +#define HLW8112_REG_POWER_PB 0x2D // Active Power B +#define HLW8112_REG_POWER_S 0x2E // Apparent Power + +#define HLW8112_REG_EMUSTATUS 0x2F // Measurement Status and Check 3 bytes 0x00b32f +#define HLW8112_REG_EMUSTATUS_CHA_SEL 21 +#define HLW8112_REG_EMUSTATUS_NOPLDB 20 +#define HLW8112_REG_EMUSTATUS_NOPLDA 19 +#define HLW8112_REG_EMUSTATUS_REVPB 18 +#define HLW8112_REG_EMUSTATUS_REVPA 17 +#define HLW8112_REG_EMUSTATUS_CHKSUMBSY 16 +#define HLW8112_REG_EMUSTATUS_CHKSUM 0 + +#define HLW8112_REG_PEAKIA 0x30 // Peak Current A 3 bytes 0x000000 +#define HLW8112_REG_PEAKIB 0x31 // Peak Current B 3 bytes 0x000000 +#define HLW8112_REG_PEAKU 0x32 // Peak Voltage +#define HLW8112_REG_INSTANTIA 0x33 // Instantaneous Current A 3 bytes 0x000000 +#define HLW8112_REG_INSTANTIB 0x34 // Instantaneous Current B +#define HLW8112_REG_INSTANTU 0x35 // Instantaneous Voltage +#define HLW8112_REG_WAVEIA 0x36 // Waveform of Current A 3 bytes 0x000000 +#define HLW8112_REG_WAVEIB 0x37 // Waveform of Current B +#define HLW8112_REG_WAVEU 0x38 // Waveform of Voltage U +#define HLW8112_REG_INSTANTP 0x3C // Instantaneous Active Power 4 bytes 0x00000000 +#define HLW8112_REG_INSTANTS 0x3D // Instantaneous Apparent Power 4 bytes 0x00000000 + +// Interrupt Registers +#define HLW8112_REG_IE 0x40 // Interrupt Enable writable 2 bytes 0x0000 +#define HLW8112_REG_IE_LEAKAGEIE 15 +#define HLW8112_REG_IE_ZX_UIE 14 +#define HLW8112_REG_IE_ZX_IBIE 13 +#define HLW8112_REG_IE_ZX_IAIE 12 +#define HLW8112_REG_IE_SAGIE 11 +#define HLW8112_REG_IE_OPIE 10 +#define HLW8112_REG_IE_OVIE 9 +#define HLW8112_REG_IE_OIBIE 8 +#define HLW8112_REG_IE_OIAIE 7 +#define HLW8112_REG_IE_INSTANIE 6 +#define HLW8112_REG_IE_PEBOIE 4 +#define HLW8112_REG_IE_PEAOIE 3 +#define HLW8112_REG_IE_PFBIE 2 +#define HLW8112_REG_IE_PFAIE 1 +#define HLW8112_REG_IE_DUPDIE 0 + +#define HLW8112_REG_INT_P2 4 +#define HLW8112_REG_INT_P1 0 + +#define HLW8112_REG_IF 0x41 // Interrupt Flag 2 bytes 0x0000 +#define HLW8112_REG_RIF 0x42 // Reset Interrupt Status 2 bytes 0x0000 +#define HLW8112_REG_IF_LEAKAGEIF 15 +#define HLW8112_REG_IF_ZX_UIF 14 +#define HLW8112_REG_IF_ZX_IBIF 13 +#define HLW8112_REG_IF_ZX_IAIF 12 +#define HLW8112_REG_IF_SAGIF 11 +#define HLW8112_REG_IF_OPIF 10 +#define HLW8112_REG_IF_OVIF 9 +#define HLW8112_REG_IF_OIBIF 8 +#define HLW8112_REG_IF_OIAIF 7 +#define HLW8112_REG_IF_INSTANIF 6 +#define HLW8112_REG_IF_PEBOIF 4 +#define HLW8112_REG_IF_PEAOIF 3 +#define HLW8112_REG_IF_PFBIF 2 +#define HLW8112_REG_IF_PFAIF 1 +#define HLW8112_REG_IF_DUPDIF 0 + + +// System Status Registers +#define HLW8112_REG_SYSSTATUS 0x43 // System Status 1 byte +#define HLW8112_REG_SYSSTATUS_CLKSEL 6 +#define HLW8112_REG_SYSSTATUS_WREN 5 +#define HLW8112_REG_SYSSTATUS_RST 0 + +#define HLW8112_REG_RDATA 0x44 // SPI Read Data 4 bytes +#define HLW8112_REG_WDATA 0x45 // SPI Written Data 4bytes + +// Calibration Coefficients Read only +#define HLW8112_REG_COF_CHECKSUM 0x6F // Coeff checksum 2 bytes 0xffff +#define HLW8112_REG_RMSIAC 0x70 // RMS Current A 2 bytes 0xffff +#define HLW8112_REG_RMSIBC 0x71 // RMS Current B 2 bytes 0xffff +#define HLW8112_REG_RMSUC 0x72 // RMS Voltage 2 bytes 0xffff +#define HLW8112_REG_POWER_PAC 0x73 // Active Power A +#define HLW8112_REG_POWER_PBC 0x74 // Active Power B +#define HLW8112_REG_POWER_SC 0x75 // Apparent Power +#define HLW8112_REG_ENERGY_AC 0x76 // Energy A +#define HLW8112_REG_ENERGY_BC 0x77 // Energy B + + +// commands +#define HLW8112_REG_COMMAND 0xEA // Special command operations +#define HLW8112_COMMAND_WRITE_EN 0xE5 // Special command operations +#define HLW8112_COMMAND_WRITE_PROTECT 0xDC // Special command operations +#define HLW8112_COMMAND_SELECT_CH_A 0x5A // Special command operations +#define HLW8112_COMMAND_SELECT_CH_B 0xA5 // Special command operations +#define HLW8112_COMMAND_RESET 0x96 // Special command operations + + + +typedef enum { + HLW8112_PGA_1 = 0, + HLW8112_PGA_2 = 1, + HLW8112_PGA_4 = 2, + HLW8112_PGA_8 = 3, + HLW8112_PGA_16 = 4, +} HLW8112_PGA_t; + +typedef enum { + HLW8112_CHANNEL_A = 0, + HLW8112_CHANNEL_B = 1 +} HLW8112_Channel_t; + +// does it work ??? +typedef enum { + HLW8112_ACTIVE_POW_CALC_METHOD_POS_NEG_ALGEBRAIC = 0, + HLW8112_ACTIVE_POW_CALC_METHOD_POS = 1, + HLW8112_ACTIVE_POW_CALC_METHOD_POS_NEG_ABSOLUTE = 2 +} HLW8112_Power_CalcMethod_t; + + +/* +typedef enum { + HLW8112_RMS_CALC_MODE_NORMAL = 0, + HLW8112_RMS_CALC_MODE_DC = 1 +} HLW8112_RMS_CalcMode_t; + + +typedef enum { + HLW8112_ZX_MODE_POSITIVE = 0, + HLW8112_ZX_MODE_NEGATIVE = 1, + HLW8112_ZX_MODE_BOTH = 2 +} HLW8112_ZX_Mode_t; + + +typedef enum { + HLW8112_INTOUT_FUNC_PULSE_PFA = 0, + HLW8112_INTOUT_FUNC_PULSE_PFB = 1, + HLW8112_INTOUT_FUNC_LEAKAGE = 2, + HLW8112_INTOUT_FUNC_IRQ = 3, + HLW8112_INTOUT_FUNC_POWER_OVERLOAD = 4, + HLW8112_INTOUT_FUNC_NEGATIVE_POWER_A = 5, + HLW8112_INTOUT_FUNC_NEGATIVE_POWER_B = 6, + HLW8112_INTOUT_FUNC_INSTAN_VALUE_UPDATE_INT = 7, + HLW8112_INTOUT_FUNC_AVG_UPDATE_INT = 8, + HLW8112_INTOUT_FUNC_VOLTAGE_ZERO_CROSSING = 9, + HLW8112_INTOUT_FUNC_CURRENT_ZERO_CROSSING_A = 10, + HLW8112_INTOUT_FUNC_CURRENT_ZERO_CROSSING_B = 11, + HLW8112_INTOUT_FUNC_OVERVOLTAGE = 12, + HLW8112_INTOUT_FUNC_UNDERVOLTAGE = 13, + HLW8112_INTOUT_FUNC_OVERCURRENT_A = 14, + HLW8112_INTOUT_FUNC_OVERCURRENT_B = 15, + HLW8112_INTOUT_FUNC_NO_CHANGE = 16 +} HLW8112_IntOutFunc_t; + + +typedef enum HLW8112_DataUpdateFreq_e { + HLW8112_DATA_UPDATE_FREQ_3_4HZ = 0, + HLW8112_DATA_UPDATE_FREQ_6_8HZ = 1, + HLW8112_DATA_UPDATE_FREQ_13_65HZ = 2, + HLW8112_DATA_UPDATE_FREQ_27_3HZ = 3 +} HLW8112_DataUpdateFreq_t; + + +typedef enum HLW8112_EnDis_e { + HLW8112_ENDIS_NOCHANGE = -1, + HLW8112_ENDIS_DISABLE = 0, + HLW8112_ENDIS_ENABLE = 1 +} HLW8112_EnDis_t; +*/ + +typedef enum { + HLW8112_SAVE_NONE = 0, + HLW8112_SAVE_A_IMP = 1, + HLW8112_SAVE_A_EXP = 2, + HLW8112_SAVE_A = 3, + HLW8112_SAVE_B_IMP = 4, + HLW8112_SAVE_B_EXP = 8, + HLW8112_SAVE_B = 12, + HLW8112_SAVE_ALL = 15, + HLW8112_SAVE_FORCE = 16, +} HLW8112_SaveFlag_t; + +typedef uint16_t HLW8112_SaveFlags_t; + +typedef struct { + uint32_t v_rms; // rms voltage + uint16_t freq; // frquency + uint32_t pf; // power factor + uint32_t ap; // Channel A aparent power + + uint32_t ia_rms; // Channel A rms current + uint32_t pa; // Channel A active power + uint32_t ea; // Channel A Energy + + uint32_t ib_rms; // Channel B rms current + uint32_t pb; // Channel B active power + uint32_t eb; // Channel B Energy + + uint8_t sysstat; // System Status 0x43 + uint32_t emustat; // Emu Status + uint16_t int_f; // Intrupt Status + +} HLW8112_Data_t; + + + +//TODO add proper units +typedef struct { + int32_t v_rms; // rms voltage + int32_t freq; // frquency + int16_t pf; // power factor + int32_t ap; // Channel A aparent power + + int32_t ia_rms; // Channel A rms current + int32_t pa; // Channel A active power + ENERGY_DATA* ea; // Channel A Energy + + int32_t ib_rms; // Channel B rms current + int32_t pb; // Channel B active power + ENERGY_DATA* eb; // Channel B Energy + +} HLW8112_UpdateData_t; + + +typedef struct { + double i; + double p; + double e; + double ap; +} HLW8112_Channel_Scale_t; + + typedef struct { + double v_rms; + double freq; + double pf; + HLW8112_Channel_Scale_t a; + HLW8112_Channel_Scale_t b; +} HLW8112_Scale_Factor_t; + +typedef struct { + struct { + uint16_t RmsIAC; + uint16_t RmsIBC; + uint16_t RmsUC; + uint16_t PowerPAC; + uint16_t PowerPBC; + uint16_t PowerSC; + uint16_t EnergyAC; + uint16_t EnergyBC; + } DeviceRegisterCoeff; + + struct { + float KU; + float KIA; + float KIB; + } ResistorCoeff ; + + + struct { + HLW8112_PGA_t U; + HLW8112_PGA_t IA; + HLW8112_PGA_t IB; + } PGA; + + uint16_t HFconst; + uint32_t CLKI; + HLW8112_Channel_t MainChannel; + HLW8112_Scale_Factor_t ScaleFactor ; + + struct + { + uint32_t _SYSCON; + uint32_t _EMUCON; + uint32_t _HFCONST; + uint32_t _PSTARTA; + uint32_t _PSTARTB; + uint32_t _PAGAIN; + uint32_t _PBGAIN; + uint32_t _PHASEA; + uint32_t _PHASEB; + uint32_t _PAOS; + uint32_t _PBOS; + uint32_t _RMSIAOS; + uint32_t _RMSIBOS; + uint32_t _IBGAIN; + uint32_t _PSGAIN; + uint32_t _PSOS; + uint32_t _EMUCON2; + } EX_REGiSTERS; + + +} HLW8112_Device_Conf_t; + + +typedef enum { + HLW8112_Channel_Voltage = 0, + HLW8112_Channel_Frequency = 1, + HLW8112_Channel_PowerFactor = 2, + HLW8112_Channel_current_A = 3, + HLW8112_Channel_current_B = 4, + HLW8112_Channel_power_A = 5, + HLW8112_Channel_power_B = 6, + HLW8112_Channel_apparent_power_A = 7, + HLW8112_Channel_export_A = 8, + HLW8112_Channel_export_B = 9, + HLW8112_Channel_import_A = 10, + HLW8112_Channel_import_B = 11, +// HLW8112_Channel_ResCof_Voltage = 12, +// HLW8112_Channel_ResCof_A = 13, +// HLW8112_Channel_ResCof_B = 14, +// HLW8112_Channel_Clk = 15, +} HLW8112_Device_channels; + + +void HLW8112_compute_scale_factor(); +void HLW8112_ScaleEnergy(HLW8112_Channel_t channel, uint32_t regValue, int32_t* value); +int HLW8112_CheckCoeffs(); +int HLW8112_UpdateCoeff(); + +#endif // __DRV_HLW8112_H__ + +#pragma GCC diagnostic pop \ No newline at end of file diff --git a/src/driver/drv_main.c b/src/driver/drv_main.c index 0fd71f26bc..c0d35b2ade 100644 --- a/src/driver/drv_main.c +++ b/src/driver/drv_main.c @@ -15,6 +15,7 @@ #include "drv_ds1820_simple.h" #include "drv_ds1820_full.h" #include "drv_ds1820_common.h" +#include "drv_hlw8112.h" typedef struct driver_s { @@ -229,6 +230,13 @@ static driver_t g_drivers[] = { //drvdetail:"requires":""} { "BL0942SPI", BL0942_SPI_Init, BL0942_SPI_RunEverySecond, BL09XX_AppendInformationToHTTPIndexPage, NULL, NULL, NULL, NULL, false }, #endif +#if ENABLE_DRIVER_HLW8112SPI + //drvdetail:{"name":"HLW8112SPI", + //drvdetail:"title":"TODO", + //drvdetail:"descr":"TODO", + //drvdetail:"requires":""} + { "HLW8112SPI", HLW8112SPI_Init, HLW8112_RunEverySecond, HLW8112_AppendInformationToHTTPIndexPage, NULL, HLW8112SPI_Stop, NULL, HLW8112_OnHassDiscovery, false }, +#endif #if ENABLE_DRIVER_CHARGINGLIMIT //drvdetail:{"name":"ChargingLimit", //drvdetail:"title":"TODO", @@ -838,6 +846,7 @@ bool DRV_IsMeasuringPower() { return DRV_IsRunning("BL0937") || DRV_IsRunning("BL0942") || DRV_IsRunning("CSE7766") || DRV_IsRunning("TESTPOWER") || DRV_IsRunning("BL0942SPI") || DRV_IsRunning("RN8209"); + // || DRV_IsRunning("HLW8112SPI"); TODO messup ha config if enabled #else return false; #endif diff --git a/src/hal/bk7231/hal_flashVars_bk7231.c b/src/hal/bk7231/hal_flashVars_bk7231.c index 0850a71732..761ed7496f 100644 --- a/src/hal/bk7231/hal_flashVars_bk7231.c +++ b/src/hal/bk7231/hal_flashVars_bk7231.c @@ -697,5 +697,41 @@ float HAL_FlashVars_GetEnergyExport() return f; } +#ifdef ENABLE_DRIVER_HLW8112SPI +void HAL_FlashVars_SaveEnergy(ENERGY_DATA** data, int channel_count) +{ +#ifndef DISABLE_FLASH_VARS_VARS + FLASH_VARS_STRUCTURE tmp; + if (data != NULL) + { + uintptr_t base = (uintptr_t) &flash_vars.emetering; + for(int i =0 ; i < channel_count; i++){ + int offset =( i * sizeof(ENERGY_DATA)); + uintptr_t flash_addr = base + offset ; + memcpy((void *)flash_addr, data[i], sizeof(ENERGY_DATA)); + } + flash_vars_write(); + flash_vars_read(&tmp); + } +#endif +} +void HAL_FlashVars_GetEnergy(ENERGY_DATA* data, ENERGY_CHANNEL channel) +{ +#ifndef DISABLE_FLASH_VARS_VARS + if (!flash_vars_initialised) + { + flash_vars_init(); + } + if (data != NULL) + { + int offset =((channel) * sizeof(ENERGY_DATA)); + uintptr_t base = (uintptr_t) &flash_vars.emetering; + uintptr_t flash_addr = base + offset; + memcpy(data ,(void *)flash_addr, sizeof(ENERGY_DATA)); + } +#endif +} +#endif + #endif diff --git a/src/hal/bk7231/hal_ota_bk7231.c b/src/hal/bk7231/hal_ota_bk7231.c index 87678e43d8..676a3484d7 100644 --- a/src/hal/bk7231/hal_ota_bk7231.c +++ b/src/hal/bk7231/hal_ota_bk7231.c @@ -10,6 +10,7 @@ #include "../../httpserver/new_http.h" #include "../../driver/drv_public.h" #include "../../driver/drv_bl_shared.h" +#include "../../driver/drv_hlw8112.h" static unsigned char *sector = (void *)0; int sectorlen = 0; @@ -155,6 +156,9 @@ int myhttpclientcallback(httprequest_t* request){ BL09XX_SaveEmeteringStatistics(); } #endif +#if ENABLE_DRIVER_HLW8112SPI + HLW8112_Save_Statistics(); +#endif rtos_delay_milliseconds(1000); bk_reboot(); break; diff --git a/src/hal/hal_flashVars.h b/src/hal/hal_flashVars.h index 95c5cf65cc..1bcbfb3f7c 100644 --- a/src/hal/hal_flashVars.h +++ b/src/hal/hal_flashVars.h @@ -28,6 +28,17 @@ typedef struct ENERGY_METERING_DATA { char actual_mday; } ENERGY_METERING_DATA; +// size 8 bytes +typedef struct { + float Import; + float Export; +} ENERGY_DATA; + +typedef enum { + ENERGY_CHANNEL_A = 0, + ENERGY_CHANNEL_B = 1, +} ENERGY_CHANNEL; + typedef struct flash_vars_structure { // offset 0 @@ -62,5 +73,10 @@ void HAL_FlashVars_SaveTotalConsumption(float total_consumption); void HAL_FlashVars_SaveEnergyExport(float f); float HAL_FlashVars_GetEnergyExport(); +#ifdef ENABLE_DRIVER_HLW8112SPI +void HAL_FlashVars_SaveEnergy(ENERGY_DATA** data, int channel_count); +void HAL_FlashVars_GetEnergy(ENERGY_DATA* data, ENERGY_CHANNEL channel); +#endif + #endif /* __HALK_FLASH_VARS_H__ */ diff --git a/src/httpserver/hass.c b/src/httpserver/hass.c index ac0d647aee..bf450b7a21 100644 --- a/src/httpserver/hass.c +++ b/src/httpserver/hass.c @@ -131,6 +131,9 @@ void hass_populate_unique_id(ENTITY_TYPE type, int index, char* uniq_id, int ase case HASS_PERCENT: sprintf(uniq_id, "%s_%s_%d", longDeviceName, "number", index); break; + case HASS_BUTTON: + sprintf(uniq_id, "%s_%s", longDeviceName, "button"); + break; default: // TODO: USE type here as well? // If type is not set, and we use "sensor" naming, we can easily make collision @@ -204,6 +207,9 @@ void hass_populate_device_config_channel(ENTITY_TYPE type, char* uniq_id, HassDe case HUMIDITY_SENSOR: sprintf(info->channel, "sensor/%s/config", uniq_id); break; + case HASS_BUTTON: + sprintf(info->channel, "button/%s/config", uniq_id); + break; default: sprintf(info->channel, "sensor/%s/config", uniq_id); break; @@ -563,13 +569,17 @@ HassDeviceInfo* hass_init_device_info(ENTITY_TYPE type, int index, const char* p case TIMESTAMP_SENSOR: sprintf(g_hassBuffer, "Timestamp"); break; + case HASS_BUTTON: + sprintf(g_hassBuffer, "%s" , ""); + break; + default: sprintf(g_hassBuffer, "%s", CHANNEL_GetLabel(index)); break; } } if (title) { - strcat(g_hassBuffer, "_"); + if (type!=HASS_BUTTON) strcat(g_hassBuffer, "_"); strcat(g_hassBuffer, title); } cJSON_AddStringToObject(info->root, "name", g_hassBuffer); @@ -588,8 +598,13 @@ HassDeviceInfo* hass_init_device_info(ENTITY_TYPE type, int index, const char* p } if (!isSensor && type != HASS_TEXTFIELD) { //Sensors (except binary_sensor) don't use payload - cJSON_AddStringToObject(info->root, "pl_on", payload_on); //payload_on - cJSON_AddStringToObject(info->root, "pl_off", payload_off); //payload_off + if(type == HASS_BUTTON) { + cJSON_AddStringToObject(info->root, "payload_press", payload_on); + } + else if(type != HASS_TEXTFIELD){ + cJSON_AddStringToObject(info->root, "pl_on", payload_on); //payload_on + cJSON_AddStringToObject(info->root, "pl_off", payload_off); //payload_off + } } cJSON_AddStringToObject(info->root, "uniq_id", info->unique_id); //unique_id @@ -804,9 +819,23 @@ HassDeviceInfo* hass_init_energy_sensor_device_info(int index, int asensdataseti // } return info; } - #endif +HassDeviceInfo* hass_init_button_device_info(char* title,char* cmd_id, char* press_payload, HASS_CATEGORY_TYPE type) { + HassDeviceInfo* info = 0; + const char* clientId = CFG_GetMQTTClientId(); + info = hass_init_device_info(HASS_BUTTON, 0, press_payload, NULL, 0, title); + if (type == HASS_CATEGORY_DIAGNOSTIC){ + cJSON_AddStringToObject(info->root, "entity_category", "diagnostic"); + } + else { + cJSON_AddStringToObject(info->root, "entity_category", "config"); + } + sprintf(g_hassBuffer, "cmnd/%s/%s", clientId, cmd_id); + cJSON_AddStringToObject(info->root, "command_topic", g_hassBuffer); + return info; +} + // generate string like "{{ float(value)*0.1|round(2) }}" // {{ float(value)*0.1 }} for value=12 give 1.2000000000000002, using round() to limit the decimal places // 2023 10 19 - it is not a perfect solution, it's better to use: diff --git a/src/httpserver/hass.h b/src/httpserver/hass.h index 891217198a..14ac8a7cba 100644 --- a/src/httpserver/hass.h +++ b/src/httpserver/hass.h @@ -100,8 +100,13 @@ typedef enum { HASS_SELECT, HASS_PERCENT, HASS_TEXTFIELD, + HASS_BUTTON, } ENTITY_TYPE; +typedef enum { + HASS_CATEGORY_CONFIG = 0, + HASS_CATEGORY_DIAGNOSTIC = 1, +} HASS_CATEGORY_TYPE; //unique_id is defined in hass_populate_unique_id and is based on CFG_GetDeviceName() whose size is CGF_DEVICE_NAME_SIZE. //Sample unique_id would be deviceName_entityType_index. //Currently supported entityType is `relay` or `light` - 5 char. @@ -147,5 +152,5 @@ const char* hass_build_discovery_json(HassDeviceInfo* info); void hass_free_device_info(HassDeviceInfo* info); char *hass_generate_multiplyAndRound_template(int decimalPlacesForRounding, int decimalPointOffset, int divider); HassDeviceInfo* hass_init_textField_info(int index); - +HassDeviceInfo* hass_init_button_device_info(char* title,char* cmd_id, char* press_payload, HASS_CATEGORY_TYPE type); #endif // ENABLE_HA_DISCOVERY diff --git a/src/httpserver/http_fns.c b/src/httpserver/http_fns.c index 7c2ffaecbf..095f174f93 100644 --- a/src/httpserver/http_fns.c +++ b/src/httpserver/http_fns.c @@ -2201,6 +2201,16 @@ void doHomeAssistantDiscovery(const char* topic, http_request_t* request) { dev_info = hass_init_sensor_device_info(ENERGY_SENSOR, i, 3, 2, 1); } break; + case ChType_EnergyExport_kWh_div1000: + { + dev_info = hass_init_sensor_device_info(ENERGY_SENSOR, i, 3, 3, 1); + } + break; + case ChType_EnergyImport_kWh_div1000: + { + dev_info = hass_init_sensor_device_info(ENERGY_SENSOR, i, 3, 3, 1); + } + break; case ChType_EnergyTotal_kWh_div1000: { dev_info = hass_init_sensor_device_info(ENERGY_SENSOR, i, 3, 3, 1); diff --git a/src/httpserver/new_http.c b/src/httpserver/new_http.c index c04bffa8b2..5d642af8c6 100644 --- a/src/httpserver/new_http.c +++ b/src/httpserver/new_http.c @@ -557,6 +557,7 @@ const char* htmlPinRoleNames[] = { "IRRecv_nPup", "StripState", "StripState_n", + "HLW_8112_SCSN", "error", "error", "error", diff --git a/src/mqtt/new_mqtt.h b/src/mqtt/new_mqtt.h index 34215c1ead..e136d22c6e 100644 --- a/src/mqtt/new_mqtt.h +++ b/src/mqtt/new_mqtt.h @@ -138,6 +138,7 @@ OBK_Publish_Result MQTT_ChannelPublish(int channel, int flags); void MQTT_ClearCallbacks(); int MQTT_RegisterCallback(const char* basetopic, const char* subscriptiontopic, int ID, mqtt_callback_fn callback); int MQTT_RemoveCallback(int ID); +const char* MQTT_RemoveClientFromTopic(const char* topic, const char *prefix); // this is called from tcp_thread context to queue received mqtt, // and then we'll retrieve them from our own thread for processing. diff --git a/src/new_pins.c b/src/new_pins.c index aeea8922a4..89a0824b76 100644 --- a/src/new_pins.c +++ b/src/new_pins.c @@ -479,7 +479,8 @@ int PIN_IOR_NofChan(int test){ || test == IOR_BL0937_CF || test == IOR_BL0937_CF1 || test == IOR_BL0937_SEL || test == IOR_LED_WIFI || test == IOR_LED_WIFI_n || test == IOR_LED_WIFI_n || (test >= IOR_IRRecv && test <= IOR_DHT11) - || (test >= IOR_SM2135_DAT && test <= IOR_BP1658CJ_CLK)) { + || (test >= IOR_SM2135_DAT && test <= IOR_BP1658CJ_CLK) + || (test == IOR_HLW8112_SCSN)) { return 0; } // all others have 1 channel @@ -1381,6 +1382,7 @@ int ChannelType_GetDivider(int type) { case ChType_EnergyTotal_kWh_div1000: case ChType_EnergyExport_kWh_div1000: case ChType_EnergyToday_kWh_div1000: + case ChType_EnergyImport_kWh_div1000: case ChType_Current_div1000: case ChType_LeakageCurrent_div1000: case ChType_ReadOnly_div1000: @@ -1422,6 +1424,7 @@ const char *ChannelType_GetUnit(int type) { case ChType_EnergyExport_kWh_div1000: case ChType_EnergyToday_kWh_div1000: case ChType_EnergyTotal_kWh_div100: + case ChType_EnergyImport_kWh_div1000: return "kWh"; case ChType_PowerFactor_div1000: case ChType_PowerFactor_div100: @@ -1477,6 +1480,8 @@ const char *ChannelType_GetTitle(int type) { return "EnergyExport"; case ChType_EnergyToday_kWh_div1000: return "EnergyToday"; + case ChType_EnergyImport_kWh_div1000: + return "EnergyImport"; case ChType_PowerFactor_div1000: case ChType_PowerFactor_div100: return "PowerFactor"; @@ -2383,6 +2388,7 @@ const char* g_channelTypeNames[] = { "OpenStopClose", "Percent", "StopUpDown", + "EnergyImport_kWh_div1000", "error", "error", }; diff --git a/src/new_pins.h b/src/new_pins.h index 32e4590644..4c26da97d8 100644 --- a/src/new_pins.h +++ b/src/new_pins.h @@ -609,6 +609,13 @@ typedef enum ioRole_e { //iodetail:"file":"new_pins.h", //iodetail:"driver":""} IOR_StripState_n, + //iodetail:{"name":"HLW8112_SCSN", + //iodetail:"title":"HLW8112 SCSN Pin", + //iodetail:"descr":"SCSN pin for HLW8112 SPI energy measuring devices.", + //iodetail:"enum":"IOR_HLW8112_SCSN", + //iodetail:"file":"new_pins.h", + //iodetail:"driver":"HLW8112SPI"} + IOR_HLW8112_SCSN, //iodetail:{"name":"Total_Options", //iodetail:"title":"TODO", //iodetail:"descr":"Current total number of available IOR roles", @@ -1057,6 +1064,13 @@ typedef enum channelType_e { //chandetail:"file":"new_pins.h", //chandetail:"driver":""} ChType_StopUpDown, + //chandetail:{"name":"EnergyImport_kWh_div1000", + //chandetail:"title":"EnergyImport_kWh_div1000", + //chandetail:"descr":"TODO", + //chandetail:"enum":"ChType_EnergyImport_kWh_div1000", + //chandetail:"file":"new_pins.h", + //chandetail:"driver":""} + ChType_EnergyImport_kWh_div1000, //chandetail:{"name":"Max", //chandetail:"title":"TODO", //chandetail:"descr":"This is the current total number of available channel types.", @@ -1317,7 +1331,11 @@ enum { CFG_OBK_VOLTAGE = 0, CFG_OBK_CURRENT, CFG_OBK_POWER, - CFG_OBK_POWER_MAX + CFG_OBK_POWER_MAX, + CFG_OBK_CLK, // HLW8112 clock freq internal or external + CFG_OBK_RES_KU, // HLW8112 voltage channel K + CFG_OBK_RES_KIA, // HLW8112 current A channel K + CFG_OBK_RES_KIB, // HLW8112 current B channel K }; typedef struct led_corr_s { // LED gamma correction and calibration data block diff --git a/src/obk_config.h b/src/obk_config.h index a8fbbd6a3c..c67801efb5 100644 --- a/src/obk_config.h +++ b/src/obk_config.h @@ -292,6 +292,9 @@ #define NEW_TCP_SERVER 1 #endif +#if PLATFORM_BK7231N +#define ENABLE_DRIVER_HLW8112SPI 0 +#endif // ENABLE_I2C_ is a syntax for // our I2C system defines for drv_i2c_main.c // #define ENABLE_I2C_ADS1115 1 @@ -322,6 +325,7 @@ #undef ENABLE_DRIVER_BL0937 #undef ENABLE_DRIVER_BL0942 #undef ENABLE_DRIVER_BL0942SPI +#undef ENABLE_DRIVER_HLW8112SPI #undef ENABLE_DRIVER_CSE7766 #undef ENABLE_DRIVER_BRIDGE #endif diff --git a/src/user_main.c b/src/user_main.c index 0edaa25f6d..44e8e8d064 100644 --- a/src/user_main.c +++ b/src/user_main.c @@ -11,6 +11,7 @@ //#include "driver/drv_ir.h" #include "driver/drv_public.h" #include "driver/drv_bl_shared.h" +#include "driver/drv_hlw8112.h" //#include "ir/ir_local.h" // Commands register, execution API and cmd tokenizer @@ -948,7 +949,10 @@ void Main_OnEverySecond() { BL09XX_SaveEmeteringStatistics(); } -#endif +#endif +#if ENABLE_DRIVER_HLW8112SPI + HLW8112_Save_Statistics(); +#endif ADDLOGF_INFO("Going to call HAL_RebootModule\r\n"); HAL_RebootModule(); } @@ -1299,6 +1303,9 @@ void Main_Init_BeforeDelay_Unsafe(bool bAutoRunScripts) { { DRV_StartDriver("GN6932"); } + if (PIN_FindPinIndexForRole(IOR_HLW8112_SCSN, -1) != -1) { + DRV_StartDriver("HLW8112SPI"); + } // if ((PIN_FindPinIndexForRole(IOR_TM1638_CLK, -1) != -1) && // (PIN_FindPinIndexForRole(IOR_TM1638_DAT, -1) != -1) && // (PIN_FindPinIndexForRole(IOR_TM1638_STB, -1) != -1))