From 3f67f334a8e6c3965187b0c428d703d14a6ca04d Mon Sep 17 00:00:00 2001 From: soylentOrange Date: Wed, 9 Oct 2024 11:33:49 +0200 Subject: [PATCH] Added Rest Api Added Rest Api for: - getting current config as JSON (HTTP_GET /rest/v1/config) - setting new config as JSON (HTTP_POST /rest/v1/config) - getting status as JSON (HTTP_GET /rest/v1/status) - reboot device (HTTP_POST /rest/v1/reboot) --- include/config.h | 11 +- include/localmodbus.h | 15 ++ include/restapi.h | 13 ++ platformio.ini | 1 + src/config.cpp | 36 ++++ src/localmodbus.cpp | 64 +++++++ src/main.cpp | 122 +++++++----- src/pages.cpp | 109 ++++++++++- src/restapi.cpp | 421 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 740 insertions(+), 52 deletions(-) create mode 100644 include/localmodbus.h create mode 100644 include/restapi.h create mode 100644 src/localmodbus.cpp create mode 100644 src/restapi.cpp diff --git a/include/config.h b/include/config.h index bb00e89..19b81b5 100644 --- a/include/config.h +++ b/include/config.h @@ -9,14 +9,17 @@ class Config{ private: Preferences *_prefs; - int16_t _tcpPort; + uint16_t _tcpPort; uint32_t _tcpTimeout; + uint32_t _modbusTimeout; unsigned long _modbusBaudRate; uint32_t _modbusConfig; int8_t _modbusRtsPin; unsigned long _serialBaudRate; uint32_t _serialConfig; int8_t _WiFiTXPower; + uint8_t _localMbEnable; + uint8_t _localMbAddress; String _hostname; public: Config(); @@ -25,6 +28,8 @@ void setTcpPort(uint16_t value); uint32_t getTcpTimeout(); void setTcpTimeout(uint32_t value); + uint32_t getModbusTimeout(); + void setModbusTimeout(uint32_t value); uint32_t getModbusConfig(); unsigned long getModbusBaudRate(); void setModbusBaudRate(unsigned long value); @@ -47,6 +52,10 @@ void setSerialStopBits(uint8_t value); int8_t getWiFiTXPower(); void setWiFiTXPower(int8_t value); + uint8_t getLocalModbusEnable(); + void setLocalModbusEnable(uint8_t value); + uint8_t getLocalModbusAddress(); + void setLocalModbusAddress(uint8_t value); String getHostname(); void setHostname(String value); }; diff --git a/include/localmodbus.h b/include/localmodbus.h new file mode 100644 index 0000000..994ab84 --- /dev/null +++ b/include/localmodbus.h @@ -0,0 +1,15 @@ +#ifndef LOCALMODBUS_H + #define LOCALMODBUS_H + + #include + #include + #include + #include "config.h" + +enum SubFunctionCode : uint16_t { + RETURN_QUERY_DATA = 0x00, + RESTART_COMMUNICATION_OPTION = 0x01, +}; + + void setupLocalModbus(uint8_t serverID, ModbusBridgeWiFi *bridge, Config *config, WiFiManager *wm); +#endif /* LOCALMODBUS_H */ \ No newline at end of file diff --git a/include/restapi.h b/include/restapi.h new file mode 100644 index 0000000..ca1d818 --- /dev/null +++ b/include/restapi.h @@ -0,0 +1,13 @@ +#ifndef RESTAPI_H + #define RESTAPI_H + + #include + #include + #include + #include + #include + #include "config.h" + #include "debug.h" + + void setupRestApi(AsyncWebServer* server, ModbusClientRTU *rtu, ModbusBridgeWiFi *bridge, Config *config, WiFiManager *wm); +#endif /* RESTAPI_H */ \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 4d3c249..06a458b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -18,6 +18,7 @@ https://github.com/tzapu/WiFiManager.git https://github.com/me-no-dev/ESPAsyncWebServer.git https://github.com/eModbus/eModbus.git + https://github.com/bblanchon/ArduinoJson.git build_flags = -Wall -DLOG_LEVEL=LOG_LEVEL_DEBUG monitor_speed = 115200 lib_ldf_mode = deep+ diff --git a/src/config.cpp b/src/config.cpp index b701c3c..da3fc79 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -4,12 +4,15 @@ Config::Config() :_prefs(NULL) ,_tcpPort(502) ,_tcpTimeout(10000) + ,_modbusTimeout(1000) ,_modbusBaudRate(9600) ,_modbusConfig(SERIAL_8N1) ,_modbusRtsPin(-1) ,_serialBaudRate(115200) ,_serialConfig(SERIAL_8N1) ,_WiFiTXPower(60) + ,_localMbEnable(0) + ,_localMbAddress(247) ,_hostname("na") {} @@ -18,12 +21,15 @@ void Config::begin(Preferences *prefs) _prefs = prefs; _tcpPort = _prefs->getUShort("tcpPort", _tcpPort); _tcpTimeout = _prefs->getULong("tcpTimeout", _tcpTimeout); + _modbusTimeout = _prefs->getULong("modbusTimeout", _modbusTimeout); _modbusBaudRate = _prefs->getULong("modbusBaudRate", _modbusBaudRate); _modbusConfig = _prefs->getULong("modbusConfig", _modbusConfig); _modbusRtsPin = _prefs->getChar("modbusRtsPin", _modbusRtsPin); _serialBaudRate = _prefs->getULong("serialBaudRate", _serialBaudRate); _serialConfig = _prefs->getULong("serialConfig", _serialConfig); _WiFiTXPower = _prefs->getChar("txPower", _WiFiTXPower); + _localMbEnable = _prefs->getUChar("localMbEn", _localMbEnable); + _localMbAddress = _prefs->getUChar("localMbAdd", _localMbAddress); _hostname = _prefs->getString("hostname", _hostname); } @@ -47,6 +53,16 @@ void Config::setTcpTimeout(uint32_t value){ _prefs->putULong("tcpTimeout", _tcpTimeout); } +uint32_t Config::getModbusTimeout(){ + return _modbusTimeout; +} + +void Config::setModbusTimeout(uint32_t value){ + if (_modbusTimeout == value) return; + _modbusTimeout = value; + _prefs->putULong("modbusTimeout", _modbusTimeout); +} + uint32_t Config::getModbusConfig(){ return _modbusConfig; } @@ -176,4 +192,24 @@ void Config::setWiFiTXPower(int8_t value){ if (_WiFiTXPower == value) return; _WiFiTXPower = value; _prefs->putChar("txPower", _WiFiTXPower); +} + +uint8_t Config::getLocalModbusAddress(){ + return _localMbAddress; +} + +void Config::setLocalModbusAddress(uint8_t value){ + if (_localMbAddress == value) return; + _localMbAddress = value; + _prefs->putUChar("localMbAdd", _localMbAddress); +} + +uint8_t Config::getLocalModbusEnable(){ + return _localMbEnable; +} + +void Config::setLocalModbusEnable(uint8_t value){ + if (_localMbEnable == value) return; + _localMbEnable = value; + _prefs->putUChar("localMbEn", _localMbEnable); } \ No newline at end of file diff --git a/src/localmodbus.cpp b/src/localmodbus.cpp new file mode 100644 index 0000000..5c948ad --- /dev/null +++ b/src/localmodbus.cpp @@ -0,0 +1,64 @@ +#include "localmodbus.h" +#define ETAG "\"" __DATE__ "" __TIME__ "\"" + +// FC03: worker do serve Modbus function code 0x03 (READ_HOLD_REGISTER) +ModbusMessage FC03(ModbusMessage request) { + uint16_t address; // requested register address + uint16_t words; // requested number of registers + ModbusMessage response; // response message to be sent back + + LOG_D("WORKER CALLED FC03"); + + // get request values + request.get(2, address); + request.get(4, words); + + // Address and words valid? We assume 10 registers here for demo + if (address && words && (address + words) <= 10) { + // Looks okay. Set up message with serverID, FC and length of data + response.add(request.getServerID(), request.getFunctionCode(), (uint8_t)(words * 2)); + // Fill response with requested data + for (uint16_t i = address; i < address + words; ++i) { + response.add(i); + } + } else { + // No, either address or words are outside the limits. Set up error response. + response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_DATA_ADDRESS); + } + return response; +} + +// FC08: worker do serve Modbus function code 0x08 (DIAGNOSTICS_SERIAL) +ModbusMessage FC08(ModbusMessage request) { + uint16_t subFunctionCode; // Sub-function code + ModbusMessage response; // response message to be sent back + + LOG_D("WORKER CALLED FC08"); + + // get request values + request.get(2, subFunctionCode); + + switch(subFunctionCode) { + case RETURN_QUERY_DATA: + LOG_D("Return Query Data"); + response = request; + break; + case RESTART_COMMUNICATION_OPTION: + LOG_D("Restart Communications Option"); + response = request; + break; + default: + LOG_D("default: ILLEGAL_FUNCTION"); + response.setError(request.getServerID(), request.getFunctionCode(), ILLEGAL_FUNCTION); + } + + return response; +} + +void setupLocalModbus(uint8_t serverID, ModbusBridgeWiFi *bridge, Config *config, WiFiManager *wm) { + String EfuseMac = String(ESP.getEfuseMac(), 16); + dbgln(EfuseMac); + bridge->registerWorker(serverID, READ_HOLD_REGISTER, &FC03); + bridge->registerWorker(serverID, DIAGNOSTICS_SERIAL, &FC08); + //bridge->registerWorker(serverID, REPORT_SERVER_ID_SERIAL, &FC08); +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 4ce46b4..8a6abb7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,6 +9,8 @@ #include #include "config.h" #include "pages.h" +#include "restapi.h" +#include "localmodbus.h" AsyncWebServer webServer(80); Config config; @@ -16,43 +18,22 @@ Preferences prefs; ModbusClientRTU *MBclient; ModbusBridgeWiFi MBbridge; WiFiManager wm; -static volatile uint32_t ip_addr(INADDR_NONE); +boolean isConnected(false); -// Callback for setting up mdns -void WiFiGotIP(WiFiEvent_t event, WiFiEventInfo_t info) { - uint32_t new_ip_addr = IPAddress(info.got_ip.ip_info.ip.addr); - dbg("new IP-Address: "); dbgln(IPAddress(new_ip_addr)); - if(new_ip_addr != ip_addr) { - ip_addr = new_ip_addr; - dbgln("re-start mDNS"); - MDNS.begin(WiFi.getHostname()); - MDNS.addService("http", "tcp", 80); - } -} - -// Callback for stopping mdns -void WiFiLostIP(WiFiEvent_t event, WiFiEventInfo_t info) { - dbgln("Lost IP-Address"); - ip_addr = INADDR_NONE; - MDNS.end(); -} +// Start mDNS for webserver and modbus +void startMDNS(uint16_t webPort = 80, uint16_t modbusPort = 502) { + // start mDNS + MDNS.begin(WiFi.getHostname()); + MDNS.addService("http", "tcp", webPort); + MDNS.addService("modbus", "tcp", modbusPort); +}; -void setup() { - // Set up debug serial port - debugSerial.begin(115200); - dbgln(); - dbgln("[config] load"); - prefs.begin("modbusRtuGw"); - config.begin(&prefs); - debugSerial.end(); - debugSerial.begin(config.getSerialBaudRate(), config.getSerialConfig()); - dbgln("[wifi] start"); +// Connect to Wifi +boolean WiFiConnect() { // Set Hostname if(config.getHostname().length() > 2) { WiFi.setHostname(config.getHostname().c_str()); } - // Enable auto-reconnect - wm.setWiFiAutoReconnect(true); // Set WiFi to station mode WiFi.mode(WIFI_STA); // Set (reduced) WiFi TX Power @@ -61,11 +42,52 @@ void setup() { wm.setClass("invert"); auto reboot = false; wm.setAPCallback([&reboot](WiFiManager *wifiManager){reboot = true;}); - wm.autoConnect(); + isConnected = wm.autoConnect(); if (reboot){ ESP.restart(); } - dbgln("[wifi] finished"); + if(WiFi.getMode() == WIFI_MODE_STA) { + dbgln("[WiFi] connected in WIFI_MODE_STA"); + wm.setWiFiAutoReconnect(false); + return true; + } else { + dbgln("[WiFi] NOT connected in WIFI_MODE_STA"); + return false; + } +}; + +// Callback for reconnecting +void WiFiLostIP(WiFiEvent_t event, WiFiEventInfo_t info) { + dbgln("[WiFi] (possibly) disconnected"); + + if(WiFi.status() != WL_CONNECTED) { + dbgln("[WiFi] trying to reconnect"); + MDNS.end(); + + isConnected = false; + isConnected = WiFiConnect(); + + // Start mDNS + if(isConnected) { + startMDNS(80, config.getTcpPort()); + } + } else { + dbgln("[WiFi] actually is connected"); + } +} + +void setup() { + // Set up debug serial port + debugSerial.begin(115200); + dbgln(); + dbgln("[config] load"); + prefs.begin("modbusRtuGw"); + config.begin(&prefs); + debugSerial.end(); + debugSerial.begin(config.getSerialBaudRate(), config.getSerialConfig()); + dbgln(); + dbgln("[wifi] start"); + isConnected = WiFiConnect(); dbgln("[modbus] start"); MBUlogLvl = LOG_LEVEL_WARNING; @@ -80,24 +102,34 @@ void setup() { #endif MBclient = new ModbusClientRTU(config.getModbusRtsPin()); - MBclient->setTimeout(1000); + MBclient->setTimeout(config.getModbusTimeout()); MBclient->begin(modbusSerial, 1); - for (uint8_t i = 1; i < 248; i++) - { - MBbridge.attachServer(i, i, ANY_FUNCTION_CODE, MBclient); + uint8_t skipAddress = (config.getLocalModbusEnable() && config.getLocalModbusAddress() > 0 && config.getLocalModbusAddress() < 248) ? config.getLocalModbusAddress() : 0; + for (uint8_t i = 1; i < 248; i++) { + if(i != skipAddress) { + MBbridge.attachServer(i, i, ANY_FUNCTION_CODE, MBclient); + } } + + // register worker for local Modbus function + if(skipAddress) { + setupLocalModbus(config.getLocalModbusAddress(), &MBbridge, &config, &wm); + } + + // Start Modbus Bridge MBbridge.start(config.getTcpPort(), 10, config.getTcpTimeout()); - dbgln("[modbus] finished"); dbgln("[server] start"); + + // Setup the pages for Webserver and Rest api (v1) setupPages(&webServer, MBclient, &MBbridge, &config, &wm); + setupRestApi(&webServer, MBclient, &MBbridge, &config, &wm); webServer.begin(); - ip_addr = WiFi.localIP(); - MDNS.begin(WiFi.getHostname()); - MDNS.addService("http", "tcp", 80); - dbgln("[server] finished"); - // Register Callbacks for re-starting mDNs after reconnect - WiFi.onEvent(WiFiLostIP, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_LOST_IP); - WiFi.onEvent(WiFiGotIP, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP); + + // Start mDNS + startMDNS(80, config.getTcpPort()); + + // Register Callbacks for re-starting mDNs after disconnect + WiFi.onEvent(WiFiLostIP, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED); dbgln("[setup] finished"); } diff --git a/src/pages.cpp b/src/pages.cpp index 6d04359..054e375 100644 --- a/src/pages.cpp +++ b/src/pages.cpp @@ -1,7 +1,9 @@ #include "pages.h" +#include "localmodbus.h" #define ETAG "\"" __DATE__ "" __TIME__ "\"" void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi *bridge, Config *config, WiFiManager *wm){ + server->on("/", HTTP_GET, [](AsyncWebServerRequest *request){ dbgln("[webserver] GET /"); auto *response = request->beginResponseStream("text/html"); @@ -15,6 +17,7 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * sendResponseTrailer(response); request->send(response); }); + server->on("/status", HTTP_GET, [rtu, bridge](AsyncWebServerRequest *request){ dbgln("[webserver] GET /status"); auto *response = request->beginResponseStream("text/html"); @@ -30,7 +33,6 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * sendTableRow(response, "ESP WiFi Quality", WiFiQuality(WiFi.RSSI())); sendTableRow(response, "ESP MAC", WiFi.macAddress()); sendTableRow(response, "ESP IP", WiFi.localIP().toString() ); - sendTableRow(response, "RTU Messages", rtu->getMessageCount()); sendTableRow(response, "RTU Pending Messages", rtu->pendingRequests()); sendTableRow(response, "RTU Errors", rtu->getErrorCount()); @@ -44,6 +46,7 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * sendResponseTrailer(response); request->send(response); }); + server->on("/reboot", HTTP_GET, [](AsyncWebServerRequest *request){ dbgln("[webserver] GET /reboot"); auto *response = request->beginResponseStream("text/html"); @@ -55,6 +58,7 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * sendResponseTrailer(response); request->send(response); }); + server->on("/reboot", HTTP_POST, [](AsyncWebServerRequest *request){ dbgln("[webserver] POST /reboot"); request->redirect("/"); @@ -62,6 +66,7 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * ESP.restart(); dbgln("[webserver] rebooted..."); }); + server->on("/config", HTTP_GET, [config](AsyncWebServerRequest *request){ dbgln("[webserver] GET /config"); auto *response = request->beginResponseStream("text/html"); @@ -116,6 +121,28 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * response->print("" "" "" + "

Local Modbus Server

" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "
" + "" + ""); + response->printf("" + "
" + "" + ""); + response->printf("", config->getLocalModbusAddress()); + response->print("
" "

Modbus RTU

" "" "" @@ -181,6 +208,14 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * "" "" "" + "" + "" + "" + "" "
" + "" + ""); + response->printf("", config->getModbusTimeout()); + response->print("
" "

Serial (Debug)

" "" @@ -240,6 +275,7 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * sendResponseTrailer(response); request->send(response); }); + server->on("/config", HTTP_POST, [config](AsyncWebServerRequest *request){ dbgln("[webserver] POST /config"); if (request->hasParam("tp", true)){ @@ -250,7 +286,7 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * if (request->hasParam("tt", true)){ auto timeout = request->getParam("tt", true)->value().toInt(); config->setTcpTimeout(timeout); - dbg("[webserver] saved timeout: "); dbgln(timeout); + dbg("[webserver] saved tcp timeout: "); dbgln(timeout); } if (request->hasParam("mb", true)){ auto baud = request->getParam("mb", true)->value().toInt(); @@ -277,6 +313,11 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * config->setModbusRtsPin(rts); dbg("[webserver] saved modbus rts pin: "); dbgln(rts); } + if (request->hasParam("mt", true)){ + auto timeout = request->getParam("mt", true)->value().toInt(); + config->setModbusTimeout(timeout); + dbg("[webserver] saved Modbus timeout: "); dbgln(timeout); + } if (request->hasParam("sb", true)){ auto baud = request->getParam("sb", true)->value().toInt(); config->setSerialBaudRate(baud); @@ -309,19 +350,44 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * dbg("[webserver] saved TX Power: "); dbg(((float)config->getWiFiTXPower())/4); dbgln("dBm"); + } + if (request->hasParam("re", true)){ + auto enabled = request->getParam("re", true)->value().toInt(); + config->setLocalModbusEnable((uint8_t) enabled); + dbg("[webserver] saved local Modbus server enable: "); + dbgln((uint8_t) enabled); + } + if (request->hasParam("ra", true)){ + auto address = request->getParam("ra", true)->value().toInt(); + config->setLocalModbusAddress((uint8_t) address); + dbg("[webserver] saved local Modbus server address: "); + dbgln((uint8_t) address); } request->redirect("/"); }); + server->on("/debug", HTTP_GET, [](AsyncWebServerRequest *request){ dbgln("[webserver] GET /debug"); auto *response = request->beginResponseStream("text/html"); - sendResponseHeader(response, "Debug"); + sendResponseHeader(response, "Debug - Read from Device"); sendDebugForm(response, "1", "1", "3", "1"); + //sendButton(response, "Read Device", "/read"); sendButton(response, "Back", "/"); sendResponseTrailer(response); request->send(response); }); - server->on("/debug", HTTP_POST, [rtu](AsyncWebServerRequest *request){ + + // 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"); String slaveId = "1"; if (request->hasParam("slave", true)){ @@ -340,14 +406,28 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * count = request->getParam("count", true)->value(); } auto *response = request->beginResponseStream("text/html"); - sendResponseHeader(response, "Debug"); + sendResponseHeader(response, "Debug - Read from Device"); response->print("
");
     auto previous = LOGDEVICE;
     auto previousLevel = MBUlogLvl;
     auto debug = WebPrint(previous, response);
     LOGDEVICE = &debug;
     MBUlogLvl = LOG_LEVEL_DEBUG;
-    ModbusMessage answer = rtu->syncRequest(0xdeadbeef, slaveId.toInt(), func.toInt(), reg.toInt(), count.toInt());
+    ModbusMessage answer;
+    // Call local worker (when enabled)
+    if(slaveId.toInt() == config->getLocalModbusAddress() && config->getLocalModbusEnable()) {
+      MBSworker lclWrkr = bridge->getWorker(slaveId.toInt(), func.toInt());
+      if(lclWrkr != NULL) {
+        LOG_D("found local worker... calling for response\n");
+        answer = lclWrkr(ModbusMessage(slaveId.toInt(), func.toInt(), reg.toInt(), count.toInt()));
+      } else {
+        LOG_D("no local worker found... answering with ILLEGAL_FUNCTION\n");
+        answer.setError(slaveId.toInt(), func.toInt(), ILLEGAL_FUNCTION);
+      }
+    } else {
+      // Call via RTU
+      answer = rtu->syncRequest(0xdeadbeef, slaveId.toInt(), func.toInt(), reg.toInt(), count.toInt());
+    }    
     MBUlogLvl = previousLevel;
     LOGDEVICE = previous;
     response->print("
"); @@ -368,7 +448,22 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * sendButton(response, "Back", "/"); sendResponseTrailer(response); request->send(response); + + // reset when requested + if(config->getLocalModbusEnable()) { + if(slaveId.toInt() == config->getLocalModbusAddress()) { + if(answer.getFunctionCode() == DIAGNOSTICS_SERIAL) { + uint16_t subFunctionCode; // Sub-function code + answer.get(2, subFunctionCode); + if(subFunctionCode == RESTART_COMMUNICATION_OPTION) { + dbgln("[webserver] rebooting..."); + ESP.restart(); + } + } + } + } }); + server->on("/update", HTTP_GET, [](AsyncWebServerRequest *request){ dbgln("[webserver] GET /update"); auto *response = request->beginResponseStream("text/html"); @@ -383,6 +478,7 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * sendResponseTrailer(response); request->send(response); }); + server->on("/update", HTTP_POST, [](AsyncWebServerRequest *request){ request->onDisconnect([](){ ESP.restart(); @@ -427,6 +523,7 @@ void setupPages(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi * return; } }); + server->on("/wifi", HTTP_GET, [](AsyncWebServerRequest *request){ dbgln("[webserver] GET /wifi"); auto *response = request->beginResponseStream("text/html"); diff --git a/src/restapi.cpp b/src/restapi.cpp new file mode 100644 index 0000000..a383b62 --- /dev/null +++ b/src/restapi.cpp @@ -0,0 +1,421 @@ +#include "restapi.h" +#define ETAG "\"" __DATE__ "" __TIME__ "\"" +#include "AsyncJson.h" +#include +AsyncCallbackJsonWebHandler* restHandler_v1_post_config; + +void setupRestApi(AsyncWebServer *server, ModbusClientRTU *rtu, ModbusBridgeWiFi *bridge, Config *config, WiFiManager *wm){ + + // Handler for setting config (via HTTP_POST) + restHandler_v1_post_config = new AsyncCallbackJsonWebHandler("/rest/v1/config", [config](AsyncWebServerRequest *request, JsonVariant &json) { + // Get existing config + uint16_t _tcpPort = config->getTcpPort(); + uint32_t _tcpTimeout = config->getTcpTimeout(); + uint32_t _modbusTimeout = config->getModbusTimeout(); + unsigned long _modbusBaudRate = config->getModbusBaudRate(); + uint8_t _modbusDataBits = config->getModbusDataBits(); + uint8_t _modbusParity = config->getModbusParity(); + uint8_t _modbusStopBits = config->getModbusStopBits(); + int8_t _modbusRtsPin = config->getModbusRtsPin(); + unsigned long _serialBaudRate = config->getSerialBaudRate(); + uint8_t _serialDataBits = config->getSerialDataBits(); + uint8_t _serialParity = config->getSerialParity(); + uint8_t _serialStopBits = config->getSerialStopBits(); + int8_t _WiFiTXPower = config->getWiFiTXPower(); + uint8_t _localMbEnable = config->getLocalModbusEnable(); + uint8_t _localMbAddress = config->getLocalModbusAddress(); + String _hostname = config->getHostname(); + + AsyncJsonResponse* response = new AsyncJsonResponse(); + JsonVariant& root = response->getRoot(); + uint8_t numAcceptableParameters = 0; + uint8_t numErraneousParameters = 0; + + if(!json["tcpPort"].isNull()) { + auto check = json["tcpPort"].as(); + if(check != 80) { + _tcpPort = check; + root["acceptableConfig"]["tcpPort"] = check; + numAcceptableParameters = numAcceptableParameters + 1; + } else { + root["erraneousConfig"]["tcpPort"] = check; + numErraneousParameters = numErraneousParameters + 1; + } + json.remove("tcpPort"); + dbg("[webserver] got tcpPort: "); dbgln(check); + } + + if(!json["tcpTimeout"].isNull()) { + auto check = json["tcpTimeout"].as(); + if(check > 0) { + _tcpTimeout = check; + root["acceptableConfig"]["tcpTimeout"] = check; + numAcceptableParameters = numAcceptableParameters + 1; + } else { + root["erraneousConfig"]["tcpTimeout"] = check; + numErraneousParameters = numErraneousParameters + 1; + } + json.remove("tcpTimeout"); + dbg("[webserver] got tcpTimeout: "); dbgln(check); + } + + if(!json["modbusTimeout"].isNull()) { + auto check = json["modbusTimeout"].as(); + if(check > 0) { + _modbusTimeout = check; + root["acceptableConfig"]["modbusTimeout"] = check; + numAcceptableParameters = numAcceptableParameters + 1; + } else { + root["erraneousConfig"]["modbusTimeout"] = check; + numErraneousParameters = numErraneousParameters + 1; + } + json.remove("modbusTimeout"); + dbg("[webserver] got modbusTimeout: "); dbgln(check); + } + + if(!json["modbusBaudRate"].isNull()) { + _modbusBaudRate = json["modbusBaudRate"].as(); + root["acceptableConfig"]["modbusBaudRate"] = _modbusBaudRate; + numAcceptableParameters = numAcceptableParameters + 1; + json.remove("modbusBaudRate"); + dbg("[webserver] got modbusBaudRate: "); dbgln(_modbusBaudRate); + } + + if(!json["modbusDataBits"].isNull()) { + auto check = json["modbusDataBits"].as(); + if(check >= 5 && check <= 8) { + _modbusDataBits = check; + root["acceptableConfig"]["modbusDataBits"] = check; + numAcceptableParameters = numAcceptableParameters + 1; + } else { + root["erraneousConfig"]["modbusDataBits"] = check; + numErraneousParameters = numErraneousParameters + 1; + } + json.remove("modbusDataBits"); + dbg("[webserver] got modbusDataBits: "); dbgln(check); + } + + if(!json["modbusParity"].isNull()) { + auto check = json["modbusParity"].as(); + if(check == 0 || check == 2 || check ==3) { + _modbusParity = check; + root["acceptableConfig"]["modbusParity"] = check; + numAcceptableParameters = numAcceptableParameters + 1; + } else { + root["erraneousConfig"]["modbusParity"] = check; + numErraneousParameters = numErraneousParameters + 1; + } + json.remove("modbusParity"); + dbg("[webserver] got modbusParity: "); dbgln(check); + } + + if(!json["modbusStopBits"].isNull()) { + auto check = json["modbusStopBits"].as(); + if(check >= 1 && check <= 3) { + _modbusStopBits = check; + root["acceptableConfig"]["modbusStopBits"] = check; + numAcceptableParameters = numAcceptableParameters + 1; + } else { + root["erraneousConfig"]["modbusStopBits"] = check; + numErraneousParameters = numErraneousParameters + 1; + } + json.remove("modbusStopBits"); + dbg("[webserver] got modbusStopBits: "); dbgln(check); + } + + if(!json["modbusRtsPin"].isNull()) { + auto check = json["modbusRtsPin"].as(); + if(check == -1 || // Auto + check == 4 || // D4 + check == 13 || // D13 + check == 14 || // D14 + check == 18 || // D18 + check == 19 || // D19 + check == 21 || // D21 + check == 22 || // D22 + check == 23 || // D23 + check == 25 || // D25 + check == 26 || // D26 + check == 27 || // D27 + check == 32 || // D32 + check == 33 // D33 + ) { + _modbusStopBits = check; + root["acceptableConfig"]["modbusRtsPin"] = check; + numAcceptableParameters = numAcceptableParameters + 1; + } else { + root["erraneousConfig"]["modbusRtsPin"] = check; + numErraneousParameters = numErraneousParameters + 1; + } + json.remove("modbusRtsPin"); + dbg("[webserver] got modbusRtsPin: "); dbgln(check); + } + + if(!json["serialBaudRate"].isNull()) { + _serialBaudRate = json["serialBaudRate"].as(); + root["acceptableConfig"]["serialBaudRate"] = _serialBaudRate; + numAcceptableParameters = numAcceptableParameters + 1; + json.remove("serialBaudRate"); + dbg("[webserver] got serialBaudRate: "); dbgln(_serialBaudRate); + } + + if(!json["serialDataBits"].isNull()) { + auto check = json["serialDataBits"].as(); + if(check >= 5 && check <= 8) { + _serialDataBits = check; + root["acceptableConfig"]["serialDataBits"] = check; + numAcceptableParameters = numAcceptableParameters + 1; + } else { + root["erraneousConfig"]["serialDataBits"] = check; + numErraneousParameters = numErraneousParameters + 1; + } + json.remove("serialDataBits"); + dbg("[webserver] got serialDataBits: "); dbgln(check); + } + + if(!json["serialParity"].isNull()) { + auto check = json["serialParity"].as(); + if(check == 0 || check == 2 || check ==3) { + _serialParity = check; + root["acceptableConfig"]["serialParity"] = check; + numAcceptableParameters = numAcceptableParameters + 1; + } else { + root["erraneousConfig"]["serialParity"] = check; + numErraneousParameters = numErraneousParameters + 1; + } + json.remove("serialParity"); + dbg("[webserver] got serialParity: "); dbgln(check); + } + + if(!json["serialStopBits"].isNull()) { + auto check = json["serialStopBits"].as(); + if(check >= 1 && check <= 3) { + _serialStopBits = check; + root["acceptableConfig"]["serialStopBits"] = check; + numAcceptableParameters = numAcceptableParameters + 1; + } else { + root["erraneousConfig"]["serialStopBits"] = check; + numErraneousParameters = numErraneousParameters + 1; + } + json.remove("serialStopBits"); + dbg("[webserver] got serialStopBits: "); dbgln(check); + } + + if(!json["hostname"].isNull()) { + auto check = json["hostname"].as(); + if(check.length() > 2 && check.length() <= 63) { + _hostname = check; + root["acceptableConfig"]["hostname"] = check; + numAcceptableParameters = numAcceptableParameters + 1; + } else { + root["erraneousConfig"]["hostname"] = check; + numErraneousParameters = numErraneousParameters + 1; + } + json.remove("hostname"); + dbg("[webserver] got hostname: "); dbgln(check); + } + + if(!json["localModbusEnable"].isNull()) { + if(json["localModbusEnable"].as()) { + _localMbEnable = 1; + config->setLocalModbusEnable(1); + } else { + _localMbEnable = 0; + config->setLocalModbusEnable(0); + } + root["acceptableConfig"]["localModbusEnable"] = (boolean) _localMbEnable; + numAcceptableParameters = numAcceptableParameters + 1; + json.remove("localModbusEnable"); + dbg("[webserver] got localModbusEnable: "); dbgln((boolean) _localMbEnable); + } + + if(!json["localModbusAddress"].isNull()) { + auto check = json["localModbusAddress"].as(); + if(check > 0 && check < 248) { + _localMbAddress = check; + root["acceptableConfig"]["localModbusAddress"] = check; + numAcceptableParameters = numAcceptableParameters + 1; + } else { + root["erraneousConfig"]["localModbusAddress"] = check; + numErraneousParameters = numErraneousParameters + 1; + } + json.remove("localModbusAddress"); + dbg("[webserver] got localModbusAddress: "); dbgln(check); + } + + if(!json["wifiTXPower"].isNull()) { + auto check = json["wifiTXPower"].as(); + + if(check == 8 || // 2dBm + check == 20 || // 5dBm + check == 28 || // 7dBm + check == 34 || // 8.5dBm + check == 44 || // 11dBm + check == 52 || // 13dBm + check == 60 || // 15dBm + check == 68 || // 17dBm + check == 74 || // 18.5dBm + check == 76 || // 19dBm + check == 78 // 19.5dBm + ) { + _WiFiTXPower = check; + root["acceptableConfig"]["wifiTXPower"] = check; + numAcceptableParameters = numAcceptableParameters + 1; + } else { + root["erraneousConfig"]["wifiTXPower"] = check; + numErraneousParameters = numErraneousParameters + 1; + } + json.remove("wifiTXPower"); + dbg("[webserver] got wifiTXPower: "); dbgln(check); + } + + // check for errors + if(numErraneousParameters == 0) { + // no errors have occurred... + // additionally, check for unknown parameters + if(json.size() != 0) { + dbgln("[webserver] unknown parameters..."); + root["unknownConfig"] = json; + response->setLength(); + response->setCode(500); + request->send(response); + } else { + // check for new parameters + if(!root["acceptableConfig"].isNull()) { + root["newConfig"] = root["acceptableConfig"]; + root.remove("acceptableConfig"); + dbgln("[webserver] new config saved..."); + + // save config + config->setTcpPort(_tcpPort); + config->setTcpTimeout(_tcpTimeout); + config->setModbusTimeout(_modbusTimeout); + config->setModbusBaudRate(_modbusBaudRate); + config->setModbusDataBits(_modbusDataBits); + config->setModbusParity(_modbusParity); + config->setModbusStopBits(_modbusStopBits); + config->setModbusRtsPin(_modbusRtsPin); + config->setSerialBaudRate(_serialBaudRate); + config->setSerialDataBits(_serialDataBits); + config->setSerialParity(_serialParity); + config->setSerialStopBits(_serialStopBits); + config->setWiFiTXPower(_WiFiTXPower); + config->setLocalModbusEnable(_localMbEnable); + config->setLocalModbusAddress(_localMbAddress); + config->setHostname(_hostname); + + // send response + response->setLength(); + response->setCode(200); + request->send(response); + } else { + dbgln("[webserver] new config is empty..."); + response->setLength(); + response->setCode(400); + request->send(response); + } + } + + } else { + // Errors have occurred while processing... + // additionally, check for unknown parameters + if(json.size() != 0) { + root["unknownConfig"] = json; + } + dbgln("[webserver] problem with config..."); + response->setLength(); + response->setCode(500); + request->send(response); + } + + // We shouldn't be here + dbgln("[webserver] unknown problem with config..."); + response->setLength(); + response->setCode(500); + request->send(response); + }); + + // Add config Rest-Handler to server for HTTP_POST only + restHandler_v1_post_config->setMethod(HTTP_POST); + server->addHandler(restHandler_v1_post_config); + + // Handler for getting config (via HTTP_GET) + server->on("/rest/v1/config", HTTP_GET, [config](AsyncWebServerRequest *request) { + dbgln("[Rest-API] GET /rest/v1/config"); + // Prepare resonpse + AsyncJsonResponse* response = new AsyncJsonResponse(); + JsonVariant& root = response->getRoot(); + root["tcpPort"] = config->getTcpPort(); + root["tcpTimeout"] = config->getTcpTimeout(); + root["modbusTimeout"] = config->getModbusTimeout(); + root["modbusBaudRate"] = config->getModbusBaudRate(); + root["modbusDataBits"] = config->getModbusDataBits(); + root["modbusParity"] = config->getModbusParity(); + root["modbusStopBits"] = config->getModbusStopBits(); + root["modbusRtsPin"] = config->getModbusRtsPin(); + root["serialBaudRate"] = config->getSerialBaudRate(); + root["serialDataBits"] = config->getSerialDataBits(); + root["serialParity"] = config->getSerialParity(); + root["serialStopBits"] = config->getSerialStopBits(); + root["hostname"] = config->getHostname(); + root["localModbusEnable"] = config->getLocalModbusEnable(); + root["localModbusAddress"] = config->getLocalModbusAddress(); + root["wifiTXPower"] = config->getWiFiTXPower(); + root["info"]["tcpTimeout"] = "in ms"; + root["info"]["modbusTimeout"] = "in ms"; + root["info"]["modbusDataBits"] = "5..8"; + root["info"]["modbusParity"] = "0: None, 2: Even, 3: Odd"; + root["info"]["modbusStopBits"] = "1: 1 bit, 2: 1.5 bits, 3: 2 bits"; + root["info"]["modbusRtsPin"] = "-1: Auto, X: Pin DX"; + root["info"]["serialDataBits"] = "5..8"; + root["info"]["serialParity"] = "0: None, 2: Even, 3: Odd"; + root["info"]["serialStopBits"] = "1: 1 bit, 2: 1.5 bits, 3: 2 bits"; + root["info"]["hostname"] = "3-63 characters"; + root["info"]["localModbusEnable"] = "0: off, 1: on"; + root["info"]["localModbusAddress"] = "1..247"; + root["info"]["wifiTXPower"] = "8: 2dBm, 20: 5dBm, 28: 7dBm, 34: 8.5dBm, 44: 11dBm, 52: 13dBm, 60: 15dBm, 68: 17dBm, 74: 18.5dBm, 76: 19dBm, 78: 19.5dBm"; + response->setLength(); + request->send(response); + }); + + // Reboot handler (via HTTP_POST) + server->on("/rest/v1/reboot", HTTP_POST, [](AsyncWebServerRequest *request) { + dbgln("[Rest-API] POST /rest/v1/reboot"); + request->send(200); + ESP.restart(); + }); + + // Handler for getting status (via HTTP_GET) + server->on("/rest/v1/status", HTTP_GET, [rtu, bridge](AsyncWebServerRequest *request) { + dbgln("[Rest-API] GET /rest/v1/status"); + // Prepare resonpse + AsyncJsonResponse* response = new AsyncJsonResponse(); + JsonVariant& root = response->getRoot(); + root["ESP"]["TxPower"]["value"] = ((float)WiFi.getTxPower())/4; + root["ESP"]["TxPower"]["unit"] = "dBm"; + root["ESP"]["Temperature"]["value"] = temperatureRead(); + root["ESP"]["Temperature"]["unit"] = "°C"; + root["ESP"]["Uptime"]["value"] = esp_timer_get_time() / 1000000; + root["ESP"]["Uptime"]["unit"] = "s"; + root["ESP"]["SSID"] = WiFi.SSID(); + root["ESP"]["RSSI"]["value"] = WiFi.RSSI(); + root["ESP"]["RSSI"]["unit"] = "dBm"; + root["ESP"]["MAC"] = WiFi.macAddress(); + root["ESP"]["IP"] = WiFi.localIP().toString(); + root["RTU"]["Messages"]["value"] = rtu->getMessageCount(); + root["RTU"]["Messages"]["unit"] = ""; + root["RTU"]["PendingMessages"]["value"] = rtu->pendingRequests(); + root["RTU"]["PendingMessages"]["unit"] = ""; + root["RTU"]["Errors"]["value"] = rtu->getErrorCount(); + root["RTU"]["Errors"]["unit"] = ""; + root["Bridge"]["Messages"]["value"] = bridge->getMessageCount(); + root["Bridge"]["Messages"]["unit"] = ""; + root["Bridge"]["Clients"]["value"] = bridge->activeClients(); + root["Bridge"]["Clients"]["unit"] = ""; + root["Bridge"]["Errors"]["value"] = bridge->getErrorCount(); + root["Bridge"]["Errors"]["unit"] = ""; + root["Info"]["BuildTime"] = __DATE__ " " __TIME__; + response->setLength(); + request->send(response); + }); +} \ No newline at end of file