diff --git a/.gitignore b/.gitignore index 7f4628e..32801f2 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ CMakeSettings.json webserver/bin webserver/build obj/ +utils/dnp3_src/CMakeFiles/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..33da6b5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "utils/ethercat_src"] + path = utils/ethercat_src + url = https://github.com/r2k-in-the-vortex/ethercat_src.git diff --git a/background_installer.sh b/background_installer.sh index 976f329..12ef97b 100755 --- a/background_installer.sh +++ b/background_installer.sh @@ -98,6 +98,16 @@ function install_all_libs { fi cd ../.. + echo "" + echo "[EtherCAT]" + cd utils/ethercat_src + mkdir -p build + sudo cmake -S . -B build/ + sudo cmake --build build/ + sudo cmake --install build/ + sudo /sbin/ldconfig -v | grep ethercat + cd ../.. + echo "" echo "[OPEN DNP3]" cd utils/dnp3_src diff --git a/utils/ethercat_src b/utils/ethercat_src new file mode 160000 index 0000000..9e7007f --- /dev/null +++ b/utils/ethercat_src @@ -0,0 +1 @@ +Subproject commit 9e7007f5f0466431c6eff65e9e860f445961f6c8 diff --git a/webserver/check_openplc_db.py b/webserver/check_openplc_db.py new file mode 100644 index 0000000..b895567 --- /dev/null +++ b/webserver/check_openplc_db.py @@ -0,0 +1,156 @@ +############################################################################## +# +# Checks and initializes default database if needed +# +############################################################################## + + +import sqlite3 +from sqlite3 import Error +import os + +builddir = r"build/" +dbfile = r"build/openplc.db" + +createTablePrograms = r"""CREATE TABLE `Programs` ( + `Prog_ID` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + `Name` TEXT NOT NULL, + `Description` TEXT, + `File` TEXT NOT NULL, + `Date_upload` INTEGER NOT NULL +)""" + +insertblankProgram = r"INSERT INTO Programs VALUES (1, 'Blank Program', 'Dummy empty program', 'blank_program.st', 1527184953)" + +createTableSettings = r"""CREATE TABLE `Settings` ( + `Key` TEXT NOT NULL UNIQUE, + `Value` TEXT NOT NULL, + PRIMARY KEY(`Key`) +)""" + + +createTableSlave_dev = r"""CREATE TABLE "Slave_dev" ( + "dev_id" INTEGER NOT NULL UNIQUE, + "dev_name" TEXT NOT NULL UNIQUE, + "dev_type" TEXT NOT NULL, + "slave_id" INTEGER NOT NULL, + "com_port" TEXT, + "baud_rate" INTEGER, + "parity" TEXT, + "data_bits" INTEGER, + "stop_bits" INTEGER, + "ip_address" TEXT, + "ip_port" INTEGER, + "di_start" INTEGER NOT NULL, + "di_size" INTEGER NOT NULL, + "coil_start" INTEGER NOT NULL, + "coil_size" INTEGER NOT NULL, + "ir_start" INTEGER NOT NULL, + "ir_size" INTEGER NOT NULL, + "hr_read_start" INTEGER NOT NULL, + "hr_read_size" INTEGER NOT NULL, + "hr_write_start" INTEGER NOT NULL, + "hr_write_size" INTEGER NOT NULL, + "pause" INTEGER, + PRIMARY KEY("dev_id" AUTOINCREMENT) +)""" + +createTableUsers = r"""CREATE TABLE "Users" ( + `user_id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + `name` TEXT NOT NULL, + `username` TEXT NOT NULL UNIQUE, + `email` TEXT, + `password` TEXT NOT NULL, + `pict_file` TEXT +)""" + +insertDefaultUser = r"INSERT INTO Users VALUES (10, 'OpenPLC User', 'openplc', 'openplc@openplc.com','openplc', NULL)" + + + +gettablesQuery = r"""SELECT name FROM sqlite_master + WHERE type='table';""" +getsettingsQuery = r"""SELECT Key FROM Settings""" + +# return true if created fresh table +def checkTableExists(conn, tablename, tablecreatecommand): + cur = conn.cursor() + cur.execute(gettablesQuery) + rows = cur.fetchall() + for row in rows: + if row[0] == tablename: + cur.close() + return False + print(tablename, " didn't exist, creating") + cur.execute(tablecreatecommand) + cur.close() + return True + +def checkTablePrograms(conn): + if checkTableExists(conn, "Programs", createTablePrograms): + cur = conn.cursor() + cur.execute(insertblankProgram) + cur.close() + return + +def checkTableUsers(conn): + if checkTableExists(conn, "Users", createTableUsers): + cur = conn.cursor() + cur.execute(insertDefaultUser) + cur.close() + return + +# if code has new features, old database might not have required settings +def checkSettingExists(conn, setting, defaultvalue): + cur = conn.cursor() + cur.execute(getsettingsQuery) + rows = cur.fetchall() + for row in rows: + if row[0] == setting: + cur.close() + return False + print(setting, " didn't exist, creating") + cur.execute("INSERT INTO Settings VALUES (?, ?)", (setting, defaultvalue)) + cur.close() + return + +def checkTableSettings(conn): + checkTableExists(conn, "Settings", createTableSettings) + checkSettingExists(conn, 'Modbus_port', '502') + checkSettingExists(conn, 'Dnp3_port', '20000') + checkSettingExists(conn, 'Start_run_mode', 'false') + checkSettingExists(conn, 'Slave_polling', '100') + checkSettingExists(conn, 'Slave_timeout', '1000') + checkSettingExists(conn, 'Enip_port', '44818') + checkSettingExists(conn, 'Pstorage_polling', 'disabled') + return + +def checkTableSlave_dev(conn): + checkTableExists(conn, "Slave_dev", createTableSlave_dev) + return + +def create_connection(): + """ create a database connection to a SQLite database """ + if not os.path.exists(builddir): + os.mkdir(builddir) + + conn = None + try: + conn = sqlite3.connect(dbfile) + checkTablePrograms(conn) + checkTableUsers(conn) + checkTableSettings(conn) + checkTableSlave_dev(conn) + except Error as e: + print(sqlite3.version) + print(e) + finally: + if conn: + # NB, if you close without commiting, last actions might not be saved + conn.commit() + return conn + + +if __name__ == '__main__': + conn = create_connection() + conn.close() \ No newline at end of file diff --git a/webserver/core/interactive_server.cpp b/webserver/core/interactive_server.cpp index 1147874..879d393 100644 --- a/webserver/core/interactive_server.cpp +++ b/webserver/core/interactive_server.cpp @@ -36,8 +36,11 @@ #include #include "ladder.h" +#define BUFFER_SIZE 1024 //Global Variables +bool ethercat_configured = 0; +char ethercat_conf_file[BUFFER_SIZE]; bool run_modbus = 0; uint16_t modbus_port = 502; bool run_dnp3 = 0; @@ -58,6 +61,12 @@ pthread_t dnp3_thread; pthread_t enip_thread; pthread_t pstorage_thread; +//----------------------------------------------------------------------------- +// Configure Ethercat +//----------------------------------------------------------------------------- +int configureEthercat(){ + return 1; +} //----------------------------------------------------------------------------- // Start the Modbus Thread //----------------------------------------------------------------------------- @@ -111,6 +120,28 @@ int readCommandArgument(unsigned char *command) return atoi(argument); } +//----------------------------------------------------------------------------- +// Read string argument from a command function +//----------------------------------------------------------------------------- +unsigned char *readCommandArgumentStr(unsigned char *command) +{ + int i = 0; + int j = 0; + unsigned char *argument; + argument = (unsigned char *)malloc(1024 * sizeof(unsigned char)); + + while (command[i] != '(' && command[i] != '\0') i++; + if (command[i] == '(') i++; + while (command[i] != ')' && command[i] != '\0') + { + argument[j] = command[i]; + i++; + j++; + argument[j] = '\0'; + } + + return argument; +} //----------------------------------------------------------------------------- // Create the socket and bind it. Returns the file descriptor for the socket @@ -235,6 +266,19 @@ void processCommand(unsigned char *buffer, int client_fd) run_openplc = 0; processing_command = false; } + else if (strncmp(buffer, "start_ethercat(", 15) == 0) + { + processing_command = true; + char *argument; + argument = (char)readCommandArgumentStr(buffer); + strcpy(ethercat_conf_file, argument); + free(argument); + sprintf(log_msg, "Issued start_ethercat() command to start with config: %s\n", ethercat_conf_file); + log(log_msg); + //Configure ethercat + ethercat_configured = configureEthercat(); + processing_command = false; + } else if (strncmp(buffer, "start_modbus(", 13) == 0) { processing_command = true; diff --git a/webserver/core/main.cpp b/webserver/core/main.cpp index 97ef94f..4316e24 100644 --- a/webserver/core/main.cpp +++ b/webserver/core/main.cpp @@ -32,6 +32,7 @@ #include "iec_types.h" #include "ladder.h" +#include "ethercat_src.h" #define OPLC_CYCLE 50000000 @@ -181,6 +182,15 @@ void handleSpecialFunctions() //insert other special functions below } +// pointers to IO *array[const][const] from cpp to c and back again don't work as expected, so instead callbacks +u_int8_t *bool_input_call_back(int a, int b){ return bool_input[a][b]; } +u_int8_t *bool_output_call_back(int a, int b){ return bool_output[a][b]; } +u_int8_t *byte_input_call_back(int a){ return byte_input[a]; } +u_int8_t *byte_output_call_back(int a){ return byte_output[a]; } +u_int16_t *int_input_call_back(int a){ return int_input[a]; } +u_int16_t *int_output_call_back(int a){ return int_output[a]; } + + int main(int argc,char **argv) { unsigned char log_msg[1000]; @@ -209,6 +219,7 @@ int main(int argc,char **argv) //====================================================== // HARDWARE INITIALIZATION //====================================================== + ethercat_configure("../utils/ethercat_src/conf/ethercatcfg.txt"); initializeHardware(); initializeMB(); initCustomLayer(); @@ -225,6 +236,8 @@ int main(int argc,char **argv) readPersistentStorage(); //pthread_t persistentThread; //pthread_create(&persistentThread, NULL, persistentStorage, NULL); + + #ifdef __linux__ //====================================================== @@ -261,9 +274,28 @@ int main(int argc,char **argv) //attached to the user variables glueVars(); + boolvar_call_back bool_input_callback = bool_input_call_back; + boolvar_call_back bool_output_callback = bool_output_call_back; + int8var_call_back byte_input_callback = byte_input_call_back; + int8var_call_back byte_output_callback = byte_output_call_back; + int16var_call_back int_input_callback = int_input_call_back; + int16var_call_back int_output_callback = int_output_call_back; + updateBuffersIn(); //read input image pthread_mutex_lock(&bufferLock); //lock mutex + + + if(ethercat_callcyclic(BUFFER_SIZE, + bool_input_callback, + bool_output_callback, + byte_input_callback, + byte_output_callback, + int_input_callback, + int_output_callback)){ + printf("EtherCAT cyclic failed\n"); + break; + } updateCustomIn(); updateBuffersIn_MB(); //update input image table with data from slave devices handleSpecialFunctions(); diff --git a/webserver/scripts/compile_program.sh b/webserver/scripts/compile_program.sh index 1993678..9ccb7c7 100755 --- a/webserver/scripts/compile_program.sh +++ b/webserver/scripts/compile_program.sh @@ -78,7 +78,7 @@ elif [ "$OPENPLC_PLATFORM" = "linux" ]; then echo "Generating glueVars..." ./glue_generator echo "Compiling main program..." - g++ -std=gnu++11 *.cpp *.o -o openplc -I ./lib -pthread -fpermissive `pkg-config --cflags --libs libmodbus` -lasiodnp3 -lasiopal -lopendnp3 -lopenpal -w + g++ -std=gnu++11 *.cpp *.o -o openplc -I ./lib -pthread -fpermissive `pkg-config --cflags --libs libmodbus` -lasiodnp3 -lasiopal -lopendnp3 -lopenpal -w -L../../utils/ethercat_src/build/lib -lethercat_src -I../../utils/ethercat_src/src if [ $? -ne 0 ]; then echo "Error compiling C files" echo "Compilation finished with errors!"