From eaf51122867c96220503967631c4baceb26f0003 Mon Sep 17 00:00:00 2001 From: thiagoralves Date: Fri, 21 Jun 2019 07:02:01 -0500 Subject: [PATCH] Initial Persistent Storage support + minor bug fixes --- webserver/core/interactive_server.cpp | 61 ++++++++++- webserver/core/ladder.h | 6 +- webserver/core/main.cpp | 4 +- webserver/core/modbus.cpp | 16 ++- webserver/core/persistent_storage.cpp | 151 ++++++++++++++++++++++++++ webserver/core/server.cpp | 6 +- webserver/openplc.db | Bin 40960 -> 40960 bytes webserver/openplc.py | 22 ++++ webserver/pages.py | 23 ++++ webserver/webserver.py | 51 ++++++++- 10 files changed, 321 insertions(+), 19 deletions(-) mode change 100755 => 100644 webserver/core/main.cpp create mode 100644 webserver/core/persistent_storage.cpp diff --git a/webserver/core/interactive_server.cpp b/webserver/core/interactive_server.cpp index 3c8e33f..1147874 100644 --- a/webserver/core/interactive_server.cpp +++ b/webserver/core/interactive_server.cpp @@ -39,11 +39,13 @@ //Global Variables bool run_modbus = 0; -int modbus_port = 502; +uint16_t modbus_port = 502; bool run_dnp3 = 0; -int dnp3_port = 20000; +uint16_t dnp3_port = 20000; bool run_enip = 0; -int enip_port = 44818; +uint16_t enip_port = 44818; +bool run_pstorage = 0; +uint16_t pstorage_polling = 10; unsigned char server_command[1024]; int command_index = 0; bool processing_command = 0; @@ -54,6 +56,7 @@ time_t end_time; pthread_t modbus_thread; pthread_t dnp3_thread; pthread_t enip_thread; +pthread_t pstorage_thread; //----------------------------------------------------------------------------- // Start the Modbus Thread @@ -79,6 +82,14 @@ void *enipThread(void *arg) startServer(enip_port, ENIP_PROTOCOL); } +//----------------------------------------------------------------------------- +// Start the Persistent Storage Thread +//----------------------------------------------------------------------------- +void *pstorageThread(void *arg) +{ + startPstorage(); +} + //----------------------------------------------------------------------------- // Read the argument from a command function //----------------------------------------------------------------------------- @@ -329,6 +340,35 @@ void processCommand(unsigned char *buffer, int client_fd) } processing_command = false; } + else if (strncmp(buffer, "start_pstorage(", 15) == 0) + { + processing_command = true; + pstorage_polling = readCommandArgument(buffer); + sprintf(log_msg, "Issued start_pstorage() command with polling rate of %d seconds\n", pstorage_polling); + log(log_msg); + if (run_pstorage) + { + sprintf(log_msg, "Persistent Storage server already active. Changing polling rate to: %d\n", pstorage_polling); + log(log_msg); + } + //Start Enip server + run_pstorage = 1; + pthread_create(&pstorage_thread, NULL, pstorageThread, NULL); + processing_command = false; + } + else if (strncmp(buffer, "stop_pstorage()", 15) == 0) + { + processing_command = true; + sprintf(log_msg, "Issued stop_pstorage() command\n"); + log(log_msg); + if (run_pstorage) + { + run_pstorage = 0; + sprintf(log_msg, "Persistent Storage thread was stopped\n"); + log(log_msg); + } + processing_command = false; + } else if (strncmp(buffer, "runtime_logs()", 14) == 0) { processing_command = true; @@ -452,9 +492,18 @@ void startInteractiveServer(int port) } } } - printf("Closing socket..."); + + printf("Shutting down internal threads\n"); + run_modbus = 0; + run_dnp3 = 0; + run_enip = 0; + run_pstorage = 0; + pthread_join(modbus_thread, NULL); + pthread_join(dnp3_thread, NULL); + pthread_join(enip_thread, NULL); + + printf("Closing socket...\n"); closeSocket(socket_fd); closeSocket(client_fd); - sprintf(log_msg, "Terminating interactive server thread\r\n"); - log(log_msg); + printf("Terminating interactive server thread\n"); } \ No newline at end of file diff --git a/webserver/core/ladder.h b/webserver/core/ladder.h index 0ae62dd..0e11f81 100644 --- a/webserver/core/ladder.h +++ b/webserver/core/ladder.h @@ -121,7 +121,7 @@ extern int log_index; void handleSpecialFunctions(); //server.cpp -void startServer(int port, int protocol_type); +void startServer(uint16_t port, int protocol_type); int getSO_ERROR(int fd); void closeSocket(int fd); bool SetSocketBlockingEnabled(int fd, bool blocking); @@ -131,6 +131,8 @@ void startInteractiveServer(int port); extern bool run_modbus; extern bool run_dnp3; extern bool run_enip; +extern bool run_pstorage; +extern uint16_t pstorage_polling; extern time_t start_time; extern time_t end_time; @@ -151,5 +153,5 @@ void updateBuffersOut_MB(); void dnp3StartServer(int port); //persistent_storage.cpp -void *persistentStorage(void *args); +void startPstorage(); int readPersistentStorage(); diff --git a/webserver/core/main.cpp b/webserver/core/main.cpp old mode 100755 new mode 100644 index ab99cea..8131e7d --- a/webserver/core/main.cpp +++ b/webserver/core/main.cpp @@ -216,7 +216,9 @@ int main(int argc,char **argv) //====================================================== // PERSISTENT STORAGE INITIALIZATION //====================================================== - //readPersistentStorage(); + glueVars(); + mapUnusedIO(); + readPersistentStorage(); //pthread_t persistentThread; //pthread_create(&persistentThread, NULL, persistentStorage, NULL); diff --git a/webserver/core/modbus.cpp b/webserver/core/modbus.cpp index 7e7e01b..b2ddf07 100755 --- a/webserver/core/modbus.cpp +++ b/webserver/core/modbus.cpp @@ -115,10 +115,20 @@ void mapUnusedIO() for (int i = 0; i <= MAX_16B_RANGE; i++) { if (i < MIN_16B_RANGE) - if (int_output[i] == NULL) int_output[i] = &mb_holding_regs[i]; + { + if (int_output[i] == NULL) + { + int_output[i] = &mb_holding_regs[i]; + } + } - if (i >= MIN_16B_RANGE && i <= MAX_16B_RANGE) - if (int_memory[i - MIN_16B_RANGE] == NULL) int_memory[i] = &mb_holding_regs[i]; + else if (i >= MIN_16B_RANGE && i <= MAX_16B_RANGE) + { + if (int_memory[i - MIN_16B_RANGE] == NULL) + { + int_memory[i - MIN_16B_RANGE] = &mb_holding_regs[i]; + } + } } pthread_mutex_unlock(&bufferLock); diff --git a/webserver/core/persistent_storage.cpp b/webserver/core/persistent_storage.cpp new file mode 100644 index 0000000..c378a3b --- /dev/null +++ b/webserver/core/persistent_storage.cpp @@ -0,0 +1,151 @@ +//----------------------------------------------------------------------------- +// Copyright 2019 Thiago Alves +// This file is part of the OpenPLC Software Stack. +// +// OpenPLC is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// OpenPLC is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with OpenPLC. If not, see . +//------ +// +// This file is responsible for the persistent storage on the OpenPLC +// Thiago Alves, Jun 2019 +//----------------------------------------------------------------------------- + +#include +#include +#include +#include + +#include "ladder.h" + +//----------------------------------------------------------------------------- +// Main function for the thread. Should create a buffer for the persistent +// data, compare it with the actual data and write back to the persistent +// file if the data has changed +//----------------------------------------------------------------------------- +void startPstorage() +{ + unsigned char log_msg[1000]; + IEC_UINT persistentBuffer[BUFFER_SIZE]; + + //Read initial buffers into persistent struct + pthread_mutex_lock(&bufferLock); //lock mutex + for (int i = 0; i < BUFFER_SIZE; i++) + { + if (int_memory[i] != NULL) persistentBuffer[i] = *int_memory[i]; + } + pthread_mutex_unlock(&bufferLock); //unlock mutex + + //Perform the first write + if (access("persistent.file", F_OK) == -1) + { + sprintf(log_msg, "Creating Persistent Storage file\n"); + log(log_msg); + } + + FILE *ps = fopen("persistent.file", "w"); //if file already exists, it will be overwritten + if (ps == NULL) + { + sprintf(log_msg, "Persistent Storage: Error creating persistent memory file!\n"); + log(log_msg); + return 0; + } + + if (fwrite(persistentBuffer, sizeof(IEC_INT), BUFFER_SIZE, ps) < BUFFER_SIZE) + { + sprintf(log_msg, "Persistent Storage: Error writing to persistent memory file!\n"); + log(log_msg); + return 0; + } + fclose(ps); + + //Run the main thread + while (run_pstorage) + { + + //Verify if persistent buffer is outdated + bool bufferOutdated = false; + pthread_mutex_lock(&bufferLock); //lock mutex + for (int i = 0; i < BUFFER_SIZE; i++) + { + if (int_memory[i] != NULL) + { + if (persistentBuffer[i] != *int_memory[i]) + { + persistentBuffer[i] = *int_memory[i]; + bufferOutdated = true; + } + } + } + pthread_mutex_unlock(&bufferLock); //unlock mutex + + //If buffer is outdated, write the changes back to the file + if (bufferOutdated) + { + FILE *fd = fopen("persistent.file", "w"); //if file already exists, it will be overwritten + if (fd == NULL) + { + sprintf(log_msg, "Persistent Storage: Error creating persistent memory file!\n"); + log(log_msg); + return 0; + } + + if (fwrite(persistentBuffer, sizeof(IEC_INT), BUFFER_SIZE, fd) < BUFFER_SIZE) + { + sprintf(log_msg, "Persistent Storage: Error writing to persistent memory file!\n"); + log(log_msg); + return 0; + } + fclose(fd); + } + + sleepms(pstorage_polling*1000); + } +} + +//----------------------------------------------------------------------------- +// This function reads the contents from persistent.file into OpenPLC internal +// buffers. Must be called when OpenPLC is initializing. If persistent storage +// is disabled, the persistent.file will not be found and the function will +// exit gracefully. +//----------------------------------------------------------------------------- +int readPersistentStorage() +{ + unsigned char log_msg[1000]; + FILE *fd = fopen("persistent.file", "r"); + if (fd == NULL) + { + sprintf(log_msg, "Warning: Persistent Storage file not found\n"); + log(log_msg); + return 0; + } + + IEC_INT persistentBuffer[BUFFER_SIZE]; + + if (fread(persistentBuffer, sizeof(IEC_INT), BUFFER_SIZE, fd) < BUFFER_SIZE) + { + sprintf(log_msg, "Persistent Storage: Error while trying to read persistent.file!\n"); + log(log_msg); + return 0; + } + fclose(fd); + + sprintf(log_msg, "Persistent Storage: Reading persistent.file into local buffers\n"); + log(log_msg); + + pthread_mutex_lock(&bufferLock); //lock mutex + for (int i = 0; i < BUFFER_SIZE; i++) + { + if (int_memory[i] != NULL) *int_memory[i] = persistentBuffer[i]; + } + pthread_mutex_unlock(&bufferLock); //unlock mutex +} \ No newline at end of file diff --git a/webserver/core/server.cpp b/webserver/core/server.cpp index 69b80f8..45d8e14 100644 --- a/webserver/core/server.cpp +++ b/webserver/core/server.cpp @@ -84,7 +84,7 @@ bool SetSocketBlockingEnabled(int fd, bool blocking) // Create the socket and bind it. Returns the file descriptor for the socket // created. //----------------------------------------------------------------------------- -int createSocket(int port) +int createSocket(uint16_t port) { unsigned char log_msg[1000]; int socket_fd; @@ -249,7 +249,7 @@ void *handleConnections(void *arguments) // creates an infinite loop to listen and parse the messages sent by the // clients //----------------------------------------------------------------------------- -void startServer(int port, int protocol_type) +void startServer(uint16_t port, int protocol_type) { unsigned char log_msg[1000]; int socket_fd, client_fd; @@ -259,7 +259,7 @@ void startServer(int port, int protocol_type) if (protocol_type == MODBUS_PROTOCOL) { - mapUnusedIO(); + //mapUnusedIO(); run_server = &run_modbus; } else if (protocol_type == ENIP_PROTOCOL) diff --git a/webserver/openplc.db b/webserver/openplc.db index 3e4d7a6a4f6dea7e3d2f021e8a37d92a0ffc61b0..994dd85fce41ee7f4e184f34cb0247e3b088dc7c 100644 GIT binary patch delta 171 zcmZoTz|?SnX@WH4_lYvjjNdmVEVLKpf6Ktm@6E}7oj;p@KmRKJI{xYW-kSv#O! 65535)) { @@ -1118,6 +1136,11 @@ settings_tail = """ alert("Please select a port number between 0 and 65535"); return false; } + if (pstorage_checkbox && Number(pstorage_poll) < 0) + { + alert("Persistent Storage polling rate must be bigger than zero"); + return false; + } return true; } diff --git a/webserver/webserver.py b/webserver/webserver.py index 599ec1d..65a03db 100644 --- a/webserver/webserver.py +++ b/webserver/webserver.py @@ -61,12 +61,26 @@ def configure_runtime(): else: print("Disabling EtherNet/IP") openplc_runtime.stop_enip() + elif (row[0] == "Pstorage_polling"): + if (row[1] != "disabled"): + print("Enabling Persistent Storage with polling rate of " + str(int(row[1])) + " seconds") + openplc_runtime.start_pstorage(int(row[1])) + else: + print("Disabling Persistent Storage") + openplc_runtime.stop_pstorage() + delete_persistent_file() except Error as e: print("error connecting to the database" + str(e)) else: print("Error opening DB") +def delete_persistent_file(): + if (os.path.isfile("persistent.file")): + os.remove("persistent.file") + print("persistent.file removed!") + + def generate_mbconfig(): database = "openplc.db" conn = create_connection(database) @@ -836,6 +850,7 @@ def compile_program(): else: print("error connecting to the database") + delete_persistent_file() openplc_runtime.compile_program(st_file) return draw_compiling_page() @@ -1763,7 +1778,6 @@ def settings():

