From 24ed333b1d96654179e608460ed8d2fdc09ad5a9 Mon Sep 17 00:00:00 2001 From: soylentOrange <soylentOrange@users.noreply.github.com> Date: Wed, 9 Oct 2024 18:05:33 +0200 Subject: [PATCH] Added Function Code 08 (DIAGNOSTICS_SERIAL) --- include/localmodbus.h | 13 +++- include/pages.h | 3 +- src/localmodbus.cpp | 48 ++++++++++-- src/main.cpp | 22 +++--- src/pages.cpp | 177 +++++++++++++++++++++++++++++++++++++----- 5 files changed, 221 insertions(+), 42 deletions(-) diff --git a/include/localmodbus.h b/include/localmodbus.h index 994ab84..24ac499 100644 --- a/include/localmodbus.h +++ b/include/localmodbus.h @@ -3,13 +3,20 @@ #include <WiFiManager.h> #include <ModbusBridgeWiFi.h> + #include <ModbusClientRTU.h> #include <Update.h> #include "config.h" enum SubFunctionCode : uint16_t { - RETURN_QUERY_DATA = 0x00, - RESTART_COMMUNICATION_OPTION = 0x01, + RETURN_QUERY_DATA = 0x00, + RESTART_COMMUNICATION_OPTION = 0x01, + RETURN_DIAGNOSTIC_REGISTER = 0x02, + CLEAR_COUNTERS_AND_DIAGNOSTIC_REGISTER = 0x0a, + RETURN_BUS_MESSAGE_COUNT = 0x0b, + RETURN_BUS_COMMUNICATION_ERROR_COUNT = 0x0c, + RETURN_SLAVE_MESSAGE_COUNT = 0x0e, + }; - void setupLocalModbus(uint8_t serverID, ModbusBridgeWiFi *bridge, Config *config, WiFiManager *wm); +void setupLocalModbus(uint8_t serverID, ModbusClientRTU *rtu, ModbusBridgeWiFi *bridge, Config *config, WiFiManager *wm); #endif /* LOCALMODBUS_H */ \ No newline at end of file diff --git a/include/pages.h b/include/pages.h index 3fe2a58..38b6a76 100644 --- a/include/pages.h +++ b/include/pages.h @@ -15,7 +15,8 @@ void sendButton(AsyncResponseStream *response, const char *title, const char *action, const char *css = ""); void sendTableRow(AsyncResponseStream *response, const char *name, uint32_t value); void sendTableRow(AsyncResponseStream *response, const char *name, String value); - void sendDebugForm(AsyncResponseStream *response, String slaveId, String reg, String function, String count); + void sendDebugForm_read(AsyncResponseStream *response, String slaveId, String reg, String function, String count); + void sendDebugForm_diagnosticSerial(AsyncResponseStream *response, String slaveId, String subFunction, String data); void sendMinCss(AsyncResponseStream *response); const String ErrorName(Modbus::Error code); const String WiFiQuality(int rssiValue); diff --git a/src/localmodbus.cpp b/src/localmodbus.cpp index 5c948ad..d5c6820 100644 --- a/src/localmodbus.cpp +++ b/src/localmodbus.cpp @@ -1,6 +1,10 @@ #include "localmodbus.h" #define ETAG "\"" __DATE__ "" __TIME__ "\"" +static ModbusClientRTU* _rtu = NULL; +static ModbusBridgeWiFi* _bridge = NULL; + + // FC03: worker do serve Modbus function code 0x03 (READ_HOLD_REGISTER) ModbusMessage FC03(ModbusMessage request) { uint16_t address; // requested register address @@ -32,32 +36,66 @@ ModbusMessage FC03(ModbusMessage request) { ModbusMessage FC08(ModbusMessage request) { uint16_t subFunctionCode; // Sub-function code ModbusMessage response; // response message to be sent back + uint16_t resultWord = 0; - LOG_D("WORKER CALLED FC08"); + LOG_D("WORKER CALLED FC08\n"); // get request values request.get(2, subFunctionCode); switch(subFunctionCode) { case RETURN_QUERY_DATA: - LOG_D("Return Query Data"); + LOG_D("Return Query Data\n"); response = request; break; case RESTART_COMMUNICATION_OPTION: - LOG_D("Restart Communications Option"); + LOG_D("Restart Communications Option\n"); + response = request; + break; + case CLEAR_COUNTERS_AND_DIAGNOSTIC_REGISTER: + LOG_D("Restart Communications Option\n"); + if(_rtu != NULL) { + _rtu->resetCounts(); + } + if(_bridge != NULL) { + _bridge->resetCounts(); + } response = request; break; + case RETURN_BUS_MESSAGE_COUNT: + LOG_D("Return Bus Message Count\n"); + if(_bridge != NULL) { + resultWord = _bridge->getMessageCount(); + } + response = ModbusMessage(request.getServerID(), DIAGNOSTICS_SERIAL, RETURN_BUS_MESSAGE_COUNT, resultWord); + break; + case RETURN_BUS_COMMUNICATION_ERROR_COUNT: + LOG_D("Return Bus Communication Error Count\n"); + if(_bridge != NULL) { + resultWord = _bridge->getErrorCount(); + } + response = ModbusMessage(request.getServerID(), DIAGNOSTICS_SERIAL, RETURN_BUS_COMMUNICATION_ERROR_COUNT, resultWord); + break; + case RETURN_SLAVE_MESSAGE_COUNT: + LOG_D("Return Slave Message Count\n"); + if(_rtu != NULL) { + resultWord = _rtu->getMessageCount(); + } + response = ModbusMessage(request.getServerID(), DIAGNOSTICS_SERIAL, RETURN_SLAVE_MESSAGE_COUNT, resultWord); + break; default: - LOG_D("default: ILLEGAL_FUNCTION"); + LOG_D("default: ILLEGAL_FUNCTION\n"); response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_FUNCTION); } return response; } -void setupLocalModbus(uint8_t serverID, ModbusBridgeWiFi *bridge, Config *config, WiFiManager *wm) { +void setupLocalModbus(uint8_t serverID, ModbusClientRTU *rtu, ModbusBridgeWiFi *bridge, Config *config, WiFiManager *wm) { String EfuseMac = String(ESP.getEfuseMac(), 16); dbgln(EfuseMac); + _rtu = rtu; + _bridge = bridge; bridge->registerWorker(serverID, READ_HOLD_REGISTER, &FC03); bridge->registerWorker(serverID, DIAGNOSTICS_SERIAL, &FC08); //bridge->registerWorker(serverID, REPORT_SERVER_ID_SERIAL, &FC08); diff --git a/src/main.cpp b/src/main.cpp index 8a6abb7..b6b7492 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -59,20 +59,16 @@ boolean WiFiConnect() { // Callback for reconnecting void WiFiLostIP(WiFiEvent_t event, WiFiEventInfo_t info) { dbgln("[WiFi] (possibly) disconnected"); + wm.disconnect(); + dbgln("[WiFi] trying to reconnect"); + MDNS.end(); - if(WiFi.status() != WL_CONNECTED) { - dbgln("[WiFi] trying to reconnect"); - MDNS.end(); - - isConnected = false; - isConnected = WiFiConnect(); + isConnected = false; + isConnected = WiFiConnect(); - // Start mDNS - if(isConnected) { - startMDNS(80, config.getTcpPort()); - } - } else { - dbgln("[WiFi] actually is connected"); + // Start mDNS + if(isConnected) { + startMDNS(80, config.getTcpPort()); } } @@ -113,7 +109,7 @@ void setup() { // register worker for local Modbus function if(skipAddress) { - setupLocalModbus(config.getLocalModbusAddress(), &MBbridge, &config, &wm); + setupLocalModbus(config.getLocalModbusAddress(), MBclient, &MBbridge, &config, &wm); } // Start Modbus Bridge diff --git a/src/pages.cpp b/src/pages.cpp index 054e375..59bdcd9 100644 --- a/src/pages.cpp +++ b/src/pages.cpp @@ -2,8 +2,13 @@ #include "localmodbus.h" #define ETAG "\"" __DATE__ "" __TIME__ "\"" +// Remember last slave-ID +static uint8_t _lastSlaveID; + void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi *bridge, Config *config, WiFiManager *wm){ + _lastSlaveID = 1; + server->on("/", HTTP_GET, [](AsyncWebServerRequest *request){ dbgln("[webserver] GET /"); auto *response = request->beginResponseStream("text/html"); @@ -26,7 +31,7 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * // show ESP infos... sendTableRow(response, "WiFi TX Power (dBm)", String(((float)WiFi.getTxPower())/4, 2)); - sendTableRow(response, "ESP Temperature (C)", String(temperatureRead(), 2)); + sendTableRow(response, "ESP Temperature (°C)", String(temperatureRead(), 2)); sendTableRow(response, "ESP Uptime (sec)", esp_timer_get_time() / 1000000); sendTableRow(response, "ESP SSID", WiFi.SSID()); sendTableRow(response, "ESP RSSI", WiFi.RSSI()); @@ -369,29 +374,40 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * server->on("/debug", HTTP_GET, [](AsyncWebServerRequest *request){ dbgln("[webserver] GET /debug"); auto *response = request->beginResponseStream("text/html"); - sendResponseHeader(response, "Debug - Read from Device"); - sendDebugForm(response, "1", "1", "3", "1"); - //sendButton(response, "Read Device", "/read"); + sendResponseHeader(response, "Modbus Debug"); + sendButton(response, "Read (FC01 - FC04)", "debug_read"); + sendButton(response, "Diagnostic Serial (FC08)", "debug_diagnosticSerial"); sendButton(response, "Back", "/"); sendResponseTrailer(response); request->send(response); }); - // server->on("read", HTTP_GET, [](AsyncWebServerRequest *request){ - // dbgln("[webserver] GET /read"); - // auto *response = request->beginResponseStream("text/html"); - // sendResponseHeader(response, "Debug - Read from Device"); - // sendDebugForm(response, "1", "1", "3", "1"); - // sendButton(response, "Back", "/debug"); - // sendResponseTrailer(response); - // request->send(response); - // }); - - server->on("/debug", HTTP_POST, [config, rtu, bridge](AsyncWebServerRequest *request){ - dbgln("[webserver] POST /debug"); + server->on("/debug_read", HTTP_GET, [](AsyncWebServerRequest *request){ + dbgln("[webserver] GET /debug_read"); + auto *response = request->beginResponseStream("text/html"); + sendResponseHeader(response, "Modbus Debug - Read from Device"); + sendDebugForm_read(response, String(_lastSlaveID), "1", "3", "1"); + sendButton(response, "Back", "debug"); + sendResponseTrailer(response); + request->send(response); + }); + + server->on("/debug_diagnosticSerial", HTTP_GET, [](AsyncWebServerRequest *request){ + dbgln("[webserver] GET /debug_diagnosticSerial"); + auto *response = request->beginResponseStream("text/html"); + sendResponseHeader(response, "Modbus Debug - Diagnostic Serial"); + sendDebugForm_diagnosticSerial(response, String(_lastSlaveID), "0", "0000"); + sendButton(response, "Back", "debug"); + sendResponseTrailer(response); + request->send(response); + }); + + server->on("/debug_read", HTTP_POST, [config, rtu, bridge](AsyncWebServerRequest *request){ + dbgln("[webserver] POST /debug_read"); String slaveId = "1"; if (request->hasParam("slave", true)){ slaveId = request->getParam("slave", true)->value(); + _lastSlaveID = (uint8_t) slaveId.toInt(); } String reg = "1"; if (request->hasParam("reg", true)){ @@ -406,7 +422,7 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * count = request->getParam("count", true)->value(); } auto *response = request->beginResponseStream("text/html"); - sendResponseHeader(response, "Debug - Read from Device"); + sendResponseHeader(response, "Modbus Debug - Read from Device"); response->print("<pre>"); auto previous = LOGDEVICE; auto previousLevel = MBUlogLvl; @@ -444,8 +460,77 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * else{ response->printf("<span class=\"e\">Error: %#02x (%s)</span>", error, ErrorName(error).c_str()); } - sendDebugForm(response, slaveId, reg, func, count); - sendButton(response, "Back", "/"); + sendDebugForm_read(response, slaveId, reg, func, count); + sendButton(response, "Back", "debug"); + sendResponseTrailer(response); + request->send(response); + }); + + server->on("/debug_diagnosticSerial", HTTP_POST, [config, rtu, bridge](AsyncWebServerRequest *request){ + dbgln("[webserver] POST /debug_diagnosticSerial"); + String slaveId = "1"; + if (request->hasParam("slave", true)){ + slaveId = request->getParam("slave", true)->value(); + _lastSlaveID = (uint8_t) slaveId.toInt(); + } + String subFunction = "0"; + if (request->hasParam("sf", true)){ + subFunction = request->getParam("sf", true)->value(); + } + String data = "0000"; + if (request->hasParam("dt", true)){ + data = request->getParam("dt", true)->value(); + } + + // convert data string to WORD + uint16_t dataWord = 0; + if(data.length() != 0) { + dataWord = (uint16_t) strtol(data.c_str(), NULL, 16); + } + + auto *response = request->beginResponseStream("text/html"); + sendResponseHeader(response, "Modbus Debug - Diagnostic Serial"); + response->print("<pre>"); + auto previous = LOGDEVICE; + auto previousLevel = MBUlogLvl; + auto debug = WebPrint(previous, response); + LOGDEVICE = &debug; + MBUlogLvl = LOG_LEVEL_DEBUG; + ModbusMessage answer; + // Call local worker (when enabled) + if(slaveId.toInt() == config->getLocalModbusAddress() && config->getLocalModbusEnable()) { + MBSworker lclWrkr = bridge->getWorker(slaveId.toInt(), DIAGNOSTICS_SERIAL); + if(lclWrkr != NULL) { + LOG_D("found local worker... calling for response\n"); + answer = lclWrkr(ModbusMessage(slaveId.toInt(), DIAGNOSTICS_SERIAL, subFunction.toInt(), dataWord)); + } else { + LOG_D("no local worker found... answering with ILLEGAL_FUNCTION\n"); + answer.setError(slaveId.toInt(), DIAGNOSTICS_SERIAL, ILLEGAL_FUNCTION); + } + } else { + // Call via RTU + answer = rtu->syncRequest(0xdeadbeef, slaveId.toInt(), DIAGNOSTICS_SERIAL, subFunction.toInt(), dataWord); + } + MBUlogLvl = previousLevel; + LOGDEVICE = previous; + response->print("</pre>"); + auto error = answer.getError(); + if (error == SUCCESS){ + auto count = answer.size() - 2; + if(count < 0) { + count = 0; + } + response->print("<span >Answer: 0x"); + for (size_t i = 0; i < count; i++) { + response->printf("%02x", answer[i + 2]); + } + response->print("</span>"); + } + else { + response->printf("<span class=\"e\">Error: %#02x (%s)</span>", error, ErrorName(error).c_str()); + } + sendDebugForm_diagnosticSerial(response, slaveId, subFunction, data); + sendButton(response, "Back", "debug"); sendResponseTrailer(response); request->send(response); @@ -540,6 +625,7 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * sendResponseTrailer(response); request->send(response); }); + server->on("/wifi", HTTP_POST, [wm](AsyncWebServerRequest *request){ dbgln("[webserver] POST /wifi"); request->redirect("/"); @@ -549,10 +635,12 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * ESP.restart(); dbgln("[webserver] rebooted..."); }); + server->on("/favicon.ico", [](AsyncWebServerRequest *request){ dbgln("[webserver] GET /favicon.ico"); request->send(204);//TODO add favicon }); + server->on("/style.css", [](AsyncWebServerRequest *request){ if (request->hasHeader("If-None-Match")){ auto header = request->getHeader("If-None-Match"); @@ -588,6 +676,7 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * response->addHeader("ETag", ETAG); request->send(response); }); + server->onNotFound([](AsyncWebServerRequest *request){ dbg("[webserver] request to "); dbg(request->url()); dbgln("not found"); request->send(404, "text/plain", "404"); @@ -672,7 +761,55 @@ void sendTableRow(AsyncResponseStream *response, const char *name, uint32_t valu "</tr>", name, value); } -void sendDebugForm(AsyncResponseStream *response, String slaveId, String reg, String function, String count){ +void sendDebugForm_diagnosticSerial(AsyncResponseStream *response, String slaveId, String subFunction, String data) { + response->print("<form method=\"post\">"); + response->print("<table>" + "<tr>" + "<td>" + "<label for=\"slave\">Slave ID</label>" + "</td>" + "<td>"); + response->printf("<input type=\"number\" min=\"0\" max=\"247\" id=\"slave\" name=\"slave\" value=\"%s\">", slaveId.c_str()); + response->print("</td>" + "</tr>" + "<tr>" + "<td>" + "<label for=\"func\">Function</label>" + "</td>" + "<td>"); + response->printf("<select id=\"sf\" name=\"sf\" data-value=\"%s\">", subFunction.c_str()); + response->print("<option value=\"0\">00 Return Query Data</option>" + "<option value=\"1\">01 Restart Communications</option>" + "<option value=\"2\">02 Return Diagnostic Register</option>" + "<option value=\"10\">10 Clear Counters and Diagnostic Register</option>" + "<option value=\"11\">11 Return Bus Message Count</option>" + "<option value=\"12\">12 Return Bus Communication Error Count</option>" + "<option value=\"14\">14 Return Slave Message Count</option>" + "</select>" + "</td>" + "</tr>" + "<tr>" + "<td>" + "<label for=\"reg\">Data 0x:</label>" + "</td>" + "<td>"); + response->printf("<input type=\"text\" minlength=\"0\" maxlength=\"4\" id=\"dt\" name=\"dt\" value=\"%s\">", data.c_str()); + response->print("</td>" + "</tr>" + "</table>"); + response->print("<button class=\"r\">Make it so</button>" + "</form>" + "<p></p>"); + response->print("<script>" + "(function(){" + "var s = document.querySelectorAll('select[data-value]');" + "for(d of s){" + "d.querySelector(`option[value='${d.dataset.value}']`).selected=true" + "}})();" + "</script>"); +} + +void sendDebugForm_read(AsyncResponseStream *response, String slaveId, String reg, String function, String count) { response->print("<form method=\"post\">"); response->print("<table>" "<tr>"