diff --git a/platformio.ini b/platformio.ini index 39656c1..e9de0fb 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-Pre4A6 +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 @@ -25,7 +25,7 @@ lib_deps = ;bblanchon/ArduinoJson @ ^6.21.2 bblanchon/ArduinoJson @ ^7.2.0 esphome/ESPAsyncTCP-esphome @ 2.0.0 - mathieucarbou/ESPAsyncWebServer @ ^3.3.16 + mathieucarbou/ESPAsyncWebServer @ 3.3.22 mathieucarbou/WebSerialLite@^6.2.0 alanswx/ESPAsyncWiFiManager @ ^0.31.0 plerup/EspSoftwareSerial @ ^8.2.0 @@ -41,21 +41,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/PI_Serial/PI_Serial.cpp b/src/PI_Serial/PI_Serial.cpp index dd72344..3a3fe14 100644 --- a/src/PI_Serial/PI_Serial.cpp +++ b/src/PI_Serial/PI_Serial.cpp @@ -199,9 +199,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 -----------------"); } @@ -400,7 +398,7 @@ 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; } bool PI_Serial::checkQFLAG(const String& flags, char symbol) { diff --git a/src/PI_Serial/PI_Serial.h b/src/PI_Serial/PI_Serial.h index 2d07300..a9f3116 100644 --- a/src/PI_Serial/PI_Serial.h +++ b/src/PI_Serial/PI_Serial.h @@ -1,20 +1,21 @@ -#include "SoftwareSerial.h" #ifndef PI_SERIAL_H #define PI_SERIAL_H #include "vector" +#include "SoftwareSerial.h" #include #include + extern JsonObject deviceJson; extern JsonObject staticData; extern JsonObject liveData; - + class PI_Serial { public: const char *startChar = "("; const char *delimiter = " "; bool requestStaticData = true; - byte protocol = NoD; + protocol_type_t protocol = NoD; bool connection = false; struct @@ -88,13 +89,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/PI_Serial/QPIGS.h b/src/PI_Serial/QPIGS.h index 18905b7..2e5f040 100644 --- a/src/PI_Serial/QPIGS.h +++ b/src/PI_Serial/QPIGS.h @@ -13,14 +13,14 @@ 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 "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 @@ -63,9 +63,9 @@ 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 - "PV_Charging_Power", // NNNN + DESCR_LIVE_PV_INPUT_VOLTAGE, // LLL + DESCR_LIVE_PV_INPUT_CURRENT, // MM.M + DESCR_LIVE_PV_CHARGING_POWER, // NNNN "PV_generation_day", // OOOOOO "PV_generation_sum", // PPPPPP "Inverter_Operation_Mode", // Q @@ -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["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[DESCR_LIVE_PV_INPUT_VOLTAGE] = (liveData["PV1_Input_Voltage"].as() + liveData["PV2_Input_Voltage"].as()); + 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 new file mode 100644 index 0000000..9a655a3 --- /dev/null +++ b/src/descriptors.h @@ -0,0 +1,92 @@ +#ifndef DESCRIPTORS_H +#define DESCRIPTORS_H + +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"; + +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 diff --git a/src/main.cpp b/src/main.cpp index 7c2ead9..5db9bc6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -190,38 +190,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); @@ -339,14 +339,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"); }); @@ -407,7 +406,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); @@ -463,7 +462,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) { @@ -478,7 +477,7 @@ void loop() if (restartNow && millis() >= (RestartTimer + 500)) { ESP.restart(); - } + } notificationLED(); // notification LED routine } @@ -556,7 +555,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()) @@ -603,7 +602,7 @@ 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 = ""; } #ifdef TEMPSENS_PIN @@ -793,16 +792,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/main.h b/src/main.h index 6ee913b..49856bc 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 @@ -26,6 +29,15 @@ #define DBG_BEGIN(...) DBG.begin(__VA_ARGS__) #define DBG_PRINTLN(...) DBG.println(__VA_ARGS__) +typedef enum +{ + NoD, + PI18, + PI30, + MODBUS_MUST, + MODBUS_DEYE, + MODBUS_ANENJI +} protocol_type_t; /** * @brief callback function for wifimanager save config data @@ -86,42 +98,42 @@ 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", "", ""}, {"Buzzer_Enabled", "tune-variant", "", ""}, {"Overload_bypass_Enabled", "tune-variant", "", ""}, {"Power_saving_Enabled", "tune-variant", "", ""}, @@ -131,34 +143,35 @@ static const char *const haStaticDescriptor[][4]{ {"LCD_backlight_Enabled", "tune-variant", "", ""}, {"Primary_source_interrupt_alarm_Enabled", "tune-variant", "", ""}, {"Record_fault_code_Enabled", "tune-variant", "", ""}}; + 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","",""}, @@ -166,49 +179,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/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..4fd946f --- /dev/null +++ b/src/modbus/device/anenji/anenji.h @@ -0,0 +1,115 @@ +#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"; + + // 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, "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 + }; + + // 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[] = { + {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"}, + }; + + 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 diff --git a/src/modbus/device/deye/deye.cpp b/src/modbus/device/deye/deye.cpp new file mode 100644 index 0000000..c23a943 --- /dev/null +++ b/src/modbus/device/deye/deye.cpp @@ -0,0 +1,57 @@ +#include "deye.h" + + +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; +} + +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/deye.h b/src/modbus/device/deye/deye.h new file mode 100644 index 0000000..20c5165 --- /dev/null +++ b/src/modbus/device/deye/deye.h @@ -0,0 +1,59 @@ +#ifndef MODBUS_DEYE_H +#define MODBUS_DEYE_H + +#include + +class Deye : public ModbusDevice +{ +public: + Deye() : 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_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}, + {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}, + {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}, + {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}, + }; + + 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 new file mode 100644 index 0000000..a62db50 --- /dev/null +++ b/src/modbus/device/modbus_device.cpp @@ -0,0 +1,40 @@ +#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() const +{ + return _baudRate; +} + +long ModbusDevice::getModbusAddr() const +{ + return _modbusAddr; +} + +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(getModbusAddr(), 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 new file mode 100644 index 0000000..d9c1b88 --- /dev/null +++ b/src/modbus/device/modbus_device.h @@ -0,0 +1,43 @@ +#ifndef MODBUS_DEVICE_H +#define MODBUS_DEVICE_H + +#include +#include +#include +#include + + +class ModbusDevice +{ +public: + 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() const; + virtual long getModbusAddr() const; + virtual protocol_type_t getProtocol() const; + + // Pure virtual function for getting the name + virtual const char *getName() const = 0; + + virtual void init(SoftwareSerial &serial, MODBUS_COM &mCom); + + virtual bool retrieveModel(MODBUS_COM &mCom, char *modelBuffer, size_t bufferSize); + + // Pure virtual methods to count registers + virtual size_t getLiveRegistersCount() const = 0; + virtual size_t getStaticRegistersCount() const = 0; + + 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/must_pv_ph18.cpp b/src/modbus/device/must_pv_ph18/must_pv_ph18.cpp new file mode 100644 index 0000000..7641da2 --- /dev/null +++ b/src/modbus/device/must_pv_ph18/must_pv_ph18.cpp @@ -0,0 +1,61 @@ +#include "must_pv_ph18.h" + +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; +} +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); +} + +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/must_pv_ph18.h b/src/modbus/device/must_pv_ph18/must_pv_ph18.h new file mode 100644 index 0000000..52a0dc3 --- /dev/null +++ b/src/modbus/device/must_pv_ph18/must_pv_ph18.h @@ -0,0 +1,99 @@ +#ifndef MODBUS_MUST_PV_PH18_H +#define MODBUS_MUST_PV_PH18_H + +#include + +#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: + MustPV_PH18() : 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; + 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; + inline static const char *const _name = "MUST PV/PH 18"; + + inline static const modbus_register_t registers_live[] = { + {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", + "GridTie", + "ByPass", + "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}, + + {25201, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "PV_Charger_Workstate", 0, {.bitfield = {"Initializtion", "Self Test", "Work", "Stop"}}}, + {15202, MODBUS_TYPE_HOLDING, REGISTER_TYPE_CUSTOM_VAL_NAME, "PV_Charger_MPPT_State", 0, {.bitfield = { + "Stop", + "MPPT", + "Current Limit", + }}}, + {15203, 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_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_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}, + {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[] = { + {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"}, + }; + + inline static const modbus_register_t registers_device_model[] = { + {20000, MODBUS_TYPE_HOLDING, REGISTER_TYPE_ASCII, DEVICE_MODEL_HIGH}, + {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 52a274c..4034fb6 100644 --- a/src/modbus/modbus.cpp +++ b/src/modbus/modbus.cpp @@ -1,36 +1,15 @@ // #define isDEBUG -#include "ArduinoJson.h" #include "modbus.h" - -extern void writeLog(const char *format, ...); - -unsigned int dir_pin; - + //---------------------------------------------------------------------- // Public Functions //---------------------------------------------------------------------- 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() -{ - digitalWrite(dir_pin, 1); + my_serialIntf = port; } - -void MODBUS::postTransmission() -{ - digitalWrite(dir_pin, 0); -} - + bool MODBUS::Init() { // Null check the serial interface @@ -40,35 +19,31 @@ 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); - this->postTransmission(); - - // Callbacks allow us to configure the RS485 transceiver correctly - mb.preTransmission(preTransmission); - mb.postTransmission(postTransmission); + + return true; +} - mb.begin(INVERTER_MODBUS_ADDR, *this->my_serialIntf); +void MODBUS::prepareRegisters() +{ + const modbus_register_t *registers_live = device->getLiveRegisters(); + const modbus_register_t *registers_static = device->getStaticRegisters(); 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(); - return true; } void MODBUS::loop() { - if (!device_found) + if (device == nullptr) { return; } @@ -83,7 +58,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; @@ -96,7 +71,7 @@ void MODBUS::loop() } connection = connectionCounter < MAX_CONNECTION_ATTEMPTS; - if (isAllRegistersRead(*cur_info_registers)) + if (_mCom.isAllRegistersRead(*cur_info_registers)) { requestStaticData = false; requestCallback(); @@ -114,302 +89,44 @@ 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) -{ - // 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, 1); - 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; - if (getModbusValue(reg->id, reg->modbus_entity, &raw_value)) - { - 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; - break; - case REGISTER_TYPE_INT16: - // writeLog("Value: %u", raw_value); - (*variant)[reg->name] = static_cast(raw_value); - 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; - } - 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; - } - 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 //---------------------------------------------------------------------- -bool MODBUS::autoDetect() // function for autodetect the inverter type +protocol_type_t MODBUS::autoDetect() // function for autodetect the inverter type { - 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; -} + protocol_type_t protocol = NoD; + char modelName[20]; -String MODBUS::retrieveModel() -{ - String model = ""; - JsonDocument doc; - 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}; + writeLog("Try Autodetect Modbus device"); - for (size_t i = 0; i < model_info.array_size * 2; 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) { - parseModbusToJson(model_info, false); - if (isAllRegistersRead(model_info)) + devices[i]->init(*my_serialIntf, _mCom); + devices[i]->retrieveModel(_mCom, 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; + + 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; } - delay(50); + delete devices[i]; } - return model; + return protocol; } diff --git a/src/modbus/modbus.h b/src/modbus/modbus.h index d96fc3d..fbaedb4 100644 --- a/src/modbus/modbus.h +++ b/src/modbus/modbus.h @@ -1,37 +1,18 @@ -#include "SoftwareSerial.h" #ifndef MODBUS_H #define MODBUS_H +#include "SoftwareSerial.h" #include -#include -#include "modbus_registers.h" +#include "modbus_com.h" +#include "device/modbus_device.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; extern JsonObject liveData; -#define RS485_DIR_PIN 14 // D5 -#define RS485_ESP01_DIR_PIN 0 - -#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: @@ -63,11 +44,8 @@ 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); - bool autoDetect(); + std::function requestCallback; + protocol_type_t autoDetect(); /** * @brief Sends a complete packet with the specified command * @details sends the command over the specified serial connection @@ -75,7 +53,6 @@ class MODBUS String requestData(String command); private: - bool device_found = false; unsigned long previousTime = 0; unsigned long cmdDelayTime = 100; @@ -85,31 +62,15 @@ class MODBUS byte qexCounter = 0; - static void preTransmission(); - static void postTransmission(); - 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); - /** - * @brief get the crc from a string - */ - uint16_t getCRC(String data); - - /** - * @brief get the crc from a string - */ - byte getCHK(String data); - - String retrieveModel(); + void prepareRegisters(); /** * @brief Serial interface used for communication * @details This is set in the constructor */ SoftwareSerial *my_serialIntf; - - 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..a1eeef1 --- /dev/null +++ b/src/modbus/modbus_com.cpp @@ -0,0 +1,359 @@ + +#include "modbus_com.h" + +//---------------------------------------------------------------------- +// Public Functions +//---------------------------------------------------------------------- + +extern void writeLog(const char *format, ...); + +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; + + 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; + + 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 register id=%d type=0x%x name=%s",raw_value, reg->id, reg->type, reg->name); + } + 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_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()); + 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..e457969 --- /dev/null +++ b/src/modbus/modbus_com.h @@ -0,0 +1,53 @@ +#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 + * @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: + 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 3be4d62..447c13b 100644 --- a/src/modbus/modbus_registers.h +++ b/src/modbus/modbus_registers.h @@ -1,10 +1,11 @@ #ifndef SRC_MODBUS_REGISTERS_H_ #define SRC_MODBUS_REGISTERS_H_ #include "Arduino.h" +#include 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,86 +16,49 @@ 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_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 = 0; optional_param_t optional_param; + modbus_callback_t callback; } 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"} -}; +typedef struct +{ + JsonObject *variant; + const modbus_register_t *registers; + size_t array_size; + size_t curr_register; +} modbus_register_info_t; #endif // SRC_MODBUS_REGISTERS_H_ \ No newline at end of file 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' : ''; + }); } /*