Settings

-
EtherNet/IP Server Port " + return_str += """ +
+
+
+ + + """ + else: + return_str += """ + + + + + " + return_str += """

@@ -1877,11 +1915,8 @@ def settings(): """ return_str += """ -
-

Slave Devices

-
" @@ -1905,6 +1940,7 @@ def settings(): modbus_port = flask.request.form.get('modbus_server_port') dnp3_port = flask.request.form.get('dnp3_server_port') enip_port = flask.request.form.get('enip_server_port') + pstorage_poll = flask.request.form.get('pstorage_thread_poll') start_run = flask.request.form.get('auto_run_text') slave_polling = flask.request.form.get('slave_polling_period') slave_timeout = flask.request.form.get('slave_timeout') @@ -1935,6 +1971,13 @@ def settings(): cur.execute("UPDATE Settings SET Value = ? WHERE Key = 'Enip_port'", (str(enip_port),)) conn.commit() + if (pstorage_poll == None): + cur.execute("UPDATE Settings SET Value = 'disabled' WHERE Key = 'Pstorage_polling'") + conn.commit() + else: + cur.execute("UPDATE Settings SET Value = ? WHERE Key = 'Pstorage_polling'", (str(pstorage_poll),)) + conn.commit() + if (start_run == 'true'): cur.execute("UPDATE Settings SET Value = 'true' WHERE Key = 'Start_run_mode'") conn.commit()