Initial Persistent Storage support + minor bug fixes

This commit is contained in:
thiagoralves 2019-06-21 07:02:01 -05:00
parent 3ad96c294c
commit eaf5112286
10 changed files with 321 additions and 19 deletions

View File

@ -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");
}

View File

@ -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();

4
webserver/core/main.cpp Executable file → Normal file
View File

@ -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);

View File

@ -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);

View File

@ -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 <http://www.gnu.org/licenses/>.
//------
//
// This file is responsible for the persistent storage on the OpenPLC
// Thiago Alves, Jun 2019
//-----------------------------------------------------------------------------
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>
#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
}

View File

@ -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)

Binary file not shown.

View File

@ -201,6 +201,28 @@ class runtime:
except:
print("Error connecting to OpenPLC runtime")
def start_pstorage(self, poll_rate):
if (self.status() == "Running"):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 43628))
s.send('start_pstorage(' + str(poll_rate) + ')\n')
data = s.recv(1000)
s.close()
except:
print("Error connecting to OpenPLC runtime")
def stop_pstorage(self):
if (self.status() == "Running"):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 43628))
s.send('stop_pstorage()\n')
data = s.recv(1000)
s.close()
except:
print("Error connecting to OpenPLC runtime")
def logs(self):
if (self.status() == "Running"):
try:

View File

@ -1034,6 +1034,8 @@ settings_tail = """
var dnp3_text = document.getElementById('dnp3_server_port');
var enip_checkbox = document.getElementById('enip_server');
var enip_text = document.getElementById('enip_server_port');
var pstorage_checkbox = document.getElementById('pstorage_thread');
var pstorage_text = document.getElementById('pstorage_thread_poll');
var auto_run_checkbox = document.getElementById('auto_run');
var auto_run_text = document.getElementById('auto_run_text');
@ -1064,6 +1066,15 @@ settings_tail = """
enip_text.disabled = true;
}
if (pstorage_checkbox.checked == true)
{
pstorage_text.disabled = false;
}
else
{
pstorage_text.disabled = true;
}
if (auto_run_checkbox.checked == true)
{
auto_run_text.value = 'true';
@ -1089,6 +1100,11 @@ settings_tail = """
setupCheckboxes();
}
document.getElementById('pstorage_thread').onchange = function()
{
setupCheckboxes();
}
document.getElementById('auto_run').onchange = function()
{
setupCheckboxes();
@ -1102,6 +1118,8 @@ settings_tail = """
var dnp3_port = document.forms["uploadForm"]["dnp3_server_port"].value;
var enip_checkbox = document.forms["uploadForm"]["enip_server"].checked;
var enip_port = document.forms["uploadForm"]["enip_server_port"].value;
var pstorage_checkbox = document.forms["uploadForm"]["pstorage_thread"].checked;
var pstorage_poll = document.forms["uploadForm"]["pstorage_thread_poll"].value;
if (modbus_checkbox && (Number(modbus_port) < 0 || Number(modbus_port) > 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;
}
</script>

View File

@ -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():
<div style="w3-container">
<br>
<h2>Settings</h2>
<br>
<form id = "uploadForm"
enctype = "multipart/form-data"
action = "settings"
@ -1790,6 +1804,8 @@ def settings():
dnp3_port = str(row[1])
elif (row[0] == "Enip_port"):
enip_port = str(row[1])
elif (row[0] == "Pstorage_polling"):
pstorage_poll = str(row[1])
elif (row[0] == "Start_run_mode"):
start_run = str(row[1])
elif (row[0] == "Slave_polling"):
@ -1856,6 +1872,28 @@ def settings():
<label for='enip_server_port'><b>EtherNet/IP Server Port</b></label>
<input type='text' id='enip_server_port' name='enip_server_port' value='""" + enip_port + "'>"
return_str += """
<br>
<br>
<br>
<label class="container">
<b>Enable Persistent Storage Thread</b>"""
if (pstorage_poll == 'disabled'):
return_str += """
<input id="pstorage_thread" type="checkbox">
<span class="checkmark"></span>
</label>
<label for='pstorage_thread_poll'><b>Persistent Storage polling rate</b></label>
<input type='text' id='pstorage_thread_poll' name='pstorage_thread_poll' value='10'>"""
else:
return_str += """
<input id="pstorage_thread" type="checkbox" checked>
<span class="checkmark"></span>
</label>
<label for='pstorage_thread_poll'><b>Persistent Storage polling rate</b></label>
<input type='text' id='pstorage_thread_poll' name='pstorage_thread_poll' value='""" + pstorage_poll + "'>"
return_str += """
<br>
<br>
@ -1877,11 +1915,8 @@ def settings():
<input type='hidden' value='true' id='auto_run_text' name='auto_run_text'/>"""
return_str += """
<br>
<br>
<br>
<h2>Slave Devices</h2>
<br>
<label for='slave_polling_period'><b>Polling Period (ms)</b></label>
<input type='text' id='slave_polling_period' name='slave_polling_period' value='""" + slave_polling + "'>"
@ -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()