From 2775b53d6c77986264739fe173d2df6ca28f0991 Mon Sep 17 00:00:00 2001 From: Artem Kovalenko Date: Fri, 4 Oct 2024 17:47:37 +0300 Subject: [PATCH 01/13] add deye device, refactor code to support multiple modbus devices --- src/PI_Serial/PI_Serial.cpp | 5 +- src/PI_Serial/PI_Serial.h | 13 +- src/PI_Serial/QMOD.h | 16 +-- src/descriptors.h | 92 +++++++++++++++ src/main.h | 189 ++++++++++++++++-------------- src/modbus/device/deye.h | 100 ++++++++++++++++ src/modbus/device/modbus_device.h | 57 +++++++++ src/modbus/device/must_pv_ph18.h | 121 +++++++++++++++++++ src/modbus/modbus.cpp | 66 +++++------ src/modbus/modbus.h | 23 ++-- src/modbus/modbus_registers.h | 58 +-------- 11 files changed, 528 insertions(+), 212 deletions(-) create mode 100644 src/descriptors.h create mode 100644 src/modbus/device/deye.h create mode 100644 src/modbus/device/modbus_device.h create mode 100644 src/modbus/device/must_pv_ph18.h diff --git a/src/PI_Serial/PI_Serial.cpp b/src/PI_Serial/PI_Serial.cpp index 55fb3ed..9de01ba 100644 --- a/src/PI_Serial/PI_Serial.cpp +++ b/src/PI_Serial/PI_Serial.cpp @@ -15,6 +15,7 @@ CRC16 crc; #include "QPIGS2.h" #include "QMOD.h" #include "QEX.h" + extern void writeLog(const char *format, ...); //---------------------------------------------------------------------- // Public Functions @@ -186,9 +187,7 @@ void PI_Serial::autoDetect() // function for autodetect the inverter type { modbus = new MODBUS(this->my_serialIntf); modbus->Init(); - if (modbus->autoDetect()){ - protocol = MODBUS_MUST; - } + protocol = modbus->autoDetect(); } writeLog("----------------- End Autodetect -----------------"); } diff --git a/src/PI_Serial/PI_Serial.h b/src/PI_Serial/PI_Serial.h index b1b9444..7b9d6d1 100644 --- a/src/PI_Serial/PI_Serial.h +++ b/src/PI_Serial/PI_Serial.h @@ -1,7 +1,8 @@ -#include "SoftwareSerial.h" #ifndef PI_SERIAL_H #define PI_SERIAL_H + +#include "SoftwareSerial.h" #include #include extern JsonObject deviceJson; @@ -14,7 +15,7 @@ class PI_Serial const char *startChar = "("; const char *delimiter = " "; bool requestStaticData = true; - byte protocol = NoD; + protocol_type_t protocol = NoD; bool connection = false; struct @@ -86,13 +87,7 @@ class PI_Serial void callback(std::function func); std::function requestCallback; - enum protocolType - { - NoD, - PI18, - PI30, - MODBUS_MUST - }; + private: unsigned int serialIntfBaud; diff --git a/src/PI_Serial/QMOD.h b/src/PI_Serial/QMOD.h index 72db7d0..61d4c0d 100644 --- a/src/PI_Serial/QMOD.h +++ b/src/PI_Serial/QMOD.h @@ -14,7 +14,7 @@ bool PI_Serial::PIXX_QMOD() } if (commandAnswer.length() == 1) { - liveData["Inverter_Operation_Mode"] = getModeDesc((char)commandAnswer.charAt(0)); + liveData[DESCR_LIVE_INVERTER_OPERATION_MODE] = getModeDesc((char)commandAnswer.charAt(0)); } return true; } @@ -35,25 +35,25 @@ bool PI_Serial::PIXX_QMOD() switch (commandAnswer.toInt()) { case 0: - liveData["Inverter_Operation_Mode"] = "Power on"; + liveData[DESCR_LIVE_INVERTER_OPERATION_MODE] = "Power on"; break; case 1: - liveData["Inverter_Operation_Mode"] = "Standby"; + liveData[DESCR_LIVE_INVERTER_OPERATION_MODE] = "Standby"; break; case 2: - liveData["Inverter_Operation_Mode"] = "Bypass"; + liveData[DESCR_LIVE_INVERTER_OPERATION_MODE] = "Bypass"; break; case 3: - liveData["Inverter_Operation_Mode"] = "Battery"; + liveData[DESCR_LIVE_INVERTER_OPERATION_MODE] = "Battery"; break; case 4: - liveData["Inverter_Operation_Mode"] = "Fault"; + liveData[DESCR_LIVE_INVERTER_OPERATION_MODE] = "Fault"; break; case 5: - liveData["Inverter_Operation_Mode"] = "Hybrid"; + liveData[DESCR_LIVE_INVERTER_OPERATION_MODE] = "Hybrid"; break; default: - liveData["Inverter_Operation_Mode"] = "No data"; + liveData[DESCR_LIVE_INVERTER_OPERATION_MODE] = "No data"; break; } diff --git a/src/descriptors.h b/src/descriptors.h new file mode 100644 index 0000000..0875dd6 --- /dev/null +++ b/src/descriptors.h @@ -0,0 +1,92 @@ +#ifndef DESCRIPTORS_H +#define DESCRIPTORS_H + +#define DESCR_STAT_AC_IN_RATING_CURRENT "AC_in_rating_current" +#define DESCR_STAT_AC_IN_RATING_VOLTAGE "AC_in_rating_voltage" +#define DESCR_STAT_AC_OUT_RATING_ACTIVE_POWER "AC_out_rating_active_power" +#define DESCR_STAT_AC_OUT_RATING_APPARENT_POWER "AC_out_rating_apparent_power" +#define DESCR_STAT_AC_OUT_RATING_CURRENT "AC_out_rating_current" +#define DESCR_STAT_AC_OUT_RATING_FREQUENCY "AC_out_rating_frequency" +#define DESCR_STAT_AC_OUT_RATING_VOLTAGE "AC_out_rating_voltage" +#define DESCR_STAT_BATTERY_BULK_VOLTAGE "Battery_bulk_voltage" +#define DESCR_STAT_BATTERY_FLOAT_VOLTAGE "Battery_float_voltage" +#define DESCR_STAT_BATTERY_RATING_VOLTAGE "Battery_rating_voltage" +#define DESCR_STAT_BATTERY_RE_CHARGE_VOLTAGE "Battery_re-charge_voltage" +#define DESCR_STAT_BATTERY_RE_DISCHARGE_VOLTAGE "Battery_re-discharge_voltage" +#define DESCR_STAT_BATTERY_TYPE "Battery_type" +#define DESCR_STAT_BATTERY_UNDER_VOLTAGE "Battery_under_voltage" +#define DESCR_STAT_CHARGER_SOURCE_PRIORITY "Charger_source_priority" +#define DESCR_STAT_CURRENT_MAX_AC_CHARGING_CURRENT "Current_max_AC_charging_current" +#define DESCR_STAT_CURRENT_MAX_CHARGING_CURRENT "Current_max_charging_current" +#define DESCR_STAT_DEVICE_MODEL "Device_Model" +#define DESCR_STAT_INPUT_VOLTAGE_RANGE "Input_voltage_range" +#define DESCR_STAT_MACHINE_TYPE "Machine_type" +#define DESCR_STAT_MAX_CHARGING_TIME_AT_CV_STAGE "Max_charging_time_at_CV_stage" +#define DESCR_STAT_MAX_DISCHARGING_CURRENT "Max_discharging_current" +#define DESCR_STAT_MPPT_STRING "MPPT_string" +#define DESCR_STAT_OPERATION_LOGIC "Operation_Logic" +#define DESCR_STAT_OUTPUT_MODE "Output_mode" +#define DESCR_STAT_OUTPUT_SOURCE_PRIORITY "Output_source_priority" +#define DESCR_STAT_PROTOCOL_ID "Protocol_ID" +#define DESCR_STAT_PV_POWER_BALANCE "PV_power_balance" +#define DESCR_STAT_SOLAR_POWER_PRIORITY "Solar_power_priority" +#define DESCR_STAT_TOPOLOGY "Topology" + +#define DESCR_LIVE_AC_IN_FREQUENZ "AC_in_Frequenz" +#define DESCR_LIVE_AC_IN_GENERATION_DAY "AC_in_generation_day" +#define DESCR_LIVE_AC_IN_GENERATION_MONTH "AC_in_generation_month" +#define DESCR_LIVE_AC_IN_GENERATION_SUM "AC_in_generation_sum" +#define DESCR_LIVE_AC_IN_GENERATION_YEAR "AC_in_generation_year" +#define DESCR_LIVE_AC_IN_VOLTAGE "AC_in_Voltage" +#define DESCR_LIVE_AC_OUT_FREQUENZ "AC_out_Frequenz" +#define DESCR_LIVE_AC_OUT_PERCENT "AC_out_percent" +#define DESCR_LIVE_AC_OUT_VA "AC_out_VA" +#define DESCR_LIVE_AC_OUT_VOLTAGE "AC_out_Voltage" +#define DESCR_LIVE_AC_OUT_WATT "AC_out_Watt" +#define DESCR_LIVE_AC_OUTPUT_CURRENT "AC_output_current" +#define DESCR_LIVE_AC_OUTPUT_FREQUENCY "AC_output_frequency" +#define DESCR_LIVE_AC_OUTPUT_POWER "AC_output_power" +#define DESCR_LIVE_AC_OUTPUT_VOLTAGE "AC_output_voltage" +#define DESCR_LIVE_BATTERY_CAPACITY "Battery_capacity" +#define DESCR_LIVE_BATTERY_LOAD "Battery_Load" +#define DESCR_LIVE_BATTERY_PERCENT "Battery_Percent" +#define DESCR_LIVE_BATTERY_POWER_DIRECTION "Battery_Power_Direction" +#define DESCR_LIVE_BATTERY_TEMPERATURE "Battery_temperature" +#define DESCR_LIVE_BATTERY_VOLTAGE "Battery_Voltage" +#define DESCR_LIVE_GRID_FREQUENCY "Grid_frequency" +#define DESCR_LIVE_GRID_VOLTAGE "Grid_voltage" +#define DESCR_LIVE_INVERTER_BUS_TEMPERATURE "Inverter_Bus_Temperature" +#define DESCR_LIVE_INVERTER_BUS_VOLTAGE "Inverter_Bus_Voltage" +#define DESCR_LIVE_INVERTER_OPERATION_MODE "Inverter_Operation_Mode" +#define DESCR_LIVE_INVERTER_TEMPERATURE "Inverter_temperature" +#define DESCR_LIVE_LOCAL_PARALLEL_ID "Local_Parallel_ID" +#define DESCR_LIVE_MPPT1_CHARGER_TEMPERATURE "MPPT1_Charger_Temperature" +#define DESCR_LIVE_MPPT2_CHARGER_TEMPERATURE "MPPT2_Charger_Temperature" +#define DESCR_LIVE_NEGATIVE_BATTERY_VOLTAGE "Negative_battery_voltage" +#define DESCR_LIVE_OUTPUT_CURRENT "Output_current" +#define DESCR_LIVE_OUTPUT_LOAD_PERCENT "Output_load_percent" +#define DESCR_LIVE_OUTPUT_POWER "Output_power" +#define DESCR_LIVE_POSITIVE_BATTERY_VOLTAGE "Positive_battery_voltage" +#define DESCR_LIVE_PV_CHARGING_POWER "PV_Charging_Power" +#define DESCR_LIVE_PV_GENERATION_DAY "PV_generation_day" +#define DESCR_LIVE_PV_GENERATION_MONTH "PV_generation_month" +#define DESCR_LIVE_PV_GENERATION_SUM "PV_generation_sum" +#define DESCR_LIVE_PV_GENERATION_YEAR "PV_generation_year" +#define DESCR_LIVE_PV_INPUT_CURRENT "PV_Input_Current" +#define DESCR_LIVE_PV_INPUT_POWER "PV_Input_Power" +#define DESCR_LIVE_PV_INPUT_VOLTAGE "PV_Input_Voltage" +#define DESCR_LIVE_PV1_INPUT_POWER "PV1_input_power" +#define DESCR_LIVE_PV1_INPUT_VOLTAGE "PV1_input_voltage" +#define DESCR_LIVE_PV2_CHARGING_POWER "PV2_Charging_Power" +#define DESCR_LIVE_PV2_INPUT_CURRENT "PV2_Input_Current" +#define DESCR_LIVE_PV2_INPUT_POWER "PV2_input_power" +#define DESCR_LIVE_PV2_INPUT_VOLTAGE "PV2_input_voltage" +#define DESCR_LIVE_PV3_INPUT_POWER "PV3_input_power" +#define DESCR_LIVE_PV3_INPUT_VOLTAGE "PV3_input_voltage" +#define DESCR_LIVE_SOLAR_FEED_TO_GRID_POWER "Solar_feed_to_grid_power" +#define DESCR_LIVE_SOLAR_FEED_TO_GRID_STATUS "Solar_feed_to_Grid_status" +#define DESCR_LIVE_TRACKER_TEMPERATURE "Tracker_temperature" +#define DESCR_LIVE_TRANSFORMER_TEMPERATURE "Transformer_temperature" +#define DESCR_LIVE_WARNING_CODE "Warning_Code" + +#endif \ No newline at end of file diff --git a/src/main.h b/src/main.h index 183f3ae..bb75b11 100644 --- a/src/main.h +++ b/src/main.h @@ -1,9 +1,12 @@ +#ifndef MAIN_H +#define MAIN_H + #include +#include "descriptors.h" #define ARDUINOJSON_USE_DOUBLE 1 #define ARDUINOJSON_USE_LONG_LONG 1 #define JSON_BUFFER 2048 - #ifdef isUART_HARDWARE #define INVERTER_TX 1 #define INVERTER_RX 3 @@ -24,6 +27,14 @@ #define DBG_BEGIN(...) DBG.begin(__VA_ARGS__) #define DBG_PRINTLN(...) DBG.println(__VA_ARGS__) +typedef enum +{ + NoD, + PI18, + PI30, + MODBUS_MUST, + MODBUS_DEYE +} protocol_type_t; /** * @brief callback function for wifimanager save config data @@ -84,70 +95,71 @@ bool sendHaDiscovery(); * @brief this function act like s/n/printf() and give the output to the configured serial and webserial * */ -void writeLog(const char* format, ...); +void writeLog(const char *format, ...); static const char *const haStaticDescriptor[][4]{ // state_topic, icon, unit_ofmeasurement, class - {"AC_in_rating_current", "current-ac", "A", "current"}, - {"AC_in_rating_voltage", "flash-triangle-outline", "V", "voltage"}, - {"AC_out_rating_active_power", "sine-wave", "W", "power"}, - {"AC_out_rating_apparent_power", "sine-wave", "W", "power"}, - {"AC_out_rating_current", "current-ac", "A", "current"}, - {"AC_out_rating_frequency", "sine-wave", "Hz", "frequency"}, - {"AC_out_rating_voltage", "flash-triangle-outline", "V", "voltage"}, - {"Battery_bulk_voltage", "car-battery", "V", "voltage"}, - {"Battery_float_voltage", "car-battery", "V", "voltage"}, - {"Battery_rating_voltage", "car-battery", "V", "voltage"}, - {"Battery_re-charge_voltage", "battery-charging-high", "V", "voltage"}, - {"Battery_re-discharge_voltage", "battery-charging-outline", "V", "voltage"}, - {"Battery_type", "car-battery", "", ""}, - {"Battery_under_voltage", "battery-remove-outline", "V", "voltage"}, - {"Charger_source_priority", "ev-station", "", ""}, - {"Current_max_AC_charging_current", "current-ac", "A", "current"}, - {"Current_max_charging_current", "battery-charging", "A", "current"}, - {"Device_Model", "battery-charging", "", ""}, - {"Input_voltage_range", "flash-triangle-outline", "", ""}, - {"Machine_type", "state-machine", "", ""}, - {"Max_charging_time_at_CV_stage", "clock-time-eight-outli", "s", "duration"}, - {"Max_discharging_current", "battery-outline", "A", "current"}, - {"MPPT_string", "string-lights", "", ""}, - {"Operation_Logic", "access-point", "", ""}, - {"Output_mode", "export", "", ""}, - {"Output_source_priority", "export", "", ""}, + {DESCR_STAT_AC_IN_RATING_CURRENT, "current-ac", "A", "current"}, + {DESCR_STAT_AC_IN_RATING_VOLTAGE, "flash-triangle-outline", "V", "voltage"}, + {DESCR_STAT_AC_OUT_RATING_ACTIVE_POWER, "sine-wave", "W", "power"}, + {DESCR_STAT_AC_OUT_RATING_APPARENT_POWER, "sine-wave", "W", "power"}, + {DESCR_STAT_AC_OUT_RATING_CURRENT, "current-ac", "A", "current"}, + {DESCR_STAT_AC_OUT_RATING_FREQUENCY, "sine-wave", "Hz", "frequency"}, + {DESCR_STAT_AC_OUT_RATING_VOLTAGE, "flash-triangle-outline", "V", "voltage"}, + {DESCR_STAT_BATTERY_BULK_VOLTAGE, "car-battery", "V", "voltage"}, + {DESCR_STAT_BATTERY_FLOAT_VOLTAGE, "car-battery", "V", "voltage"}, + {DESCR_STAT_BATTERY_RATING_VOLTAGE, "car-battery", "V", "voltage"}, + {DESCR_STAT_BATTERY_RE_CHARGE_VOLTAGE, "battery-charging-high", "V", "voltage"}, + {DESCR_STAT_BATTERY_RE_DISCHARGE_VOLTAGE, "battery-charging-outline", "V", "voltage"}, + {DESCR_STAT_BATTERY_TYPE, "car-battery", "", ""}, + {DESCR_STAT_BATTERY_UNDER_VOLTAGE, "battery-remove-outline", "V", "voltage"}, + {DESCR_STAT_CHARGER_SOURCE_PRIORITY, "ev-station", "", ""}, + {DESCR_STAT_CURRENT_MAX_AC_CHARGING_CURRENT, "current-ac", "A", "current"}, + {DESCR_STAT_CURRENT_MAX_CHARGING_CURRENT, "battery-charging", "A", "current"}, + {DESCR_STAT_DEVICE_MODEL, "battery-charging", "", ""}, + {DESCR_STAT_INPUT_VOLTAGE_RANGE, "flash-triangle-outline", "", ""}, + {DESCR_STAT_MACHINE_TYPE, "state-machine", "", ""}, + {DESCR_STAT_MAX_CHARGING_TIME_AT_CV_STAGE, "clock-time-eight-outli", "s", "duration"}, + {DESCR_STAT_MAX_DISCHARGING_CURRENT, "battery-outline", "A", "current"}, + {DESCR_STAT_MPPT_STRING, "string-lights", "", ""}, + {DESCR_STAT_OPERATION_LOGIC, "access-point", "", ""}, + {DESCR_STAT_OUTPUT_MODE, "export", "", ""}, + {DESCR_STAT_OUTPUT_SOURCE_PRIORITY, "export", "", ""}, //{"Parallel_max_num","","",""}, - {"Protocol_ID", "protocol", "", ""}, + {DESCR_STAT_PROTOCOL_ID, "protocol", "", ""}, //{"PV_OK_condition_for_parallel","solar-panel","",""}, - {"PV_power_balance", "solar-panel", "", ""}, - {"Solar_power_priority", "priority-high", "", ""}, - {"Topology", "earth", "", ""}}; + {DESCR_STAT_PV_POWER_BALANCE, "solar-panel", "", ""}, + {DESCR_STAT_SOLAR_POWER_PRIORITY, "priority-high", "", ""}, + {DESCR_STAT_TOPOLOGY, "earth", "", ""}}; + static const char *const haLiveDescriptor[][4]{ // state_topic, icon, unit_ofmeasurement, class - {"AC_in_Frequenz", "import", "Hz", "frequency"}, - {"AC_in_generation_day", "import", "Wh", "energy"}, - {"AC_in_generation_month", "import", "Wh", "energy"}, - {"AC_in_generation_sum", "import", "Wh", "energy"}, - {"AC_in_generation_year", "import", "Wh", "energy"}, - {"AC_in_Voltage", "import", "V", "voltage"}, - {"AC_out_Frequenz", "export", "Hz", "frequency"}, - {"AC_out_percent", "export", "%", "power_factor"}, - {"AC_out_VA", "export", "VA", "apparent_power"}, - {"AC_out_Voltage", "export", "V", "voltage"}, - {"AC_out_Watt", "export", "W", "power"}, - {"AC_output_current", "export", "A", "current"}, - {"AC_output_frequency", "export", "Hz", "frequency"}, - {"AC_output_power", "export", "W", "power"}, - {"AC_output_voltage", "export", "V", "voltage"}, + {DESCR_LIVE_AC_IN_FREQUENZ, "import", "Hz", "frequency"}, + {DESCR_LIVE_AC_IN_GENERATION_DAY, "import", "Wh", "energy"}, + {DESCR_LIVE_AC_IN_GENERATION_MONTH, "import", "Wh", "energy"}, + {DESCR_LIVE_AC_IN_GENERATION_SUM, "import", "Wh", "energy"}, + {DESCR_LIVE_AC_IN_GENERATION_YEAR, "import", "Wh", "energy"}, + {DESCR_LIVE_AC_IN_VOLTAGE, "import", "V", "voltage"}, + {DESCR_LIVE_AC_OUT_FREQUENZ, "export", "Hz", "frequency"}, + {DESCR_LIVE_AC_OUT_PERCENT, "export", "%", "power_factor"}, + {DESCR_LIVE_AC_OUT_VA, "export", "VA", "apparent_power"}, + {DESCR_LIVE_AC_OUT_VOLTAGE, "export", "V", "voltage"}, + {DESCR_LIVE_AC_OUT_WATT, "export", "W", "power"}, + {DESCR_LIVE_AC_OUTPUT_CURRENT, "export", "A", "current"}, + {DESCR_LIVE_AC_OUTPUT_FREQUENCY, "export", "Hz", "frequency"}, + {DESCR_LIVE_AC_OUTPUT_POWER, "export", "W", "power"}, + {DESCR_LIVE_AC_OUTPUT_VOLTAGE, "export", "V", "voltage"}, //{"ACDC_Power_Direction","sign-direction","",""}, - {"Battery_capacity", "battery-high", "%", "battery"}, + {DESCR_LIVE_BATTERY_CAPACITY, "battery-high", "%", "battery"}, //{"Battery_Charge_Current","battery-charging-high","A","current"}, //{"Battery_Discharge_Current","battery-charging-outli","A","current"}, - {"Battery_Load", "battery-charging-high", "A", "current"}, - {"Battery_Percent", "battery-charging-high", "%", "battery"}, - {"Battery_Power_Direction", "battery-charging-high", "", ""}, + {DESCR_LIVE_BATTERY_LOAD, "battery-charging-high", "A", "current"}, + {DESCR_LIVE_BATTERY_PERCENT, "battery-charging-high", "%", "battery"}, + {DESCR_LIVE_BATTERY_POWER_DIRECTION, "battery-charging-high", "", ""}, //{"Battery_SCC_Volt","battery-high","V","voltage"}, //{"Battery_SCC2_Volt","battery-high","V","voltage"}, - {"Battery_temperature", "thermometer-lines", "°C", "temperature"}, - {"Battery_Voltage", "battery-high", "V", "voltage"}, + {DESCR_LIVE_BATTERY_TEMPERATURE, "thermometer-lines", "°C", "temperature"}, + {DESCR_LIVE_BATTERY_VOLTAGE, "battery-high", "V", "voltage"}, //{"Battery_voltage_offset_fans_on","fan","",""}, //{"Configuration_State","state-machine","",""}, //{"Country","earth","",""}, @@ -155,49 +167,50 @@ static const char *const haLiveDescriptor[][4]{ //{"EEPROM_Version","chip","",""}, //{"Fan_speed","fan","",""}, //{"Fault_code","alert-outline","",""}, - {"Grid_frequency", "import", "Hz", "frequency"}, - {"Grid_voltage", "import", "V", "voltage"}, - {"Inverter_Bus_Temperature", "thermometer-lines", "°C", "temperature"}, - {"Inverter_Bus_Voltage", "flash-triangle-outline", "V", "voltage"}, + {DESCR_LIVE_GRID_FREQUENCY, "import", "Hz", "frequency"}, + {DESCR_LIVE_GRID_VOLTAGE, "import", "V", "voltage"}, + {DESCR_LIVE_INVERTER_BUS_TEMPERATURE, "thermometer-lines", "°C", "temperature"}, + {DESCR_LIVE_INVERTER_BUS_VOLTAGE, "flash-triangle-outline", "V", "voltage"}, //{"Inverter_charge_state","car-turbocharger","",""}, - {"Inverter_Operation_Mode", "car-turbocharger", "", ""}, - {"Inverter_temperature", "thermometer-lines", "°C", "temperature"}, + {DESCR_LIVE_INVERTER_OPERATION_MODE, "car-turbocharger", "", ""}, + {DESCR_LIVE_INVERTER_TEMPERATURE, "thermometer-lines", "°C", "temperature"}, //{"Line_Power_Direction","transmission-tower","",""}, //{"Load_Connection","connection","",""}, - {"Local_Parallel_ID", "card-account-details-outline", "", ""}, + {DESCR_LIVE_LOCAL_PARALLEL_ID, "card-account-details-outline", "", ""}, //{"Max_temperature","thermometer-plus","C","temperature"}, //{"MPPT1_Charger_Status","car-turbocharger","",""}, - {"MPPT1_Charger_Temperature", "thermometer-lines", "°C", "temperature"}, + {DESCR_LIVE_MPPT1_CHARGER_TEMPERATURE, "thermometer-lines", "°C", "temperature"}, //{"MPPT2_CHarger_Status","car-turbocharger","",""}, - {"MPPT2_Charger_Temperature", "thermometer-lines", "°C", "temperature"}, - {"Negative_battery_voltage", "battery-minus-outline", "V", "voltage"}, - {"Output_current", "export", "A", "current"}, - {"Output_load_percent", "export", "%", "battery"}, - {"Output_power", "export", "W", "power"}, + {DESCR_LIVE_MPPT2_CHARGER_TEMPERATURE, "thermometer-lines", "°C", "temperature"}, + {DESCR_LIVE_NEGATIVE_BATTERY_VOLTAGE, "battery-minus-outline", "V", "voltage"}, + {DESCR_LIVE_OUTPUT_CURRENT, "export", "A", "current"}, + {DESCR_LIVE_OUTPUT_LOAD_PERCENT, "export", "%", "battery"}, + {DESCR_LIVE_OUTPUT_POWER, "export", "W", "power"}, //{"PBUS_voltage","","V","voltage"}, - {"Positive_battery_voltage", "car-battery", "V", "voltage"}, - {"PV_Charging_Power", "solar-power-variant", "W", "power"}, - {"PV_generation_day", "solar-power-variant", "Wh", "energy"}, - {"PV_generation_month", "solar-power-variant", "Wh", "energy"}, - {"PV_generation_sum", "solar-power-variant", "Wh", "energy"}, - {"PV_generation_year", "solar-power-variant", "Wh", "energy"}, - {"PV_Input_Current", "solar-power-variant", "A", "current"}, - {"PV_Input_Power", "solar-power-variant", "W", "power"}, - {"PV_Input_Voltage", "solar-power-variant", "V", "voltage"}, - {"PV1_input_power", "solar-power-variant", "W", "power"}, - {"PV1_input_voltage", "solar-power-variant", "V", "voltage"}, - {"PV2_Charging_Power", "solar-power-variant", "W", "power"}, - {"PV2_Input_Current", "solar-power-variant", "A", "current"}, - {"PV2_input_power", "solar-power-variant", "W", "power"}, - {"PV2_input_voltage", "solar-power-variant", "V", "voltage"}, - {"PV3_input_power", "solar-power-variant", "W", "power"}, - {"PV3_input_voltage", "solar-power-variant", "V", "voltage"}, + {DESCR_LIVE_POSITIVE_BATTERY_VOLTAGE, "car-battery", "V", "voltage"}, + {DESCR_LIVE_PV_CHARGING_POWER, "solar-power-variant", "W", "power"}, + {DESCR_LIVE_PV_GENERATION_DAY, "solar-power-variant", "Wh", "energy"}, + {DESCR_LIVE_PV_GENERATION_MONTH, "solar-power-variant", "Wh", "energy"}, + {DESCR_LIVE_PV_GENERATION_SUM, "solar-power-variant", "Wh", "energy"}, + {DESCR_LIVE_PV_GENERATION_YEAR, "solar-power-variant", "Wh", "energy"}, + {DESCR_LIVE_PV_INPUT_CURRENT, "solar-power-variant", "A", "current"}, + {DESCR_LIVE_PV_INPUT_POWER, "solar-power-variant", "W", "power"}, + {DESCR_LIVE_PV_INPUT_VOLTAGE, "solar-power-variant", "V", "voltage"}, + {DESCR_LIVE_PV1_INPUT_POWER, "solar-power-variant", "W", "power"}, + {DESCR_LIVE_PV1_INPUT_VOLTAGE, "solar-power-variant", "V", "voltage"}, + {DESCR_LIVE_PV2_CHARGING_POWER, "solar-power-variant", "W", "power"}, + {DESCR_LIVE_PV2_INPUT_CURRENT, "solar-power-variant", "A", "current"}, + {DESCR_LIVE_PV2_INPUT_POWER, "solar-power-variant", "W", "power"}, + {DESCR_LIVE_PV2_INPUT_VOLTAGE, "solar-power-variant", "V", "voltage"}, + {DESCR_LIVE_PV3_INPUT_POWER, "solar-power-variant", "W", "power"}, + {DESCR_LIVE_PV3_INPUT_VOLTAGE, "solar-power-variant", "V", "voltage"}, //{"SBUS_voltage","flash-triangle-outline","V","voltage"}, - {"Solar_feed_to_grid_power", "solar-power-variant", "W", "power"}, - {"Solar_feed_to_Grid_status", "solar-power-variant", "", ""}, + {DESCR_LIVE_SOLAR_FEED_TO_GRID_POWER, "solar-power-variant", "W", "power"}, + {DESCR_LIVE_SOLAR_FEED_TO_GRID_STATUS, "solar-power-variant", "", ""}, //{"Status_Flag","flag","",""}, //{"Time_until_absorb_charge","solar-power-variant","s","duration"}, //{"Time_until_float_charge","solar-power-variant","s","duration"}, - {"Tracker_temperature", "thermometer-lines", "°C", "temperature"}, - {"Transformer_temperature", "thermometer-lines", "°C", "temperature"}, - {"Warning_Code", "alert-outline", "", ""}}; \ No newline at end of file + {DESCR_LIVE_TRACKER_TEMPERATURE, "thermometer-lines", "°C", "temperature"}, + {DESCR_LIVE_TRANSFORMER_TEMPERATURE, "thermometer-lines", "°C", "temperature"}, + {DESCR_LIVE_WARNING_CODE, "alert-outline", "", "" }}; +#endif \ No newline at end of file diff --git a/src/modbus/device/deye.h b/src/modbus/device/deye.h new file mode 100644 index 0000000..24bb1f5 --- /dev/null +++ b/src/modbus/device/deye.h @@ -0,0 +1,100 @@ +#ifndef MODBUS_DEYE_H +#define MODBUS_DEYE_H +#include "modbus_device.h" +#include + +class DEYE : public ModbusDevice +{ +public: + DEYE() : ModbusDevice(_baudRate, _modbusAddr, _protocol) {} + + virtual const modbus_register_t *getLiveRegisters() const override + { + return registers_live; + } + + virtual const modbus_register_t *getStaticRegisters() const override + { + return registers_static; + } + + const char *getName() const override + { + return _name; + } + + bool retrieveModel(MODBUS &modbus, char *modelBuffer, size_t bufferSize) override; + +private: + static const long _baudRate = 9600; + static const uint32_t _modbusAddr = 1; + static const protocol_type_t _protocol = MODBUS_DEYE; + static const char *const _name; + + static const modbus_register_t registers_live[]; + static const modbus_register_t registers_static[]; + static const modbus_register_t registers_device_serial[]; +}; + +const char *const DEYE::_name = "DEYE"; + +const modbus_register_t DEYE::registers_live[] = { + {175, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_OUTPUT_POWER}, + {178, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_AC_OUTPUT_POWER}, + + {182, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_BATTERY_TEMPERATURE}, + + {183, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, DESCR_LIVE_BATTERY_VOLTAGE}, + {184, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_BATTERY_PERCENT}, +}; + +const modbus_register_t DEYE::registers_static[] = { + + {10110, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "Battery_type", {.bitfield = { + "No choose", + "User defined", + "Lithium", + "Sealed Lead", + "AGM", + "GEL", + "Flooded", + }}}, +}; + +const modbus_register_t DEYE::registers_device_serial[] = { + {3, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN1"}, + {4, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN2"}, + {5, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN3"}, + {6, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN4"}, + {7, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN5"}}; + +bool DEYE::retrieveModel(MODBUS &modbus, char *modelBuffer, size_t bufferSize) +{ + modelBuffer[0] = '\0'; // Clear the buffer + DynamicJsonDocument doc(100); + JsonObject jsonObj = doc.to(); // Create and get JsonObject + modbus_register_info_t model_info = { + .variant = &jsonObj, + .registers = registers_device_serial, + .array_size = sizeof(registers_device_serial) / sizeof(modbus_register_t), + .curr_register = 0}; + + for (size_t i = 0; i < model_info.array_size; i++) + { + modbus.parseModbusToJson(model_info, false); + if (modbus.isAllRegistersRead(model_info)) + { + const char *sn1 = doc["SN1"]; + const char *sn2 = doc["SN2"]; + const char *sn3 = doc["SN3"]; + const char *sn4 = doc["SN4"]; + const char *sn5 = doc["SN5"]; + snprintf(modelBuffer, bufferSize, "%s%s%s%s%s", sn1, sn2, sn3, sn4, sn5); + return true; + } + delay(50); + } + return false; +} + +#endif \ No newline at end of file diff --git a/src/modbus/device/modbus_device.h b/src/modbus/device/modbus_device.h new file mode 100644 index 0000000..3d1db5d --- /dev/null +++ b/src/modbus/device/modbus_device.h @@ -0,0 +1,57 @@ +#ifndef MODBUS_DEVICE_H +#define MODBUS_DEVICE_H + +#include +#include +#include +#include +#include + +class MODBUS; // Forward declaration of the MODBUS class + +class ModbusDevice +{ +public: + ModbusDevice(long baudRate, uint32_t modbusAddr, protocol_type_t protocol) : _baudRate(baudRate), _modbusAddr(modbusAddr), _protocol(protocol) {} + + virtual const modbus_register_t *getLiveRegisters() const = 0; + virtual const modbus_register_t *getStaticRegisters() const = 0; + + virtual long getBaudRate() + { + return _baudRate; + } + + virtual long getModbusAddr() + { + return _modbusAddr; + } + + virtual protocol_type_t getProtocol() + { + return _protocol; + } + + virtual const char *getName() const = 0; // Pure virtual function + + virtual void init(SoftwareSerial &serial, ModbusMaster &mb) + { + serial.begin(getBaudRate(), SWSERIAL_8N1); + mb.begin(_modbusAddr, serial); + } + + virtual bool retrieveModel(MODBUS &modbus, char *modelBuffer, size_t bufferSize) + { + modelBuffer[0] = '\0'; + return false; + } + + virtual ~ModbusDevice() {} + +protected: + long _baudRate; + uint32_t _modbusAddr; + protocol_type_t _protocol; +}; + +#endif \ No newline at end of file diff --git a/src/modbus/device/must_pv_ph18.h b/src/modbus/device/must_pv_ph18.h new file mode 100644 index 0000000..53c2180 --- /dev/null +++ b/src/modbus/device/must_pv_ph18.h @@ -0,0 +1,121 @@ +#ifndef MODBUS_MUST_PV_PH18_H +#define MODBUS_MUST_PV_PH18_H +#include "modbus_device.h" +#include + +#define DEVICE_MODEL_HIGH "Device_Model_Hight" +#define DEVICE_MODEL_LOW "Device_Model_Low" + +class MustPV_PH18 : public ModbusDevice +{ +public: + MustPV_PH18() : ModbusDevice(_baudRate, _modbusAddr, _protocol) {} + + virtual const modbus_register_t *getLiveRegisters() const override + { + return registers_live; + } + + virtual const modbus_register_t *getStaticRegisters() const override + { + return registers_static; + } + + const char* getName() const override { + return _name; + } + + bool retrieveModel(MODBUS &modbus, char *modelBuffer, size_t bufferSize) override; + +private: + static const long _baudRate = 19200; + static const uint32_t _modbusAddr = 4; + static const protocol_type_t _protocol = MODBUS_MUST; + static const char* const _name; + + static const modbus_register_t registers_live[]; + static const modbus_register_t registers_static[]; + static const modbus_register_t registers_device_model[]; +}; + +const char* const MustPV_PH18::_name = "MUST_PV/PH18"; + +const modbus_register_t MustPV_PH18::registers_live[] = { + + {25201, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, DESCR_LIVE_INVERTER_OPERATION_MODE, {.bitfield = { + "Power On", + "Self Test", + "OffGrid", + "GridTie", + "ByPass", + "Stop", + "GridCharging", + }}}, + + {25205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_BATTERY_VOLTAGE}, + {25206, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "AC_out_Voltage"}, + {25207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "AC_in_Voltage"}, + {25208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Inverter_Bus_Voltage"}, + {25225, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "AC_out_Frequenz"}, + {25226, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "AC_in_Frequenz"}, + + {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Output_load_percent"}, + {15205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "PV_Input_Voltage"}, + {15208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "PV_Charging_Power"}, + {15207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "PV_Input_Current"}, + {25233, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Inverter_Bus_Temperature"}, + {25234, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Transformer_temperature"}, + {15209, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "MPPT1_Charger_Temperature"}, + + {25215, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_out_Watt"}, // W + {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_out_percent"}, //% + + {25274, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "Battery_Load"}, +}; + +const modbus_register_t MustPV_PH18::registers_static[] = { + + {10110, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "Battery_type", {.bitfield = { + "No choose", + "User defined", + "Lithium", + "Sealed Lead", + "AGM", + "GEL", + "Flooded", + }}}, + {10103, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Battery_float_voltage"}, +}; + +const modbus_register_t MustPV_PH18::registers_device_model[] = { + {20000, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, DEVICE_MODEL_HIGH}, + {20001, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DEVICE_MODEL_LOW}}; + +bool MustPV_PH18::retrieveModel(MODBUS &modbus, char *modelBuffer, size_t bufferSize) +{ + modelBuffer[0] = '\0'; // Clear the buffer + DynamicJsonDocument doc(100); + JsonObject jsonObj = doc.to(); // Create and get JsonObject + modbus_register_info_t model_info = { + .variant = &jsonObj, + .registers = registers_device_model, + .array_size = sizeof(registers_device_model) / sizeof(modbus_register_t), + .curr_register = 0}; + + for (size_t i = 0; i < model_info.array_size; i++) + { + modbus.parseModbusToJson(model_info, false); + if (modbus.isAllRegistersRead(model_info)) + { + const char *modelHigh = doc[DEVICE_MODEL_HIGH]; + int modelLow = doc[DEVICE_MODEL_LOW]; + snprintf(modelBuffer, bufferSize, "%s%d", modelHigh, modelLow); + return true; + } + delay(50); + } + + return false; +} + +#endif diff --git a/src/modbus/modbus.cpp b/src/modbus/modbus.cpp index c8ad0db..4c1da81 100644 --- a/src/modbus/modbus.cpp +++ b/src/modbus/modbus.cpp @@ -1,6 +1,7 @@ // #define isDEBUG -#include "ArduinoJson.h" #include "modbus.h" +#include "device/must_pv_ph18.h" +#include "device/deye.h" extern void writeLog(const char *format, ...); @@ -40,7 +41,6 @@ bool MODBUS::Init() return false; } this->my_serialIntf->setTimeout(2000); - this->my_serialIntf->begin(RS485_BAUDRATE, SWSERIAL_8N1); // Init in receive mode pinMode(dir_pin, OUTPUT); @@ -50,7 +50,13 @@ bool MODBUS::Init() mb.preTransmission(preTransmission); mb.postTransmission(postTransmission); - mb.begin(INVERTER_MODBUS_ADDR, *this->my_serialIntf); + return true; +} + +void MODBUS::prepareRegisters() +{ + const modbus_register_t *registers_live = device->getLiveRegisters(); + const modbus_register_t *registers_static = device->getStaticRegisters(); live_info = { .variant = &liveData, @@ -63,12 +69,11 @@ bool MODBUS::Init() .array_size = sizeof(registers_static) / sizeof(modbus_register_t), .curr_register = 0}; previousTime = millis(); - return true; } void MODBUS::loop() { - if (!device_found) + if (device == nullptr) { return; } @@ -373,43 +378,32 @@ bool MODBUS::isAllRegistersRead(modbus_register_info_t ®ister_info) //---------------------------------------------------------------------- // Private Functions //---------------------------------------------------------------------- -bool MODBUS::autoDetect() // function for autodetect the inverter type +protocol_type_t MODBUS::autoDetect() // function for autodetect the inverter type { + protocol_type_t protocol = NoD; + char modelName[20]; + writeLog("Try Autodetect Modbus device"); - device_found = false; - String modelName = retrieveModel(); - if (!modelName.isEmpty()) - { - writeLog(" Found Modbus device: %s", modelName); - staticData["Device_Model"] = modelName; - device_found = true; - } - return device_found; -} -String MODBUS::retrieveModel() -{ - String model = ""; - DynamicJsonDocument doc(256); - JsonObject jsonObj = doc.to(); // Create and get JsonObject - modbus_register_info_t model_info = { - .variant = &jsonObj, - .registers = registers_device_model, - .array_size = sizeof(registers_device_model) / sizeof(modbus_register_t), - .curr_register = 0}; + ModbusDevice *devices[] = {new MustPV_PH18(), new DEYE()}; - for (size_t i = 0; i < model_info.array_size * 2; i++) + for (size_t i = 0; i < sizeof(devices) / sizeof(devices[0]); ++i) { - parseModbusToJson(model_info, false); - if (isAllRegistersRead(model_info)) + devices[i]->init(*my_serialIntf, mb); + + writeLog("Try to use: %s protocol", devices[i]->getName()); + devices[i]->retrieveModel(*this, modelName, sizeof(modelName)); + if (strlen(modelName) != 0) { - const char *modelHigh = doc[DEVICE_MODEL_HIGH]; - int modelLow = doc[DEVICE_MODEL_LOW]; - model = String(modelHigh) + String(modelLow); - break; + writeLog(" Found Modbus device: %s", modelName); + staticData["Device_Model"] = modelName; + prepareRegisters(); + protocol = devices[i]->getProtocol(); + device = devices[i]; + return protocol; } - delay(50); - } + delete devices[i]; + } - return model; + return protocol; } diff --git a/src/modbus/modbus.h b/src/modbus/modbus.h index d96fc3d..4cb10cb 100644 --- a/src/modbus/modbus.h +++ b/src/modbus/modbus.h @@ -1,10 +1,12 @@ -#include "SoftwareSerial.h" #ifndef MODBUS_H #define MODBUS_H +#include "SoftwareSerial.h" #include #include -#include "modbus_registers.h" +#include "modbus_registers.h" +#include "device/modbus_device.h" + extern JsonObject deviceJson; extern JsonObject staticData; extern JsonObject liveData; @@ -63,22 +65,22 @@ class MODBUS * */ void callback(std::function func); - std::function requestCallback; + std::function requestCallback; bool readModbusRegisterToJson(const modbus_register_t *reg, JsonObject *variant); response_type_t parseModbusToJson(modbus_register_info_t ®ister_info, bool skip_reg_on_error = true); bool isAllRegistersRead(modbus_register_info_t ®ister_info); - bool autoDetect(); + protocol_type_t autoDetect(); /** * @brief Sends a complete packet with the specified command * @details sends the command over the specified serial connection */ String requestData(String command); -private: - bool device_found = false; +private: unsigned long previousTime = 0; unsigned long cmdDelayTime = 100; + byte requestCounter = 0; long long int connectionCounter = 0; @@ -87,6 +89,7 @@ class MODBUS static void preTransmission(); static void postTransmission(); + void prepareRegisters(); String toBinary(uint16_t input); bool decodeDiematicDecimal(uint16_t int_input, int8_t decimals, float *value_ptr); bool getModbusResultMsg(uint8_t result); @@ -100,15 +103,13 @@ class MODBUS * @brief get the crc from a string */ byte getCHK(String data); - - String retrieveModel(); - - /** + + /** * @brief Serial interface used for communication * @details This is set in the constructor */ SoftwareSerial *my_serialIntf; - + ModbusDevice* device = nullptr; ModbusMaster mb; }; diff --git a/src/modbus/modbus_registers.h b/src/modbus/modbus_registers.h index 3be4d62..d4fef11 100644 --- a/src/modbus/modbus_registers.h +++ b/src/modbus/modbus_registers.h @@ -40,61 +40,5 @@ typedef struct const char *name; optional_param_t optional_param; } modbus_register_t; - -const modbus_register_t registers_live[] = { - - {25201, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "Inverter_Operation_Mode", {.bitfield = { - "Power On", - "Self Test", - "OffGrid", - "GridTie", - "ByPass", - "Stop", - "GridCharging", - }}}, - - {25205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Battery_Voltage"}, - {25206, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "AC_out_Voltage"}, - {25207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "AC_in_Voltage"}, - {25208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Inverter_Bus_Voltage"}, - {25225, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "AC_out_Frequenz"}, - {25226, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "AC_in_Frequenz"}, - - {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Output_load_percent"}, - {15205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "PV_Input_Voltage"}, - {15208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "PV_Charging_Power"}, - {15207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "PV_Input_Current"}, - {25233, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Inverter_Bus_Temperature"}, - {25234, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Transformer_temperature"}, - {15209, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "MPPT1_Charger_Temperature"}, - - {25215, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_out_Watt"}, //W - {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_out_percent"}, //% - - {25274, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "Battery_Load"}, -}; - -const modbus_register_t registers_static[] = { - - {10110, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "Battery_type", {.bitfield = { - "No choose", - "User defined", - "Lithium", - "Sealed Lead", - "AGM", - "GEL", - "Flooded", - }}}, - {10103, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Battery_float_voltage"}, -}; - - -#define DEVICE_MODEL_HIGH "Device_Model_Hight" -#define DEVICE_MODEL_LOW "Device_Model_Low" - -const modbus_register_t registers_device_model[] = { - {20000, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "Device_Model_Hight"}, - {20001, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Device_Model_Low"} -}; - + #endif // SRC_MODBUS_REGISTERS_H_ \ No newline at end of file From 46705f0a07394fa33ceb0e12e0e2b88d7dd71c62 Mon Sep 17 00:00:00 2001 From: Artem Kovalenko Date: Fri, 4 Oct 2024 19:09:39 +0300 Subject: [PATCH 02/13] add more registers types. Add more registers for deye --- src/PI_Serial/QPIGS.h | 14 ++--- src/modbus/device/deye.h | 40 +++++++------- src/modbus/device/must_pv_ph18.h | 89 ++++++++++++++++---------------- src/modbus/modbus.cpp | 33 ++++++++---- src/modbus/modbus.h | 15 +++--- src/modbus/modbus_registers.h | 25 ++++----- 6 files changed, 118 insertions(+), 98 deletions(-) diff --git a/src/PI_Serial/QPIGS.h b/src/PI_Serial/QPIGS.h index 18905b7..50a63de 100644 --- a/src/PI_Serial/QPIGS.h +++ b/src/PI_Serial/QPIGS.h @@ -13,8 +13,8 @@ static const char *const qpigsList[][24] = { "Battery_Charge_Current", // KKK "Battery_Percent", // OOO "Inverter_Bus_Temperature", // TTTT - "PV_Input_Current", // EE.E - "PV_Input_Voltage", // UUU.U + DESCR_LIVE_PV_INPUT_CURRENT, // EE.E + DESCR_LIVE_PV_INPUT_VOLTAGE, // UUU.U "Battery_SCC_Volt", // WW.WW "Battery_Discharge_Current", // PPPP "Status_Flag", // b0-b7 @@ -63,8 +63,8 @@ static const char *const qallList[] = { "Battery_Percent", // III "Battery_Charge_Current", // JJJ "Battery_Discharge_Current", // KKK - "PV_Input_Voltage", // LLL - "PV_Input_Current", // MM.M + DESCR_LIVE_PV_INPUT_VOLTAGE, // LLL + DESCR_LIVE_PV_INPUT_CURRENT, // MM.M "PV_Charging_Power", // NNNN "PV_generation_day", // OOOOOO "PV_generation_sum", // PPPPPP @@ -163,7 +163,7 @@ bool PI_Serial::PIXX_QPIGS() } // make some things pretty liveData["Battery_Load"] = (liveData["Battery_Charge_Current"].as() - liveData["Battery_Discharge_Current"].as()); - liveData["PV_Input_Power"] = (liveData["PV_Input_Voltage"].as() * liveData["PV_Input_Current"].as()); + liveData[DESCR_LIVE_PV_INPUT_POWER] = (liveData[DESCR_LIVE_PV_INPUT_VOLTAGE].as() * liveData[DESCR_LIVE_PV_INPUT_CURRENT].as()); } if (get.raw.qall.length() > 10 /*get.raw.qall != "NAK" || get.raw.qall != "ERCRC" || get.raw.qall != ""*/) @@ -248,9 +248,9 @@ bool PI_Serial::PIXX_QPIGS() } // make some things pretty - liveData["PV_Input_Voltage"] = (liveData["PV1_Input_Voltage"].as() + liveData["PV2_Input_Voltage"].as()); + liveData[DESCR_LIVE_PV_INPUT_VOLTAGE] = (liveData["PV1_Input_Voltage"].as() + liveData["PV2_Input_Voltage"].as()); liveData["PV_Charging_Power"] = (liveData["PV1_Input_Power"].as() + liveData["PV2_Input_Power"].as()); - liveData["PV_Input_Current"] = (int)((liveData["PV_Charging_Power"].as() / (liveData["PV_Input_Voltage"].as()+0.5)) * 100) / 100.0; + liveData["PV_Input_Current"] = (int)((liveData["PV_Charging_Power"].as() / (liveData[DESCR_LIVE_PV_INPUT_VOLTAGE].as()+0.5)) * 100) / 100.0; liveData["Battery_Load"] = (liveData["Battery_Charge_Current"].as() - liveData["Battery_Discharge_Current"].as()); } return true; diff --git a/src/modbus/device/deye.h b/src/modbus/device/deye.h index 24bb1f5..4eb678d 100644 --- a/src/modbus/device/deye.h +++ b/src/modbus/device/deye.h @@ -38,27 +38,31 @@ class DEYE : public ModbusDevice const char *const DEYE::_name = "DEYE"; -const modbus_register_t DEYE::registers_live[] = { - {175, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_OUTPUT_POWER}, - {178, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_AC_OUTPUT_POWER}, - - {182, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_BATTERY_TEMPERATURE}, - - {183, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, DESCR_LIVE_BATTERY_VOLTAGE}, - {184, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_BATTERY_PERCENT}, +const modbus_register_t DEYE::registers_live[] = { + {59, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, DESCR_LIVE_INVERTER_OPERATION_MODE, 0, {.bitfield = { + "Standby", + "Self Test", + "Normal", + "Alerts", + "Fault", + }}}, + + {109, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_VOLTAGE, 0}, + {110, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_CURRENT, 0}, + {186, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_PV_INPUT_POWER, 0}, + + {175, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_OUTPUT_POWER, 0}, + {178, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_AC_OUTPUT_POWER, 0}, + + {90, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_INVERTER_TEMPERATURE, -100}, + {182, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_BATTERY_TEMPERATURE, -100}, + + {183, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, DESCR_LIVE_BATTERY_VOLTAGE, 0}, + {184, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_BATTERY_PERCENT, 0}, }; const modbus_register_t DEYE::registers_static[] = { - - {10110, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "Battery_type", {.bitfield = { - "No choose", - "User defined", - "Lithium", - "Sealed Lead", - "AGM", - "GEL", - "Flooded", - }}}, + {16, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U32_ONE_DECIMAL, DESCR_STAT_AC_OUT_RATING_ACTIVE_POWER, 0}, }; const modbus_register_t DEYE::registers_device_serial[] = { diff --git a/src/modbus/device/must_pv_ph18.h b/src/modbus/device/must_pv_ph18.h index 53c2180..1711bf1 100644 --- a/src/modbus/device/must_pv_ph18.h +++ b/src/modbus/device/must_pv_ph18.h @@ -21,7 +21,8 @@ class MustPV_PH18 : public ModbusDevice return registers_static; } - const char* getName() const override { + const char *getName() const override + { return _name; } @@ -31,65 +32,65 @@ class MustPV_PH18 : public ModbusDevice static const long _baudRate = 19200; static const uint32_t _modbusAddr = 4; static const protocol_type_t _protocol = MODBUS_MUST; - static const char* const _name; + static const char *const _name; static const modbus_register_t registers_live[]; static const modbus_register_t registers_static[]; static const modbus_register_t registers_device_model[]; }; -const char* const MustPV_PH18::_name = "MUST_PV/PH18"; +const char *const MustPV_PH18::_name = "MUST_PV/PH18"; const modbus_register_t MustPV_PH18::registers_live[] = { - {25201, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, DESCR_LIVE_INVERTER_OPERATION_MODE, {.bitfield = { - "Power On", - "Self Test", - "OffGrid", - "GridTie", - "ByPass", - "Stop", - "GridCharging", - }}}, - - {25205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_BATTERY_VOLTAGE}, - {25206, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "AC_out_Voltage"}, - {25207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "AC_in_Voltage"}, - {25208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Inverter_Bus_Voltage"}, - {25225, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "AC_out_Frequenz"}, - {25226, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "AC_in_Frequenz"}, - - {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Output_load_percent"}, - {15205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "PV_Input_Voltage"}, - {15208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "PV_Charging_Power"}, - {15207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "PV_Input_Current"}, - {25233, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Inverter_Bus_Temperature"}, - {25234, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Transformer_temperature"}, - {15209, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "MPPT1_Charger_Temperature"}, - - {25215, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_out_Watt"}, // W - {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_out_percent"}, //% - - {25274, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "Battery_Load"}, + {25201, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, DESCR_LIVE_INVERTER_OPERATION_MODE, 0, {.bitfield = { + "Power On", + "Self Test", + "OffGrid", + "GridTie", + "ByPass", + "Stop", + "GridCharging", + }}}, + + {25205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_BATTERY_VOLTAGE, 0}, + {25206, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "AC_out_Voltage", 0}, + {25207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "AC_in_Voltage", 0}, + {25208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Inverter_Bus_Voltage", 0}, + {25225, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "AC_out_Frequenz", 0}, + {25226, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "AC_in_Frequenz", 0}, + + {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Output_load_percent", 0}, + {15205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_VOLTAGE, 0}, + {15208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "PV_Charging_Power", 0}, + {15207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_CURRENT, 0}, + {25233, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Inverter_Bus_Temperature", 0}, + {25234, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Transformer_temperature", 0}, + {15209, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "MPPT1_Charger_Temperature", 0}, + + {25215, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_out_Watt", 0}, // W + {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_out_percent", 0}, //% + + {25274, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "Battery_Load", 0}, }; const modbus_register_t MustPV_PH18::registers_static[] = { - {10110, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "Battery_type", {.bitfield = { - "No choose", - "User defined", - "Lithium", - "Sealed Lead", - "AGM", - "GEL", - "Flooded", - }}}, - {10103, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Battery_float_voltage"}, + {10110, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "Battery_type", 0, {.bitfield = { + "No choose", + "User defined", + "Lithium", + "Sealed Lead", + "AGM", + "GEL", + "Flooded", + }}}, + {10103, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Battery_float_voltage", 0}, }; const modbus_register_t MustPV_PH18::registers_device_model[] = { - {20000, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, DEVICE_MODEL_HIGH}, - {20001, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DEVICE_MODEL_LOW}}; + {20000, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, DEVICE_MODEL_HIGH, 0}, + {20001, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DEVICE_MODEL_LOW, 0}}; bool MustPV_PH18::retrieveModel(MODBUS &modbus, char *modelBuffer, size_t bufferSize) { diff --git a/src/modbus/modbus.cpp b/src/modbus/modbus.cpp index 4c1da81..7893d72 100644 --- a/src/modbus/modbus.cpp +++ b/src/modbus/modbus.cpp @@ -161,7 +161,7 @@ bool MODBUS::getModbusResultMsg(uint8_t result) return false; } -bool MODBUS::getModbusValue(uint16_t register_id, modbus_entity_t modbus_entity, uint16_t *value_ptr) +bool MODBUS::getModbusValue(uint16_t register_id, modbus_entity_t modbus_entity, uint16_t *value_ptr, uint16_t readBytes) { // writeLog("Requesting data"); for (uint8_t i = 0; i < MODBUS_RETRIES; i++) @@ -172,7 +172,7 @@ bool MODBUS::getModbusValue(uint16_t register_id, modbus_entity_t modbus_entity, } if (modbus_entity == MODBUS_TYPE_HOLDING) { - uint8_t result = mb.readHoldingRegisters(register_id, 1); + uint8_t result = mb.readHoldingRegisters(register_id, readBytes); bool is_received = getModbusResultMsg(result); if (is_received) { @@ -235,7 +235,15 @@ bool MODBUS::readModbusRegisterToJson(const modbus_register_t *reg, JsonObject * uint16_t raw_value = 0; float final_value; - if (getModbusValue(reg->id, reg->modbus_entity, &raw_value)) + + uint16_t readBytes = 1; + + if (reg->type == REGISTER_TYPE_U32 || reg->type == REGISTER_TYPE_U32_ONE_DECIMAL) + { + readBytes = 2; + } + + if (getModbusValue(reg->id, reg->modbus_entity, &raw_value, readBytes)) { writeLog("Raw value: %s=%#06x\n", reg->name, raw_value); @@ -243,18 +251,25 @@ bool MODBUS::readModbusRegisterToJson(const modbus_register_t *reg, JsonObject * { case REGISTER_TYPE_U16: // writeLog("Value: %u", raw_value); - (*variant)[reg->name] = raw_value; + (*variant)[reg->name] = raw_value + reg->offset; break; case REGISTER_TYPE_INT16: // writeLog("Value: %u", raw_value); - (*variant)[reg->name] = static_cast(raw_value); + (*variant)[reg->name] = static_cast(raw_value) + reg->offset; + break; + case REGISTER_TYPE_U32: + // writeLog("Value: %u", raw_value); + (*variant)[reg->name] = (raw_value + (mb.getResponseBuffer(1) << 16)) + reg->offset; + break; + case REGISTER_TYPE_U32_ONE_DECIMAL: + // writeLog("Value: %u", raw_value); + (*variant)[reg->name] = (raw_value + (mb.getResponseBuffer(1) << 16)) * 0.1 + reg->offset; break; - case REGISTER_TYPE_DIEMATIC_ONE_DECIMAL: if (decodeDiematicDecimal(raw_value, 1, &final_value)) { // writeLog("Raw value: %#06x, floatValue: %f",raw_value, final_value); - (*variant)[reg->name] = (int)(final_value * 100 + 0.5) / 100.0; + (*variant)[reg->name] = ((int)(final_value * 100 + 0.5) / 100.0) + reg->offset; } else { @@ -266,7 +281,7 @@ bool MODBUS::readModbusRegisterToJson(const modbus_register_t *reg, JsonObject * if (decodeDiematicDecimal(raw_value, 2, &final_value)) { // writeLog("Value: %.1f", final_value); - (*variant)[reg->name] = (int)(final_value * 1000 + 0.5) / 1000.0; + (*variant)[reg->name] = ((int)(final_value * 1000 + 0.5) / 1000.0) + reg->offset; } else { @@ -403,7 +418,7 @@ protocol_type_t MODBUS::autoDetect() // function for autodetect the inverter typ return protocol; } delete devices[i]; - } + } return protocol; } diff --git a/src/modbus/modbus.h b/src/modbus/modbus.h index 4cb10cb..3c0ea84 100644 --- a/src/modbus/modbus.h +++ b/src/modbus/modbus.h @@ -4,7 +4,7 @@ #include "SoftwareSerial.h" #include #include -#include "modbus_registers.h" +#include "modbus_registers.h" #include "device/modbus_device.h" extern JsonObject deviceJson; @@ -65,7 +65,7 @@ class MODBUS * */ void callback(std::function func); - std::function requestCallback; + std::function requestCallback; bool readModbusRegisterToJson(const modbus_register_t *reg, JsonObject *variant); response_type_t parseModbusToJson(modbus_register_info_t ®ister_info, bool skip_reg_on_error = true); bool isAllRegistersRead(modbus_register_info_t ®ister_info); @@ -76,11 +76,10 @@ class MODBUS */ String requestData(String command); -private: +private: unsigned long previousTime = 0; unsigned long cmdDelayTime = 100; - byte requestCounter = 0; long long int connectionCounter = 0; @@ -93,7 +92,7 @@ class MODBUS String toBinary(uint16_t input); bool decodeDiematicDecimal(uint16_t int_input, int8_t decimals, float *value_ptr); bool getModbusResultMsg(uint8_t result); - bool getModbusValue(uint16_t register_id, modbus_entity_t modbus_entity, uint16_t *value_ptr); + bool getModbusValue(uint16_t register_id, modbus_entity_t modbus_entity, uint16_t *value_ptr, uint16_t readBytes = 1); /** * @brief get the crc from a string */ @@ -103,13 +102,13 @@ class MODBUS * @brief get the crc from a string */ byte getCHK(String data); - - /** + + /** * @brief Serial interface used for communication * @details This is set in the constructor */ SoftwareSerial *my_serialIntf; - ModbusDevice* device = nullptr; + ModbusDevice *device = nullptr; ModbusMaster mb; }; diff --git a/src/modbus/modbus_registers.h b/src/modbus/modbus_registers.h index d4fef11..ca8d49e 100644 --- a/src/modbus/modbus_registers.h +++ b/src/modbus/modbus_registers.h @@ -4,7 +4,7 @@ typedef enum { - MODBUS_TYPE_HOLDING = 0x00, /*!< Modbus Holding register. */ + MODBUS_TYPE_HOLDING = 0x00, /*!< Modbus Holding register. */ // MODBUS_TYPE_INPUT, /*!< Modbus Input register. */ // MODBUS_TYPE_COIL, /*!< Modbus Coils. */ // MODBUS_TYPE_DISCRETE, /*!< Modbus Discrete bits. */ @@ -15,16 +15,16 @@ typedef enum typedef enum { // REGISTER_TYPE_U8 = 0x00, /*!< Unsigned 8 */ - REGISTER_TYPE_U16 = 0x01, /*!< Unsigned 16 */ - REGISTER_TYPE_INT16 = 0x02, /*!< Signed 16 */ - // REGISTER_TYPE_U32 = 0x02, /*!< Unsigned 32 */ - // REGISTER_TYPE_FLOAT = 0x03, /*!< Float type */ - REGISTER_TYPE_ASCII = 0x04, /*!< ASCII type */ - REGISTER_TYPE_DIEMATIC_ONE_DECIMAL = 0x05, - REGISTER_TYPE_DIEMATIC_TWO_DECIMAL = 0x06, - REGISTER_TYPE_BITFIELD = 0x07, - REGISTER_TYPE_DEBUG = 0x08, - REGISTER_TYPE_CUSTOM_VAL_NAME = 0x09, + REGISTER_TYPE_U16, /*!< Unsigned 16 */ + REGISTER_TYPE_INT16, /*!< Signed 16 */ + REGISTER_TYPE_U32, /*!< Unsigned 32 */ + REGISTER_TYPE_U32_ONE_DECIMAL, /*!< Unsigned 32 multiply 0.1*/ + REGISTER_TYPE_ASCII, /*!< ASCII type */ + REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, + REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, + REGISTER_TYPE_BITFIELD, + REGISTER_TYPE_DEBUG, + REGISTER_TYPE_CUSTOM_VAL_NAME, } register_type_t; typedef union @@ -38,7 +38,8 @@ typedef struct modbus_entity_t modbus_entity; /*!< Type of modbus parameter */ register_type_t type; /*!< Float, U8, U16, U32, ASCII, etc. */ const char *name; + int16_t offset; optional_param_t optional_param; } modbus_register_t; - + #endif // SRC_MODBUS_REGISTERS_H_ \ No newline at end of file From 362a7ab9745347cd594b3d298dab4d5ee4f87c90 Mon Sep 17 00:00:00 2001 From: Artem Kovalenko Date: Fri, 4 Oct 2024 21:35:15 +0300 Subject: [PATCH 03/13] add custom pins modbus control for dongle --- src/modbus/modbus.cpp | 30 +++++++++++++++++++++++++++--- src/modbus/modbus.h | 3 +++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/modbus/modbus.cpp b/src/modbus/modbus.cpp index 7893d72..ca18853 100644 --- a/src/modbus/modbus.cpp +++ b/src/modbus/modbus.cpp @@ -24,12 +24,28 @@ MODBUS::MODBUS(SoftwareSerial *port) void MODBUS::preTransmission() { - digitalWrite(dir_pin, 1); + if (strcmp(HWBOARD, "esp12e") == 0) + { + digitalWrite(MAX485_DONGLE_RE_NEG_PIN, 1); + digitalWrite(MAX485_DONGLE_DE_PIN, 1); + } + else + { + digitalWrite(dir_pin, 1); + } } void MODBUS::postTransmission() { - digitalWrite(dir_pin, 0); + if (strcmp(HWBOARD, "esp12e") == 0) + { + digitalWrite(MAX485_DONGLE_RE_NEG_PIN, 0); + digitalWrite(MAX485_DONGLE_DE_PIN, 0); + } + else + { + digitalWrite(dir_pin, 0); + } } bool MODBUS::Init() @@ -43,7 +59,15 @@ bool MODBUS::Init() this->my_serialIntf->setTimeout(2000); // Init in receive mode - pinMode(dir_pin, OUTPUT); + if (strcmp(HWBOARD, "esp12e") == 0) + { + pinMode(MAX485_DONGLE_RE_NEG_PIN, OUTPUT); + pinMode(MAX485_DONGLE_DE_PIN, OUTPUT); + } + else + { + pinMode(dir_pin, OUTPUT); + } this->postTransmission(); // Callbacks allow us to configure the RS485 transceiver correctly diff --git a/src/modbus/modbus.h b/src/modbus/modbus.h index 3c0ea84..b801d4e 100644 --- a/src/modbus/modbus.h +++ b/src/modbus/modbus.h @@ -14,6 +14,9 @@ extern JsonObject liveData; #define RS485_DIR_PIN 14 // D5 #define RS485_ESP01_DIR_PIN 0 +#define MAX485_DONGLE_DE_PIN 5 // D1, DE pin on the TTL to RS485 converter +#define MAX485_DONGLE_RE_NEG_PIN 4 + #define RS485_BAUDRATE 19200 #define INVERTER_MODBUS_ADDR 4 From 0261bd4d998de96c54f82832e8c713790fa36422 Mon Sep 17 00:00:00 2001 From: Artem Kovalenko Date: Mon, 7 Oct 2024 12:35:03 +0300 Subject: [PATCH 04/13] refactor classes --- src/PI_Serial/PI_Serial.cpp | 2 +- src/PI_Serial/PI_Serial.h | 4 +- src/modbus/device/deye.cpp | 96 ++++++++ src/modbus/device/deye.h | 103 ++------- src/modbus/device/modbus_device.cpp | 35 +++ src/modbus/device/modbus_device.h | 48 ++-- src/modbus/device/must_pv_ph18.cpp | 110 +++++++++ src/modbus/device/must_pv_ph18.h | 112 +-------- src/modbus/modbus.cpp | 343 +--------------------------- src/modbus/modbus.h | 57 +---- src/modbus/modbus_com.cpp | 335 +++++++++++++++++++++++++++ src/modbus/modbus_com.h | 44 ++++ src/modbus/modbus_registers.h | 10 + 13 files changed, 695 insertions(+), 604 deletions(-) create mode 100644 src/modbus/device/deye.cpp create mode 100644 src/modbus/device/modbus_device.cpp create mode 100644 src/modbus/device/must_pv_ph18.cpp create mode 100644 src/modbus/modbus_com.cpp create mode 100644 src/modbus/modbus_com.h diff --git a/src/PI_Serial/PI_Serial.cpp b/src/PI_Serial/PI_Serial.cpp index 9de01ba..629b85e 100644 --- a/src/PI_Serial/PI_Serial.cpp +++ b/src/PI_Serial/PI_Serial.cpp @@ -386,5 +386,5 @@ char *PI_Serial::getModeDesc(char mode) // get the char from QMOD and make reada bool PI_Serial::isModbus() { - return protocol == MODBUS_MUST; + return protocol == MODBUS_MUST || protocol == MODBUS_DEYE; } diff --git a/src/PI_Serial/PI_Serial.h b/src/PI_Serial/PI_Serial.h index 7b9d6d1..2dd0c23 100644 --- a/src/PI_Serial/PI_Serial.h +++ b/src/PI_Serial/PI_Serial.h @@ -1,14 +1,16 @@ #ifndef PI_SERIAL_H #define PI_SERIAL_H - #include "SoftwareSerial.h" #include #include + extern JsonObject deviceJson; extern JsonObject staticData; extern JsonObject liveData; +class MODBUS; + class PI_Serial { public: diff --git a/src/modbus/device/deye.cpp b/src/modbus/device/deye.cpp new file mode 100644 index 0000000..f260711 --- /dev/null +++ b/src/modbus/device/deye.cpp @@ -0,0 +1,96 @@ +#include "deye.h" + +const long Deye::_baudRate = 9600; +const uint32_t Deye::_modbusAddr = 1; +const protocol_type_t Deye::_protocol = MODBUS_DEYE; + +const char *const Deye::_name = "DEYE"; + +const modbus_register_t *Deye::getLiveRegisters() const +{ + return registers_live; +} + +const modbus_register_t *Deye::getStaticRegisters() const +{ + return registers_static; +} + +const char *Deye::getName() const +{ + return _name; +} + +const modbus_register_t Deye::registers_live[] = { + {59, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, DESCR_LIVE_INVERTER_OPERATION_MODE, 0, {.bitfield = { + "Standby", + "Self Test", + "Normal", + "Alerts", + "Fault", + }}}, + + {109, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_VOLTAGE, 0}, + {110, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_CURRENT, 0}, + {186, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_PV_INPUT_POWER, 0}, + + {175, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_OUTPUT_POWER, 0}, + {178, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_AC_OUTPUT_POWER, 0}, + + {90, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_INVERTER_TEMPERATURE, -100}, + {182, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_BATTERY_TEMPERATURE, -100}, + + {183, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, DESCR_LIVE_BATTERY_VOLTAGE, 0}, + {184, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_BATTERY_PERCENT, 0}, +}; + +const modbus_register_t Deye::registers_static[] = { + {16, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U32_ONE_DECIMAL, DESCR_STAT_AC_OUT_RATING_ACTIVE_POWER, 0}, +}; + +const modbus_register_t Deye::registers_device_serial[] = { + {3, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN1"}, + {4, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN2"}, + {5, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN3"}, + {6, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN4"}, + {7, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN5"}}; + +bool Deye::retrieveModel(MODBUS_COM &mCom, char *modelBuffer, size_t bufferSize) +{ + modelBuffer[0] = '\0'; // Clear the buffer + DynamicJsonDocument doc(100); + JsonObject jsonObj = doc.to(); // Create and get JsonObject + modbus_register_info_t model_info = { + .variant = &jsonObj, + .registers = registers_device_serial, + .array_size = sizeof(registers_device_serial) / sizeof(modbus_register_t), + .curr_register = 0}; + + for (size_t i = 0; i < model_info.array_size; i++) + { + mCom.parseModbusToJson(model_info, false); + if (mCom.isAllRegistersRead(model_info)) + { + const char *sn1 = doc["SN1"]; + const char *sn2 = doc["SN2"]; + const char *sn3 = doc["SN3"]; + const char *sn4 = doc["SN4"]; + const char *sn5 = doc["SN5"]; + snprintf(modelBuffer, bufferSize, "%s%s%s%s%s", sn1, sn2, sn3, sn4, sn5); + return true; + } + delay(50); + } + return false; +} + +// Define the size calculation after the arrays are defined +size_t Deye::getLiveRegistersCount() const +{ + return sizeof(registers_live) / sizeof(modbus_register_t); +} + +size_t Deye::getStaticRegistersCount() const +{ + return sizeof(registers_static) / sizeof(modbus_register_t); +} \ No newline at end of file diff --git a/src/modbus/device/deye.h b/src/modbus/device/deye.h index 4eb678d..cbb1314 100644 --- a/src/modbus/device/deye.h +++ b/src/modbus/device/deye.h @@ -1,34 +1,24 @@ #ifndef MODBUS_DEYE_H #define MODBUS_DEYE_H -#include "modbus_device.h" -#include -class DEYE : public ModbusDevice +#include "modbus_device.h" + +class Deye : public ModbusDevice { public: - DEYE() : ModbusDevice(_baudRate, _modbusAddr, _protocol) {} + Deye() : ModbusDevice(_baudRate, _modbusAddr, _protocol) {} - virtual const modbus_register_t *getLiveRegisters() const override - { - return registers_live; - } - - virtual const modbus_register_t *getStaticRegisters() const override - { - return registers_static; - } - - const char *getName() const override - { - return _name; - } - - bool retrieveModel(MODBUS &modbus, char *modelBuffer, size_t bufferSize) override; + virtual const modbus_register_t *getLiveRegisters() const override; + virtual const modbus_register_t *getStaticRegisters() const override; + const char *getName() const override; + bool retrieveModel(MODBUS_COM &mCom, char *modelBuffer, size_t bufferSize) override; + size_t getLiveRegistersCount() const override; + size_t getStaticRegistersCount() const override; private: - static const long _baudRate = 9600; - static const uint32_t _modbusAddr = 1; - static const protocol_type_t _protocol = MODBUS_DEYE; + static const long _baudRate; + static const uint32_t _modbusAddr; + static const protocol_type_t _protocol; static const char *const _name; static const modbus_register_t registers_live[]; @@ -36,69 +26,4 @@ class DEYE : public ModbusDevice static const modbus_register_t registers_device_serial[]; }; -const char *const DEYE::_name = "DEYE"; - -const modbus_register_t DEYE::registers_live[] = { - {59, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, DESCR_LIVE_INVERTER_OPERATION_MODE, 0, {.bitfield = { - "Standby", - "Self Test", - "Normal", - "Alerts", - "Fault", - }}}, - - {109, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_VOLTAGE, 0}, - {110, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_CURRENT, 0}, - {186, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_PV_INPUT_POWER, 0}, - - {175, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_OUTPUT_POWER, 0}, - {178, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_AC_OUTPUT_POWER, 0}, - - {90, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_INVERTER_TEMPERATURE, -100}, - {182, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_BATTERY_TEMPERATURE, -100}, - - {183, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, DESCR_LIVE_BATTERY_VOLTAGE, 0}, - {184, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_BATTERY_PERCENT, 0}, -}; - -const modbus_register_t DEYE::registers_static[] = { - {16, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U32_ONE_DECIMAL, DESCR_STAT_AC_OUT_RATING_ACTIVE_POWER, 0}, -}; - -const modbus_register_t DEYE::registers_device_serial[] = { - {3, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN1"}, - {4, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN2"}, - {5, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN3"}, - {6, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN4"}, - {7, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN5"}}; - -bool DEYE::retrieveModel(MODBUS &modbus, char *modelBuffer, size_t bufferSize) -{ - modelBuffer[0] = '\0'; // Clear the buffer - DynamicJsonDocument doc(100); - JsonObject jsonObj = doc.to(); // Create and get JsonObject - modbus_register_info_t model_info = { - .variant = &jsonObj, - .registers = registers_device_serial, - .array_size = sizeof(registers_device_serial) / sizeof(modbus_register_t), - .curr_register = 0}; - - for (size_t i = 0; i < model_info.array_size; i++) - { - modbus.parseModbusToJson(model_info, false); - if (modbus.isAllRegistersRead(model_info)) - { - const char *sn1 = doc["SN1"]; - const char *sn2 = doc["SN2"]; - const char *sn3 = doc["SN3"]; - const char *sn4 = doc["SN4"]; - const char *sn5 = doc["SN5"]; - snprintf(modelBuffer, bufferSize, "%s%s%s%s%s", sn1, sn2, sn3, sn4, sn5); - return true; - } - delay(50); - } - return false; -} - -#endif \ No newline at end of file +#endif diff --git a/src/modbus/device/modbus_device.cpp b/src/modbus/device/modbus_device.cpp new file mode 100644 index 0000000..9de7815 --- /dev/null +++ b/src/modbus/device/modbus_device.cpp @@ -0,0 +1,35 @@ +#include "modbus_device.h" +// Constructor definition +ModbusDevice::ModbusDevice(long baudRate, uint32_t modbusAddr, protocol_type_t protocol) + : _baudRate(baudRate), _modbusAddr(modbusAddr), _protocol(protocol) {} + +// Method implementations for non-virtual functions +long ModbusDevice::getBaudRate() +{ + return _baudRate; +} + +long ModbusDevice::getModbusAddr() +{ + return _modbusAddr; +} + +protocol_type_t ModbusDevice::getProtocol() +{ + return _protocol; +} + +void ModbusDevice::init(SoftwareSerial &serial, MODBUS_COM &mCom) +{ + serial.begin(getBaudRate(), SWSERIAL_8N1); + mCom.getModbusMaster().begin(_modbusAddr, serial); +} + +bool ModbusDevice::retrieveModel(MODBUS_COM &mCom, char *modelBuffer, size_t bufferSize) +{ + modelBuffer[0] = '\0'; + return false; +} + +// Destructor definition +ModbusDevice::~ModbusDevice() {} \ No newline at end of file diff --git a/src/modbus/device/modbus_device.h b/src/modbus/device/modbus_device.h index 3d1db5d..7ec6a51 100644 --- a/src/modbus/device/modbus_device.h +++ b/src/modbus/device/modbus_device.h @@ -1,52 +1,37 @@ #ifndef MODBUS_DEVICE_H #define MODBUS_DEVICE_H -#include -#include +#include #include #include -#include - -class MODBUS; // Forward declaration of the MODBUS class +#include + class ModbusDevice { public: - ModbusDevice(long baudRate, uint32_t modbusAddr, protocol_type_t protocol) : _baudRate(baudRate), _modbusAddr(modbusAddr), _protocol(protocol) {} + ModbusDevice(long baudRate, uint32_t modbusAddr, protocol_type_t protocol); + // Pure virtual functions - these need to be implemented in derived classes virtual const modbus_register_t *getLiveRegisters() const = 0; virtual const modbus_register_t *getStaticRegisters() const = 0; - virtual long getBaudRate() - { - return _baudRate; - } - - virtual long getModbusAddr() - { - return _modbusAddr; - } + virtual long getBaudRate(); + virtual long getModbusAddr(); + virtual protocol_type_t getProtocol(); - virtual protocol_type_t getProtocol() - { - return _protocol; - } + // Pure virtual function for getting the name + virtual const char *getName() const = 0; - virtual const char *getName() const = 0; // Pure virtual function + virtual void init(SoftwareSerial &serial, MODBUS_COM &mCom); - virtual void init(SoftwareSerial &serial, ModbusMaster &mb) - { - serial.begin(getBaudRate(), SWSERIAL_8N1); - mb.begin(_modbusAddr, serial); - } + virtual bool retrieveModel(MODBUS_COM &mCom, char *modelBuffer, size_t bufferSize); - virtual bool retrieveModel(MODBUS &modbus, char *modelBuffer, size_t bufferSize) - { - modelBuffer[0] = '\0'; - return false; - } + // Pure virtual methods to count registers + virtual size_t getLiveRegistersCount() const = 0; + virtual size_t getStaticRegistersCount() const = 0; - virtual ~ModbusDevice() {} + virtual ~ModbusDevice(); protected: long _baudRate; @@ -54,4 +39,5 @@ class ModbusDevice protocol_type_t _protocol; }; + #endif \ No newline at end of file diff --git a/src/modbus/device/must_pv_ph18.cpp b/src/modbus/device/must_pv_ph18.cpp new file mode 100644 index 0000000..aa220a2 --- /dev/null +++ b/src/modbus/device/must_pv_ph18.cpp @@ -0,0 +1,110 @@ +#include "must_pv_ph18.h" + +const long MustPV_PH18::_baudRate = 19200; +const uint32_t MustPV_PH18::_modbusAddr = 4; +const protocol_type_t MustPV_PH18::_protocol = MODBUS_MUST; + +const char *const MustPV_PH18::_name = "MUST PV/PH 18"; + +const modbus_register_t *MustPV_PH18::getLiveRegisters() const +{ + return registers_live; +} + +const modbus_register_t *MustPV_PH18::getStaticRegisters() const +{ + return registers_static; +} + +const char *MustPV_PH18::getName() const +{ + return _name; +} + +const modbus_register_t MustPV_PH18::registers_live[] = { + + {25201, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, DESCR_LIVE_INVERTER_OPERATION_MODE, 0, {.bitfield = { + "Power On", + "Self Test", + "OffGrid", + "GridTie", + "ByPass", + "Stop", + "GridCharging", + }}}, + + {25205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_BATTERY_VOLTAGE, 0}, + {25206, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "AC_out_Voltage", 0}, + {25207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "AC_in_Voltage", 0}, + {25208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Inverter_Bus_Voltage", 0}, + {25225, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "AC_out_Frequenz", 0}, + {25226, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "AC_in_Frequenz", 0}, + + {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Output_load_percent", 0}, + {15205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_VOLTAGE, 0}, + {15208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "PV_Charging_Power", 0}, + {15207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_CURRENT, 0}, + {25233, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Inverter_Bus_Temperature", 0}, + {25234, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Transformer_temperature", 0}, + {15209, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "MPPT1_Charger_Temperature", 0}, + + {25215, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_out_Watt", 0}, // W + {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_out_percent", 0}, //% + + {25274, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "Battery_Load", 0}, +}; + +const modbus_register_t MustPV_PH18::registers_static[] = { + {10110, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "Battery_type", 0, {.bitfield = { + "No choose", + "User defined", + "Lithium", + "Sealed Lead", + "AGM", + "GEL", + "Flooded", + }}}, + {10103, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Battery_float_voltage", 0}, +}; + +const modbus_register_t MustPV_PH18::registers_device_model[] = { + {20000, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, DEVICE_MODEL_HIGH, 0}, + {20001, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DEVICE_MODEL_LOW, 0}}; + +bool MustPV_PH18::retrieveModel(MODBUS_COM &mCom, char *modelBuffer, size_t bufferSize) +{ + modelBuffer[0] = '\0'; // Clear the buffer + DynamicJsonDocument doc(100); + JsonObject jsonObj = doc.to(); // Create and get JsonObject + modbus_register_info_t model_info = { + .variant = &jsonObj, + .registers = registers_device_model, + .array_size = sizeof(registers_device_model) / sizeof(modbus_register_t), + .curr_register = 0}; + + for (size_t i = 0; i < model_info.array_size; i++) + { + mCom.parseModbusToJson(model_info, false); + if (mCom.isAllRegistersRead(model_info)) + { + const char *modelHigh = doc[DEVICE_MODEL_HIGH]; + int modelLow = doc[DEVICE_MODEL_LOW]; + snprintf(modelBuffer, bufferSize, "%s%d", modelHigh, modelLow); + return true; + } + delay(50); + } + + return false; +} + +// Define the size calculation after the arrays are defined +size_t MustPV_PH18::getLiveRegistersCount() const +{ + return sizeof(registers_live) / sizeof(modbus_register_t); +} + +size_t MustPV_PH18::getStaticRegistersCount() const +{ + return sizeof(registers_static) / sizeof(modbus_register_t); +} \ No newline at end of file diff --git a/src/modbus/device/must_pv_ph18.h b/src/modbus/device/must_pv_ph18.h index 1711bf1..6c826e1 100644 --- a/src/modbus/device/must_pv_ph18.h +++ b/src/modbus/device/must_pv_ph18.h @@ -1,7 +1,7 @@ #ifndef MODBUS_MUST_PV_PH18_H #define MODBUS_MUST_PV_PH18_H -#include "modbus_device.h" -#include + +#include "modbus_device.h" #define DEVICE_MODEL_HIGH "Device_Model_Hight" #define DEVICE_MODEL_LOW "Device_Model_Low" @@ -11,27 +11,17 @@ class MustPV_PH18 : public ModbusDevice public: MustPV_PH18() : ModbusDevice(_baudRate, _modbusAddr, _protocol) {} - virtual const modbus_register_t *getLiveRegisters() const override - { - return registers_live; - } - - virtual const modbus_register_t *getStaticRegisters() const override - { - return registers_static; - } - - const char *getName() const override - { - return _name; - } - - bool retrieveModel(MODBUS &modbus, char *modelBuffer, size_t bufferSize) override; + virtual const modbus_register_t *getLiveRegisters() const override; + virtual const modbus_register_t *getStaticRegisters() const override; + const char *getName() const override; + bool retrieveModel(MODBUS_COM &mCom, char *modelBuffer, size_t bufferSize) override; + size_t getLiveRegistersCount() const override; + size_t getStaticRegistersCount() const override; private: - static const long _baudRate = 19200; - static const uint32_t _modbusAddr = 4; - static const protocol_type_t _protocol = MODBUS_MUST; + static const long _baudRate; + static const uint32_t _modbusAddr; + static const protocol_type_t _protocol; static const char *const _name; static const modbus_register_t registers_live[]; @@ -39,84 +29,4 @@ class MustPV_PH18 : public ModbusDevice static const modbus_register_t registers_device_model[]; }; -const char *const MustPV_PH18::_name = "MUST_PV/PH18"; - -const modbus_register_t MustPV_PH18::registers_live[] = { - - {25201, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, DESCR_LIVE_INVERTER_OPERATION_MODE, 0, {.bitfield = { - "Power On", - "Self Test", - "OffGrid", - "GridTie", - "ByPass", - "Stop", - "GridCharging", - }}}, - - {25205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_BATTERY_VOLTAGE, 0}, - {25206, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "AC_out_Voltage", 0}, - {25207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "AC_in_Voltage", 0}, - {25208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Inverter_Bus_Voltage", 0}, - {25225, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "AC_out_Frequenz", 0}, - {25226, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "AC_in_Frequenz", 0}, - - {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Output_load_percent", 0}, - {15205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_VOLTAGE, 0}, - {15208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "PV_Charging_Power", 0}, - {15207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_CURRENT, 0}, - {25233, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Inverter_Bus_Temperature", 0}, - {25234, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Transformer_temperature", 0}, - {15209, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "MPPT1_Charger_Temperature", 0}, - - {25215, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_out_Watt", 0}, // W - {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_out_percent", 0}, //% - - {25274, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "Battery_Load", 0}, -}; - -const modbus_register_t MustPV_PH18::registers_static[] = { - - {10110, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "Battery_type", 0, {.bitfield = { - "No choose", - "User defined", - "Lithium", - "Sealed Lead", - "AGM", - "GEL", - "Flooded", - }}}, - {10103, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Battery_float_voltage", 0}, -}; - -const modbus_register_t MustPV_PH18::registers_device_model[] = { - {20000, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, DEVICE_MODEL_HIGH, 0}, - {20001, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DEVICE_MODEL_LOW, 0}}; - -bool MustPV_PH18::retrieveModel(MODBUS &modbus, char *modelBuffer, size_t bufferSize) -{ - modelBuffer[0] = '\0'; // Clear the buffer - DynamicJsonDocument doc(100); - JsonObject jsonObj = doc.to(); // Create and get JsonObject - modbus_register_info_t model_info = { - .variant = &jsonObj, - .registers = registers_device_model, - .array_size = sizeof(registers_device_model) / sizeof(modbus_register_t), - .curr_register = 0}; - - for (size_t i = 0; i < model_info.array_size; i++) - { - modbus.parseModbusToJson(model_info, false); - if (modbus.isAllRegistersRead(model_info)) - { - const char *modelHigh = doc[DEVICE_MODEL_HIGH]; - int modelLow = doc[DEVICE_MODEL_LOW]; - snprintf(modelBuffer, bufferSize, "%s%d", modelHigh, modelLow); - return true; - } - delay(50); - } - - return false; -} - #endif diff --git a/src/modbus/modbus.cpp b/src/modbus/modbus.cpp index ca18853..2bb9eb6 100644 --- a/src/modbus/modbus.cpp +++ b/src/modbus/modbus.cpp @@ -1,12 +1,8 @@ // #define isDEBUG #include "modbus.h" -#include "device/must_pv_ph18.h" -#include "device/deye.h" extern void writeLog(const char *format, ...); -unsigned int dir_pin; - //---------------------------------------------------------------------- // Public Functions //---------------------------------------------------------------------- @@ -14,40 +10,9 @@ unsigned int dir_pin; MODBUS::MODBUS(SoftwareSerial *port) { my_serialIntf = port; - dir_pin = RS485_DIR_PIN; - - if (strcmp(HWBOARD, "esp01_1m") == 0) - { - dir_pin = RS485_ESP01_DIR_PIN; - } -} - -void MODBUS::preTransmission() -{ - if (strcmp(HWBOARD, "esp12e") == 0) - { - digitalWrite(MAX485_DONGLE_RE_NEG_PIN, 1); - digitalWrite(MAX485_DONGLE_DE_PIN, 1); - } - else - { - digitalWrite(dir_pin, 1); - } -} - -void MODBUS::postTransmission() -{ - if (strcmp(HWBOARD, "esp12e") == 0) - { - digitalWrite(MAX485_DONGLE_RE_NEG_PIN, 0); - digitalWrite(MAX485_DONGLE_DE_PIN, 0); - } - else - { - digitalWrite(dir_pin, 0); - } + } - + bool MODBUS::Init() { // Null check the serial interface @@ -57,23 +22,7 @@ bool MODBUS::Init() return false; } this->my_serialIntf->setTimeout(2000); - - // Init in receive mode - if (strcmp(HWBOARD, "esp12e") == 0) - { - pinMode(MAX485_DONGLE_RE_NEG_PIN, OUTPUT); - pinMode(MAX485_DONGLE_DE_PIN, OUTPUT); - } - else - { - pinMode(dir_pin, OUTPUT); - } - this->postTransmission(); - - // Callbacks allow us to configure the RS485 transceiver correctly - mb.preTransmission(preTransmission); - mb.postTransmission(postTransmission); - + return true; } @@ -112,7 +61,7 @@ void MODBUS::loop() { cur_info_registers = &static_info; } - switch (parseModbusToJson(*cur_info_registers)) + switch (_mCom.parseModbusToJson(*cur_info_registers)) { case READ_OK: connectionCounter = 0; @@ -125,7 +74,7 @@ void MODBUS::loop() } connection = connectionCounter < MAX_CONNECTION_ATTEMPTS; - if (isAllRegistersRead(*cur_info_registers)) + if (_mCom.isAllRegistersRead(*cur_info_registers)) { requestStaticData = false; requestCallback(); @@ -143,276 +92,7 @@ String MODBUS::requestData(String command) { requestStaticData = true; return ""; -} - -bool MODBUS::getModbusResultMsg(uint8_t result) -{ - String tmpstr2 = ""; - switch (result) - { - case mb.ku8MBSuccess: - return true; - break; - case mb.ku8MBIllegalFunction: - tmpstr2 = "Illegal Function"; - break; - case mb.ku8MBIllegalDataAddress: - tmpstr2 = "Illegal Data Address"; - break; - case mb.ku8MBIllegalDataValue: - tmpstr2 = "Illegal Data Value"; - break; - case mb.ku8MBSlaveDeviceFailure: - tmpstr2 = "Slave Device Failure"; - break; - case mb.ku8MBInvalidSlaveID: - tmpstr2 = "Invalid Slave ID"; - break; - case mb.ku8MBInvalidFunction: - tmpstr2 = "Invalid Function"; - break; - case mb.ku8MBResponseTimedOut: - tmpstr2 = "Response Timed Out"; - break; - case mb.ku8MBInvalidCRC: - tmpstr2 = "Invalid CRC"; - break; - default: - tmpstr2 = "Unknown error: " + String(result); - break; - } - writeLog("%s", tmpstr2.c_str()); - return false; -} - -bool MODBUS::getModbusValue(uint16_t register_id, modbus_entity_t modbus_entity, uint16_t *value_ptr, uint16_t readBytes) -{ - // writeLog("Requesting data"); - for (uint8_t i = 0; i < MODBUS_RETRIES; i++) - { - if (MODBUS_RETRIES > 1) - { - // writeLog("Trial %d/%d", i + 1, MODBUS_RETRIES); - } - if (modbus_entity == MODBUS_TYPE_HOLDING) - { - uint8_t result = mb.readHoldingRegisters(register_id, readBytes); - bool is_received = getModbusResultMsg(result); - if (is_received) - { - *value_ptr = mb.getResponseBuffer(0); - return true; - } - } - else - { - writeLog("Unsupported Modbus entity type"); - value_ptr = nullptr; - return false; - } - } - // Time-out - writeLog("Time-out"); - value_ptr = nullptr; - return false; -} - -String MODBUS::toBinary(uint16_t input) -{ - String output; - while (input != 0) - { - output = (input % 2 == 0 ? "0" : "1") + output; - input /= 2; - } - return output; -} - -bool MODBUS::decodeDiematicDecimal(uint16_t int_input, int8_t decimals, float *value_ptr) -{ - if (int_input == 65535) - { - value_ptr = nullptr; - return false; - } - else - { - uint16_t masked_input = int_input & 0x7FFF; - float output = static_cast(masked_input); - if (int_input >> 15 == 1) - { - output = -output; - } - *value_ptr = output / pow(10, decimals); - return true; - } -} - -bool MODBUS::readModbusRegisterToJson(const modbus_register_t *reg, JsonObject *variant) -{ - // register found - if (reg == nullptr || variant == nullptr) - { - return false; // Return false if invalid input - } - // writeLog("Register id=%d type=0x%x name=%s", reg->id, reg->type, reg->name); - uint16_t raw_value = 0; - - float final_value; - - uint16_t readBytes = 1; - - if (reg->type == REGISTER_TYPE_U32 || reg->type == REGISTER_TYPE_U32_ONE_DECIMAL) - { - readBytes = 2; - } - - if (getModbusValue(reg->id, reg->modbus_entity, &raw_value, readBytes)) - { - writeLog("Raw value: %s=%#06x\n", reg->name, raw_value); - - switch (reg->type) - { - case REGISTER_TYPE_U16: - // writeLog("Value: %u", raw_value); - (*variant)[reg->name] = raw_value + reg->offset; - break; - case REGISTER_TYPE_INT16: - // writeLog("Value: %u", raw_value); - (*variant)[reg->name] = static_cast(raw_value) + reg->offset; - break; - case REGISTER_TYPE_U32: - // writeLog("Value: %u", raw_value); - (*variant)[reg->name] = (raw_value + (mb.getResponseBuffer(1) << 16)) + reg->offset; - break; - case REGISTER_TYPE_U32_ONE_DECIMAL: - // writeLog("Value: %u", raw_value); - (*variant)[reg->name] = (raw_value + (mb.getResponseBuffer(1) << 16)) * 0.1 + reg->offset; - break; - case REGISTER_TYPE_DIEMATIC_ONE_DECIMAL: - if (decodeDiematicDecimal(raw_value, 1, &final_value)) - { - // writeLog("Raw value: %#06x, floatValue: %f",raw_value, final_value); - (*variant)[reg->name] = ((int)(final_value * 100 + 0.5) / 100.0) + reg->offset; - } - else - { - writeLog("Invalid Diematic value"); - } - break; - - case REGISTER_TYPE_DIEMATIC_TWO_DECIMAL: - if (decodeDiematicDecimal(raw_value, 2, &final_value)) - { - // writeLog("Value: %.1f", final_value); - (*variant)[reg->name] = ((int)(final_value * 1000 + 0.5) / 1000.0) + reg->offset; - } - else - { - writeLog("Invalid Diematic value"); - } - break; - case REGISTER_TYPE_BITFIELD: - - for (uint8_t j = 0; j < 16; ++j) - { - const char *bit_varname = reg->optional_param.bitfield[j]; - if (bit_varname == nullptr) - { - writeLog("[bit%02d] end of bitfield reached", j); - break; - } - const uint8_t bit_value = raw_value >> j & 1; - writeLog(" [bit%02d] %s=%d", j, bit_varname, bit_value); - (*variant)[bit_varname] = bit_value; - } - break; - - case REGISTER_TYPE_CUSTOM_VAL_NAME: - { - bool isfound = false; - for (uint8_t j = 0; j < 16; ++j) - { - const char *bit_varname = reg->optional_param.bitfield[j]; - if (bit_varname == nullptr) - { - writeLog("bitfield[%d] is null", j); - break; - } - if (j == raw_value) - { - // writeLog("Match found, value: %s", bit_varname); - (*variant)[reg->name] = bit_varname; - isfound = true; - break; - } - } - if (!isfound) - { - writeLog("CUSTOM_VAL_NAME not found for raw_value=%d", raw_value); - } - break; - } - case REGISTER_TYPE_ASCII: - { - String out = ""; - char high_byte = (raw_value >> 8) & 0xFF; - char low_byte = raw_value & 0xFF; - - out += high_byte; - out += low_byte; - (*variant)[reg->name] = out; - break; - } - case REGISTER_TYPE_DEBUG: - - writeLog("Raw DEBUG value: %s=%#06x %s", reg->name, raw_value, toBinary(raw_value).c_str()); - break; - - default: - // Unsupported type - - writeLog("Unsupported register type"); - break; - } - } - else - { - writeLog("Request failed!"); - return false; - } - // writeLog("Request OK!"); - return true; -} - -response_type_t MODBUS::parseModbusToJson(modbus_register_info_t ®ister_info, bool skip_reg_on_error) -{ - - if (register_info.curr_register >= register_info.array_size) - { - register_info.curr_register = 0; - } - while (register_info.curr_register < register_info.array_size) - { - bool ret_val = readModbusRegisterToJson(®ister_info.registers[register_info.curr_register], register_info.variant); - if (ret_val || skip_reg_on_error) - { - register_info.curr_register++; - } - - return ret_val ? READ_OK : READ_FAIL; - } - return READ_FAIL; -} - -bool MODBUS::isAllRegistersRead(modbus_register_info_t ®ister_info) -{ - if (register_info.curr_register >= register_info.array_size) - { - return true; - } - return false; -} +} //---------------------------------------------------------------------- // Private Functions @@ -424,21 +104,22 @@ protocol_type_t MODBUS::autoDetect() // function for autodetect the inverter typ writeLog("Try Autodetect Modbus device"); - ModbusDevice *devices[] = {new MustPV_PH18(), new DEYE()}; + ModbusDevice *devices[] = {new MustPV_PH18()};//, new DEYE()}; for (size_t i = 0; i < sizeof(devices) / sizeof(devices[0]); ++i) { - devices[i]->init(*my_serialIntf, mb); + devices[i]->init(*my_serialIntf, _mCom); writeLog("Try to use: %s protocol", devices[i]->getName()); - devices[i]->retrieveModel(*this, modelName, sizeof(modelName)); + devices[i]->retrieveModel(_mCom, modelName, sizeof(modelName)); if (strlen(modelName) != 0) { writeLog(" Found Modbus device: %s", modelName); staticData["Device_Model"] = modelName; - prepareRegisters(); - protocol = devices[i]->getProtocol(); + device = devices[i]; + prepareRegisters(); + protocol = device->getProtocol(); return protocol; } delete devices[i]; diff --git a/src/modbus/modbus.h b/src/modbus/modbus.h index b801d4e..62faf7f 100644 --- a/src/modbus/modbus.h +++ b/src/modbus/modbus.h @@ -3,40 +3,15 @@ #include "SoftwareSerial.h" #include -#include -#include "modbus_registers.h" +#include "modbus_com.h" #include "device/modbus_device.h" +#include "device/must_pv_ph18.h" +// #include "device/deye.h" extern JsonObject deviceJson; extern JsonObject staticData; extern JsonObject liveData; -#define RS485_DIR_PIN 14 // D5 -#define RS485_ESP01_DIR_PIN 0 - -#define MAX485_DONGLE_DE_PIN 5 // D1, DE pin on the TTL to RS485 converter -#define MAX485_DONGLE_RE_NEG_PIN 4 - -#define RS485_BAUDRATE 19200 - -#define INVERTER_MODBUS_ADDR 4 - -#define MODBUS_RETRIES 2 - -typedef enum -{ - READ_FAIL = 0, - READ_OK = 1, -} response_type_t; - -typedef struct -{ - JsonObject *variant; - const modbus_register_t *registers; - uint8_t array_size; - uint8_t curr_register; -} modbus_register_info_t; - class MODBUS { public: @@ -68,10 +43,7 @@ class MODBUS * */ void callback(std::function func); - std::function requestCallback; - bool readModbusRegisterToJson(const modbus_register_t *reg, JsonObject *variant); - response_type_t parseModbusToJson(modbus_register_info_t ®ister_info, bool skip_reg_on_error = true); - bool isAllRegistersRead(modbus_register_info_t ®ister_info); + std::function requestCallback; protocol_type_t autoDetect(); /** * @brief Sends a complete packet with the specified command @@ -89,30 +61,15 @@ class MODBUS byte qexCounter = 0; - static void preTransmission(); - static void postTransmission(); - void prepareRegisters(); - String toBinary(uint16_t input); - bool decodeDiematicDecimal(uint16_t int_input, int8_t decimals, float *value_ptr); - bool getModbusResultMsg(uint8_t result); - bool getModbusValue(uint16_t register_id, modbus_entity_t modbus_entity, uint16_t *value_ptr, uint16_t readBytes = 1); - /** - * @brief get the crc from a string - */ - uint16_t getCRC(String data); - - /** - * @brief get the crc from a string - */ - byte getCHK(String data); + void prepareRegisters(); /** * @brief Serial interface used for communication * @details This is set in the constructor */ SoftwareSerial *my_serialIntf; - ModbusDevice *device = nullptr; - ModbusMaster mb; + ModbusDevice *device = nullptr; + MODBUS_COM _mCom; }; #endif diff --git a/src/modbus/modbus_com.cpp b/src/modbus/modbus_com.cpp new file mode 100644 index 0000000..6174587 --- /dev/null +++ b/src/modbus/modbus_com.cpp @@ -0,0 +1,335 @@ +// #define isDEBUG +#include "modbus_com.h" + +extern void writeLog(const char *format, ...); + +//---------------------------------------------------------------------- +// Public Functions +//---------------------------------------------------------------------- + +int _dir_pin; + +MODBUS_COM::MODBUS_COM() +{ + _dir_pin = RS485_DIR_PIN; + + if (strcmp(HWBOARD, "esp01_1m") == 0) + { + _dir_pin = RS485_ESP01_DIR_PIN; + } + + // Init in receive mode + if (strcmp(HWBOARD, "esp12e") == 0) + { + pinMode(MAX485_DONGLE_RE_NEG_PIN, OUTPUT); + pinMode(MAX485_DONGLE_DE_PIN, OUTPUT); + } + else + { + pinMode(_dir_pin, OUTPUT); + } + this->postTransmission(); + + // Callbacks allow us to configure the RS485 transceiver correctly + _mb.preTransmission(preTransmission); + _mb.postTransmission(postTransmission); +} + +void MODBUS_COM::preTransmission() +{ + if (strcmp(HWBOARD, "esp12e") == 0) + { + digitalWrite(MAX485_DONGLE_RE_NEG_PIN, 1); + digitalWrite(MAX485_DONGLE_DE_PIN, 1); + } + else + { + digitalWrite(_dir_pin, 1); + } +} + +void MODBUS_COM::postTransmission() +{ + if (strcmp(HWBOARD, "esp12e") == 0) + { + digitalWrite(MAX485_DONGLE_RE_NEG_PIN, 0); + digitalWrite(MAX485_DONGLE_DE_PIN, 0); + } + else + { + digitalWrite(_dir_pin, 0); + } +} + + ModbusMaster MODBUS_COM::getModbusMaster(){ + return _mb; + } + +bool MODBUS_COM::getModbusResultMsg(uint8_t result) +{ + String tmpstr2 = ""; + switch (result) + { + case _mb.ku8MBSuccess: + return true; + break; + case _mb.ku8MBIllegalFunction: + tmpstr2 = "Illegal Function"; + break; + case _mb.ku8MBIllegalDataAddress: + tmpstr2 = "Illegal Data Address"; + break; + case _mb.ku8MBIllegalDataValue: + tmpstr2 = "Illegal Data Value"; + break; + case _mb.ku8MBSlaveDeviceFailure: + tmpstr2 = "Slave Device Failure"; + break; + case _mb.ku8MBInvalidSlaveID: + tmpstr2 = "Invalid Slave ID"; + break; + case _mb.ku8MBInvalidFunction: + tmpstr2 = "Invalid Function"; + break; + case _mb.ku8MBResponseTimedOut: + tmpstr2 = "Response Timed Out"; + break; + case _mb.ku8MBInvalidCRC: + tmpstr2 = "Invalid CRC"; + break; + default: + tmpstr2 = "Unknown error: " + String(result); + break; + } + writeLog("%s", tmpstr2.c_str()); + return false; +} + +bool MODBUS_COM::getModbusValue(uint16_t register_id, modbus_entity_t modbus_entity, uint16_t *value_ptr, uint16_t readBytes) +{ + // writeLog("Requesting data"); + for (uint8_t i = 0; i < MODBUS_RETRIES; i++) + { + if (MODBUS_RETRIES > 1) + { + // writeLog("Trial %d/%d", i + 1, MODBUS_RETRIES); + } + if (modbus_entity == MODBUS_TYPE_HOLDING) + { + uint8_t result = _mb.readHoldingRegisters(register_id, readBytes); + bool is_received = getModbusResultMsg(result); + if (is_received) + { + *value_ptr = _mb.getResponseBuffer(0); + return true; + } + } + else + { + writeLog("Unsupported Modbus entity type"); + value_ptr = nullptr; + return false; + } + } + // Time-out + writeLog("Time-out"); + value_ptr = nullptr; + return false; +} + +String MODBUS_COM::toBinary(uint16_t input) +{ + String output; + while (input != 0) + { + output = (input % 2 == 0 ? "0" : "1") + output; + input /= 2; + } + return output; +} + +bool MODBUS_COM::decodeDiematicDecimal(uint16_t int_input, int8_t decimals, float *value_ptr) +{ + if (int_input == 65535) + { + value_ptr = nullptr; + return false; + } + else + { + uint16_t masked_input = int_input & 0x7FFF; + float output = static_cast(masked_input); + if (int_input >> 15 == 1) + { + output = -output; + } + *value_ptr = output / pow(10, decimals); + return true; + } +} + +bool MODBUS_COM::readModbusRegisterToJson(const modbus_register_t *reg, JsonObject *variant) +{ + // register found + if (reg == nullptr || variant == nullptr) + { + return false; // Return false if invalid input + } + // writeLog("Register id=%d type=0x%x name=%s", reg->id, reg->type, reg->name); + uint16_t raw_value = 0; + + float final_value; + + uint16_t readBytes = 1; + + if (reg->type == REGISTER_TYPE_U32 || reg->type == REGISTER_TYPE_U32_ONE_DECIMAL) + { + readBytes = 2; + } + + if (getModbusValue(reg->id, reg->modbus_entity, &raw_value, readBytes)) + { + writeLog("Raw value: %s=%#06x\n", reg->name, raw_value); + + switch (reg->type) + { + case REGISTER_TYPE_U16: + // writeLog("Value: %u", raw_value); + (*variant)[reg->name] = raw_value + reg->offset; + break; + case REGISTER_TYPE_INT16: + // writeLog("Value: %u", raw_value); + (*variant)[reg->name] = static_cast(raw_value) + reg->offset; + break; + case REGISTER_TYPE_U32: + // writeLog("Value: %u", raw_value); + (*variant)[reg->name] = (raw_value + (_mb.getResponseBuffer(1) << 16)) + reg->offset; + break; + case REGISTER_TYPE_U32_ONE_DECIMAL: + // writeLog("Value: %u", raw_value); + (*variant)[reg->name] = (raw_value + (_mb.getResponseBuffer(1) << 16)) * 0.1 + reg->offset; + break; + case REGISTER_TYPE_DIEMATIC_ONE_DECIMAL: + if (decodeDiematicDecimal(raw_value, 1, &final_value)) + { + // writeLog("Raw value: %#06x, floatValue: %f",raw_value, final_value); + (*variant)[reg->name] = ((int)(final_value * 100 + 0.5) / 100.0) + reg->offset; + } + else + { + writeLog("Invalid Diematic value"); + } + break; + + case REGISTER_TYPE_DIEMATIC_TWO_DECIMAL: + if (decodeDiematicDecimal(raw_value, 2, &final_value)) + { + // writeLog("Value: %.1f", final_value); + (*variant)[reg->name] = ((int)(final_value * 1000 + 0.5) / 1000.0) + reg->offset; + } + else + { + writeLog("Invalid Diematic value"); + } + break; + case REGISTER_TYPE_BITFIELD: + + for (uint8_t j = 0; j < 16; ++j) + { + const char *bit_varname = reg->optional_param.bitfield[j]; + if (bit_varname == nullptr) + { + writeLog("[bit%02d] end of bitfield reached", j); + break; + } + const uint8_t bit_value = raw_value >> j & 1; + writeLog(" [bit%02d] %s=%d", j, bit_varname, bit_value); + (*variant)[bit_varname] = bit_value; + } + break; + + case REGISTER_TYPE_CUSTOM_VAL_NAME: + { + bool isfound = false; + for (uint8_t j = 0; j < 16; ++j) + { + const char *bit_varname = reg->optional_param.bitfield[j]; + if (bit_varname == nullptr) + { + writeLog("bitfield[%d] is null", j); + break; + } + if (j == raw_value) + { + // writeLog("Match found, value: %s", bit_varname); + (*variant)[reg->name] = bit_varname; + isfound = true; + break; + } + } + if (!isfound) + { + writeLog("CUSTOM_VAL_NAME not found for raw_value=%d", raw_value); + } + break; + } + case REGISTER_TYPE_ASCII: + { + String out = ""; + char high_byte = (raw_value >> 8) & 0xFF; + char low_byte = raw_value & 0xFF; + + out += high_byte; + out += low_byte; + (*variant)[reg->name] = out; + break; + } + case REGISTER_TYPE_DEBUG: + + writeLog("Raw DEBUG value: %s=%#06x %s", reg->name, raw_value, toBinary(raw_value).c_str()); + break; + + default: + // Unsupported type + + writeLog("Unsupported register type"); + break; + } + } + else + { + writeLog("Request failed!"); + return false; + } + // writeLog("Request OK!"); + return true; +} + +response_type_t MODBUS_COM::parseModbusToJson(modbus_register_info_t ®ister_info, bool skip_reg_on_error) +{ + writeLog("parseModbusToJson %d", register_info.array_size); + if (register_info.curr_register >= register_info.array_size) + { + register_info.curr_register = 0; + } + while (register_info.curr_register < register_info.array_size) + { + bool ret_val = readModbusRegisterToJson(®ister_info.registers[register_info.curr_register], register_info.variant); + if (ret_val || skip_reg_on_error) + { + register_info.curr_register++; + } + + return ret_val ? READ_OK : READ_FAIL; + } + return READ_FAIL; +} + +bool MODBUS_COM::isAllRegistersRead(modbus_register_info_t ®ister_info) +{ + if (register_info.curr_register >= register_info.array_size) + { + return true; + } + return false; +} diff --git a/src/modbus/modbus_com.h b/src/modbus/modbus_com.h new file mode 100644 index 0000000..e60a509 --- /dev/null +++ b/src/modbus/modbus_com.h @@ -0,0 +1,44 @@ +#ifndef MODBUS_COM_H +#define MODBUS_COM_H + +#include "SoftwareSerial.h" +#include +#include +#include "modbus_registers.h" + +#define MODBUS_RETRIES 2 + +#define RS485_DIR_PIN 14 // D5 +#define RS485_ESP01_DIR_PIN 0 + +#define MAX485_DONGLE_DE_PIN 5 // D1, DE pin on the TTL to RS485 converter +#define MAX485_DONGLE_RE_NEG_PIN 4 + +typedef enum +{ + READ_FAIL = 0, + READ_OK = 1, +} response_type_t; + +class MODBUS_COM +{ +public: + MODBUS_COM(); + + bool readModbusRegisterToJson(const modbus_register_t *reg, JsonObject *variant); + response_type_t parseModbusToJson(modbus_register_info_t ®ister_info, bool skip_reg_on_error = true); + bool isAllRegistersRead(modbus_register_info_t ®ister_info); + ModbusMaster getModbusMaster(); +private: + String toBinary(uint16_t input); + bool decodeDiematicDecimal(uint16_t int_input, int8_t decimals, float *value_ptr); + bool getModbusResultMsg(uint8_t result); + bool getModbusValue(uint16_t register_id, modbus_entity_t modbus_entity, uint16_t *value_ptr, uint16_t readBytes = 1); + + static void preTransmission(); + static void postTransmission(); + + ModbusMaster _mb; +}; + +#endif diff --git a/src/modbus/modbus_registers.h b/src/modbus/modbus_registers.h index ca8d49e..c5e38f2 100644 --- a/src/modbus/modbus_registers.h +++ b/src/modbus/modbus_registers.h @@ -1,6 +1,7 @@ #ifndef SRC_MODBUS_REGISTERS_H_ #define SRC_MODBUS_REGISTERS_H_ #include "Arduino.h" +#include typedef enum { @@ -42,4 +43,13 @@ typedef struct optional_param_t optional_param; } modbus_register_t; + +typedef struct +{ + JsonObject *variant; + const modbus_register_t *registers; + uint8_t array_size; + uint8_t curr_register; +} modbus_register_info_t; + #endif // SRC_MODBUS_REGISTERS_H_ \ No newline at end of file From 41e5eb2a2e3c962da407ca47a798e81efda9f921 Mon Sep 17 00:00:00 2001 From: Artem Kovalenko Date: Mon, 7 Oct 2024 15:27:56 +0300 Subject: [PATCH 05/13] finish refactoring for mulitple modbus devices --- src/PI_Serial/PI_Serial.h | 4 +- src/main.cpp | 103 ++++++++++++++-------------- src/modbus/device/deye.cpp | 39 ----------- src/modbus/device/deye.h | 50 +++++++++++--- src/modbus/device/modbus_device.cpp | 15 ++-- src/modbus/device/modbus_device.h | 6 +- src/modbus/device/must_pv_ph18.cpp | 55 --------------- src/modbus/device/must_pv_ph18.h | 65 +++++++++++++++--- src/modbus/modbus.cpp | 17 ++--- src/modbus/modbus.h | 2 +- src/modbus/modbus_com.cpp | 11 +-- src/modbus/modbus_com.h | 13 +++- src/modbus/modbus_registers.h | 4 +- 13 files changed, 186 insertions(+), 198 deletions(-) diff --git a/src/PI_Serial/PI_Serial.h b/src/PI_Serial/PI_Serial.h index 2dd0c23..78a13d1 100644 --- a/src/PI_Serial/PI_Serial.h +++ b/src/PI_Serial/PI_Serial.h @@ -8,9 +8,7 @@ extern JsonObject deviceJson; extern JsonObject staticData; extern JsonObject liveData; - -class MODBUS; - + class PI_Serial { public: diff --git a/src/main.cpp b/src/main.cpp index 60b2a15..8866410 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -178,38 +178,38 @@ void setup() // wm.setConnectTimeout(15); // how long to try to connect for before continuing // wm.setConfigPortalTimeout(120); // auto close configportal after n seconds wm.setSaveConfigCallback(saveConfigCallback); -/* - DEBUG_PRINTLN(); - DEBUG_PRINTF("Device Name:\t"); - DEBUG_PRINTLN(settings.data.deviceName); - DEBUG_PRINTF("Mqtt Server:\t"); - DEBUG_PRINTLN(settings.data.mqttServer); - DEBUG_PRINTF("Mqtt Port:\t"); - DEBUG_PRINTLN(settings.data.mqttPort); - DEBUG_PRINTF("Mqtt User:\t"); - DEBUG_PRINTLN(settings.data.mqttUser); - DEBUG_PRINTF("Mqtt Passwort:\t"); - DEBUG_PRINTLN(settings.data.mqttPassword); - DEBUG_PRINTF("Mqtt Interval:\t"); - DEBUG_PRINTLN(settings.data.mqttRefresh); - DEBUG_PRINTF("Mqtt Topic:\t"); - DEBUG_PRINTLN(settings.data.mqttTopic); - DEBUG_WEBLN(); - DEBUG_WEBF("Device Name:\t"); - DEBUG_WEBLN(settings.data.deviceName); - DEBUG_WEBF("Mqtt Server:\t"); - DEBUG_WEBLN(settings.data.mqttServer); - DEBUG_WEBF("Mqtt Port:\t"); - DEBUG_WEBLN(settings.data.mqttPort); - DEBUG_WEBF("Mqtt User:\t"); - DEBUG_WEBLN(settings.data.mqttUser); - DEBUG_WEBF("Mqtt Passwort:\t"); - DEBUG_WEBLN(settings.data.mqttPassword); - DEBUG_WEBF("Mqtt Interval:\t"); - DEBUG_WEBLN(settings.data.mqttRefresh); - DEBUG_WEBF("Mqtt Topic:\t"); - DEBUG_WEBLN(settings.data.mqttTopic); -*/ + /* + DEBUG_PRINTLN(); + DEBUG_PRINTF("Device Name:\t"); + DEBUG_PRINTLN(settings.data.deviceName); + DEBUG_PRINTF("Mqtt Server:\t"); + DEBUG_PRINTLN(settings.data.mqttServer); + DEBUG_PRINTF("Mqtt Port:\t"); + DEBUG_PRINTLN(settings.data.mqttPort); + DEBUG_PRINTF("Mqtt User:\t"); + DEBUG_PRINTLN(settings.data.mqttUser); + DEBUG_PRINTF("Mqtt Passwort:\t"); + DEBUG_PRINTLN(settings.data.mqttPassword); + DEBUG_PRINTF("Mqtt Interval:\t"); + DEBUG_PRINTLN(settings.data.mqttRefresh); + DEBUG_PRINTF("Mqtt Topic:\t"); + DEBUG_PRINTLN(settings.data.mqttTopic); + DEBUG_WEBLN(); + DEBUG_WEBF("Device Name:\t"); + DEBUG_WEBLN(settings.data.deviceName); + DEBUG_WEBF("Mqtt Server:\t"); + DEBUG_WEBLN(settings.data.mqttServer); + DEBUG_WEBF("Mqtt Port:\t"); + DEBUG_WEBLN(settings.data.mqttPort); + DEBUG_WEBF("Mqtt User:\t"); + DEBUG_WEBLN(settings.data.mqttUser); + DEBUG_WEBF("Mqtt Passwort:\t"); + DEBUG_WEBLN(settings.data.mqttPassword); + DEBUG_WEBF("Mqtt Interval:\t"); + DEBUG_WEBLN(settings.data.mqttRefresh); + DEBUG_WEBF("Mqtt Topic:\t"); + DEBUG_WEBLN(settings.data.mqttTopic); + */ // create custom wifimanager fields AsyncWiFiManagerParameter custom_mqtt_server("mqtt_server", "MQTT server", NULL, 40); @@ -327,14 +327,13 @@ void setup() server.on("/set", HTTP_GET, [](AsyncWebServerRequest *request) { if(strlen(settings.data.httpUser) > 0 && !request->authenticate(settings.data.httpUser, settings.data.httpPass)) return request->requestAuthentication(); - String message; + if (request->hasParam("CC")) { - message = request->getParam("CC")->value(); - commandFromUser = (message); + const AsyncWebParameter *p = request->getParam("CC"); + commandFromUser = p->value(); } if (request->hasParam("ha")) { - message = request->getParam("ha")->value(); - haDiscTrigger = true; + haDiscTrigger = true; } request->send(200, "text/plain", "message received"); }); @@ -395,7 +394,7 @@ void setup() { request->send(418, "text/plain", "418 I'm a teapot"); }); // set the device name - + MDNS.begin(settings.data.deviceName); MDNS.addService("http", "tcp", 80); ws.onEvent(onEvent); @@ -436,7 +435,7 @@ void loop() mqtttimer = 0; } ws.cleanupClients(); // clean unused client connections - mppClient.loop(); // Call the PI Serial Library loop + mppClient.loop(); // Call the PI Serial Library loop mqttclient.loop(); if ((haDiscTrigger || settings.data.haDiscovery) && measureJson(Json) > jsonSize) { @@ -451,7 +450,7 @@ void loop() if (restartNow && millis() >= (RestartTimer + 500)) { ESP.restart(); - } + } notificationLED(); // notification LED routine } @@ -510,7 +509,7 @@ bool connectMQTT() if (!mqttclient.connected()) { firstPublish = false; - + if (mqttclient.connect(mqttClientId, settings.data.mqttUser, settings.data.mqttPassword, (topicBuilder(buff, "Alive")), 0, true, "false", true)) { if (mqttclient.connected()) @@ -557,10 +556,10 @@ bool sendtoMQTT() if (mppClient.get.raw.commandAnswer.length() > 0) { mqttclient.publish((String(settings.data.mqttTopic) + String("/DeviceControl/Set_Command_answer")).c_str(), (mppClient.get.raw.commandAnswer).c_str()); - writeLog("raw command answer: ",mppClient.get.raw.commandAnswer); + writeLog("raw command answer: ", mppClient.get.raw.commandAnswer); mppClient.get.raw.commandAnswer = ""; } -// RAW + // RAW mqttclient.publish(topicBuilder(buff, "RAW/Q1"), (mppClient.get.raw.q1).c_str()); mqttclient.publish(topicBuilder(buff, "RAW/QPIGS"), (mppClient.get.raw.qpigs).c_str()); mqttclient.publish(topicBuilder(buff, "RAW/QPIGS2"), (mppClient.get.raw.qpigs2).c_str()); @@ -692,16 +691,16 @@ bool sendHaDiscovery() return true; } -void writeLog(const char* format, ...) +void writeLog(const char *format, ...) { - char msg[100]; - va_list args; + char msg[100]; + va_list args; - va_start(args, format); - vsnprintf(msg, sizeof(msg), format, args); // do check return value - va_end(args); + va_start(args, format); + vsnprintf(msg, sizeof(msg), format, args); // do check return value + va_end(args); - // write msg to the log - DBG_PRINTLN(msg); - DBG_WEBLN(msg); + // write msg to the log + DBG_PRINTLN(msg); + DBG_WEBLN(msg); } \ No newline at end of file diff --git a/src/modbus/device/deye.cpp b/src/modbus/device/deye.cpp index f260711..c23a943 100644 --- a/src/modbus/device/deye.cpp +++ b/src/modbus/device/deye.cpp @@ -1,10 +1,5 @@ #include "deye.h" -const long Deye::_baudRate = 9600; -const uint32_t Deye::_modbusAddr = 1; -const protocol_type_t Deye::_protocol = MODBUS_DEYE; - -const char *const Deye::_name = "DEYE"; const modbus_register_t *Deye::getLiveRegisters() const { @@ -21,40 +16,6 @@ const char *Deye::getName() const return _name; } -const modbus_register_t Deye::registers_live[] = { - {59, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, DESCR_LIVE_INVERTER_OPERATION_MODE, 0, {.bitfield = { - "Standby", - "Self Test", - "Normal", - "Alerts", - "Fault", - }}}, - - {109, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_VOLTAGE, 0}, - {110, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_CURRENT, 0}, - {186, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_PV_INPUT_POWER, 0}, - - {175, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_OUTPUT_POWER, 0}, - {178, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_AC_OUTPUT_POWER, 0}, - - {90, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_INVERTER_TEMPERATURE, -100}, - {182, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_BATTERY_TEMPERATURE, -100}, - - {183, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, DESCR_LIVE_BATTERY_VOLTAGE, 0}, - {184, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_BATTERY_PERCENT, 0}, -}; - -const modbus_register_t Deye::registers_static[] = { - {16, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U32_ONE_DECIMAL, DESCR_STAT_AC_OUT_RATING_ACTIVE_POWER, 0}, -}; - -const modbus_register_t Deye::registers_device_serial[] = { - {3, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN1"}, - {4, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN2"}, - {5, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN3"}, - {6, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN4"}, - {7, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN5"}}; - bool Deye::retrieveModel(MODBUS_COM &mCom, char *modelBuffer, size_t bufferSize) { modelBuffer[0] = '\0'; // Clear the buffer diff --git a/src/modbus/device/deye.h b/src/modbus/device/deye.h index cbb1314..540d541 100644 --- a/src/modbus/device/deye.h +++ b/src/modbus/device/deye.h @@ -1,8 +1,8 @@ #ifndef MODBUS_DEYE_H #define MODBUS_DEYE_H -#include "modbus_device.h" - +#include "modbus_device.h" + class Deye : public ModbusDevice { public: @@ -16,14 +16,44 @@ class Deye : public ModbusDevice size_t getStaticRegistersCount() const override; private: - static const long _baudRate; - static const uint32_t _modbusAddr; - static const protocol_type_t _protocol; - static const char *const _name; - - static const modbus_register_t registers_live[]; - static const modbus_register_t registers_static[]; - static const modbus_register_t registers_device_serial[]; + static const long _baudRate = 9600; + static const uint32_t _modbusAddr = 1; + static const protocol_type_t _protocol = MODBUS_DEYE; + inline static const char *const _name = "DEYE"; + + inline static const modbus_register_t registers_live[] = { + {59, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, DESCR_LIVE_INVERTER_OPERATION_MODE, 0, {.bitfield = { + "Standby", + "Self Test", + "Normal", + "Alerts", + "Fault", + }}}, + + {109, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_VOLTAGE, 0}, + {110, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_CURRENT, 0}, + {186, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_PV_INPUT_POWER, 0}, + + {175, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_OUTPUT_POWER, 0}, + {178, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_AC_OUTPUT_POWER, 0}, + + {90, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_INVERTER_TEMPERATURE, -100}, + {182, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_BATTERY_TEMPERATURE, -100}, + + {183, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, DESCR_LIVE_BATTERY_VOLTAGE, 0}, + {184, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_BATTERY_PERCENT, 0}, + }; + + inline static const modbus_register_t registers_static[] = { + {16, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U32_ONE_DECIMAL, DESCR_STAT_AC_OUT_RATING_ACTIVE_POWER, 0}, + }; + + inline static const modbus_register_t registers_device_serial[] = { + {3, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN1"}, + {4, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN2"}, + {5, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN3"}, + {6, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN4"}, + {7, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN5"}}; }; #endif diff --git a/src/modbus/device/modbus_device.cpp b/src/modbus/device/modbus_device.cpp index 9de7815..a62db50 100644 --- a/src/modbus/device/modbus_device.cpp +++ b/src/modbus/device/modbus_device.cpp @@ -1,28 +1,33 @@ #include "modbus_device.h" // Constructor definition ModbusDevice::ModbusDevice(long baudRate, uint32_t modbusAddr, protocol_type_t protocol) - : _baudRate(baudRate), _modbusAddr(modbusAddr), _protocol(protocol) {} +{ + _baudRate = baudRate; + _modbusAddr = modbusAddr; + _protocol = protocol; +} // Method implementations for non-virtual functions -long ModbusDevice::getBaudRate() +long ModbusDevice::getBaudRate() const { return _baudRate; } -long ModbusDevice::getModbusAddr() +long ModbusDevice::getModbusAddr() const { return _modbusAddr; } -protocol_type_t ModbusDevice::getProtocol() +protocol_type_t ModbusDevice::getProtocol() const { return _protocol; } void ModbusDevice::init(SoftwareSerial &serial, MODBUS_COM &mCom) { + writeLog("Init %s protocol, baud %d, modbusAddr %d", getName(), getBaudRate(), getModbusAddr()); serial.begin(getBaudRate(), SWSERIAL_8N1); - mCom.getModbusMaster().begin(_modbusAddr, serial); + mCom.getModbusMaster()->begin(getModbusAddr(), serial); } bool ModbusDevice::retrieveModel(MODBUS_COM &mCom, char *modelBuffer, size_t bufferSize) diff --git a/src/modbus/device/modbus_device.h b/src/modbus/device/modbus_device.h index 7ec6a51..d9c1b88 100644 --- a/src/modbus/device/modbus_device.h +++ b/src/modbus/device/modbus_device.h @@ -16,9 +16,9 @@ class ModbusDevice virtual const modbus_register_t *getLiveRegisters() const = 0; virtual const modbus_register_t *getStaticRegisters() const = 0; - virtual long getBaudRate(); - virtual long getModbusAddr(); - virtual protocol_type_t getProtocol(); + virtual long getBaudRate() const; + virtual long getModbusAddr() const; + virtual protocol_type_t getProtocol() const; // Pure virtual function for getting the name virtual const char *getName() const = 0; diff --git a/src/modbus/device/must_pv_ph18.cpp b/src/modbus/device/must_pv_ph18.cpp index aa220a2..6a407c6 100644 --- a/src/modbus/device/must_pv_ph18.cpp +++ b/src/modbus/device/must_pv_ph18.cpp @@ -1,10 +1,6 @@ #include "must_pv_ph18.h" -const long MustPV_PH18::_baudRate = 19200; -const uint32_t MustPV_PH18::_modbusAddr = 4; -const protocol_type_t MustPV_PH18::_protocol = MODBUS_MUST; -const char *const MustPV_PH18::_name = "MUST PV/PH 18"; const modbus_register_t *MustPV_PH18::getLiveRegisters() const { @@ -20,57 +16,6 @@ const char *MustPV_PH18::getName() const { return _name; } - -const modbus_register_t MustPV_PH18::registers_live[] = { - - {25201, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, DESCR_LIVE_INVERTER_OPERATION_MODE, 0, {.bitfield = { - "Power On", - "Self Test", - "OffGrid", - "GridTie", - "ByPass", - "Stop", - "GridCharging", - }}}, - - {25205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_BATTERY_VOLTAGE, 0}, - {25206, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "AC_out_Voltage", 0}, - {25207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "AC_in_Voltage", 0}, - {25208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Inverter_Bus_Voltage", 0}, - {25225, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "AC_out_Frequenz", 0}, - {25226, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "AC_in_Frequenz", 0}, - - {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Output_load_percent", 0}, - {15205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_VOLTAGE, 0}, - {15208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "PV_Charging_Power", 0}, - {15207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_CURRENT, 0}, - {25233, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Inverter_Bus_Temperature", 0}, - {25234, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Transformer_temperature", 0}, - {15209, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "MPPT1_Charger_Temperature", 0}, - - {25215, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_out_Watt", 0}, // W - {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_out_percent", 0}, //% - - {25274, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "Battery_Load", 0}, -}; - -const modbus_register_t MustPV_PH18::registers_static[] = { - {10110, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "Battery_type", 0, {.bitfield = { - "No choose", - "User defined", - "Lithium", - "Sealed Lead", - "AGM", - "GEL", - "Flooded", - }}}, - {10103, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Battery_float_voltage", 0}, -}; - -const modbus_register_t MustPV_PH18::registers_device_model[] = { - {20000, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, DEVICE_MODEL_HIGH, 0}, - {20001, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DEVICE_MODEL_LOW, 0}}; - bool MustPV_PH18::retrieveModel(MODBUS_COM &mCom, char *modelBuffer, size_t bufferSize) { modelBuffer[0] = '\0'; // Clear the buffer diff --git a/src/modbus/device/must_pv_ph18.h b/src/modbus/device/must_pv_ph18.h index 6c826e1..4c469ad 100644 --- a/src/modbus/device/must_pv_ph18.h +++ b/src/modbus/device/must_pv_ph18.h @@ -1,14 +1,14 @@ #ifndef MODBUS_MUST_PV_PH18_H #define MODBUS_MUST_PV_PH18_H -#include "modbus_device.h" +#include "modbus_device.h" #define DEVICE_MODEL_HIGH "Device_Model_Hight" #define DEVICE_MODEL_LOW "Device_Model_Low" class MustPV_PH18 : public ModbusDevice { -public: +public: MustPV_PH18() : ModbusDevice(_baudRate, _modbusAddr, _protocol) {} virtual const modbus_register_t *getLiveRegisters() const override; @@ -19,14 +19,59 @@ class MustPV_PH18 : public ModbusDevice size_t getStaticRegistersCount() const override; private: - static const long _baudRate; - static const uint32_t _modbusAddr; - static const protocol_type_t _protocol; - static const char *const _name; - - static const modbus_register_t registers_live[]; - static const modbus_register_t registers_static[]; - static const modbus_register_t registers_device_model[]; + static const long _baudRate = 19200; + static const uint32_t _modbusAddr = 4; + static const protocol_type_t _protocol = MODBUS_MUST; + inline static const char *const _name = "MUST PV/PH 18"; + + inline static const modbus_register_t registers_live[] = { + {25201, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, DESCR_LIVE_INVERTER_OPERATION_MODE, 0, {.bitfield = { + "Power On", + "Self Test", + "OffGrid", + "GridTie", + "ByPass", + "Stop", + "GridCharging", + }}}, + + {25205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_BATTERY_VOLTAGE, 0}, + {25206, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "AC_out_Voltage", 0}, + {25207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "AC_in_Voltage", 0}, + {25208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Inverter_Bus_Voltage", 0}, + {25225, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "AC_out_Frequenz", 0}, + {25226, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "AC_in_Frequenz", 0}, + + {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Output_load_percent", 0}, + {15205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_VOLTAGE, 0}, + {15208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "PV_Charging_Power", 0}, + {15207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_CURRENT, 0}, + {25233, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Inverter_Bus_Temperature", 0}, + {25234, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Transformer_temperature", 0}, + {15209, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "MPPT1_Charger_Temperature", 0}, + + {25215, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_out_Watt", 0}, // W + {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_out_percent", 0}, //% + + {25274, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "Battery_Load", 0}, + }; + + inline static const modbus_register_t registers_static[] = { + {10110, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "Battery_type", 0, {.bitfield = { + "No choose", + "User defined", + "Lithium", + "Sealed Lead", + "AGM", + "GEL", + "Flooded", + }}}, + {10103, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Battery_float_voltage", 0}, + }; + + inline static const modbus_register_t registers_device_model[] = { + {20000, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, DEVICE_MODEL_HIGH, 0}, + {20001, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DEVICE_MODEL_LOW, 0}}; }; #endif diff --git a/src/modbus/modbus.cpp b/src/modbus/modbus.cpp index 2bb9eb6..a17787f 100644 --- a/src/modbus/modbus.cpp +++ b/src/modbus/modbus.cpp @@ -1,16 +1,13 @@ // #define isDEBUG #include "modbus.h" - -extern void writeLog(const char *format, ...); - + //---------------------------------------------------------------------- // Public Functions //---------------------------------------------------------------------- MODBUS::MODBUS(SoftwareSerial *port) { - my_serialIntf = port; - + my_serialIntf = port; } bool MODBUS::Init() @@ -34,12 +31,12 @@ void MODBUS::prepareRegisters() live_info = { .variant = &liveData, .registers = registers_live, - .array_size = sizeof(registers_live) / sizeof(modbus_register_t), + .array_size = device->getLiveRegistersCount(), .curr_register = 0}; static_info = { .variant = &staticData, .registers = registers_static, - .array_size = sizeof(registers_static) / sizeof(modbus_register_t), + .array_size = device->getStaticRegistersCount(), .curr_register = 0}; previousTime = millis(); } @@ -104,13 +101,11 @@ protocol_type_t MODBUS::autoDetect() // function for autodetect the inverter typ writeLog("Try Autodetect Modbus device"); - ModbusDevice *devices[] = {new MustPV_PH18()};//, new DEYE()}; + ModbusDevice *devices[] = {new MustPV_PH18(), new Deye()}; for (size_t i = 0; i < sizeof(devices) / sizeof(devices[0]); ++i) { - devices[i]->init(*my_serialIntf, _mCom); - - writeLog("Try to use: %s protocol", devices[i]->getName()); + devices[i]->init(*my_serialIntf, _mCom); devices[i]->retrieveModel(_mCom, modelName, sizeof(modelName)); if (strlen(modelName) != 0) { diff --git a/src/modbus/modbus.h b/src/modbus/modbus.h index 62faf7f..ed85b39 100644 --- a/src/modbus/modbus.h +++ b/src/modbus/modbus.h @@ -6,7 +6,7 @@ #include "modbus_com.h" #include "device/modbus_device.h" #include "device/must_pv_ph18.h" -// #include "device/deye.h" +#include "device/deye.h" extern JsonObject deviceJson; extern JsonObject staticData; diff --git a/src/modbus/modbus_com.cpp b/src/modbus/modbus_com.cpp index 6174587..cee3e8c 100644 --- a/src/modbus/modbus_com.cpp +++ b/src/modbus/modbus_com.cpp @@ -1,12 +1,13 @@ -// #define isDEBUG -#include "modbus_com.h" -extern void writeLog(const char *format, ...); +#include "modbus_com.h" + //---------------------------------------------------------------------- // Public Functions //---------------------------------------------------------------------- +extern void writeLog(const char *format, ...); + int _dir_pin; MODBUS_COM::MODBUS_COM() @@ -61,8 +62,8 @@ void MODBUS_COM::postTransmission() } } - ModbusMaster MODBUS_COM::getModbusMaster(){ - return _mb; + ModbusMaster * MODBUS_COM::getModbusMaster(){ + return &_mb; } bool MODBUS_COM::getModbusResultMsg(uint8_t result) diff --git a/src/modbus/modbus_com.h b/src/modbus/modbus_com.h index e60a509..e457969 100644 --- a/src/modbus/modbus_com.h +++ b/src/modbus/modbus_com.h @@ -19,7 +19,15 @@ typedef enum READ_FAIL = 0, READ_OK = 1, } response_type_t; - + + +/** + * @class MODBUS_COM + * @brief This class is responsible for Modbus communication, managing the RS485 transceiver, and interacting with a Modbus master. + * + * The class is designed to handle communication through Modbus over an RS485 network. It supports reading Modbus registers and converting them to JSON format. + * The communication direction (transmission/reception) is managed through digital pins, and the class provides helper functions for decoding various register types. + */ class MODBUS_COM { public: @@ -28,7 +36,8 @@ class MODBUS_COM bool readModbusRegisterToJson(const modbus_register_t *reg, JsonObject *variant); response_type_t parseModbusToJson(modbus_register_info_t ®ister_info, bool skip_reg_on_error = true); bool isAllRegistersRead(modbus_register_info_t ®ister_info); - ModbusMaster getModbusMaster(); + ModbusMaster *getModbusMaster(); + private: String toBinary(uint16_t input); bool decodeDiematicDecimal(uint16_t int_input, int8_t decimals, float *value_ptr); diff --git a/src/modbus/modbus_registers.h b/src/modbus/modbus_registers.h index c5e38f2..b5ef3a1 100644 --- a/src/modbus/modbus_registers.h +++ b/src/modbus/modbus_registers.h @@ -48,8 +48,8 @@ typedef struct { JsonObject *variant; const modbus_register_t *registers; - uint8_t array_size; - uint8_t curr_register; + size_t array_size; + size_t curr_register; } modbus_register_info_t; #endif // SRC_MODBUS_REGISTERS_H_ \ No newline at end of file From b4be709cc40be8324b16ef9329cd2882d026b67b Mon Sep 17 00:00:00 2001 From: Artem Kovalenko Date: Fri, 11 Oct 2024 10:59:16 +0300 Subject: [PATCH 06/13] add more register for must --- src/PI_Serial/QPIGS.h | 8 +-- src/descriptors.h | 2 +- src/modbus/device/deye.h | 16 +++--- src/modbus/device/must_pv_ph18.cpp | 10 +++- src/modbus/device/must_pv_ph18.h | 78 +++++++++++++++++++----------- src/modbus/modbus_com.cpp | 35 +++++++++++--- src/modbus/modbus_registers.h | 13 ++++- 7 files changed, 111 insertions(+), 51 deletions(-) diff --git a/src/PI_Serial/QPIGS.h b/src/PI_Serial/QPIGS.h index 50a63de..2e5f040 100644 --- a/src/PI_Serial/QPIGS.h +++ b/src/PI_Serial/QPIGS.h @@ -20,7 +20,7 @@ static const char *const qpigsList[][24] = { "Status_Flag", // b0-b7 "Battery_voltage_offset_fans_on", // QQ "EEPROM_Version", // VV - "PV_Charging_Power", // MMMM + DESCR_LIVE_PV_CHARGING_POWER, // MMMM "Device_Status", // b8-b10 "Solar_feed_to_Grid_status", // Y "Country", // ZZ @@ -65,7 +65,7 @@ static const char *const qallList[] = { "Battery_Discharge_Current", // KKK DESCR_LIVE_PV_INPUT_VOLTAGE, // LLL DESCR_LIVE_PV_INPUT_CURRENT, // MM.M - "PV_Charging_Power", // NNNN + DESCR_LIVE_PV_CHARGING_POWER, // NNNN "PV_generation_day", // OOOOOO "PV_generation_sum", // PPPPPP "Inverter_Operation_Mode", // Q @@ -249,8 +249,8 @@ bool PI_Serial::PIXX_QPIGS() // make some things pretty liveData[DESCR_LIVE_PV_INPUT_VOLTAGE] = (liveData["PV1_Input_Voltage"].as() + liveData["PV2_Input_Voltage"].as()); - liveData["PV_Charging_Power"] = (liveData["PV1_Input_Power"].as() + liveData["PV2_Input_Power"].as()); - liveData["PV_Input_Current"] = (int)((liveData["PV_Charging_Power"].as() / (liveData[DESCR_LIVE_PV_INPUT_VOLTAGE].as()+0.5)) * 100) / 100.0; + liveData[DESCR_LIVE_PV_CHARGING_POWER] = (liveData["PV1_Input_Power"].as() + liveData["PV2_Input_Power"].as()); + liveData["PV_Input_Current"] = (int)((liveData[DESCR_LIVE_PV_CHARGING_POWER].as() / (liveData[DESCR_LIVE_PV_INPUT_VOLTAGE].as()+0.5)) * 100) / 100.0; liveData["Battery_Load"] = (liveData["Battery_Charge_Current"].as() - liveData["Battery_Discharge_Current"].as()); } return true; diff --git a/src/descriptors.h b/src/descriptors.h index 0875dd6..27ba249 100644 --- a/src/descriptors.h +++ b/src/descriptors.h @@ -37,7 +37,7 @@ #define DESCR_LIVE_AC_IN_GENERATION_MONTH "AC_in_generation_month" #define DESCR_LIVE_AC_IN_GENERATION_SUM "AC_in_generation_sum" #define DESCR_LIVE_AC_IN_GENERATION_YEAR "AC_in_generation_year" -#define DESCR_LIVE_AC_IN_VOLTAGE "AC_in_Voltage" +#define DESCR_LIVE_AC_IN_VOLTAGE "AC_in_Voltage" #define DESCR_LIVE_AC_OUT_FREQUENZ "AC_out_Frequenz" #define DESCR_LIVE_AC_OUT_PERCENT "AC_out_percent" #define DESCR_LIVE_AC_OUT_VA "AC_out_VA" diff --git a/src/modbus/device/deye.h b/src/modbus/device/deye.h index 540d541..3eb8810 100644 --- a/src/modbus/device/deye.h +++ b/src/modbus/device/deye.h @@ -30,22 +30,22 @@ class Deye : public ModbusDevice "Fault", }}}, - {109, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_VOLTAGE, 0}, - {110, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_CURRENT, 0}, - {186, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_PV_INPUT_POWER, 0}, + {109, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_VOLTAGE}, + {110, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_CURRENT}, + {186, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_PV_INPUT_POWER}, - {175, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_OUTPUT_POWER, 0}, - {178, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_AC_OUTPUT_POWER, 0}, + {175, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_OUTPUT_POWER}, + {178, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_AC_OUTPUT_POWER}, {90, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_INVERTER_TEMPERATURE, -100}, {182, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_BATTERY_TEMPERATURE, -100}, - {183, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, DESCR_LIVE_BATTERY_VOLTAGE, 0}, - {184, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_BATTERY_PERCENT, 0}, + {183, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, DESCR_LIVE_BATTERY_VOLTAGE}, + {184, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_BATTERY_PERCENT}, }; inline static const modbus_register_t registers_static[] = { - {16, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U32_ONE_DECIMAL, DESCR_STAT_AC_OUT_RATING_ACTIVE_POWER, 0}, + {16, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U32_ONE_DECIMAL, DESCR_STAT_AC_OUT_RATING_ACTIVE_POWER}, }; inline static const modbus_register_t registers_device_serial[] = { diff --git a/src/modbus/device/must_pv_ph18.cpp b/src/modbus/device/must_pv_ph18.cpp index 6a407c6..7641da2 100644 --- a/src/modbus/device/must_pv_ph18.cpp +++ b/src/modbus/device/must_pv_ph18.cpp @@ -1,7 +1,5 @@ #include "must_pv_ph18.h" - - const modbus_register_t *MustPV_PH18::getLiveRegisters() const { return registers_live; @@ -52,4 +50,12 @@ size_t MustPV_PH18::getLiveRegistersCount() const size_t MustPV_PH18::getStaticRegistersCount() const { return sizeof(registers_static) / sizeof(modbus_register_t); +} + +void MustPV_PH18::generationSum(JsonObject *variant, uint16_t *registerValue, const modbus_register_t *reg, MODBUS_COM &mCom) +{ + if ((*variant).containsKey(MUST_DEVICE_CHARGER_HIGH) && (*variant).containsKey(MUST_DEVICE_CHARGER_LOW)) + { + (*variant)[reg->name] = (*variant)[MUST_DEVICE_CHARGER_HIGH].as() * 1000 + (*variant)[MUST_DEVICE_CHARGER_LOW].as(); + } } \ No newline at end of file diff --git a/src/modbus/device/must_pv_ph18.h b/src/modbus/device/must_pv_ph18.h index 4c469ad..1883345 100644 --- a/src/modbus/device/must_pv_ph18.h +++ b/src/modbus/device/must_pv_ph18.h @@ -3,12 +3,15 @@ #include "modbus_device.h" -#define DEVICE_MODEL_HIGH "Device_Model_Hight" -#define DEVICE_MODEL_LOW "Device_Model_Low" +#define DEVICE_MODEL_HIGH "must_device_model_h" +#define DEVICE_MODEL_LOW "must_device_model_l" + +#define MUST_DEVICE_CHARGER_HIGH "ch_h" +#define MUST_DEVICE_CHARGER_LOW "ch_l" class MustPV_PH18 : public ModbusDevice { -public: +public: MustPV_PH18() : ModbusDevice(_baudRate, _modbusAddr, _protocol) {} virtual const modbus_register_t *getLiveRegisters() const override; @@ -17,15 +20,17 @@ class MustPV_PH18 : public ModbusDevice bool retrieveModel(MODBUS_COM &mCom, char *modelBuffer, size_t bufferSize) override; size_t getLiveRegistersCount() const override; size_t getStaticRegistersCount() const override; + static void generationSum(JsonObject *variant, uint16_t *registerValue, const modbus_register_t *reg, MODBUS_COM &mCom); private: - static const long _baudRate = 19200; - static const uint32_t _modbusAddr = 4; - static const protocol_type_t _protocol = MODBUS_MUST; + static const long _baudRate = 19200; + static const uint32_t _modbusAddr = 4; + static const protocol_type_t _protocol = MODBUS_MUST; inline static const char *const _name = "MUST PV/PH 18"; inline static const modbus_register_t registers_live[] = { - {25201, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, DESCR_LIVE_INVERTER_OPERATION_MODE, 0, {.bitfield = { + {0, MODBUS_TYPE_HOLDING, REGISTER_TYPE_VIRTUAL_CALLBACK, DESCR_LIVE_PV_GENERATION_SUM, 0, {}, MustPV_PH18::generationSum}, + {15201, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, DESCR_LIVE_INVERTER_OPERATION_MODE, 0, {.bitfield = { "Power On", "Self Test", "OffGrid", @@ -34,26 +39,43 @@ class MustPV_PH18 : public ModbusDevice "Stop", "GridCharging", }}}, + {15205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_VOLTAGE}, + {15207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_CURRENT}, + {15208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_PV_CHARGING_POWER}, + {15209, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_MPPT1_CHARGER_TEMPERATURE}, + {15212, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "PV_Relay"}, + {15217, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, MUST_DEVICE_CHARGER_HIGH}, + {15218, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, MUST_DEVICE_CHARGER_LOW}, + {15219, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_PV_GENERATION_DAY}, - {25205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_BATTERY_VOLTAGE, 0}, - {25206, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "AC_out_Voltage", 0}, - {25207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "AC_in_Voltage", 0}, - {25208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Inverter_Bus_Voltage", 0}, - {25225, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "AC_out_Frequenz", 0}, - {25226, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "AC_in_Frequenz", 0}, - - {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Output_load_percent", 0}, - {15205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_VOLTAGE, 0}, - {15208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "PV_Charging_Power", 0}, - {15207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_CURRENT, 0}, - {25233, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Inverter_Bus_Temperature", 0}, - {25234, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Transformer_temperature", 0}, - {15209, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "MPPT1_Charger_Temperature", 0}, - - {25215, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_out_Watt", 0}, // W - {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_out_percent", 0}, //% - - {25274, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "Battery_Load", 0}, + {25201, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "PV_Charger_Workstate", 0, {.bitfield = {"Initializtion", "Self Test", "Work", "Stop"}}}, + {25202, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "PV_Charger_MPPT_State", 0, {.bitfield = { + "Stop", + "MPPT", + "Current Limit", + }}}, + {25203, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "PV_Charger_Charge_State", 0, {.bitfield = { + "Stop", + "Absorb", + "Float", + "EQ", + }}}, + {25205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_BATTERY_VOLTAGE}, + {25206, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_AC_OUT_VOLTAGE}, + {25207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_AC_IN_VOLTAGE}, + {25208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_INVERTER_BUS_VOLTAGE}, + {25210, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_OUTPUT_CURRENT}, + {25211, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_AC_OUTPUT_CURRENT}, + {25212, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Inverter_Load_Current"}, + {25213, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_OUTPUT_POWER}, + {25214, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_in_Power"}, + {25215, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_AC_OUT_WATT}, // W + {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_OUTPUT_LOAD_PERCENT}, + {25225, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, DESCR_LIVE_AC_OUT_FREQUENZ}, + {25226, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, DESCR_LIVE_AC_IN_FREQUENZ}, + {25233, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_INVERTER_BUS_TEMPERATURE}, + {25234, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_TRANSFORMER_TEMPERATURE}, + {25274, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_BATTERY_LOAD}, }; inline static const modbus_register_t registers_static[] = { @@ -66,11 +88,11 @@ class MustPV_PH18 : public ModbusDevice "GEL", "Flooded", }}}, - {10103, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Battery_float_voltage", 0}, + {10103, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Battery_float_voltage"}, }; inline static const modbus_register_t registers_device_model[] = { - {20000, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, DEVICE_MODEL_HIGH, 0}, + {20000, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, DEVICE_MODEL_HIGH}, {20001, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DEVICE_MODEL_LOW, 0}}; }; diff --git a/src/modbus/modbus_com.cpp b/src/modbus/modbus_com.cpp index cee3e8c..edb62ac 100644 --- a/src/modbus/modbus_com.cpp +++ b/src/modbus/modbus_com.cpp @@ -1,6 +1,5 @@ #include "modbus_com.h" - //---------------------------------------------------------------------- // Public Functions @@ -8,7 +7,7 @@ extern void writeLog(const char *format, ...); -int _dir_pin; +int _dir_pin; MODBUS_COM::MODBUS_COM() { @@ -17,7 +16,7 @@ MODBUS_COM::MODBUS_COM() if (strcmp(HWBOARD, "esp01_1m") == 0) { _dir_pin = RS485_ESP01_DIR_PIN; - } + } // Init in receive mode if (strcmp(HWBOARD, "esp12e") == 0) @@ -62,9 +61,10 @@ void MODBUS_COM::postTransmission() } } - ModbusMaster * MODBUS_COM::getModbusMaster(){ +ModbusMaster *MODBUS_COM::getModbusMaster() +{ return &_mb; - } +} bool MODBUS_COM::getModbusResultMsg(uint8_t result) { @@ -179,6 +179,19 @@ bool MODBUS_COM::readModbusRegisterToJson(const modbus_register_t *reg, JsonObje // writeLog("Register id=%d type=0x%x name=%s", reg->id, reg->type, reg->name); uint16_t raw_value = 0; + if (reg->type == REGISTER_TYPE_VIRTUAL_CALLBACK) + { + if (reg->callback != nullptr) + { + reg->callback(variant, 0, reg, *(this)); + } + else + { + writeLog("No callback specified for %s", reg->name); + } + return true; + } + float final_value; uint16_t readBytes = 1; @@ -285,6 +298,16 @@ bool MODBUS_COM::readModbusRegisterToJson(const modbus_register_t *reg, JsonObje (*variant)[reg->name] = out; break; } + case REGISTER_TYPE_CALLBACK: + if (reg->callback != nullptr) + { + reg->callback(variant, 0, reg, *(this)); + } + else + { + writeLog("No callback specified for %s", reg->name); + } + break; case REGISTER_TYPE_DEBUG: writeLog("Raw DEBUG value: %s=%#06x %s", reg->name, raw_value, toBinary(raw_value).c_str()); @@ -308,7 +331,7 @@ bool MODBUS_COM::readModbusRegisterToJson(const modbus_register_t *reg, JsonObje response_type_t MODBUS_COM::parseModbusToJson(modbus_register_info_t ®ister_info, bool skip_reg_on_error) { - writeLog("parseModbusToJson %d", register_info.array_size); + // writeLog("parseModbusToJson %d", register_info.array_size); if (register_info.curr_register >= register_info.array_size) { register_info.curr_register = 0; diff --git a/src/modbus/modbus_registers.h b/src/modbus/modbus_registers.h index b5ef3a1..447c13b 100644 --- a/src/modbus/modbus_registers.h +++ b/src/modbus/modbus_registers.h @@ -26,21 +26,30 @@ typedef enum REGISTER_TYPE_BITFIELD, REGISTER_TYPE_DEBUG, REGISTER_TYPE_CUSTOM_VAL_NAME, + REGISTER_TYPE_CALLBACK, /*call custom callback after register read*/ + REGISTER_TYPE_VIRTUAL_CALLBACK, /*allows to call callback without register read*/ } register_type_t; + + typedef union { const char *bitfield[16]; } optional_param_t; + +class MODBUS_COM; -typedef struct +typedef void (*modbus_callback_t)( JsonObject *variant, uint16_t *registerValue, const struct modbus_register_t *reg, MODBUS_COM &mCom); // Define a function pointer type for the callback + +typedef struct modbus_register_t { uint16_t id; modbus_entity_t modbus_entity; /*!< Type of modbus parameter */ register_type_t type; /*!< Float, U8, U16, U32, ASCII, etc. */ const char *name; - int16_t offset; + int16_t offset = 0; optional_param_t optional_param; + modbus_callback_t callback; } modbus_register_t; From 586f2ca30bef94afba792afb6834e4d6864214a9 Mon Sep 17 00:00:00 2001 From: Artem Kovalenko Date: Mon, 14 Oct 2024 10:04:33 +0300 Subject: [PATCH 07/13] simplify elements mapping in frontend. Display all available parameters on man page --- src/modbus/device/must_pv_ph18.h | 4 +- src/webpages/HTML_HEAD.html | 8 +++- src/webpages/HTML_MAIN.html | 81 ++++++++++++++++---------------- 3 files changed, 49 insertions(+), 44 deletions(-) diff --git a/src/modbus/device/must_pv_ph18.h b/src/modbus/device/must_pv_ph18.h index 1883345..d84d028 100644 --- a/src/modbus/device/must_pv_ph18.h +++ b/src/modbus/device/must_pv_ph18.h @@ -68,9 +68,9 @@ class MustPV_PH18 : public ModbusDevice {25211, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_AC_OUTPUT_CURRENT}, {25212, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Inverter_Load_Current"}, {25213, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_OUTPUT_POWER}, - {25214, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "AC_in_Power"}, + {25214, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "AC_in_Power"}, {25215, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_AC_OUT_WATT}, // W - {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_OUTPUT_LOAD_PERCENT}, + {25216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_AC_OUT_PERCENT}, {25225, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, DESCR_LIVE_AC_OUT_FREQUENZ}, {25226, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, DESCR_LIVE_AC_IN_FREQUENZ}, {25233, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_INVERTER_BUS_TEMPERATURE}, diff --git a/src/webpages/HTML_HEAD.html b/src/webpages/HTML_HEAD.html index b3754c9..61e5af1 100644 --- a/src/webpages/HTML_HEAD.html +++ b/src/webpages/HTML_HEAD.html @@ -17,6 +17,12 @@ %pre_device_name% + + @@ -24,4 +30,4 @@ We're sorry but it doesn't work properly without JavaScript enabled. Please enable it to continue. -
+
\ No newline at end of file diff --git a/src/webpages/HTML_MAIN.html b/src/webpages/HTML_MAIN.html index 8d490fb..88b17f7 100644 --- a/src/webpages/HTML_MAIN.html +++ b/src/webpages/HTML_MAIN.html @@ -98,8 +98,7 @@

%pre_device_name%

var alertListArr = []; var alertListitem = 0; var kickRefresh = true; - var dataFields = document.getElementsByClassName("dF"); - var dataRows = document.getElementsByClassName("dR"); + var dataFields = document.querySelectorAll('.dR'); function initWebSocket() { //console.log('Trying to open a WebSocket connection...'); @@ -124,50 +123,50 @@

%pre_device_name%

} function onMessage(event) { var data = JSON.parse(event.data); - document.getElementById("packSOC").innerHTML = data.Battery_Percent + '%%'; - $('#packSOC').width(data.Battery_Percent + '%%').attr('aria-valuenow', data.Battery_Percent); - - document.getElementById("pv_volt").innerHTML = data.PV_Input_Voltage + 'V '; - document.getElementById("pv_current").innerHTML = data.PV_Input_Current + 'A '; - document.getElementById("pv_power").innerHTML = Math.round(data.PV_Charging_Power) + 'W '; - - document.getElementById("pv2_volt").innerHTML = data.PV2_Input_Voltage + 'V '; - document.getElementById("pv2_current").innerHTML = data.PV2_Input_Current + 'A '; - document.getElementById("pv2_power").innerHTML = Math.round(data.PV2_Charging_Power) + 'W '; - - document.getElementById("grid_volt").innerHTML = data.AC_in_Voltage + 'V '; - document.getElementById("grid_hz").innerHTML = data.AC_in_Frequenz + 'Hz '; - - document.getElementById("ac_out_volt").innerHTML = data.AC_out_Voltage + 'V '; - document.getElementById("ac_out_hz").innerHTML = data.AC_out_Frequenz + 'Hz '; - - document.getElementById("ac_out_power").innerHTML = data.AC_out_Watt + 'W '; - document.getElementById("ac_out_percent").innerHTML = data.AC_out_percent + '%%'; + const mappings = [ + ["packSOC", "Battery_Percent", "%%"], + ["pv_volt", "PV_Input_Voltage", "V"], + ["pv_current", "PV_Input_Current", "A"], + ["pv_power", "PV_Charging_Power", "W", true], // 'true' indicates rounding + ["pv2_volt", "PV2_Input_Voltage", "V"], + ["pv2_current", "PV2_Input_Current", "A"], + ["pv2_power", "PV2_Charging_Power", "W", true], + ["grid_volt", "AC_in_Voltage", "V"], + ["grid_hz", "AC_in_Frequenz", "Hz"], + ["ac_out_volt", "AC_out_Voltage", "V"], + ["ac_out_hz", "AC_out_Frequenz", "Hz"], + ["ac_out_power", "AC_out_Watt", "W"], + ["ac_out_percent", "AC_out_percent", "%%"], + ["heatsink_temp", "Inverter_Bus_Temperature", "°C"], + ["batt_volt", "Battery_Voltage", "V"], + ["batt_percent", "Battery_Percent", "%%"], + ["batt_load", "Battery_Load", "A"], + ["ivmode", "Inverter_Operation_Mode", ""] + ]; + + mappings.forEach(([id, dataKey, unit, round = false]) => { + const value = round ? Math.round(data[dataKey]) : data[dataKey]; + document.getElementById(id).innerHTML = value + unit; + }); - document.getElementById("heatsink_temp").innerHTML = data.Inverter_Bus_Temperature + '°C '; - document.getElementById("batt_volt").innerHTML = data.Battery_Voltage + 'V '; - document.getElementById("batt_percent").innerHTML = data.Battery_Percent + '%% '; - document.getElementById("batt_load").innerHTML = data.Battery_Load + 'A '; - - document.getElementById("ivmode").innerHTML = data.Inverter_Operation_Mode; + $('#packSOC').width(data.Battery_Percent + '%%').attr('aria-valuenow', data.Battery_Percent); - for (var i = 0; i < dataFields.length; i++) { - if (dataFields[i].innerHTML.indexOf("undefined") > -1) { - dataFields[i].style.display = 'none'; - } else { - dataFields[i].style.display = ''; + dataFields.forEach(dRDiv => { + const spans = dRDiv.querySelectorAll('.dF span'); + let allUndefined = true; + if (spans.length) { + spans.forEach(span => { + const isInvalid = span.innerHTML.startsWith('undefined') || span.innerHTML.startsWith('NaN'); + span.style.display = isInvalid ? 'none' : ''; + if (!isInvalid) allUndefined = false; + }); } - } - - for (var i = 0; i < dataRows.length; i++) { - if (dataRows[i].innerHTML.indexOf("undefined") > -1) { - dataRows[i].style.display = 'none'; - } else { - dataRows[i].style.display = ''; + else { + allUndefined = dRDiv.querySelector('.dF').innerHTML.startsWith('undefined'); } - } - + dRDiv.style.display = allUndefined ? 'none' : ''; + }); } /* From 3c8bb0e2e072df3a4663228c0182d5696f1566aa Mon Sep 17 00:00:00 2001 From: Artem Kovalenko Date: Mon, 21 Oct 2024 11:52:51 +0300 Subject: [PATCH 08/13] fix must registers --- src/modbus/device/must_pv_ph18.h | 4 ++-- src/modbus/modbus_com.cpp | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/modbus/device/must_pv_ph18.h b/src/modbus/device/must_pv_ph18.h index d84d028..78a820d 100644 --- a/src/modbus/device/must_pv_ph18.h +++ b/src/modbus/device/must_pv_ph18.h @@ -49,12 +49,12 @@ class MustPV_PH18 : public ModbusDevice {15219, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_PV_GENERATION_DAY}, {25201, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "PV_Charger_Workstate", 0, {.bitfield = {"Initializtion", "Self Test", "Work", "Stop"}}}, - {25202, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "PV_Charger_MPPT_State", 0, {.bitfield = { + {15202, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "PV_Charger_MPPT_State", 0, {.bitfield = { "Stop", "MPPT", "Current Limit", }}}, - {25203, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "PV_Charger_Charge_State", 0, {.bitfield = { + {15203, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "PV_Charger_Charge_State", 0, {.bitfield = { "Stop", "Absorb", "Float", diff --git a/src/modbus/modbus_com.cpp b/src/modbus/modbus_com.cpp index edb62ac..a1eeef1 100644 --- a/src/modbus/modbus_com.cpp +++ b/src/modbus/modbus_com.cpp @@ -203,7 +203,7 @@ bool MODBUS_COM::readModbusRegisterToJson(const modbus_register_t *reg, JsonObje if (getModbusValue(reg->id, reg->modbus_entity, &raw_value, readBytes)) { - writeLog("Raw value: %s=%#06x\n", reg->name, raw_value); + //writeLog("Raw value: %s=%#06x\n", reg->name, raw_value); switch (reg->type) { @@ -257,7 +257,7 @@ bool MODBUS_COM::readModbusRegisterToJson(const modbus_register_t *reg, JsonObje break; } const uint8_t bit_value = raw_value >> j & 1; - writeLog(" [bit%02d] %s=%d", j, bit_varname, bit_value); + //writeLog(" [bit%02d] %s=%d", j, bit_varname, bit_value); (*variant)[bit_varname] = bit_value; } break; @@ -283,7 +283,7 @@ bool MODBUS_COM::readModbusRegisterToJson(const modbus_register_t *reg, JsonObje } if (!isfound) { - writeLog("CUSTOM_VAL_NAME not found for raw_value=%d", raw_value); + writeLog("CUSTOM_VAL_NAME not found for raw_value=%d register id=%d type=0x%x name=%s",raw_value, reg->id, reg->type, reg->name); } break; } From adacc03b281eda8b44a5c569d168e18b9b182709 Mon Sep 17 00:00:00 2001 From: Artem Kovalenko Date: Mon, 21 Oct 2024 12:09:51 +0300 Subject: [PATCH 09/13] replace descriptor from define to static pointer --- src/descriptors.h | 172 +++++++++++++++++++++++----------------------- 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/src/descriptors.h b/src/descriptors.h index 27ba249..9a655a3 100644 --- a/src/descriptors.h +++ b/src/descriptors.h @@ -1,92 +1,92 @@ #ifndef DESCRIPTORS_H #define DESCRIPTORS_H -#define DESCR_STAT_AC_IN_RATING_CURRENT "AC_in_rating_current" -#define DESCR_STAT_AC_IN_RATING_VOLTAGE "AC_in_rating_voltage" -#define DESCR_STAT_AC_OUT_RATING_ACTIVE_POWER "AC_out_rating_active_power" -#define DESCR_STAT_AC_OUT_RATING_APPARENT_POWER "AC_out_rating_apparent_power" -#define DESCR_STAT_AC_OUT_RATING_CURRENT "AC_out_rating_current" -#define DESCR_STAT_AC_OUT_RATING_FREQUENCY "AC_out_rating_frequency" -#define DESCR_STAT_AC_OUT_RATING_VOLTAGE "AC_out_rating_voltage" -#define DESCR_STAT_BATTERY_BULK_VOLTAGE "Battery_bulk_voltage" -#define DESCR_STAT_BATTERY_FLOAT_VOLTAGE "Battery_float_voltage" -#define DESCR_STAT_BATTERY_RATING_VOLTAGE "Battery_rating_voltage" -#define DESCR_STAT_BATTERY_RE_CHARGE_VOLTAGE "Battery_re-charge_voltage" -#define DESCR_STAT_BATTERY_RE_DISCHARGE_VOLTAGE "Battery_re-discharge_voltage" -#define DESCR_STAT_BATTERY_TYPE "Battery_type" -#define DESCR_STAT_BATTERY_UNDER_VOLTAGE "Battery_under_voltage" -#define DESCR_STAT_CHARGER_SOURCE_PRIORITY "Charger_source_priority" -#define DESCR_STAT_CURRENT_MAX_AC_CHARGING_CURRENT "Current_max_AC_charging_current" -#define DESCR_STAT_CURRENT_MAX_CHARGING_CURRENT "Current_max_charging_current" -#define DESCR_STAT_DEVICE_MODEL "Device_Model" -#define DESCR_STAT_INPUT_VOLTAGE_RANGE "Input_voltage_range" -#define DESCR_STAT_MACHINE_TYPE "Machine_type" -#define DESCR_STAT_MAX_CHARGING_TIME_AT_CV_STAGE "Max_charging_time_at_CV_stage" -#define DESCR_STAT_MAX_DISCHARGING_CURRENT "Max_discharging_current" -#define DESCR_STAT_MPPT_STRING "MPPT_string" -#define DESCR_STAT_OPERATION_LOGIC "Operation_Logic" -#define DESCR_STAT_OUTPUT_MODE "Output_mode" -#define DESCR_STAT_OUTPUT_SOURCE_PRIORITY "Output_source_priority" -#define DESCR_STAT_PROTOCOL_ID "Protocol_ID" -#define DESCR_STAT_PV_POWER_BALANCE "PV_power_balance" -#define DESCR_STAT_SOLAR_POWER_PRIORITY "Solar_power_priority" -#define DESCR_STAT_TOPOLOGY "Topology" +static const char *const DESCR_STAT_AC_IN_RATING_CURRENT = "AC_in_rating_current"; +static const char *const DESCR_STAT_AC_IN_RATING_VOLTAGE = "AC_in_rating_voltage"; +static const char *const DESCR_STAT_AC_OUT_RATING_ACTIVE_POWER = "AC_out_rating_active_power"; +static const char *const DESCR_STAT_AC_OUT_RATING_APPARENT_POWER = "AC_out_rating_apparent_power"; +static const char *const DESCR_STAT_AC_OUT_RATING_CURRENT = "AC_out_rating_current"; +static const char *const DESCR_STAT_AC_OUT_RATING_FREQUENCY = "AC_out_rating_frequency"; +static const char *const DESCR_STAT_AC_OUT_RATING_VOLTAGE = "AC_out_rating_voltage"; +static const char *const DESCR_STAT_BATTERY_BULK_VOLTAGE = "Battery_bulk_voltage"; +static const char *const DESCR_STAT_BATTERY_FLOAT_VOLTAGE = "Battery_float_voltage"; +static const char *const DESCR_STAT_BATTERY_RATING_VOLTAGE = "Battery_rating_voltage"; +static const char *const DESCR_STAT_BATTERY_RE_CHARGE_VOLTAGE = "Battery_re-charge_voltage"; +static const char *const DESCR_STAT_BATTERY_RE_DISCHARGE_VOLTAGE = "Battery_re-discharge_voltage"; +static const char *const DESCR_STAT_BATTERY_TYPE = "Battery_type"; +static const char *const DESCR_STAT_BATTERY_UNDER_VOLTAGE = "Battery_under_voltage"; +static const char *const DESCR_STAT_CHARGER_SOURCE_PRIORITY = "Charger_source_priority"; +static const char *const DESCR_STAT_CURRENT_MAX_AC_CHARGING_CURRENT = "Current_max_AC_charging_current"; +static const char *const DESCR_STAT_CURRENT_MAX_CHARGING_CURRENT = "Current_max_charging_current"; +static const char *const DESCR_STAT_DEVICE_MODEL = "Device_Model"; +static const char *const DESCR_STAT_INPUT_VOLTAGE_RANGE = "Input_voltage_range"; +static const char *const DESCR_STAT_MACHINE_TYPE = "Machine_type"; +static const char *const DESCR_STAT_MAX_CHARGING_TIME_AT_CV_STAGE = "Max_charging_time_at_CV_stage"; +static const char *const DESCR_STAT_MAX_DISCHARGING_CURRENT = "Max_discharging_current"; +static const char *const DESCR_STAT_MPPT_STRING = "MPPT_string"; +static const char *const DESCR_STAT_OPERATION_LOGIC = "Operation_Logic"; +static const char *const DESCR_STAT_OUTPUT_MODE = "Output_mode"; +static const char *const DESCR_STAT_OUTPUT_SOURCE_PRIORITY = "Output_source_priority"; +static const char *const DESCR_STAT_PROTOCOL_ID = "Protocol_ID"; +static const char *const DESCR_STAT_PV_POWER_BALANCE = "PV_power_balance"; +static const char *const DESCR_STAT_SOLAR_POWER_PRIORITY = "Solar_power_priority"; +static const char *const DESCR_STAT_TOPOLOGY = "Topology"; -#define DESCR_LIVE_AC_IN_FREQUENZ "AC_in_Frequenz" -#define DESCR_LIVE_AC_IN_GENERATION_DAY "AC_in_generation_day" -#define DESCR_LIVE_AC_IN_GENERATION_MONTH "AC_in_generation_month" -#define DESCR_LIVE_AC_IN_GENERATION_SUM "AC_in_generation_sum" -#define DESCR_LIVE_AC_IN_GENERATION_YEAR "AC_in_generation_year" -#define DESCR_LIVE_AC_IN_VOLTAGE "AC_in_Voltage" -#define DESCR_LIVE_AC_OUT_FREQUENZ "AC_out_Frequenz" -#define DESCR_LIVE_AC_OUT_PERCENT "AC_out_percent" -#define DESCR_LIVE_AC_OUT_VA "AC_out_VA" -#define DESCR_LIVE_AC_OUT_VOLTAGE "AC_out_Voltage" -#define DESCR_LIVE_AC_OUT_WATT "AC_out_Watt" -#define DESCR_LIVE_AC_OUTPUT_CURRENT "AC_output_current" -#define DESCR_LIVE_AC_OUTPUT_FREQUENCY "AC_output_frequency" -#define DESCR_LIVE_AC_OUTPUT_POWER "AC_output_power" -#define DESCR_LIVE_AC_OUTPUT_VOLTAGE "AC_output_voltage" -#define DESCR_LIVE_BATTERY_CAPACITY "Battery_capacity" -#define DESCR_LIVE_BATTERY_LOAD "Battery_Load" -#define DESCR_LIVE_BATTERY_PERCENT "Battery_Percent" -#define DESCR_LIVE_BATTERY_POWER_DIRECTION "Battery_Power_Direction" -#define DESCR_LIVE_BATTERY_TEMPERATURE "Battery_temperature" -#define DESCR_LIVE_BATTERY_VOLTAGE "Battery_Voltage" -#define DESCR_LIVE_GRID_FREQUENCY "Grid_frequency" -#define DESCR_LIVE_GRID_VOLTAGE "Grid_voltage" -#define DESCR_LIVE_INVERTER_BUS_TEMPERATURE "Inverter_Bus_Temperature" -#define DESCR_LIVE_INVERTER_BUS_VOLTAGE "Inverter_Bus_Voltage" -#define DESCR_LIVE_INVERTER_OPERATION_MODE "Inverter_Operation_Mode" -#define DESCR_LIVE_INVERTER_TEMPERATURE "Inverter_temperature" -#define DESCR_LIVE_LOCAL_PARALLEL_ID "Local_Parallel_ID" -#define DESCR_LIVE_MPPT1_CHARGER_TEMPERATURE "MPPT1_Charger_Temperature" -#define DESCR_LIVE_MPPT2_CHARGER_TEMPERATURE "MPPT2_Charger_Temperature" -#define DESCR_LIVE_NEGATIVE_BATTERY_VOLTAGE "Negative_battery_voltage" -#define DESCR_LIVE_OUTPUT_CURRENT "Output_current" -#define DESCR_LIVE_OUTPUT_LOAD_PERCENT "Output_load_percent" -#define DESCR_LIVE_OUTPUT_POWER "Output_power" -#define DESCR_LIVE_POSITIVE_BATTERY_VOLTAGE "Positive_battery_voltage" -#define DESCR_LIVE_PV_CHARGING_POWER "PV_Charging_Power" -#define DESCR_LIVE_PV_GENERATION_DAY "PV_generation_day" -#define DESCR_LIVE_PV_GENERATION_MONTH "PV_generation_month" -#define DESCR_LIVE_PV_GENERATION_SUM "PV_generation_sum" -#define DESCR_LIVE_PV_GENERATION_YEAR "PV_generation_year" -#define DESCR_LIVE_PV_INPUT_CURRENT "PV_Input_Current" -#define DESCR_LIVE_PV_INPUT_POWER "PV_Input_Power" -#define DESCR_LIVE_PV_INPUT_VOLTAGE "PV_Input_Voltage" -#define DESCR_LIVE_PV1_INPUT_POWER "PV1_input_power" -#define DESCR_LIVE_PV1_INPUT_VOLTAGE "PV1_input_voltage" -#define DESCR_LIVE_PV2_CHARGING_POWER "PV2_Charging_Power" -#define DESCR_LIVE_PV2_INPUT_CURRENT "PV2_Input_Current" -#define DESCR_LIVE_PV2_INPUT_POWER "PV2_input_power" -#define DESCR_LIVE_PV2_INPUT_VOLTAGE "PV2_input_voltage" -#define DESCR_LIVE_PV3_INPUT_POWER "PV3_input_power" -#define DESCR_LIVE_PV3_INPUT_VOLTAGE "PV3_input_voltage" -#define DESCR_LIVE_SOLAR_FEED_TO_GRID_POWER "Solar_feed_to_grid_power" -#define DESCR_LIVE_SOLAR_FEED_TO_GRID_STATUS "Solar_feed_to_Grid_status" -#define DESCR_LIVE_TRACKER_TEMPERATURE "Tracker_temperature" -#define DESCR_LIVE_TRANSFORMER_TEMPERATURE "Transformer_temperature" -#define DESCR_LIVE_WARNING_CODE "Warning_Code" +static const char *const DESCR_LIVE_AC_IN_FREQUENZ = "AC_in_Frequenz"; +static const char *const DESCR_LIVE_AC_IN_GENERATION_DAY = "AC_in_generation_day"; +static const char *const DESCR_LIVE_AC_IN_GENERATION_MONTH = "AC_in_generation_month"; +static const char *const DESCR_LIVE_AC_IN_GENERATION_SUM = "AC_in_generation_sum"; +static const char *const DESCR_LIVE_AC_IN_GENERATION_YEAR = "AC_in_generation_year"; +static const char *const DESCR_LIVE_AC_IN_VOLTAGE = "AC_in_Voltage"; +static const char *const DESCR_LIVE_AC_OUT_FREQUENZ = "AC_out_Frequenz"; +static const char *const DESCR_LIVE_AC_OUT_PERCENT = "AC_out_percent"; +static const char *const DESCR_LIVE_AC_OUT_VA = "AC_out_VA"; +static const char *const DESCR_LIVE_AC_OUT_VOLTAGE = "AC_out_Voltage"; +static const char *const DESCR_LIVE_AC_OUT_WATT = "AC_out_Watt"; +static const char *const DESCR_LIVE_AC_OUTPUT_CURRENT = "AC_output_current"; +static const char *const DESCR_LIVE_AC_OUTPUT_FREQUENCY = "AC_output_frequency"; +static const char *const DESCR_LIVE_AC_OUTPUT_POWER = "AC_output_power"; +static const char *const DESCR_LIVE_AC_OUTPUT_VOLTAGE = "AC_output_voltage"; +static const char *const DESCR_LIVE_BATTERY_CAPACITY = "Battery_capacity"; +static const char *const DESCR_LIVE_BATTERY_LOAD = "Battery_Load"; +static const char *const DESCR_LIVE_BATTERY_PERCENT = "Battery_Percent"; +static const char *const DESCR_LIVE_BATTERY_POWER_DIRECTION = "Battery_Power_Direction"; +static const char *const DESCR_LIVE_BATTERY_TEMPERATURE = "Battery_temperature"; +static const char *const DESCR_LIVE_BATTERY_VOLTAGE = "Battery_Voltage"; +static const char *const DESCR_LIVE_GRID_FREQUENCY = "Grid_frequency"; +static const char *const DESCR_LIVE_GRID_VOLTAGE = "Grid_voltage"; +static const char *const DESCR_LIVE_INVERTER_BUS_TEMPERATURE = "Inverter_Bus_Temperature"; +static const char *const DESCR_LIVE_INVERTER_BUS_VOLTAGE = "Inverter_Bus_Voltage"; +static const char *const DESCR_LIVE_INVERTER_OPERATION_MODE = "Inverter_Operation_Mode"; +static const char *const DESCR_LIVE_INVERTER_TEMPERATURE = "Inverter_temperature"; +static const char *const DESCR_LIVE_LOCAL_PARALLEL_ID = "Local_Parallel_ID"; +static const char *const DESCR_LIVE_MPPT1_CHARGER_TEMPERATURE = "MPPT1_Charger_Temperature"; +static const char *const DESCR_LIVE_MPPT2_CHARGER_TEMPERATURE = "MPPT2_Charger_Temperature"; +static const char *const DESCR_LIVE_NEGATIVE_BATTERY_VOLTAGE = "Negative_battery_voltage"; +static const char *const DESCR_LIVE_OUTPUT_CURRENT = "Output_current"; +static const char *const DESCR_LIVE_OUTPUT_LOAD_PERCENT = "Output_load_percent"; +static const char *const DESCR_LIVE_OUTPUT_POWER = "Output_power"; +static const char *const DESCR_LIVE_POSITIVE_BATTERY_VOLTAGE = "Positive_battery_voltage"; +static const char *const DESCR_LIVE_PV_CHARGING_POWER = "PV_Charging_Power"; +static const char *const DESCR_LIVE_PV_GENERATION_DAY = "PV_generation_day"; +static const char *const DESCR_LIVE_PV_GENERATION_MONTH = "PV_generation_month"; +static const char *const DESCR_LIVE_PV_GENERATION_SUM = "PV_generation_sum"; +static const char *const DESCR_LIVE_PV_GENERATION_YEAR = "PV_generation_year"; +static const char *const DESCR_LIVE_PV_INPUT_CURRENT = "PV_Input_Current"; +static const char *const DESCR_LIVE_PV_INPUT_POWER = "PV_Input_Power"; +static const char *const DESCR_LIVE_PV_INPUT_VOLTAGE = "PV_Input_Voltage"; +static const char *const DESCR_LIVE_PV1_INPUT_POWER = "PV1_input_power"; +static const char *const DESCR_LIVE_PV1_INPUT_VOLTAGE = "PV1_input_voltage"; +static const char *const DESCR_LIVE_PV2_CHARGING_POWER = "PV2_Charging_Power"; +static const char *const DESCR_LIVE_PV2_INPUT_CURRENT = "PV2_Input_Current"; +static const char *const DESCR_LIVE_PV2_INPUT_POWER = "PV2_input_power"; +static const char *const DESCR_LIVE_PV2_INPUT_VOLTAGE = "PV2_input_voltage"; +static const char *const DESCR_LIVE_PV3_INPUT_POWER = "PV3_input_power"; +static const char *const DESCR_LIVE_PV3_INPUT_VOLTAGE = "PV3_input_voltage"; +static const char *const DESCR_LIVE_SOLAR_FEED_TO_GRID_POWER = "Solar_feed_to_grid_power"; +static const char *const DESCR_LIVE_SOLAR_FEED_TO_GRID_STATUS = "Solar_feed_to_Grid_status"; +static const char *const DESCR_LIVE_TRACKER_TEMPERATURE = "Tracker_temperature"; +static const char *const DESCR_LIVE_TRANSFORMER_TEMPERATURE = "Transformer_temperature"; +static const char *const DESCR_LIVE_WARNING_CODE = "Warning_Code"; #endif \ No newline at end of file From 32304256e936716b9a5782027ee25a023019b351 Mon Sep 17 00:00:00 2001 From: Artem Kovalenko Date: Tue, 5 Nov 2024 16:41:36 +0200 Subject: [PATCH 10/13] add anenji device --- src/main.h | 3 +- src/modbus/device/anenji/anenji.cpp | 58 +++++++++++++++++ src/modbus/device/anenji/anenji.h | 65 +++++++++++++++++++ src/modbus/device/{ => deye}/deye.cpp | 0 src/modbus/device/{ => deye}/deye.h | 2 +- .../{ => must_pv_ph18}/must_pv_ph18.cpp | 0 .../device/{ => must_pv_ph18}/must_pv_ph18.h | 2 +- src/modbus/modbus.cpp | 14 +++- src/modbus/modbus.h | 5 +- 9 files changed, 141 insertions(+), 8 deletions(-) create mode 100644 src/modbus/device/anenji/anenji.cpp create mode 100644 src/modbus/device/anenji/anenji.h rename src/modbus/device/{ => deye}/deye.cpp (100%) rename src/modbus/device/{ => deye}/deye.h (98%) rename src/modbus/device/{ => must_pv_ph18}/must_pv_ph18.cpp (100%) rename src/modbus/device/{ => must_pv_ph18}/must_pv_ph18.h (99%) diff --git a/src/main.h b/src/main.h index 675f547..49856bc 100644 --- a/src/main.h +++ b/src/main.h @@ -35,7 +35,8 @@ typedef enum PI18, PI30, MODBUS_MUST, - MODBUS_DEYE + MODBUS_DEYE, + MODBUS_ANENJI } protocol_type_t; /** diff --git a/src/modbus/device/anenji/anenji.cpp b/src/modbus/device/anenji/anenji.cpp new file mode 100644 index 0000000..8c03dc0 --- /dev/null +++ b/src/modbus/device/anenji/anenji.cpp @@ -0,0 +1,58 @@ +#include "anenji.h" + + +const modbus_register_t *Anenji::getLiveRegisters() const +{ + return registers_live; +} + +const modbus_register_t *Anenji::getStaticRegisters() const +{ + return registers_static; +} + +const char *Anenji::getName() const +{ + return _name; +} + +bool Anenji::retrieveModel(MODBUS_COM &mCom, char *modelBuffer, size_t bufferSize) +{ + modelBuffer[0] = '\0'; // Clear the buffer + DynamicJsonDocument doc(100); + JsonObject jsonObj = doc.to(); // Create and get JsonObject + modbus_register_info_t model_info = { + .variant = &jsonObj, + .registers = registers_device_serial, + .array_size = sizeof(registers_device_serial) / sizeof(modbus_register_t), + .curr_register = 0}; + + for (size_t i = 0; i < model_info.array_size; i++) + { + mCom.parseModbusToJson(model_info, false); + if (mCom.isAllRegistersRead(model_info)) + { + const char *sn1 = doc["SN1"]; + const char *sn2 = doc["SN2"]; + const char *sn3 = doc["SN3"]; + const char *sn4 = doc["SN4"]; + const char *sn5 = doc["SN5"]; + const char *sn6 = doc["SN6"]; + snprintf(modelBuffer, bufferSize, "%s%s%s%s%s%s", sn1, sn2, sn3, sn4, sn5, sn6); + return true; + } + delay(50); + } + return false; +} + +// Define the size calculation after the arrays are defined +size_t Anenji::getLiveRegistersCount() const +{ + return sizeof(registers_live) / sizeof(modbus_register_t); +} + +size_t Anenji::getStaticRegistersCount() const +{ + return sizeof(registers_static) / sizeof(modbus_register_t); +} \ No newline at end of file diff --git a/src/modbus/device/anenji/anenji.h b/src/modbus/device/anenji/anenji.h new file mode 100644 index 0000000..7d5243a --- /dev/null +++ b/src/modbus/device/anenji/anenji.h @@ -0,0 +1,65 @@ +#ifndef MODBUS_ANENJI_H +#define MODBUS_ANENJI_H + +#include + +class Anenji : public ModbusDevice +{ +public: + Anenji() : ModbusDevice(_baudRate, _modbusAddr, _protocol) {} + + virtual const modbus_register_t *getLiveRegisters() const override; + virtual const modbus_register_t *getStaticRegisters() const override; + const char *getName() const override; + bool retrieveModel(MODBUS_COM &mCom, char *modelBuffer, size_t bufferSize) override; + size_t getLiveRegistersCount() const override; + size_t getStaticRegistersCount() const override; + +private: + static const long _baudRate = 9600; + static const uint32_t _modbusAddr = 1; + static const protocol_type_t _protocol = MODBUS_ANENJI; + inline static const char *const _name = "Anenji"; + + inline static const modbus_register_t registers_live[] = { + {201, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, DESCR_LIVE_INVERTER_OPERATION_MODE, 0, {.bitfield = { + "Power On", + "Standby", + "Mains", + "Off-Grid", + "Bypass", + "Charging", + "Fault", + }}}, + {223, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_PV_INPUT_POWER}, + {219, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_VOLTAGE}, + {220, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_CURRENT}, + + {229, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_BATTERY_PERCENT}, + {215, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_BATTERY_VOLTAGE}, + {227, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_INVERTER_TEMPERATURE}, + {226, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_TRANSFORMER_TEMPERATURE}, + {225, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_OUTPUT_LOAD_PERCENT}, + }; + + inline static const modbus_register_t registers_static[] = { + {322, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, DESCR_STAT_BATTERY_TYPE, 0, {.bitfield = { + "AGM", + "FLD", + "USER", + "SMK1", + "PYL", + "FOX", + }}}, + }; + + inline static const modbus_register_t registers_device_serial[] = { + {186, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN1"}, + {188, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN2"}, + {190, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN3"}, + {192, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN4"}, + {194, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN5"}, + {196, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN6"}}; +}; + +#endif diff --git a/src/modbus/device/deye.cpp b/src/modbus/device/deye/deye.cpp similarity index 100% rename from src/modbus/device/deye.cpp rename to src/modbus/device/deye/deye.cpp diff --git a/src/modbus/device/deye.h b/src/modbus/device/deye/deye.h similarity index 98% rename from src/modbus/device/deye.h rename to src/modbus/device/deye/deye.h index 3eb8810..20c5165 100644 --- a/src/modbus/device/deye.h +++ b/src/modbus/device/deye/deye.h @@ -1,7 +1,7 @@ #ifndef MODBUS_DEYE_H #define MODBUS_DEYE_H -#include "modbus_device.h" +#include class Deye : public ModbusDevice { diff --git a/src/modbus/device/must_pv_ph18.cpp b/src/modbus/device/must_pv_ph18/must_pv_ph18.cpp similarity index 100% rename from src/modbus/device/must_pv_ph18.cpp rename to src/modbus/device/must_pv_ph18/must_pv_ph18.cpp diff --git a/src/modbus/device/must_pv_ph18.h b/src/modbus/device/must_pv_ph18/must_pv_ph18.h similarity index 99% rename from src/modbus/device/must_pv_ph18.h rename to src/modbus/device/must_pv_ph18/must_pv_ph18.h index 78a820d..52a0dc3 100644 --- a/src/modbus/device/must_pv_ph18.h +++ b/src/modbus/device/must_pv_ph18/must_pv_ph18.h @@ -1,7 +1,7 @@ #ifndef MODBUS_MUST_PV_PH18_H #define MODBUS_MUST_PV_PH18_H -#include "modbus_device.h" +#include #define DEVICE_MODEL_HIGH "must_device_model_h" #define DEVICE_MODEL_LOW "must_device_model_l" diff --git a/src/modbus/modbus.cpp b/src/modbus/modbus.cpp index a17787f..4034fb6 100644 --- a/src/modbus/modbus.cpp +++ b/src/modbus/modbus.cpp @@ -101,12 +101,14 @@ protocol_type_t MODBUS::autoDetect() // function for autodetect the inverter typ writeLog("Try Autodetect Modbus device"); - ModbusDevice *devices[] = {new MustPV_PH18(), new Deye()}; - - for (size_t i = 0; i < sizeof(devices) / sizeof(devices[0]); ++i) + ModbusDevice *devices[] = {new MustPV_PH18(), new Deye(), new Anenji()}; + const size_t deviceCount = sizeof(devices) / sizeof(devices[0]); + + for (size_t i = 0; i < deviceCount; ++i) { devices[i]->init(*my_serialIntf, _mCom); devices[i]->retrieveModel(_mCom, modelName, sizeof(modelName)); + if (strlen(modelName) != 0) { writeLog(" Found Modbus device: %s", modelName); @@ -115,6 +117,12 @@ protocol_type_t MODBUS::autoDetect() // function for autodetect the inverter typ device = devices[i]; prepareRegisters(); protocol = device->getProtocol(); + + // Clean up other devices not selected + for (size_t j = 0; j < deviceCount; ++j) + { + if (j != i) delete devices[j]; + } return protocol; } delete devices[i]; diff --git a/src/modbus/modbus.h b/src/modbus/modbus.h index ed85b39..fbaedb4 100644 --- a/src/modbus/modbus.h +++ b/src/modbus/modbus.h @@ -5,8 +5,9 @@ #include #include "modbus_com.h" #include "device/modbus_device.h" -#include "device/must_pv_ph18.h" -#include "device/deye.h" +#include "device/must_pv_ph18/must_pv_ph18.h" +#include "device/deye/deye.h" +#include "device/anenji/anenji.h" extern JsonObject deviceJson; extern JsonObject staticData; From 12206ee02e9a9a78c9598e0733d9433bf8cfacc5 Mon Sep 17 00:00:00 2001 From: Artem Kovalenko Date: Tue, 5 Nov 2024 18:57:09 +0200 Subject: [PATCH 11/13] update ESPAsyncWebServer to fix reboots in webpage --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 81f0e96..6b4e5ef 100644 --- a/platformio.ini +++ b/platformio.ini @@ -24,7 +24,7 @@ extra_scripts = pre:tools/mini_html.py lib_deps = bblanchon/ArduinoJson @ ^6.21.2 esphome/ESPAsyncTCP-esphome @ 2.0.0 - mathieucarbou/ESPAsyncWebServer @ 3.3.7 + mathieucarbou/ESPAsyncWebServer @ 3.3.22 mathieucarbou/WebSerialLite@^6.2.0 alanswx/ESPAsyncWiFiManager @ ^0.31.0 plerup/EspSoftwareSerial @ ^8.2.0 From 2c86b8f609c82d5e52808cfe2810b10b61730c85 Mon Sep 17 00:00:00 2001 From: "DCT.Leni" Date: Tue, 12 Nov 2024 02:08:59 +0200 Subject: [PATCH 12/13] Add more Anenji --- platformio.ini | 23 +----- src/modbus/device/anenji/anenji.h | 116 +++++++++++++++++++++--------- 2 files changed, 86 insertions(+), 53 deletions(-) diff --git a/platformio.ini b/platformio.ini index 6b4e5ef..6697487 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,11 +12,11 @@ platform = espressif8266@4.2.1 framework = arduino monitor_speed = 115200 -custom_prog_version = 1.2.0-Pre4 +custom_prog_version = 1.2.0-Anj build_flags = -DVERSION=${this.custom_prog_version} -DPIO_SRC_NAM="Solar2MQTT" - -DESP8266 -DATOMIC_FS_UPDATE + -DESP8266 -DATOMIC_FS_UPDATE -Wno-unused-function extra_scripts = pre:tools/mini_html.py pre:tools/pre_compile.py @@ -40,21 +40,4 @@ board_build.ldscript = eagle.flash.4m.ld build_flags = ${env.build_flags} custom_hardwareserial = false monitor_filters = esp8266_exception_decoder, default, time, printable, colorize -upload_speed = 921600 - -[env:WiFi-Dongle] -board = esp12e -board_build.ldscript = eagle.flash.4m.ld -custom_hardwareserial = true -build_flags = ${env.build_flags} -custom_prog_version = ${env.custom_prog_version} -monitor_filters = esp8266_exception_decoder, default, time, printable, colorize -upload_speed = 921600 - -[env:esp01_1m] -board = esp01_1m -board_build.ldscript = eagle.flash.1m.ld -custom_hardwareserial = true -build_flags = ${env.build_flags} -custom_prog_version = ${env.custom_prog_version} -monitor_filters = esp8266_exception_decoder, default, time, printable, colorize +upload_speed = 921600 \ No newline at end of file diff --git a/src/modbus/device/anenji/anenji.h b/src/modbus/device/anenji/anenji.h index 7d5243a..4fd946f 100644 --- a/src/modbus/device/anenji/anenji.h +++ b/src/modbus/device/anenji/anenji.h @@ -3,8 +3,7 @@ #include -class Anenji : public ModbusDevice -{ +class Anenji : public ModbusDevice { public: Anenji() : ModbusDevice(_baudRate, _modbusAddr, _protocol) {} @@ -16,41 +15,80 @@ class Anenji : public ModbusDevice size_t getStaticRegistersCount() const override; private: - static const long _baudRate = 9600; - static const uint32_t _modbusAddr = 1; - static const protocol_type_t _protocol = MODBUS_ANENJI; + static const long _baudRate = 9600; + static const uint32_t _modbusAddr = 1; + static const protocol_type_t _protocol = MODBUS_ANENJI; inline static const char *const _name = "Anenji"; + // CRC lookup tables (fill in with values from the protocol document) + static constexpr uint8_t auchCRCHi[256] = { + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, // and so on + }; + static constexpr uint8_t auchCRCLo[256] = { + 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, // and so on + }; + + // Live Registers (dynamic data from device) inline static const modbus_register_t registers_live[] = { - {201, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, DESCR_LIVE_INVERTER_OPERATION_MODE, 0, {.bitfield = { - "Power On", - "Standby", - "Mains", - "Off-Grid", - "Bypass", - "Charging", - "Fault", - }}}, - {223, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_PV_INPUT_POWER}, - {219, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_VOLTAGE}, - {220, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_PV_INPUT_CURRENT}, - - {229, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, DESCR_LIVE_BATTERY_PERCENT}, - {215, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, DESCR_LIVE_BATTERY_VOLTAGE}, - {227, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_INVERTER_TEMPERATURE}, - {226, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_TRANSFORMER_TEMPERATURE}, - {225, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, DESCR_LIVE_OUTPUT_LOAD_PERCENT}, + {201, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "Inverter Operation Mode", 0, {.bitfield = { + "Power On", "Standby", "Mains", "Off-Grid", "Bypass", "Charging", "Fault"}}}, + {202, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Effective Mains Voltage"}, + {203, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "Mains Frequency"}, + {204, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "Average Mains Power"}, + {205, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Inverter Voltage"}, + {206, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Inverter Current"}, + {207, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "Inverter Frequency"}, + {208, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "Average Inverter Power"}, + {209, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "Inverter Charging Power"}, + {210, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Output Effective Voltage"}, + {211, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Output Effective Current"}, + {212, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "Output Frequency"}, + {213, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "Output Active Power"}, + {214, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "Output Apparent Power"}, + {215, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Battery Voltage"}, + {216, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Battery Current"}, + {217, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "Battery Power"}, + {219, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "PV Voltage"}, + {220, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "PV Current"}, + {223, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "PV Power"}, + {224, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "PV Charging Power"}, + {225, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "Load Percentage"}, + {226, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "DCDC Temperature"}, + {227, MODBUS_TYPE_HOLDING, REGISTER_TYPE_INT16, "Inverter Temperature"}, + {229, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Battery Percentage"}, + {232, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Battery Average Current"}, + {233, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Inverter Charging Current"}, + {234, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "PV Charging Current"}, + // Add additional live registers as needed }; - inline static const modbus_register_t registers_static[] = { - {322, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, DESCR_STAT_BATTERY_TYPE, 0, {.bitfield = { - "AGM", - "FLD", - "USER", - "SMK1", - "PYL", - "FOX", - }}}, + // Static Registers (configuration data) + inline static const modbus_register_t registers_static[] = { + {300, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Output Mode", 0, {.bitfield = { + "Single", "Parallel", "3 Phase-P1", "3 Phase-P2", "3 Phase-P3", "Split Phase-P1", "Split Phase-P2"}}}, + {301, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Output Priority", 0, {.bitfield = { + "UTI", "SOL", "SBU", "SUB"}}}, + {302, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Input Voltage Range", 0, {.bitfield = { + "Wide", "Narrow"}}}, + {303, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Buzzer Mode", 0, {.bitfield = { + "Mute", "Warning", "Fault", "Fault Only"}}}, + {305, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "LCD Backlight", 0, {.bitfield = { + "Timed Off", "Always On"}}}, + {306, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "LCD Return to Homepage", 0, {.bitfield = { + "Do Not Return", "Return After 1 Minute"}}}, + {307, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Energy-Saving Mode", 0, {.bitfield = { + "Off", "On"}}}, + {308, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Overload Auto-Restart", 0, {.bitfield = { + "No Restart", "Automatic Restart"}}}, + {310, MODBUS_TYPE_HOLDING, REGISTER_TYPE_U16, "Overload Bypass Enable", 0, {.bitfield = { + "Disable", "Enable"}}}, + {322, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "Battery Type", 0, {.bitfield = { + "AGM", "FLD", "USER", "SMK1", "PYL", "FOX"}}}, + {320, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Output Voltage"}, + {321, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_TWO_DECIMAL, "Output Frequency"}, + {332, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Max Charging Current"}, + {333, MODBUS_TYPE_HOLDING, REGISTER_TYPE_DIEMATIC_ONE_DECIMAL, "Max Mains Charging Current"}, + // Add additional static registers as needed }; inline static const modbus_register_t registers_device_serial[] = { @@ -59,7 +97,19 @@ class Anenji : public ModbusDevice {190, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN3"}, {192, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN4"}, {194, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN5"}, - {196, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN6"}}; + {196, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, "SN6"}, + }; + + uint16_t calculateCRC(const uint8_t *data, uint16_t length) const { + uint8_t crcHigh = 0xFF; + uint8_t crcLow = 0xFF; + while (length--) { + uint8_t index = crcHigh ^ *data++; + crcHigh = crcLow ^ auchCRCHi[index]; + crcLow = auchCRCLo[index]; + } + return (crcHigh << 8 | crcLow); + } }; #endif From 992ef8b1cfd62f13a8d4373b973e0c2c35730a96 Mon Sep 17 00:00:00 2001 From: "DCT.Leni" <25935037+lenivetsDCT@users.noreply.github.com> Date: Wed, 13 Nov 2024 02:25:57 +0200 Subject: [PATCH 13/13] Add more Anenji --- platformio.ini | 8 -------- 1 file changed, 8 deletions(-) diff --git a/platformio.ini b/platformio.ini index 41223ed..e9de0fb 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,11 +12,7 @@ platform = espressif8266@4.2.1 framework = arduino monitor_speed = 115200 -<<<<<<< HEAD custom_prog_version = 1.2.0-Anj -======= -custom_prog_version = 1.2.0-Pre4A6 ->>>>>>> d73f9d3a84bc835e0f9c9976b11d88972f4c5556 build_flags = -DVERSION=${this.custom_prog_version} -DPIO_SRC_NAM="Solar2MQTT" @@ -29,11 +25,7 @@ lib_deps = ;bblanchon/ArduinoJson @ ^6.21.2 bblanchon/ArduinoJson @ ^7.2.0 esphome/ESPAsyncTCP-esphome @ 2.0.0 -<<<<<<< HEAD mathieucarbou/ESPAsyncWebServer @ 3.3.22 -======= - mathieucarbou/ESPAsyncWebServer @ ^3.3.16 ->>>>>>> d73f9d3a84bc835e0f9c9976b11d88972f4c5556 mathieucarbou/WebSerialLite@^6.2.0 alanswx/ESPAsyncWiFiManager @ ^0.31.0 plerup/EspSoftwareSerial @ ^8.2.0