Initial Persistent Storage support + minor bug fixes
This commit is contained in:
parent
3ad96c294c
commit
eaf5112286
|
@ -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");
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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.
|
@ -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:
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue