From 383c49a6e11521d887f32b32ef1e2c6dfe295bdd Mon Sep 17 00:00:00 2001 From: Steffen Pohle Date: Mon, 6 Sep 2021 11:31:00 +0200 Subject: [PATCH] adding files --- Changelog | 3 + HowTo-Cross-Compile.txt | 206 +++++++ LICENSE | 6 + Makefile | 128 ++++ Makefile.rules.crosswindows | 8 + Makefile.rules.linux | 8 + Makefile.rules.windows | 8 + README.md | 34 ++ gui.cc | 773 ++++++++++++++++++++++++ gui.h | 88 +++ guimodbusdata.cc | 281 +++++++++ guimodbusdata.h | 39 ++ guivalues.cc | 554 +++++++++++++++++ guivalues.h | 61 ++ json.cc | 494 ++++++++++++++++ json.h | 77 +++ main.cc | 107 ++++ mbsconfig.cc | 32 + mbsconfig.h | 47 ++ modbus.cc | 891 ++++++++++++++++++++++++++++ modbus.h | 113 ++++ tcp.cc | 521 ++++++++++++++++ tcp.h | 123 ++++ test-fc15.cc | 110 ++++ test-fc16.cc | 115 ++++ testmodbus-server.png | Bin 0 -> 4996 bytes testmodbus-server.ui | 1118 +++++++++++++++++++++++++++++++++++ 27 files changed, 5945 insertions(+) create mode 100644 Changelog create mode 100644 HowTo-Cross-Compile.txt create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 Makefile.rules.crosswindows create mode 100644 Makefile.rules.linux create mode 100644 Makefile.rules.windows create mode 100644 README.md create mode 100644 gui.cc create mode 100644 gui.h create mode 100644 guimodbusdata.cc create mode 100644 guimodbusdata.h create mode 100644 guivalues.cc create mode 100644 guivalues.h create mode 100644 json.cc create mode 100644 json.h create mode 100644 main.cc create mode 100644 mbsconfig.cc create mode 100644 mbsconfig.h create mode 100644 modbus.cc create mode 100644 modbus.h create mode 100644 tcp.cc create mode 100644 tcp.h create mode 100644 test-fc15.cc create mode 100644 test-fc16.cc create mode 100644 testmodbus-server.png create mode 100644 testmodbus-server.ui diff --git a/Changelog b/Changelog new file mode 100644 index 0000000..6d56a79 --- /dev/null +++ b/Changelog @@ -0,0 +1,3 @@ + +Initial Application: + diff --git a/HowTo-Cross-Compile.txt b/HowTo-Cross-Compile.txt new file mode 100644 index 0000000..e4f4aff --- /dev/null +++ b/HowTo-Cross-Compile.txt @@ -0,0 +1,206 @@ +Cross Compile +=============================================================================== + +needed: + wine + wine64 + mingw-w64 + mingw-w64-tools + + += += lib-glib +=============================================================================== + +cross-file.ini + + [host_machine] + system = 'windows' + cpu_family = 'x86_64' + cpu = 'x86_64' + endian = 'little' + + [properties] + c_args = [] + c_link_args = [] + + [binaries] + c = '/usr/bin/x86_64-w64-mingw32-gcc' + cpp = '/usr/bin/x86_64-w64-mingw32-g++' + ar = '/usr/bin/x86_64-w64-mingw32-ar' + strip = '/usr/bin/x86_64-w64-mingw32-strip' + pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config' + windres = '/usr/bin/x86_64-w64-mingw32-windres' + ld = '/usr/bin/x86_64-w64-mingw32-ld' + exe_wrapper = 'wine64' + +mkdir build +cd build +meson --buildtype=release --prefix=/opt/W64-cross-compile/ --cross-file ../cross-file.ini +mseon compile +meson install +cp `find -name "*.dll"` /opt/W64-cross-compile/lib/ + + += += atk +=============================================================================== + +cross-file.ini + + [host_machine] + system = 'windows' + cpu_family = 'x86_64' + cpu = 'x86_64' + endian = 'little' + + [properties] + c_args = [] + c_link_args = [] + + [binaries] + c = '/usr/bin/x86_64-w64-mingw32-gcc' + cpp = '/usr/bin/x86_64-w64-mingw32-g++' + ar = '/usr/bin/x86_64-w64-mingw32-ar' + strip = '/usr/bin/x86_64-w64-mingw32-strip' + pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config' + windres = '/usr/bin/x86_64-w64-mingw32-windres' + ld = '/usr/bin/x86_64-w64-mingw32-ld' + exe_wrapper = 'wine64' + +mkdir build +cd build +PKG_CONFIG_PATH=/opt/W64-cross-compile/lib/pkgconfig meson --buildtype=release --prefix=/opt/W64-cross-compile/ --cross-file ../cross-file.ini +mseon compile +meson install +cp `find -name "*.dll"` /opt/W64-cross-compile/lib/ + += += pango +=============================================================================== + +cross-file.ini + + [host_machine] + system = 'windows' + cpu_family = 'x86_64' + cpu = 'x86_64' + endian = 'little' + + [properties] + c_args = [] + c_link_args = [] + + [binaries] + c = '/usr/bin/x86_64-w64-mingw32-gcc' + cpp = '/usr/bin/x86_64-w64-mingw32-g++' + ar = '/usr/bin/x86_64-w64-mingw32-ar' + strip = '/usr/bin/x86_64-w64-mingw32-strip' + pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config' + windres = '/usr/bin/x86_64-w64-mingw32-windres' + ld = '/usr/bin/x86_64-w64-mingw32-ld' + exe_wrapper = 'wine64' + +mkdir build +cd build +PKG_CONFIG_PATH=/opt/W64-cross-compile/lib/pkgconfig meson --buildtype=release --prefix=/opt/W64-cross-compile/ --cross-file ../cross-file.ini +mseon compile +meson install +cp `find -name "*.dll"` /opt/W64-cross-compile/lib/ + + += += gdk-pixbuf +=============================================================================== + +cross-file.ini + + [host_machine] + system = 'windows' + cpu_family = 'x86_64' + cpu = 'x86_64' + endian = 'little' + + [properties] + c_args = [] + c_link_args = [] + + [binaries] + c = '/usr/bin/x86_64-w64-mingw32-gcc' + cpp = '/usr/bin/x86_64-w64-mingw32-g++' + ar = '/usr/bin/x86_64-w64-mingw32-ar' + strip = '/usr/bin/x86_64-w64-mingw32-strip' + pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config' + windres = '/usr/bin/x86_64-w64-mingw32-windres' + ld = '/usr/bin/x86_64-w64-mingw32-ld' + exe_wrapper = 'wine64' + +mkdir build +cd build +PKG_CONFIG_PATH=/opt/W64-cross-compile/lib/pkgconfig meson --buildtype=release --prefix=/opt/W64-cross-compile/ --cross-file ../cross-file.ini +mseon compile +meson install +cp `find -name "*.dll"` /opt/W64-cross-compile/lib/ + + += += libepoxy +=============================================================================== + +cross-file.ini + + [host_machine] + system = 'windows' + cpu_family = 'x86_64' + cpu = 'x86_64' + endian = 'little' + + [properties] + c_args = [] + c_link_args = [] + + [binaries] + c = '/usr/bin/x86_64-w64-mingw32-gcc' + cpp = '/usr/bin/x86_64-w64-mingw32-g++' + ar = '/usr/bin/x86_64-w64-mingw32-ar' + strip = '/usr/bin/x86_64-w64-mingw32-strip' + pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config' + windres = '/usr/bin/x86_64-w64-mingw32-windres' + ld = '/usr/bin/x86_64-w64-mingw32-ld' + exe_wrapper = 'wine64' + +mkdir build +cd build +PKG_CONFIG_PATH=/opt/W64-cross-compile/lib/pkgconfig meson --buildtype=release --prefix=/opt/W64-cross-compile/ --cross-file ../cross-file.ini +mseon compile +meson install +cp `find -name "*.dll"` /opt/W64-cross-compile/lib/ + + + += += GTK +=============================================================================== +cp /opt/W64-cross-compile/bin/*.exe ~/.wine/drive_c/windows/system32/ +cp /opt/W64-cross-compile/lib/*.dll ~/.wine/drive_c/windows/system32/ + +vim gdk/win32/gdkprivate-win32.h .... +search around line 300.. and add the extern expression +300 /* The singleton selection object pointer */ +301 extern GdkWin32Selection *_win32_selection; + +PKG_CONFIG_PATH=/opt/W64-cross-compile/lib/pkgconfig ./configure --prefix=/opt/W64-cross-compile/ --host=x86_64-w64-mingw32 +PKG_CONFIG_PATH=/opt/W64-cross-compile/lib/pkgconfig make +make install + +will fail with +/bin/bash: Zeile 1: ../../gtk/gtk-update-icon-cache.exe: Kann die Binärdatei nicht ausführen: Fehler im Format der Programmdatei +make[3]: *** [Makefile:1673: install-update-icon-cache] Fehler 126 +edit the Makefile in demos/gtk-demos and add in line the wine prefix. +the same we do for +/bin/bash: Zeile 1: ../../gtk/gtk-update-icon-cache.exe: Kann die Binärdatei nicht ausführen: Fehler im Format der Programmdatei +make[5]: *** [Makefile:1184: install-update-icon-cache] Fehler 126 +make[5]: Verzeichnis „/home/steffen/gtkcrosscompile/gtk+-3.24.0/demos/widget-factory“ wird verlassen +make install +cp `find -name "*.dll"` /opt/W64-cross-compile/lib/ + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c10bda2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,6 @@ + +The license model for this software (TestModbus-Server) is not yet choosen. +At the moment you can use this software free of charge at your own risk. + +06.Sep.2021 Steffen Pohle + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d861fef --- /dev/null +++ b/Makefile @@ -0,0 +1,128 @@ +.SILENT: help +VERSION = 1.0.0 +APP = testmodbus-server + +-include Makefile.rules + +OBJECTS = gui.oo main.oo mbsconfig.oo modbus.oo guimodbusdata.oo guivalues.oo json.oo tcp.oo +DISTNAME=testmodbus-server-$(VERSION) + +ifeq ($(TARGET),) +noconfig: help +endif + +all: Makefile.rules $(TARGET) + +help: + echo "set up configuration" + echo " make configwindows to generate the windows build" + echo " make configcross to generate the windows cross build" + echo " make configlinux to generate the linix build" + echo " make buildwindows to generate the build for windows (uses cross compiler)" + +configlinux: clean + cp -f Makefile.rules.linux Makefile.rules + make config + +configwindows: clean + cp -f Makefile.rules.windows Makefile.rules + make config + +configcross: clean + cp -f Makefile.rules.crosswindows Makefile.rules + make config + +config: Makefile.rules + echo "#ifndef _CONFIG_H_" > config.h + echo "#define _CONFIG_H_" >> config.h + echo "" >> config.h + echo "#define VERSION \"$(VERSION)\"" >> config.h + echo "" >> config.h + echo "#endif" >> config.h + + +$(TARGET): $(OBJECTS) + $(CPP) -o $(TARGET) $(OBJECTS) $(LDFLAGS) $(LIBS) + +.SUFFIXES: +.SUFFIXES: .c .cc .C .cpp .oo + +.cc.oo : $(INCLUDES) + $(CPP) -o $@ -c $(CPPFLAGS) $< + +clean: + rm -f *.o *.oo *.c~ *.h~ *.cc~ *.ui~ $(APP) Makefile~ + rm -rf *.dll + rm -rf *.exe + rm -rf Makefile.rules + rm -rf test-fc16 + rm -rf test-fc15 + +dist: clean + rm -rf $(DISTNAME) + mkdir $(DISTNAME) + cp Makefile* $(DISTNAME) + cp Readme $(DISTNAME) + cp COPYING $(DISTNAME) + cp Changelog $(DISTNAME) + cp *.ui $(DISTNAME) + mkdir $(DISTNAME)/othersources + cp -r othersources/gdk-pixbuf-2.42.6.tar.xz $(DISTNAME)/othersources + cp -r othersources/glib-main.tar.bz2 $(DISTNAME)/othersources + cp -r othersources/gtk+-3.24.0.tar.xz $(DISTNAME)/othersources + cp -r othersources/libepoxy-master.zip $(DISTNAME)/othersources + cp -r othersources/*.txt $(DISTNAME)/othersources + cp -r othersources/pango-1.48.7.tar.xz $(DISTNAME)/othersources + cp -rf *.h $(DISTNAME) + cp -rf *.cc $(DISTNAME) + tar cvzf $(DISTNAME).tgz $(DISTNAME) + rm -rf $(DISTNAME) + +dep: + $(CXX) -MM `ls *.cc` $(CXXFLAGS) > $(DEPENDFILE) + +test-fc16: test-fc16.cc + $(CPP) -o test-fc16 test-fc16.cc $(LDFLAGS) -I/usr/include -I/usr/local/include -L/usr/local/lib -L/usr/lib + +test-fc15: test-fc15.cc + $(CPP) -o test-fc15 test-fc15.cc $(LDFLAGS) -I/usr/include -I/usr/local/include -L/usr/local/lib -L/usr/lib + +copydll: $(TARGET) + echo "delete all librarys" + rm -rf *.dll + echo "copy linked dll first level" + cp -vf `ldd testmodbus-server.exe | grep .dll | grep /mingw64/bin | cut -d' ' -f3 ` ./ + echo "copy network dll" + cp /usr/lib/*UDPTCPNetwork.dll ./ + echo "copy linked dll second level" + cp -vf `ldd testmodbus-server.exe | grep .dll | grep /mingw64/bin | cut -d' ' -f3 ` ./ + + +buildwindows: + rm -rf TestModbus-Server-$(VERSION) + mkdir TestModbus-Server-$(VERSION) + make clean + make configcross + make $(TARGET) + cp testmodbus-server.exe TestModbus-Server-$(VERSION)/ + cp testmodbus-server.ui TestModbus-Server-$(VERSION)/ + cp testmodbus-server.png TestModbus-Server-$(VERSION)/ + cp Readme TestModbus-Server-$(VERSION)/ + cp Changelog TestModbus-Server-$(VERSION)/ + cp COPYING TestModbus-Server-$(VERSION)/ + mkdir TestModbus-Server-$(VERSION)/othersources + cp -r othersources/gdk-pixbuf-2.42.6.tar.xz TestModbus-Server-$(VERSION)/othersources + cp -r othersources/glib-main.tar.bz2 TestModbus-Server-$(VERSION)/othersources + cp -r othersources/gtk+-3.24.0.tar.xz TestModbus-Server-$(VERSION)/othersources + cp -r othersources/libepoxy-master.zip TestModbus-Server-$(VERSION)/othersources + cp -r othersources/*.txt TestModbus-Server-$(VERSION)/othersources + cp -r othersources/pango-1.48.7.tar.xz TestModbus-Server-$(VERSION)/othersources + tar xvzf othersources/winbuild-dll.tgz -C TestModbus-Server-$(VERSION)/ + tar xvzf othersources/winbuild-share.tgz -C TestModbus-Server-$(VERSION)/ + + +-include $(DEPENDFILE) + +.PHONY: all +.PHONY: count +.PHONY: clean diff --git a/Makefile.rules.crosswindows b/Makefile.rules.crosswindows new file mode 100644 index 0000000..605f71c --- /dev/null +++ b/Makefile.rules.crosswindows @@ -0,0 +1,8 @@ + +TARGET = $(APP).exe + +CPP = /usr/bin/x86_64-w64-mingw32-g++ +CPPFLAGS = -ggdb -Wall -O0 `PKG_CONFIG_PATH=/opt/W64-cross-compile/lib/pkgconfig pkg-config --cflags gtk+-3.0 gmodule-export-2.0` -Wl,--export-dynamic -DBUILD_WINDOWS=1 -Wdeprecated +INCLUDES = +LDFLAGS = -lws2_32 +LIBS = `PKG_CONFIG_PATH=/opt/W64-cross-compile/lib/pkgconfig pkg-config --libs gtk+-3.0 gmodule-export-2.0` -L/usr/lib -mwindows diff --git a/Makefile.rules.linux b/Makefile.rules.linux new file mode 100644 index 0000000..081b8a9 --- /dev/null +++ b/Makefile.rules.linux @@ -0,0 +1,8 @@ + +TARGET = $(APP) + +CPP = c++ +CPPFLAGS = -ggdb -Wall -O0 `pkg-config --cflags gtk+-3.0 gmodule-export-2.0` -Wl,--export-dynamic -I/usr/include -DBUILD_LINUX=1 +INCLUDES = +LDFLAGS = +LIBS = `pkg-config --libs gtk+-3.0 gmodule-export-2.0` -L/usr/lib diff --git a/Makefile.rules.windows b/Makefile.rules.windows new file mode 100644 index 0000000..883e91d --- /dev/null +++ b/Makefile.rules.windows @@ -0,0 +1,8 @@ + +TARGET = $(APP).exe + +CPP = g++ +CPPFLAGS = -ggdb -Wall -O0 `pkg-config --cflags gtk+-3.0 gmodule-export-2.0` -Wl,--export-dynamic -I/usr/include -DBUILD_WINDOWS=1 +INCLUDES = +LDFLAGS = -lWs2_32 +LIBS = `pkg-config --libs gtk+-3.0 gmodule-export-2.0` -L/usr/lib diff --git a/README.md b/README.md new file mode 100644 index 0000000..3cf749b --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# testmodbus-server + +This programm is used for testing modbus-tcpip configuations. +If you do not know what modbus-tcpip mean stop using this programm. + +This software is written by Steffen Pohle (steffen@gulpe.de), check for a +newer version of this software at https://steffen.gulpe.de/modbus-tcpip/ + +![Image_01](https://steffen.gulpe.de/modbus-tcpip/screenshot-winver1-0-0-regs.PNG) +![Image_02](https://steffen.gulpe.de/modbus-tcpip/screenshot-winver1-0-0-values.PNG) + + +# Compilation + make configlinux + make + + +# Installation +not supported. + + +# Cross Compilation for Windows (build on Debian, target Windows) +Configue the Makefiles.rules.crosswindows file to your needs. +A little manual on my cross compilation setup can be found +in HowTo-Cross-Compile.txt. Make sure you copy all needed dll +files to dll in this directory. + + make buildwindows + +All needed files will be placed in a separate subfolder together with all +needed librarys (needs to prepared, see Makefile for some hints). + +For precompiled windows binarys look at this [link](https://steffen.gulpe.de/modbus-tcpip/). + diff --git a/gui.cc b/gui.cc new file mode 100644 index 0000000..dd098cf --- /dev/null +++ b/gui.cc @@ -0,0 +1,773 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// gui.cc is part of TestModbus-Server. +// +///////////////////////////////////////////////////////////////////////////////// + +#if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__) +#else + #include /* close() */ +#endif +#include +#include +#include "gui.h" +#include "modbus.h" +#include "guivalues.h" +#include "json.h" +#include "mbsconfig.h" +#include "config.h" + +extern GtkBuilder *_builder_; // work around for threads +extern void addvar_displaywithvalues (gpointer data, GuiValue *v); + +////////////////////////////////////////////////////////////////////////////////////////////////// +// +// call back functions +// + +gboolean cb_window_delete_event (GtkWidget *widget, GdkEvent *event, gpointer data) { +// GtkBuilder *builder = (GtkBuilder *) data; + + cb_menu_save(widget, data); + + return FALSE; +} + +void cb_window_show (GtkWidget *widget, gpointer data) { +// GtkBuilder *builder = (GtkBuilder *) data; + mbdata_show(widget, data); + valdata_show(widget, data); + + g_timeout_add(100, Value_Loop, NULL); +}; + + +void cb_menu_new (GtkWidget *widget, gpointer data) { +// GtkBuilder *builder = (GtkBuilder *) data; + modbus.EnableAll(0); + MBData_EnableAll(0); + modbus.RequestsClear(); + MBData_ReqReset (); + Value_DelAll(); +}; + + +void cb_menu_open (GtkWidget *widget, gpointer data) { + GtkBuilder *builder = (GtkBuilder *) data; + GtkWindow *window = GTK_WINDOW (gtk_builder_get_object (builder, "testmodbus-server")); + GtkWidget *dialog; + GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN; + GtkFileFilter *filter; + gint res; + + printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + dialog = gtk_file_chooser_dialog_new ("Open File", + window, + action, + "_Cancel", + GTK_RESPONSE_CANCEL, + "_Open", + GTK_RESPONSE_ACCEPT, + NULL); + + filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, "*.modbus"); + gtk_file_filter_set_name(filter, "Test Modbus Config"); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter); + filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, "*.*"); + gtk_file_filter_set_name(filter, "All Files"); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter); + + res = gtk_dialog_run (GTK_DIALOG (dialog)); + if (res == GTK_RESPONSE_ACCEPT) { + char *filename; + GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog); + filename = gtk_file_chooser_get_filename (chooser); + config.SetFilename(filename); + g_free (filename); + load_file(config.GetFilename().c_str()); + } + gtk_widget_destroy (dialog); +}; + + +void cb_menu_save (GtkWidget *widget, gpointer data) { +// GtkBuilder *builder = (GtkBuilder *) data; + + if (config.GetFilename().length() == 0) + cb_menu_saveas (widget, data); + else { + save_file(config.GetFilename().c_str()); + } +}; + + +void cb_menu_saveas (GtkWidget *widget, gpointer data) { + GtkBuilder *builder = (GtkBuilder *) data; + GtkWindow *window = GTK_WINDOW (gtk_builder_get_object (builder, "testmodbus-server")); + GtkWidget *dialog; + GtkFileChooser *chooser; + GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SAVE; + GtkFileFilter *filter; + gint res; + + dialog = gtk_file_chooser_dialog_new ("Save File", + window, + action, + "_Cancel", + GTK_RESPONSE_CANCEL, + "_Save", + GTK_RESPONSE_ACCEPT, + NULL); + chooser = GTK_FILE_CHOOSER (dialog); + + filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, "*.modbus"); + gtk_file_filter_set_name(filter, "Test Modbus Config"); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter); + filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, "*.*"); + gtk_file_filter_set_name(filter, "All Files"); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter); + + gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE); + if (config.GetFilename().length () == 0) + gtk_file_chooser_set_current_name (chooser, "noname.modbus"); + else + gtk_file_chooser_set_filename (chooser, config.GetFilename().c_str()); + + res = gtk_dialog_run (GTK_DIALOG (dialog)); + + if (res == GTK_RESPONSE_ACCEPT) { + char *filename; + + filename = gtk_file_chooser_get_filename (chooser); + + config.SetFilename(filename); + cb_menu_save (widget, data); + g_free (filename); + } + + gtk_widget_destroy (dialog); +}; + + +void cb_menu_quit (GtkWidget *widget, gpointer data) { + GtkBuilder *builder = (GtkBuilder *) data; + GtkWindow *window = GTK_WINDOW (gtk_builder_get_object (builder, "testmodbus-server")); + + cb_menu_save(widget, data); + + gtk_window_close(window); +}; + + +// +// try to set new port +void cb_port_enter (GtkWidget *widget, gpointer data) { + config.SetPort(atoi(gtk_entry_get_text (GTK_ENTRY(widget)))); + gtk_entry_set_text(GTK_ENTRY(widget), std::to_string(config.GetPort()).c_str()); +}; + + +void cb_btn_start (GtkWidget *widget, gpointer data) { + GtkBuilder *builder = (GtkBuilder *) data; + GtkWidget *portentry = GTK_WIDGET (gtk_builder_get_object (builder, "port_entry")); + + int port = atoi(gtk_entry_get_text(GTK_ENTRY(portentry))); + + if (modbus.isRunning()) { + printf ("Stop Server\n"); + modbus.Stop(); + gtk_widget_set_sensitive(portentry, TRUE); + gtk_button_set_label(GTK_BUTTON(widget), _("Start")); + } + else { + printf ("Start Server (port: %d)\n", port); + if (modbus.Start(port)) { + gtk_button_set_label(GTK_BUTTON(widget),_("Stop")); + gtk_widget_set_sensitive(portentry, FALSE); + } + usleep (250000); + if (modbus.isRunning() == 0) { + modbus.Stop(); + displayerror("modbus server could not been started.\nSee console output for errors."); + gtk_widget_set_sensitive(portentry, TRUE); + gtk_button_set_label(GTK_BUTTON(widget), _("Start")); + } + } +}; + + + +void addvar_displaywithvalues (gpointer data, GuiValue *v) { + GtkBuilder *builder = (GtkBuilder *) data; + GtkWidget *dlg = GTK_WIDGET (gtk_builder_get_object (builder, "addvar")); + GtkWidget *reg = GTK_WIDGET (gtk_builder_get_object (builder, "adddlg_regnum")); + GtkWidget *typeCB = GTK_WIDGET (gtk_builder_get_object (builder, "adddlg_type")); + GtkWidget *simCB = GTK_WIDGET (gtk_builder_get_object (builder, "adddlg_simmulation")); + GtkWidget *name = GTK_WIDGET (gtk_builder_get_object (builder, "adddlg_name")); + GtkWidget *value = GTK_WIDGET (gtk_builder_get_object (builder, "adddlg_value")); + GtkWidget *change = GTK_WIDGET (gtk_builder_get_object (builder, "adddlg_change")); + GtkWidget *fc[4]; + int i, fcnum; + + GtkWidget *type = gtk_bin_get_child(GTK_BIN(typeCB)); + GtkWidget *sim = gtk_bin_get_child(GTK_BIN(simCB)); + + gtk_entry_set_text(GTK_ENTRY(name), "noname"); + + if (v == NULL) { + fcnum = 1; + } + else if (v->fc < 1 || v-> fc > 4) fcnum = 1; + else fcnum = v->fc; + + for (i = 0; i < 4; i++) { + fc[i] = GTK_WIDGET (gtk_builder_get_object (builder, ((string)"adddlg_FC"+std::to_string(i+1)).c_str())); + } + + if (v != NULL) { + gtk_entry_set_text(GTK_ENTRY(name), v->name.c_str()); + gtk_entry_set_text(GTK_ENTRY(value), v->value.c_str()); + gtk_entry_set_text(GTK_ENTRY(type), v->type.c_str()); + gtk_entry_set_text(GTK_ENTRY(sim), v->sim.c_str()); + gtk_entry_set_text(GTK_ENTRY(reg), std::to_string(v->reg).c_str()); + } + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fc[fcnum-1]), true); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(change), false); + + gint result = gtk_dialog_run(GTK_DIALOG(dlg)); + if (result == GTK_RESPONSE_ACCEPT || result == GTK_RESPONSE_APPLY) { + GuiValue vn; + vn.value = gtk_entry_get_text(GTK_ENTRY(value)); + vn.name = gtk_entry_get_text(GTK_ENTRY(name)); + vn.type = gtk_entry_get_text(GTK_ENTRY(type)); + vn.sim = gtk_entry_get_text(GTK_ENTRY(sim)); + vn.reg = atoi (gtk_entry_get_text(GTK_ENTRY(reg))); + for (i = 0; i < 4; i++) { + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(fc[i]))) vn.fc = i+1; + } + Value_Set(&vn); // this will reset the vn.value with the reigsters.. + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(change)) == true) { + uint16_t regvals[4]; + int regstowrite = 1; + + vn.value = gtk_entry_get_text(GTK_ENTRY(value)); // load the from the entry + if (Value_SetValue(vn.value, vn.type, vn.fc, ®stowrite, regvals)) { + modbus.SetRegValue(vn.fc, vn.reg, regstowrite, (uint16_t*)regvals); + } + + } + } + gtk_widget_hide(GTK_WIDGET(dlg)); +}; + +void cb_btn_addvar (GtkWidget *widget, gpointer data) { + addvar_displaywithvalues(data, NULL); +}; + + +void cb_btn_editvar (GtkWidget *widget, gpointer data) { + GuiValue v; + GtkTreeIter iter; + GtkTreeModel *model; + GtkWidget *vars = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "vars_tv")); + gchar *v_name; + gchar *v_fc; + gchar *v_reg; + gchar *v_type; + gchar *v_sim; + gchar *v_value; + GtkTreeSelection *sel; + + sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(vars)); + if (sel && gtk_tree_selection_get_selected(GTK_TREE_SELECTION(sel), &model, &iter)) { + gtk_tree_model_get (model, &iter, + VALDATA_COL_NAME, &v_name, + VALDATA_COL_FC, &v_fc, + VALDATA_COL_REGSTART, &v_reg, + VALDATA_COL_TYPE, &v_type, + VALDATA_COL_SIM, &v_sim, + VALDATA_COL_VALUE, &v_value, + -1); + + v.name = v_name; + v.reg = atoi (v_reg); + v.fc = atoi (v_fc); + v.type = v_type; + v.sim = v_sim; + v.value = v_value; + + addvar_displaywithvalues(data, &v); + } +}; + +void cb_btn_delvar (GtkWidget *widget, gpointer data) { + GtkTreeIter iter; + GtkTreeModel *model; + GtkWidget *vars = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "vars_tv")); + gchar *v_name; + GtkTreeSelection *sel; + + sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(vars)); + if (sel && gtk_tree_selection_get_selected(GTK_TREE_SELECTION(sel), &model, &iter)) { + gtk_tree_model_get (model, &iter, + VALDATA_COL_NAME, &v_name, + -1); + Value_Del(v_name); + g_free (v_name); + } + +}; + +////////////////////////////////////////////////////// +// +// update gui/add netdata side +// +gboolean cb_thread_network_data_add (gpointer data) { + GtkWidget *textview = GTK_WIDGET(gtk_builder_get_object (_builder_, "network_text")); + GtkTextBuffer *textbuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview)); + GtkTextIter start, end; + + if (data) { + struct modbus_data *mbdata = (struct modbus_data *)data; + std::string text; + char timetext[255]; + GtkTextTag *tag_datetime; + GtkTextTag *tag_data; + GtkTextTag *tag_info; + GtkTextTag *tag_modbus; + time_t _tm =time(NULL); + struct tm * curtime = localtime (&_tm); + static int _once = 0; + + if (_once == 0) { + _once = 1; + tag_datetime = gtk_text_buffer_create_tag (textbuffer, "Date_Time", + "foreground", "blue", "style", PANGO_WEIGHT_BOLD, "family", "Monospace", NULL); + tag_data = gtk_text_buffer_create_tag (textbuffer, "Data", + "foreground", "black", "style", PANGO_WEIGHT_NORMAL, "family", "Monospace", NULL); + tag_info = gtk_text_buffer_create_tag (textbuffer, "Info", + "foreground", "green", "style", PANGO_WEIGHT_THIN, "family", "Sans", NULL); + tag_modbus = gtk_text_buffer_create_tag (textbuffer, "Modbus", + "foreground", "black", "style", PANGO_WEIGHT_NORMAL, "family", "Sans", NULL); + } + strftime (timetext, 255, "%H:%M:%S", curtime); + + // + // build hex dump + // + text = ""; + for (int i = 0; i < mbdata->bufferlen; i++) { + unsigned char c = mbdata->buffer[i]; + char hexnum[] = "0123456789ABCDEF"; + + if (i == 0) text += " "; + else if (i % 32 == 0) text += "\n "; + else if (i % 4 == 0 && i > 0) text += " : "; + else if (i % 2 == 0 && i > 0) text += ":"; +// else text += " "; + + text += hexnum[c/16]; + text += hexnum[c%16]; + } + gtk_text_buffer_get_start_iter(textbuffer, &start); + gtk_text_buffer_insert_with_tags_by_name(textbuffer, &start, text.c_str(), -1, "Data", NULL); + + // + // first line + // + text = ""; + if (mbdata->fc != 0) { + text = text + "\n TransactionID: " + std::to_string(mbdata->transactionid); + text = text + " ProtocolID: " + std::to_string(mbdata->protoolid); + text = text + " Length: " + std::to_string(mbdata->length); + text = text + "\n UnitID: " + std::to_string(mbdata->unitid); + if (mbdata->fc < 10) text = text + " FC0" + std::to_string(mbdata->fc); + else text = text + " FC" + std::to_string(mbdata->fc); + if (mbdata->fc >= 5) { + text = text + " WRITE "; + if (mbdata->fc == 5) + text = text + " Flag/Coil: " + std::to_string(mbdata->regstart); + else if (mbdata->fc == 6) + text = text + " Register: " + std::to_string(mbdata->regstart); + else if (mbdata->fc == 15) + text = text + " Flag/Coil: " + std::to_string(mbdata->regstart) + " Count: " + std::to_string (mbdata->regcnt); + else if (mbdata->fc == 16) + text = text + " Register: " + std::to_string(mbdata->regstart) + " Count: " + std::to_string (mbdata->regcnt); + } + else { + text = text + " Registers: " + std::to_string (mbdata->regstart); + text = text + " Bytes: " + std::to_string (mbdata->regcnt); + } + } + text = text + "\n"; + gtk_text_buffer_get_start_iter(textbuffer, &start); + gtk_text_buffer_insert_with_tags_by_name(textbuffer, &start, text.c_str(), -1, "Modbus", NULL); + + // + // + text =""; + text += mbdata->hostname; + if (mbdata->direction == 0) text = text + " RECV "; + else text = text + " SEND "; + text = text + std::to_string(mbdata->bufferlen) + " Bytes"; + gtk_text_buffer_get_start_iter(textbuffer, &start); + gtk_text_buffer_insert_with_tags_by_name(textbuffer, &start, text.c_str(), -1, "Info", NULL); + + + // + // add date + // + text = "\n"; + text += timetext; + text += " "; + gtk_text_buffer_get_start_iter(textbuffer, &start); + gtk_text_buffer_insert_with_tags_by_name(textbuffer, &start, text.c_str(), -1, "Date_Time", NULL); + + free (mbdata); + } + + gtk_text_buffer_get_iter_at_line(textbuffer, &start, 2000); + gtk_text_buffer_get_iter_at_line(textbuffer, &end, 2050); + gtk_text_buffer_delete(textbuffer, &start, &end); + + return FALSE; +}; + + +////////////////////////////////////////////////////// +// +// add a text line +// +gboolean cb_thread_network_text_add (gpointer data) { + if (data) { + std::string text; + char timetext[255]; + GtkWidget *textview = GTK_WIDGET(gtk_builder_get_object (_builder_, "network_text")); + GtkTextBuffer *textbuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview)); + GtkTextIter start, end; + GtkTextTag *tag_datetime; + GtkTextTag *tag_info; + char *textprm = (char*) data; + time_t _tm =time(NULL); + struct tm * curtime = localtime (&_tm); + static int _once = 0; + + if (_once == 0) { + _once = 1; + tag_datetime = gtk_text_buffer_create_tag (textbuffer, "Date_Time", + "foreground", "blue", "style", PANGO_WEIGHT_BOLD, "family", "Monospace", NULL); + tag_info = gtk_text_buffer_create_tag (textbuffer, "Connection", + "foreground", "red", "style", PANGO_WEIGHT_BOLD, "family", "Sans", NULL); + } + + strftime (timetext, 255, "%H:%M:%S", curtime); + + // + // build hex dump + // + // + // + text =""; + text += textprm; + gtk_text_buffer_get_start_iter(textbuffer, &start); + gtk_text_buffer_insert_with_tags_by_name(textbuffer, &start, text.c_str(), -1, "Connection", NULL); + + // + // add date + // + text = "\n"; + text += timetext; + text += " "; + gtk_text_buffer_get_start_iter(textbuffer, &start); + gtk_text_buffer_insert_with_tags_by_name(textbuffer, &start, text.c_str(), -1, "Date_Time", NULL); + + free (textprm); + } + + return FALSE; +}; + + +gboolean cb_thread_status (gpointer data) { + string *s = (string *)data; + GtkWidget *status = GTK_WIDGET(gtk_builder_get_object (_builder_, "status_lb")); + gtk_label_set_text(GTK_LABEL(status), s->c_str()); + return FALSE; +} + +void cb_networkdata_show (GtkWidget *widget, gpointer data) { + GtkWidget *textview = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(data), "network_text")); + GtkTextBuffer *textbuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview)); + + printf ("*************************** \n"); + +}; + +void cb_addvar_close (GtkWidget *widget, gpointer data) { + GtkBuilder *builder = (GtkBuilder *) data; + GtkWidget *dlg = GTK_WIDGET (gtk_builder_get_object (builder, "addvar")); + printf ("%s\n", __FUNCTION__); + + gtk_dialog_response(GTK_DIALOG(dlg), GTK_RESPONSE_CANCEL); +}; + + +void displayerror (string error) { + GtkWidget *dialog; + GtkWidget *window = GTK_WIDGET (gtk_builder_get_object (_builder_, "testmodbus-server")); + dialog = gtk_message_dialog_new(GTK_WINDOW(window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + error.c_str()); + gtk_window_set_title(GTK_WINDOW(dialog), "Error"); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); +} + +void cb_addvar_addedit (GtkWidget *widget, gpointer data) { + GtkBuilder *builder = (GtkBuilder *) data; + GtkWidget *dlg = GTK_WIDGET (gtk_builder_get_object (builder, "addvar")); + GtkWidget *name = GTK_WIDGET (gtk_builder_get_object (builder, "adddlg_name")); + + GtkWidget *dialog; + int result; + + if (Value_Exist((string)gtk_entry_get_text(GTK_ENTRY(name))) == true) { + dialog = gtk_message_dialog_new(GTK_WINDOW(dlg), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, + "Are you sure you want to save?"); + gtk_window_set_title(GTK_WINDOW(dialog), "Question"); + result = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + if (result == GTK_RESPONSE_YES) gtk_dialog_response(GTK_DIALOG(dlg), GTK_RESPONSE_APPLY); + } + else { + gtk_dialog_response(GTK_DIALOG(dlg), GTK_RESPONSE_APPLY); + } + + return; +}; + + +void cb_menu_about(GtkWidget *widget, gpointer data) { + GtkBuilder *builder = (GtkBuilder *) data; + GtkWidget *dlg = GTK_WIDGET (gtk_builder_get_object (builder, "dlgabout")); + GtkWidget *label = GTK_WIDGET (gtk_builder_get_object (builder, "dlgabout_version")); + gtk_label_set_text(GTK_LABEL(label), VERSION); + gint result = gtk_dialog_run(GTK_DIALOG(dlg)); + gtk_widget_hide(GTK_WIDGET(dlg)); + + return; +} + + +void cb_about_btnclose(GtkWidget *widget, gpointer data) { + GtkBuilder *builder = (GtkBuilder *) data; + GtkWidget *dlg = GTK_WIDGET (gtk_builder_get_object (builder, "dlgabout")); + gtk_dialog_response(GTK_DIALOG(dlg), GTK_RESPONSE_CLOSE); +} + +void cb_btn_enableall (GtkWidget *widget, gpointer data) { + modbus.EnableAll(1); + MBData_EnableAll(1); +}; + + +void cb_btn_disableall (GtkWidget *widget, gpointer data) { + modbus.EnableAll(0); + MBData_EnableAll(0); +}; + + +void cb_btn_clearreq (GtkWidget *widget, gpointer data) { + modbus.RequestsClear(); + MBData_ReqReset (); +}; + + +void cb_btn_enablevalues (GtkWidget *widget, gpointer data) { + GtkWidget *vars = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "vars_tv")); + GtkTreeIter iter; + GtkTreeModel *model; + gboolean result; + gchar *v_name = NULL; + gchar *v_reg = NULL; + gchar *v_fc = NULL; + gchar *v_type = NULL; + int cnt = 0; + + model = gtk_tree_view_get_model(GTK_TREE_VIEW(vars)); + for (cnt = 0, result = gtk_tree_model_get_iter_first(model, &iter); result == TRUE; + result = gtk_tree_model_iter_next(model, &iter), cnt++) { + gtk_tree_model_get (model, &iter, + VALDATA_COL_NAME, &v_name, + VALDATA_COL_FC, &v_fc, + VALDATA_COL_REGSTART, &v_reg, + VALDATA_COL_TYPE, &v_type, + -1); + + if (v_name == NULL) { + if (v_fc != NULL) free (v_fc); + if (v_reg != NULL) free (v_reg); + if (v_type != NULL) free (v_type); + break; + } + + modbus.Enable(atoi(v_fc), atoi(v_reg), Value_GetSize(v_type), 1); + MBData_Enable(atoi(v_fc), atoi(v_reg), Value_GetSize(v_type), 1); + + if (v_name != NULL) free (v_name); + if (v_fc != NULL) free (v_fc); + if (v_reg != NULL) free (v_reg); + if (v_type != NULL) free (v_type); + } +}; + + + +void save_file(std::string fn) { + GtkWidget *guiautovalue = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "conf_autoaddvalues")); + GtkWidget *vars = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "vars_tv")); + GtkTreeIter iter; + GtkTreeModel *model; + gboolean result; + gchar *v_name = NULL; + gchar *v_reg = NULL; + gchar *v_fc = NULL; + gchar *v_type = NULL; + gchar *v_value = NULL; + gchar *v_sim = NULL; + int cnt = 0; + JSONParse jp; + JSONParse jv; + JSONElement je; + std::list values; + FILE *f; + + jp.Clear(); + values.clear(); + je.Clear(); + je.type = JSON_T_ARRAY; + je.name = "values"; + + model = gtk_tree_view_get_model(GTK_TREE_VIEW(vars)); + for (cnt = 0, result = gtk_tree_model_get_iter_first(model, &iter); result == TRUE; + result = gtk_tree_model_iter_next(model, &iter), cnt++) { + gtk_tree_model_get (model, &iter, + VALDATA_COL_NAME, &v_name, + VALDATA_COL_FC, &v_fc, + VALDATA_COL_REGSTART, &v_reg, + VALDATA_COL_TYPE, &v_type, + VALDATA_COL_SIM, &v_sim, + VALDATA_COL_VALUE, &v_value, + -1); + + if (v_name == NULL) { + if (v_fc != NULL) free (v_fc); + if (v_reg != NULL) free (v_reg); + if (v_type != NULL) free (v_type); + if (v_sim != NULL) free (v_sim); + if (v_value != NULL) free (v_value); + break; + } + + jv.Clear(); + jv.AddObject("name", v_name); + jv.AddObject("type", v_type); + jv.AddObject("fc", v_fc); + jv.AddObject("reg", v_reg); + jv.AddObject("sim", v_sim); + jv.AddObject("value", v_value); + + if(cnt != 0) + je.value += ','; + je.value += jv.ToString(); + + if (v_name != NULL) free (v_name); + if (v_fc != NULL) free (v_fc); + if (v_reg != NULL) free (v_reg); + if (v_type != NULL) free (v_type); + if (v_sim != NULL) free (v_sim); + if (v_value != NULL) free (v_value); + } + + jp.AddObject(je); + jp.AddObject("port", config.GetPort()); + + cnt = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(guiautovalue)); + jp.AddObject("auto_valueadd", cnt); + + f = fopen(fn.c_str(),"w"); + if (f != NULL) { + int err = fprintf (f, "%s\n",jp.ToString().c_str()); + if (err < 0) printf ("error on writing: %s\n", strerror(errno)); + fclose (f); + } + else printf ("error on open (write): %s\n", strerror(errno)); +}; + +#define FILEBUFFER 0x10000 +void load_file(std::string fn) { + GtkWidget *guiautovalue = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "conf_autoaddvalues")); + GtkWidget *guiport = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "port_entry")); + GuiValue g; + int autoadd = 0; + + + modbus.EnableAll(0); + MBData_EnableAll(0); + modbus.RequestsClear(); + MBData_ReqReset (); + Value_DelAll(); + std::string confdata; + FILE *f; + char buffer[FILEBUFFER]; + int i; + JSONParse json; + JSONParse value; + std::string temp; + + confdata = ""; + + f = fopen(fn.c_str(),"r"); + if (f != NULL) { + while (!feof(f)) { + fgets (buffer, FILEBUFFER, f); + confdata += buffer; + } + fclose (f); + } + else printf ("error on open (read): %s\n", strerror(errno)); + + json.Set(confdata); + + if (json.GetValueInt("port", &i)) { + config.SetPort(i); + gtk_entry_set_text(GTK_ENTRY(guiport), std::to_string(i).c_str()); + } + + if (json.GetValueInt("auto_valueadd", &i)) { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(guiautovalue), i); + } + + + for (i = 0; json.GetObjectIdx("values", i, &value); i++) { + value.GetValue("name", &g.name); + value.GetValue("type", &g.type); + value.GetValue("sim", &g.sim); + value.GetValue("value", &g.value); + value.GetValue("fc", &temp); g.fc = atoi(temp.c_str()); + value.GetValue("reg", &temp); g.reg = atoi(temp.c_str()); + + Value_Add(&g); + } +}; + + diff --git a/gui.h b/gui.h new file mode 100644 index 0000000..cc6188e --- /dev/null +++ b/gui.h @@ -0,0 +1,88 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// gui.h is part of TestModbus-Server. +// +///////////////////////////////////////////////////////////////////////////////// + +#ifndef _GUI_H_ +#define _GUI_H_ + +#include +#include +#include + +#include +#include +#include + +#define BUILDER_FILE "testmodbus-server.ui" + +#include "guivalues.h" +#include "guimodbusdata.h" +std::string to_hex16 (int v); +extern uint16_t modbusdata[4][0x10000]; + +void load_file(std::string fn); +void save_file(std::string fn); +void displayerror (string error); + +#ifdef __cplusplus +extern "C" { +#endif + + +// *********************************************************************** +// +// main windows call backs +// +G_MODULE_EXPORT void cb_window_show (GtkWidget *widget, gpointer data); +G_MODULE_EXPORT gboolean cb_window_delete_event (GtkWidget *widget, + GdkEvent *event, gpointer data); +G_MODULE_EXPORT void cb_menu_new (GtkWidget *widget, gpointer data); +G_MODULE_EXPORT void cb_menu_open (GtkWidget *widget, gpointer data); +G_MODULE_EXPORT void cb_menu_save (GtkWidget *widget, gpointer data); +G_MODULE_EXPORT void cb_menu_saveas (GtkWidget *widget, gpointer data); +G_MODULE_EXPORT void cb_menu_quit (GtkWidget *widget, gpointer data); +G_MODULE_EXPORT void cb_menu_about (GtkWidget *widget, gpointer data); + +// +// About Dialog +G_MODULE_EXPORT void cb_about_btnclose(GtkWidget *widget, gpointer data); + +// +// Variable Buttons +G_MODULE_EXPORT void cb_btn_addvar (GtkWidget *widget, gpointer data); +G_MODULE_EXPORT void cb_btn_editvar (GtkWidget *widget, gpointer data); +G_MODULE_EXPORT void cb_btn_delvar (GtkWidget *widget, gpointer data); + +// +// Add Variable Dialog +G_MODULE_EXPORT void cb_addvar_close (GtkWidget *widget, gpointer data); +G_MODULE_EXPORT void cb_addvar_addedit (GtkWidget *widget, gpointer data); + +// +// Register Buttons +G_MODULE_EXPORT void cb_btn_enableall (GtkWidget *widget, gpointer data); +G_MODULE_EXPORT void cb_btn_disableall (GtkWidget *widget, gpointer data); +G_MODULE_EXPORT void cb_btn_enablevalues (GtkWidget *widget, gpointer data); +G_MODULE_EXPORT void cb_btn_clearreq (GtkWidget *widget, gpointer data); + + +// +// Port Entry Field and Connect Buttons +// +G_MODULE_EXPORT void cb_port_enter (GtkWidget *widget, gpointer data); +G_MODULE_EXPORT void cb_btn_start (GtkWidget *widget, gpointer data); +G_MODULE_EXPORT gboolean cb_thread_network_data_add (gpointer data); +G_MODULE_EXPORT gboolean cb_thread_network_text_add (gpointer data); +G_MODULE_EXPORT gboolean cb_thread_status (gpointer data); + +G_MODULE_EXPORT gboolean modbus_callback (gpointer data); + + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/guimodbusdata.cc b/guimodbusdata.cc new file mode 100644 index 0000000..6130c96 --- /dev/null +++ b/guimodbusdata.cc @@ -0,0 +1,281 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// guimodbusdata.cc is part of TestModbus-Server. +// +///////////////////////////////////////////////////////////////////////////////// + +// +// +// show all modbus registers and the value +// +// + +#include "gui.h" +#include "guimodbusdata.h" +#include "modbus.h" + +extern Modbus modbus; + +extern GtkBuilder *_builder_; // work around for threads +void mbdata_enabletoggled_cb(GtkCellRendererToggle *cellrenderer, char *path, gpointer data); + +#define MAXREG 0x10000 +#define STEPREG 1000 +GtkTreeModel *MBData_create_with_data() { + GtkTreeStore *store; + GtkTreeIter iter, toplevel, secondlevel; + ModbusRegister r; + string txt = ""; + string regname, regname1, regname2; + int regnum, fc; + + store = gtk_tree_store_new (MBDATA_COLCOUNT, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_STRING); + // fill in FC1..FC4 + for (fc = 1; fc <= 4; fc++) { + regname = "FC" + std::to_string(fc); + gtk_tree_store_append (store, &toplevel, NULL); + gtk_tree_store_set (store, &toplevel, + MBDATA_COL_FCREG, regname.c_str(), + -1); + + for (regnum = 0; regnum < MAXREG; regnum++) { + if (regnum % STEPREG == 0) { + regname1 = "FC" + std::to_string(fc) + " " + std::to_string(regnum) + ".." + std::to_string(regnum+STEPREG); + gtk_tree_store_append (store, &secondlevel, &toplevel); + gtk_tree_store_set (store, &secondlevel, MBDATA_COL_FCREG, regname1.c_str(),-1); + } + + modbus.GetRegister(fc, regnum, &r); + regname2 = "FC" + std::to_string(fc) + " " + std::to_string(regnum); + if (fc < 3) { + if (r.value == 0) txt = "false"; + else txt = "true"; + } + else + txt = to_hex16(r.value) + " (" +std::to_string(r.value)+")"; + gtk_tree_store_append (store, &iter, &secondlevel); + gtk_tree_store_set (store, &iter, + MBDATA_COL_FCREG, regname2.c_str(), + MBDATA_COL_REQREAD, r.requested & 1, + MBDATA_COL_REQWRITE, r.requested & 2, + MBDATA_COL_ENABLED, (r.enabled), + MBDATA_COL_VALUE, txt.c_str(), + -1); + } + } + + return GTK_TREE_MODEL (store); +}; + + +void mbdata_show(GtkWidget *widget, gpointer data) { + GtkWidget *view = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(data), "regs_tv")); + GtkCellRenderer *renderer; + GtkTreeModel *model; + + // + renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), + -1, + "FC", + renderer, + "text", MBDATA_COL_FCREG, + NULL); + renderer = gtk_cell_renderer_toggle_new (); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), + -1, + "Req. Read", + renderer, + "active", MBDATA_COL_REQREAD, + NULL); + renderer = gtk_cell_renderer_toggle_new (); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), + -1, + "Req. Write", + renderer, + "active", MBDATA_COL_REQWRITE, + NULL); + renderer = gtk_cell_renderer_toggle_new (); + gtk_cell_renderer_toggle_set_activatable (GTK_CELL_RENDERER_TOGGLE(renderer), true); + g_signal_connect (G_OBJECT (renderer), "toggled", + G_CALLBACK (mbdata_enabletoggled_cb), + NULL); + + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), + -1, + "Enabled", + renderer, + "active", MBDATA_COL_ENABLED, + NULL); + renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), + -1, + "Value", + renderer, + "text", MBDATA_COL_VALUE, + NULL); + + model = MBData_create_with_data(); + gtk_tree_view_set_model (GTK_TREE_VIEW (view), model); + + g_object_unref(model); +}; + + +void mbdata_enabletoggled_cb(GtkCellRendererToggle *cellrenderer, char *path, gpointer data) { + GtkWidget *mbdata = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "regs_tv")); + GtkTreeIter iter; + GtkTreeModel *model; + int enabled; + char *fcreg = NULL; + int pathint[3]; + int reg; + int fc; + + // retrieve fc and register from path, return if invalid + for (fc = 0; fc < 3; fc++) pathint[fc] = -1; + sscanf (path, "%d:%d:%d", pathint, pathint+1, pathint+2); + fc = pathint[0]+1; + reg = pathint[1]*STEPREG+pathint[2]; + if (pathint[2] == -1) return; + + // read current enable setting, inverse, set to modbus object and set in treeview + model = gtk_tree_view_get_model(GTK_TREE_VIEW(mbdata)); + if (gtk_tree_model_get_iter_from_string(model, &iter, path) == true) { + gtk_tree_model_get(model, &iter, + MBDATA_COL_FCREG, &fcreg, + MBDATA_COL_ENABLED, &enabled, + -1); + if (fcreg != NULL) { + if (enabled == 0) enabled = 1; + else enabled = 0; + gtk_tree_store_set(GTK_TREE_STORE(model), &iter, + MBDATA_COL_ENABLED, enabled, + -1); + modbus.Enable(fc, reg, 1, enabled); + g_free (fcreg); + fcreg = NULL; + } + } +}; + + + +// walk throught the registers and draw them.. if unknown//not yet set check for auto add +void MBData_ChangeRegs (int fc, int regstart, int count, ModbusRegister *r) { + GtkWidget *mbdata = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "regs_tv")); + GtkTreeIter iter; + GtkTreeModel *model; + int i,j; + gchar *v_fcreg = NULL; + string path; + char value[16] = "0000"; + char hex[] = "0123456789ABCDEF"; + + model = gtk_tree_view_get_model(GTK_TREE_VIEW(mbdata)); + + for (i = 0; regstart < MAXREG && i < count; i++, regstart++) { + for (j = 0; j < 4; j++) value[3-j] = hex[(r[i].value >> j)%16]; + + path = std::to_string(fc-1) + ":" + std::to_string(regstart / STEPREG) + ":" + std::to_string(regstart % STEPREG); + // printf ("fc:%d reg:%d val:%d enabled:%d requested:%d path:%s\n", fc, regstart, r[i].value, r[i].enabled, r[i].requested, path.c_str()); + if (gtk_tree_model_get_iter_from_string(model, &iter, path.c_str()) == true) { + gtk_tree_model_get (model, &iter, + MBDATA_COL_FCREG, &v_fcreg, + -1); + if (v_fcreg == NULL) break; + + if (r[i].enabled) { + if (fc == 1) + memcpy (&modbusdata[0][regstart], &r[i].value, 2); + if (fc == 2) + memcpy (&modbusdata[1][regstart], &r[i].value, 2); + if (fc == 4) + memcpy (&modbusdata[3][regstart], &r[i].value, 2); + if (fc == 3 || fc == 6 || fc == 16) + memcpy (&modbusdata[2][regstart], &r[i].value, 2); + } + + if (fc == 1 || fc == 2) { + gtk_tree_store_set(GTK_TREE_STORE(model), &iter, + MBDATA_COL_FCREG, v_fcreg, + MBDATA_COL_REQREAD, (r[i].requested & 1), + MBDATA_COL_REQWRITE, (r[i].requested & 2), + MBDATA_COL_ENABLED, r[i].enabled, + MBDATA_COL_VALUE, r[i].value ? " true" : "false", + -1); + } + if (fc == 3 || fc == 4) { + gtk_tree_store_set(GTK_TREE_STORE(model), &iter, + MBDATA_COL_FCREG, v_fcreg, + MBDATA_COL_REQREAD, (r[i].requested & 1), + MBDATA_COL_REQWRITE, (r[i].requested & 2), + MBDATA_COL_ENABLED, r[i].enabled, + MBDATA_COL_VALUE, ((string)value + " (" + std::to_string(r[i].value) + ")").c_str(), + -1); + } + g_free(v_fcreg); + } + } +}; + + +void MBData_Enable (int fc, int regstart, int count, int onoff) { + GtkWidget *mbdata = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "regs_tv")); + GtkTreeIter iter; + GtkTreeModel *model; + int i; + string path; + + model = gtk_tree_view_get_model(GTK_TREE_VIEW(mbdata)); + + for (i = 0; regstart < MAXREG && i < count; i++, regstart++) { + path = std::to_string(fc-1) + ":" + std::to_string(regstart / STEPREG) + ":" + std::to_string(regstart % STEPREG); + if (gtk_tree_model_get_iter_from_string(model, &iter, path.c_str()) == true) { + gtk_tree_store_set(GTK_TREE_STORE(model), &iter, + MBDATA_COL_ENABLED, onoff, + -1); + } + } +}; + + + +void MBData_EnableAll (int onoff) { + GtkWidget *mbdata = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "regs_tv")); + GtkTreeIter iter; + GtkTreeModel *model; + int reg, fc; + string path; + + model = gtk_tree_view_get_model(GTK_TREE_VIEW(mbdata)); + for (fc = 1; fc < 5; fc++) for (reg = 0; reg < 0x10000; reg++) { + path = std::to_string(fc-1) + ":" + std::to_string(reg / STEPREG) + ":" + std::to_string(reg % STEPREG); + if (gtk_tree_model_get_iter_from_string(model, &iter, path.c_str()) == true) { + gtk_tree_store_set(GTK_TREE_STORE(model), &iter, + MBDATA_COL_ENABLED, onoff, + -1); + } + } +}; + + +void MBData_ReqReset () { + GtkWidget *mbdata = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "regs_tv")); + GtkTreeIter iter; + GtkTreeModel *model; + int reg, fc; + string path; + + model = gtk_tree_view_get_model(GTK_TREE_VIEW(mbdata)); + for (fc = 1; fc < 5; fc++) for (reg = 0; reg < 0x10000; reg++) { + path = std::to_string(fc-1) + ":" + std::to_string(reg / STEPREG) + ":" + std::to_string(reg % STEPREG); + if (gtk_tree_model_get_iter_from_string(model, &iter, path.c_str()) == true) { + gtk_tree_store_set(GTK_TREE_STORE(model), &iter, + MBDATA_COL_REQREAD, 0, + MBDATA_COL_REQWRITE, 0, + -1); + } + } +}; + diff --git a/guimodbusdata.h b/guimodbusdata.h new file mode 100644 index 0000000..e2bc8f4 --- /dev/null +++ b/guimodbusdata.h @@ -0,0 +1,39 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// guimodbusdata.h is part of TestModbus-Server. +// +///////////////////////////////////////////////////////////////////////////////// + +#ifndef _GUIMODBUSDATA_H_ +#define _GUIMODBUSDATA_H_ + +#include "gui.h" +#include "modbus.h" +#include "guivalues.h" + +enum { + MBDATA_COL_FCREG = 0, + MBDATA_COL_REQREAD, + MBDATA_COL_REQWRITE, + MBDATA_COL_ENABLED, + MBDATA_COL_VALUE, + MBDATA_COLCOUNT +}; + +GtkTreeModel *MBData_create_with_data(); +void MBData_ChangeRegs (int fc, int regstart, int count, ModbusRegister *r); +void MBData_Enable (int fc, int regstart, int count, int onoff); +void MBData_EnableAll (int onoff); +void MBData_ReqReset (); + +#ifdef __cplusplus +extern "C" { +#endif + +G_MODULE_EXPORT void mbdata_show(GtkWidget *widget, gpointer data); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/guivalues.cc b/guivalues.cc new file mode 100644 index 0000000..0f7fa27 --- /dev/null +++ b/guivalues.cc @@ -0,0 +1,554 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// guivalues.cc is part of TestModbus-Server. +// +///////////////////////////////////////////////////////////////////////////////// + +// +// +// show all variables and the value +// +// + +#include +#include + +#include "gui.h" +#include "config.h" +#include "mbsconfig.h" +#include "modbus.h" +#include "guivalues.h" + + +extern GtkBuilder *_builder_; // work around for threads + +GuiValue::GuiValue() { + sim = ""; + name = ""; + type = ""; + fc = 0; + reg = 0; +}; + + +GuiValue::~GuiValue() { + +}; + + +// walk throught the registers and draw them.. if unknown//not yet set check for auto add +void Values_ChangeRegs (int fc, int regstart, int count, ModbusRegister *r) { + int autoadd = 0; + int reg, idx; + GtkWidget *cb_autovalue = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "conf_autoaddvalues")); + GtkWidget *vars = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "vars_tv")); + GtkTreeIter iter; + GtkTreeModel *model; + gboolean result; + gchar *v_name = NULL; + gchar *v_fc = NULL; + gchar *v_reg = NULL; + gchar *v_type = NULL; + gchar *v_sim = NULL; + int found = 0; + int regsize = 1; + + + model = gtk_tree_view_get_model(GTK_TREE_VIEW(vars)); + autoadd = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb_autovalue)); + + for (idx = 0, reg = regstart; idx < count; reg++, idx++) { + for (found = 0, result = gtk_tree_model_get_iter_first(model, &iter); found == 0 && result == TRUE; + result = gtk_tree_model_iter_next(model, &iter)) { + gtk_tree_model_get (model, &iter, + VALDATA_COL_NAME, &v_name, + VALDATA_COL_FC, &v_fc, + VALDATA_COL_REGSTART, &v_reg, + VALDATA_COL_TYPE, &v_type, + VALDATA_COL_SIM, &v_sim, + -1); + + if (v_name == NULL || v_reg == NULL || v_fc == NULL || v_type == NULL) { + if (v_name != NULL) g_free(v_name); + if (v_reg != NULL) g_free(v_reg); + if (v_fc != NULL) g_free(v_fc); + if (v_type != NULL) g_free(v_type); + if (v_sim != NULL) g_free(v_sim); + break; + } + + regsize = Value_GetSize(v_type); + if (atoi(v_fc) == fc && reg >= atoi(v_reg) && reg < (atoi(v_reg) + regsize)) { + found = 1; + GuiValue v; + v.name = v_name; + v.fc = atoi(v_fc); + v.reg = atoi(v_reg); + v.type = v_type; + v.sim = v_sim; + Value_Set (&v); + } + + g_free(v_name); + g_free(v_fc); + g_free(v_reg); + g_free(v_sim); + g_free(v_type); + } + + if (autoadd && found == 0) { + GuiValue v; + v.name = "_auto_FC" + std::to_string(fc) + "_REG_" + std::to_string (reg); + v.sim = "NONE"; + v.fc = fc; + v.reg = reg; + if (v.fc == 1 || v.fc == 2) v.type = "BOOL"; + else v.type = "WORD"; + Value_Set (&v); + } + } +}; + + +std::string findparm(std::string t, std::string parm) { + std::string res = ""; + string::size_type i = t.find(parm+"="); + string::size_type j,k; + + if (i != string::npos) { + res = t.substr (i, string::npos); + j = res.find("="); + k = res.find(";"); + if (k != string::npos) k = k -j -1; + if (j != string::npos) res = res.substr(j+1, k); + } + + return res; +} + + +int Value_Simulation(GuiValue *v) { + struct timeval tv; + string s; + + gettimeofday (&tv, NULL); + string sim = v->sim.substr(0, v->sim.find(";")); + + if (sim.compare("PULSE") == 0) { + int ton = 10; + s = findparm(v->sim, "ton"); + if(s.length()>0) ton = atoi(s.c_str()); + + int toff = 50; + s = findparm(v->sim, "toff"); + if(s.length()>0) toff = atoi(s.c_str()); + + int td = ton +toff; + td = tv.tv_sec % td; + if (td <= ton) v->value = "true"; + else v->value = "false"; + + return 1; + } + else if (sim.compare("SIN") == 0) { + int td = 60; + s = findparm(v->sim, "t"); + if(s.length()>0) td = atoi(s.c_str()); + + float min = -100; + s = findparm(v->sim, "min"); + if(s.length()>0) min = atof(s.c_str()); + + float max = 100; + s = findparm(v->sim, "max"); + if(s.length()>0) max = atof(s.c_str()); + + float t = ((float)(tv.tv_sec % td)) + (((float)tv.tv_usec) / 1000000); + t = 4.0 * t * M_PI / td; + v->value = std::to_string((0.5*(max-min)*sin(t))+(min+max)*0.5); + + return 1; + } + else if (sim.compare("SAW") == 0) { + int td = 60; + s = findparm(v->sim, "t"); + if(s.length()>0) td = atoi(s.c_str()); + + float min = -100; + s = findparm(v->sim, "min"); + if(s.length()>0) min = atof(s.c_str()); + + float max = 100; + s = findparm(v->sim, "max"); + if(s.length()>0) max = atof(s.c_str()); + + float t = ((float)(tv.tv_sec % td)) + (((float)tv.tv_usec) / 1000000); + t = t / td; + v->value = std::to_string((max-min)*t+min); + + return 1; + } + + return 0; +}; + + +gboolean Value_Loop(gpointer data) { + GtkWidget *vars = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "vars_tv")); + GtkTreeIter iter; + GtkTreeModel *model; + gboolean result; + gchar *v_name = NULL; + gchar *v_type = NULL; + gchar *v_value = NULL; + gchar *v_sim = NULL; + gchar *v_fc = NULL; + gchar *v_reg = NULL; + GuiValue v; + int changed = 0; + + model = gtk_tree_view_get_model(GTK_TREE_VIEW(vars)); + for (result = gtk_tree_model_get_iter_first(model, &iter); result == TRUE; + result = gtk_tree_model_iter_next(model, &iter)) { + gtk_tree_model_get (model, &iter, + VALDATA_COL_NAME, &v_name, + VALDATA_COL_TYPE, &v_type, + VALDATA_COL_SIM, &v_sim, + VALDATA_COL_VALUE, &v_value, + VALDATA_COL_FC, &v_fc, + VALDATA_COL_REGSTART, &v_reg, + -1); + if (v_name == NULL) break; + + GuiValue v; + v.name = v_name; + v.fc = atoi(v_fc); + v.reg = atoi(v_reg); + v.type = v_type; + v.sim = v_sim; + Value_Set (&v); + + changed = Value_Simulation(&v); + if (changed) { + uint16_t regvals[4]; + int regstowrite = 1; + if (Value_SetValue(v.value, v.type, v.fc, ®stowrite, regvals)) { + modbus.SetRegValue(v.fc, v.reg, regstowrite, (uint16_t*)regvals); + } + } + + g_free(v_name); + g_free(v_type); + g_free(v_sim); + g_free(v_value); + g_free(v_reg); + g_free(v_fc); + } + + return TRUE; // continue timer +} + +void Value_Set(GuiValue *g) { + GtkWidget *vars = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "vars_tv")); + GtkTreeIter iter; + GtkTreeModel *model; + gboolean result; + gchar *v_name = NULL; + int changed = 0; + + model = gtk_tree_view_get_model(GTK_TREE_VIEW(vars)); + for (result = gtk_tree_model_get_iter_first(model, &iter); result == TRUE && changed == 0; + result = gtk_tree_model_iter_next(model, &iter)) { + gtk_tree_model_get (model, &iter, + VALDATA_COL_NAME, &v_name, + -1); + if (v_name == NULL) break; + if (strcmp(v_name, g->name.c_str()) == 0) { + Value_ModStore(model, &iter, g); + changed = 1; + } + g_free(v_name); + } + + if (changed == 0) + Value_Add(g); +} + + +int Value_GetSize(std::string type) { + if (type.compare("WORD") == 0) return 1; + if (type.compare("DWORD") == 0) return 2; + if (type.compare("FLOAT") == 0) return 2; + if (type.compare("DWORD_SWAP") == 0) return 2; + if (type.compare("FLOAT_SWAP") == 0) return 2; + + return 1; +} + + +void Value_Add(GuiValue *g) { + GtkWidget *vars = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "vars_tv")); + GtkTreeIter iter; + GtkTreeModel *model; + int count; + + model = gtk_tree_view_get_model(GTK_TREE_VIEW(vars)); + gtk_list_store_append (GTK_LIST_STORE(model), &iter); + Value_ModStore(model, &iter, g); + + count = Value_GetSize(g->type); + modbus.Enable(g->fc, g->reg, count, 1); + MBData_Enable(g->fc, g->reg, count, 1); +} + + +void Value_ModStore(GtkTreeModel *model, GtkTreeIter *iter, GuiValue *g) { + g->value = Value_GetValue(g->fc, g->reg, g->type); + gtk_list_store_set(GTK_LIST_STORE(model), iter, + VALDATA_COL_NAME, g->name.c_str(), + VALDATA_COL_FC, std::to_string(g->fc).c_str(), + VALDATA_COL_REGSTART, std::to_string(g->reg).c_str(), + VALDATA_COL_TYPE, g->type.c_str(), + VALDATA_COL_VALUE, Value_GetValue(g->fc, g->reg, g->type).c_str(), + VALDATA_COL_SIM, g->sim.c_str(), + -1); + + // for (int i = 0; i < 10; i++) { + // float *f = (float *)&modbusdata[2][i]; + // printf ("%f ", *f); + // } + // printf ("\n"); +}; + + +/////////////////////////////////////////////////// +// return valuze as string +std::string Value_GetValue(int fc, int reg, string type) { + std::string result = ""; + + if (fc == 1 || fc == 2) { + if (modbusdata[fc-1][reg]) result = "true"; + else result = "false"; + } + else if (fc == 3 || fc == 4) { + if (type.compare ("WORD") == 0) result = std::to_string(modbusdata[fc-1][reg]); + else if (type.compare ("FLOAT") == 0) { + uint8_t data[4]; + float *f = (float*)&data; + + memcpy (data, &modbusdata[fc-1][reg], 2); + memcpy (data+2, &modbusdata[fc-1][reg+1], 2); + + result = std::to_string(*f); + } + else if (type.compare ("FLOAT_SWAP") == 0) { + uint8_t data[4]; + float *f = (float*)&data; + + memcpy (data, &modbusdata[fc-1][reg+1], 2); + memcpy (data+2, &modbusdata[fc-1][reg], 2); + + result = std::to_string((double)*f); + } + else if (type.compare ("DWORD") == 0) { + uint8_t data[4]; + uint32_t *f = (uint32_t*)&data; + + memcpy (data, &modbusdata[fc-1][reg], 2); + memcpy (data+2, &modbusdata[fc-1][reg+1], 2); + + result = std::to_string(*f); + } + else if (type.compare ("DWORD_SWAP") == 0) { + uint8_t data[4]; + uint32_t *f = (uint32_t*)&data; + + memcpy (data, &modbusdata[fc-1][reg+1], 2); + memcpy (data+2, &modbusdata[fc-1][reg], 2); + + result = std::to_string(*f); + } + else if (type.compare ("BOOL") == 0) { + int c; + + for (result = "", c = 0; c < 16; c++) { + if (modbusdata[fc-1][reg] & (1< + +enum { + VALDATA_COL_NAME = 0, + VALDATA_COL_FC, + VALDATA_COL_REGSTART, + VALDATA_COL_TYPE, + VALDATA_COL_SIM, + VALDATA_COL_VALUE, + VALDATA_COLCOUNT +}; + + +class GuiValue { +public: + GuiValue (); + ~GuiValue (); + std::string name; + std::string type; + std::string sim; + std::string value; + int reg; + int fc; +}; + + +void Values_ChangeRegs (int fc, int regstart, int count, ModbusRegister *r); +void Value_Set(GuiValue *v); +void Value_Add(GuiValue *v); +void Value_Del(std::string name); +void Value_DelAll(); +void Value_ModStore(GtkTreeModel *model, GtkTreeIter *iter, GuiValue *g); +int Value_GetSize(std::string type); +std::string Value_GetValue(int fc, int reg, string type); +int Value_SetValue(string value, string type, int fc, int *registers, uint16_t *regs); +gboolean Value_Exist (std::string name); +gboolean Value_Loop(gpointer data); + +#ifdef __cplusplus +extern "C" { +#endif + +G_MODULE_EXPORT void valdata_show(GtkWidget *widget, gpointer data); + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/json.cc b/json.cc new file mode 100644 index 0000000..31c6043 --- /dev/null +++ b/json.cc @@ -0,0 +1,494 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// json.cc is part of TestModbus-Server. +// +///////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include "json.h" + +/*********************************************************************** + *********************************************************************** + * + * JSONParse + * + *********************************************************************** + *********************************************************************** + */ + +enum { + STEP_NONE = 0, + STEP_STARTNAME, + STEP_NAME, + STEP_STARTVALUE, + STEP_VALUE, + STEP_END +}; + + +///////////////////////////////////////////////////////// +// +// clear out all data +// +void JSONParse::Clear() { + jsondata = ""; + names.clear(); +} + +///////////////////////////////////////////////////////// +// +// read every element and keep only this in memory. +// +int JSONParse::Set(string json) { + int i; + int step; + int level; + bool ignorenext; + + JSONElement jelement; + + Clear(); + + // find start and read until end + for (step = STEP_NONE, i = 0, ignorenext = false; i < json.length(); i++) { + // need to copy next character +// debug (0, "JSONParse: step:%d i:%d name:'%s' value:'%s'", step, i, jelement.name.c_str(), jelement.value.c_str()); + if (ignorenext) { + ignorenext = false; + if (step == STEP_NAME) jelement.name += json[i]; + if (step == STEP_VALUE) jelement.value += json[i]; + } + + // searching for startname + else if (step == STEP_NONE) { + if (json[i] == '{') { + step = STEP_STARTNAME; + continue; + } + } + + // searching for startname + else if (step == STEP_STARTNAME) { + if (json[i] == '"') { + step = STEP_NAME; + continue; + } + } + + // copy name + else if (step == STEP_NAME) { + if (json[i] == '"') { + step = STEP_STARTVALUE; + continue; + } + else { + jelement.name += json[i]; + continue; + } + } + + // searching for startvalue + else if (step == STEP_STARTVALUE) { + if (json[i] == '"') { + step = STEP_VALUE; + jelement.type = JSON_T_STRING; + continue; + } + if (json[i] == '{') { + step = STEP_VALUE; + level = 0; + jelement.type = JSON_T_OBJECT; + jelement.value = "{"; + continue; + } + if (json[i] == '[') { + step = STEP_VALUE; + level = 0; + jelement.type = JSON_T_ARRAY; + jelement.value = "["; + continue; + } + if ((json[i] >= '0' && json[i] <= '9') || + (json[i] == '+' || json[i] == '-')) { + step = STEP_VALUE; + level = 0; + jelement.type = JSON_T_NUMBER; + jelement.value = json[i]; + continue; + } + } + + // copy value + else if (step == STEP_VALUE) { + if (jelement.type == JSON_T_STRING) { + if (json[i] == '"') step = STEP_END; + else jelement.value += json[i]; + continue; + } + else if (jelement.type == JSON_T_OBJECT) { + if (json[i] == '}' && level == 0) { + jelement.value += json[i]; + step = STEP_END; + } + else { + if (json[i] == '{') level++; // increase level + if (json[i] == '}') level--; // decrease level + jelement.value += json[i]; + } + continue; + } + else if (jelement.type == JSON_T_ARRAY) { + if (json[i] == ']' && level == 0) { + jelement.value += json[i]; + step = STEP_END; + } + else { + if (json[i] == '[') level++; // increase level + if (json[i] == ']') level--; // decrease level + jelement.value += json[i]; + } + continue; + } + else if (jelement.type == JSON_T_NUMBER) { + if ((json[i] < '0' || json[i] > '9') && json[i] != '.' && + json[i] != '+' && json[i] != 'e' && json[i] != 'E') step = STEP_END; + else { + jelement.value += json[i]; + continue; + } + } + } + + // another element? + if (step == STEP_END) { + if (json[i] == ',') { +// debug (0, "* JSON.Set Add name:%s", jelement.name.c_str()); + if (jelement.type != JSON_T_NONE) { +// debug (0, "%s:%d json add element type:%d", __FILE__, __LINE__, jelement.type); + names.push_back (jelement); + } + jelement.Clear(); + step = STEP_STARTNAME; + } + continue; + } + } +// debug (0, "* JSON.Set Add name:%s", jelement.name.c_str()); + if (jelement.type != JSON_T_NONE) { +// debug (0, "%s:%d json add element type:%d", __FILE__, __LINE__, jelement.type); + names.push_back (jelement); + } + + return 0; +}; + + +int JSONParse::GetValue(string varname, string *dest) { + list::iterator iter; + + if (dest == NULL) return 0; + *dest = ""; + + for (iter = names.begin(); iter != names.end(); iter++) { + if (varname.compare(iter->name) == 0) { + *dest = iter->value; + return 1; + } + } + + return 0; +}; + + +int JSONParse::GetValueInt(string varname, int *dest) { + string s; + int res = GetValue(varname, &s); + if (res) { + *dest = atoi (s.c_str()); + return 1; + } + return 0; +}; + + +int JSONParse::GetValueInt64(string varname, int64_t *dest) { + string s; + int res = GetValue(varname, &s); + if (res) { + *dest = atol (s.c_str()); + return 1; + } + return 0; +}; + + +int JSONParse::GetObject(string varname, JSONParse *dest) { + list::iterator iter; + + if (dest == NULL) return 0; + + for (iter = names.begin(); iter != names.end(); iter++) { + if (varname.compare(iter->name) == 0) { + dest->Set(iter->value); + return 1; + } + } + + return 0; +}; + + +#define MAXRECURSIVE 255 +int JSONParse::GetIdx(string src, int idx, string *dest) { + char recursive[MAXRECURSIVE]; + int i = 0, rcnt = 0, cnt = 0; + + (*dest) = ""; +// printf("\n***************************************idx:%d\n", idx); + + for (i = 0; i < MAXRECURSIVE; i++) recursive[i] = 0; + for (i = 0; i < src.length() && rcnt < MAXRECURSIVE && cnt <= idx; i++) { +// printf ("i:%d rcnt:%d['%c'] cnt:%d char:'%c' ous:'%s'\n", +// i, rcnt, recursive[rcnt], cnt, src[i], dest->c_str()); + if (src[i] == '[') { + recursive[rcnt++] = src[i]; + continue; + } + else if (src[i] == '{' && recursive[rcnt] != '"') recursive[++rcnt] = src[i]; + else if (src[i] == '}' && recursive[rcnt] == '{') rcnt--; + else if (src[i] == '"' && recursive[rcnt] == '"') rcnt--; + else if (src[i] == '"') recursive[++rcnt] = src[i]; + else if (src[i] == ',' && rcnt == 1) { + cnt++; + continue; + } + else if (src[i] == ']' && rcnt == 1) { + continue; + } + + if (rcnt > 0 && cnt == idx) { + (*dest) += src[i]; + if (src[i] == '\\') (*dest) += src[i]; + } + else { + if (src[i] == '\\')i++; + } + } + +// printf("\n***************************************idx:%d cnt:%d\n", idx, cnt); +// printf("in:'%s'\n***\nout:'%s'\n\n*****\n", src.c_str(), dest->c_str()); + + // + // final checks + if (cnt == 0 && idx == 0 && // empty source/array? + dest->size() == 0) return 0; // + if (cnt >= idx) return 1; // found the right element + return 0; // element not found +} +#undef MAXRECURSIVE + +int JSONParse::GetValueIdx(string varname, int idx, string *dest) { + list::iterator iter; + + if (dest == NULL) return 0; + + for (iter = names.begin(); iter != names.end(); iter++) { + if (varname.compare(iter->name) == 0) { + return GetIdx(iter->value, idx, dest); + } + } + + return 0; +}; + +int JSONParse::GetObjectIdx(string varname, int idx, JSONParse *dest) { + list::iterator iter; + string deststr; + int ret = 0; + + if (dest == NULL) return 0; + + for (iter = names.begin(); iter != names.end(); iter++) { + if (varname.compare(iter->name) == 0) { + ret = GetIdx(iter->value, idx, &deststr); + if (ret == 1) dest->Set(deststr); + return ret; + } + } + + return 0; +}; + +list JSONParse::GetElements() { + list l; + list::iterator iter; + + l.clear(); + for (iter = names.begin(); iter != names.end(); iter++) { + l.push_back(*iter); + } + + return l; +}; + + +void JSONParse::AddObject (JSONElement element) { + names.push_back (element); +}; + + +void JSONParse::AddObject (string name, JSONParse jp) { + JSONElement je; + je.SetObject(name, jp.ToString()); + names.push_back(je); +}; + + +void JSONParse::AddObject (string name, int val) { + JSONElement je; + je.Set(name, val); + names.push_back(je); +}; + + +void JSONParse::AddObject (string name, int64_t val) { + JSONElement je; + je.Set(name, val); + names.push_back(je); +}; + + +void JSONParse::AddObject (string name, string val) { + JSONElement je; + je.Set(name, val); + names.push_back(je); +}; + + +void JSONParse::AddObject (string name, double val) { + JSONElement je; + je.Set(name, val); + names.push_back(je); +}; + + +string JSONParse::ToString() { + list::iterator iter; + string output; + int level, i; + + output = "{"; + + for (level = 1, iter = names.begin(); iter != names.end(); iter++) { + if (iter != names.begin()) output += ","; + output += "\n"; + for (i = 0; i < 4*level; i++) output += " "; + output += iter->GetString(); + } + + output += "\n}\n"; + + return output; +}; + + +/*********************************************************************** + *********************************************************************** + * + * JSONElement + * + *********************************************************************** + *********************************************************************** + */ + + +void JSONElement::Set (string n, double v) { + name = n; + value = to_string(v); + type = JSON_T_NUMBER; +}; + + +void JSONElement::Set (string n, int v) { + name = n; + value = to_string(v); + type = JSON_T_NUMBER; +}; + + +void JSONElement::Set (string n, int64_t v) { + name = n; + value = to_string(v); + type = JSON_T_NUMBER; +}; + + +void JSONElement::Set (string n, string v) { + name = n; + value = v; + type = JSON_T_STRING; +}; + + +void JSONElement::SetArray (string n, list *l) { + list::iterator iter; + + name = n; + value = "["; + type = JSON_T_STRING; + + for (iter = l->begin(); iter != l->end(); iter++) { + if (iter != l->begin()) value += ","; + value += iter->GetString(); + } + value += "]"; +}; + + +void JSONElement::SetObject (string n, string s) { + name = n; + value = s; + type = JSON_T_OBJECT; +}; + + +string JSONElement::GetString () { + string output = ""; + string filename = __FILE__; + + switch (type) { + case(JSON_T_NUMBER): + output += "\"" + name + "\" : " + value; + break; + case(JSON_T_STRING): + if (value.length()==0) { + output += "\"" + name + "\" : \"\""; + } + else if (value[0] != '"') { + output += "\"" + name + "\" : \"" + value + "\""; + } + else output += "\"" + name + "\" : " + value; + break; + case(JSON_T_OBJECT): + output += "\"" + name + "\" : " + value; + break; + case(JSON_T_ARRAY): + if (value.length()==0) { + output += "\"" + name + "\" : []"; + } + else if (value[0] != '[') { + output += "\"" + name + "\" : [" + value + "]"; + } + else output += "\"" + name + "\" : " + value; + break; + default: + output += "\"error\" : \""+ filename + ":" + to_string(__LINE__) + " JSONElement unknown type error\"("+to_string(type)+")"; + break; + } + + return output; +}; + diff --git a/json.h b/json.h new file mode 100644 index 0000000..338f862 --- /dev/null +++ b/json.h @@ -0,0 +1,77 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// json.h is part of TestModbus-Server. +// +///////////////////////////////////////////////////////////////////////////////// + +#ifndef _JSON_H_ +#define _JSON_H_ + +#include +#include +#include + +using namespace std; + +enum { + JSON_T_NONE, + JSON_T_STRING, + JSON_T_NUMBER, + JSON_T_OBJECT, + JSON_T_ARRAY +}; + +class JSONElement { +public: + int type; + string name; + string value; + + JSONElement() { Clear(); }; + ~JSONElement() {}; + + void Clear() { type = JSON_T_NONE; name = ""; value = ""; }; + void Set (string n, double v); + void Set (string n, int v); + void Set (string n, int64_t v); + void Set (string n, string v); + void SetArray (string n, list *l); + void SetObject (string n, string s); + string GetString(); +}; + +class JSONParse { +private: + string jsondata; + list names; + +public: + JSONParse() { Set("{}"); }; + JSONParse(string json) { Set(json); }; + ~JSONParse() {}; + + void Clear(); + int Set(string json); + + int GetValue(string varname, string *dest); + int GetValueInt(string varname, int *dest); + int GetValueInt64(string varname, int64_t *dest); + int GetObject(string varname, JSONParse *dest); + + int GetIdx(string src, int idx, string *dest); + int GetValueIdx(string varname, int idx, string *dest); + int GetObjectIdx(string varname, int idx, JSONParse *dest); + + list GetElements(); + + void AddObject (JSONElement element); + void AddObject (string name, int val); + void AddObject (string name, int64_t val); + void AddObject (string name, string val); + void AddObject (string name, double val); + void AddObject (string name, JSONParse jp); + + string ToString(); +}; + +#endif // _JSON_H_ diff --git a/main.cc b/main.cc new file mode 100644 index 0000000..6b8b40d --- /dev/null +++ b/main.cc @@ -0,0 +1,107 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// main.cc is part of TestModbus-Server. +// +///////////////////////////////////////////////////////////////////////////////// + +#include + +#include "config.h" +#include "mbsconfig.h" +#include "gui.h" +#include "modbus.h" + +////////////////////////////////////////////////////////////////////////////////////////////////// +// +// global variables +// + +Config config; +Modbus modbus; +GtkBuilder *_builder_ = NULL; // work around for the thread situation +uint16_t modbusdata[4][0x10000]; // needed for work with the gui will by synced by the modbus call back functions + +gboolean modbus_callback(gpointer data); + +int main (int argc, char **argv) { + GtkBuilder *builder; + GObject *window; + +#ifdef BUILD_WINDOWS + char buffer[16]; + setvbuf (stdout, buffer, _IONBF, 16); +#endif + + printf ("TestModbus-Server - %s\n", VERSION); + printf (" https://steffen.gulpe.de/modbus-tcpip\n"); + printf (" written by Steffen Pohle \n"); + + gtk_init (&argc, &argv); + _builder_ = builder = gtk_builder_new (); + gtk_builder_add_from_file (builder, BUILDER_FILE, NULL); + gtk_builder_connect_signals(builder, builder); + + // + // #if defined _WIN32 || defined _WIN64 || defined __CYGWIN__ + // #else + // #endif + // + + modbus.SetCallback(&modbus_callback); + window = gtk_builder_get_object (builder, "testmodbus-server"); + gtk_widget_show_all (GTK_WIDGET(window)); + + if (argc == 2) { + printf ("Load File:%s\n", argv[1]); + config.SetFilename(argv[1]); + load_file(config.GetFilename()); + } + else { + printf ("Load File:default.modbus\n"); + config.SetFilename("default.modbus"); + load_file(config.GetFilename()); + } + + gtk_main (); + + return 0; +} + +string to_hex16 (int v) { + char HEX[] = "0123456789ABCDEF"; + int i = v; + int n; + string txt = ""; + + for (n = 0; n < 4; n++) { + txt = HEX[i%16]+ txt; + i = i / 16; + } + + return txt; +} + + +gboolean modbus_callback(gpointer data) { + struct modbus_callback_data *mdata = (struct modbus_callback_data *) data; + + MBData_ChangeRegs(mdata->fc, mdata->regstart, mdata->count, mdata->r); + Values_ChangeRegs(mdata->fc, mdata->regstart, mdata->count, mdata->r); + + if (mdata->r) free (mdata->r); + free (mdata); + + return FALSE; +}; + + +float get_cycletime(struct timeval *t) { + struct timeval t1; + float f = 0.0; + + t1 = *t; + gettimeofday(t, NULL); + f = (float)(t->tv_sec - t1.tv_sec) + ((t->tv_usec - t1.tv_usec) / 1000000.0); + + return f; +} diff --git a/mbsconfig.cc b/mbsconfig.cc new file mode 100644 index 0000000..ae4a4ab --- /dev/null +++ b/mbsconfig.cc @@ -0,0 +1,32 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// mbsconfig.cc is part of TestModbus-Server. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "mbsconfig.h" + +Config::Config () { + port = 502; + filename = ""; +}; + + +Config::~Config () { +}; + + +void Config::SetPort (int p) { + if (p < 0) port = 502; + else if (p > 0xFFFF) port = 502; + else port = p; + + printf ("Config::%s (%d)\n", __FUNCTION__, port); +}; + + +void Config::SetFilename (std::string f) { + filename = f; + + printf ("Config::%s (%s)\n", __FUNCTION__, f.c_str()); +}; diff --git a/mbsconfig.h b/mbsconfig.h new file mode 100644 index 0000000..b73d3f7 --- /dev/null +++ b/mbsconfig.h @@ -0,0 +1,47 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// mbsconfig.h is part of TestModbus-Server. +// +///////////////////////////////////////////////////////////////////////////////// + +/* + modbusserver configuration + */ + +#ifndef _MBSCONFIG_H_ +#define _MBDCONFIG_H_ + +#include "config.h" +#include "modbus.h" +#include + +// maybe soon internationalisation +#define _(__s) __s + +class Config { + private: + std::string filename; + int port; + public: + Config (); + ~Config(); + + void SetPort (int p); + int GetPort () { return port; }; + + void SetFilename(std::string f); + std::string GetFilename() { return filename; }; + + int Save(); // RETURN 0 on error + int Read(); // first set filename RETURN 0 on errro +}; + + + +// +// declared in main.cc +// +extern Config config; +extern Modbus modbus; + +#endif diff --git a/modbus.cc b/modbus.cc new file mode 100644 index 0000000..78860e2 --- /dev/null +++ b/modbus.cc @@ -0,0 +1,891 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// modbus.cc is part of TestModbus-Server. +// +///////////////////////////////////////////////////////////////////////////////// + +#include +#include +#if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__) +#else + #include /* close() */ +#endif + +#include "gui.h" +#include "modbus.h" +#include "mbsconfig.h" +#include "config.h" + +// +// C / C++ Wrapper +gpointer _ServerThread (gpointer data) { + modbus.ServerThread (); + return NULL; +}; + +// +// + +Modbus::Modbus () { + onchangecallback = NULL; + ModbusRegister r = {0, false, 0, false}; + port = 502; + g_mutex_init (&servermutex); + serverthread = NULL; + for (int i = 0; i < MODBUS_MAXCLIENTS; i++) { + clients[i] = NULL; + } + + for (int i = 0; i < MODBUS_IOBUFFER; i++) { + mbarray[FC1][i] = r; + mbarray[FC2][i] = r; + mbarray[FC3][i] = r; + mbarray[FC4][i] = r; + } +}; + +Modbus::~Modbus () { + for (int i = 0; i < MODBUS_MAXCLIENTS; i++) { + if (clients[i] != NULL) { + delete clients[i]; + clients[i] = NULL; + } + } +}; + + +int Modbus::isRunning() { + return tcpserver.IsListen(); +}; + + +int Modbus::Start(int serverport) { + + port = serverport; + serverthread = g_thread_new("network thread", _ServerThread, NULL); + + return 1; +}; + +void Modbus::Stop() { + g_mutex_lock(&servermutex); + for (int i = 0; i < MODBUS_MAXCLIENTS; i++) { + if (clients[i] != NULL) { + delete clients[i]; + clients[i] = NULL; + } + } + tcpserver.Close(); + g_mutex_unlock(&servermutex); +}; + + +void Modbus::CloseConnection(int slot) { + if (slot < 0 || slot >= MODBUS_MAXCLIENTS) return; // slot out of bound + if (clients[slot] != NULL) + delete clients[slot]; + clients[slot] = NULL; +}; + + +void Modbus::SetCallback (gboolean (*callback_func)(gpointer data)) { + onchangecallback = callback_func; +} + +/*************************************************************************************************** + * this should only be called from within the ServerThread function + */ +void Modbus::ReadData(gpointer pdata) { + int slot; + long int len; + + for (slot = 0; slot < MODBUS_MAXCLIENTS; slot++) if (&clients[slot] == pdata) break; + + if (slot < 0 || slot >= MODBUS_MAXCLIENTS) return; // slot out of bound + if (clients[slot] == NULL) return; // slot not connected? + + len = clients[slot]->Read(inbuffer, MODBUS_IOBUFFER); + if (len < 0) { + CloseConnection(slot); + } +}; + + +void Modbus::ServerThread() { + int keep_running = 1; + int ret; + struct timeval cycletimestamp = { 0 }; + float cycletime = 0.0; + string s; + + g_mutex_lock(&servermutex); + ret = tcpserver.Listen(port); + if (ret != 1) { + printf ("error:%s\n", strerror(errno)); + keep_running = 0; + } + g_mutex_unlock(&servermutex); + + // + // primary network loop + // + while (keep_running) { + TCP *tcp = NULL; + int slot; + int len; + + cycletime = get_cycletime (&cycletimestamp); + + // + // accept new connection? + g_mutex_lock(&servermutex); + if ((tcp = tcpserver.Accept()) != NULL) { + for (slot = 0; slot < MODBUS_MAXCLIENTS; slot++) { + if (clients[slot] == NULL) break; + } + + if (slot < MODBUS_MAXCLIENTS && tcp) { + char *msg; + clients[slot] = tcp; + msg = (char*) malloc (255); + snprintf (msg, 255, "new connection from %s\n", clients[slot]->GetRemoteAddr().c_str()); + gdk_threads_add_idle(cb_thread_network_text_add, msg); + } + else { + // no free slot, accept and close right away. + if (tcp) delete tcp; + } + } + + // + // + // loop through all clients + for (slot = 0; slot < MODBUS_MAXCLIENTS; slot++) { + if (clients[slot] != NULL) { + len = clients[slot]->ReadTimeout(inbuffer, MODBUS_IOBUFFER, 0); + if (len < 0) { + delete clients[slot]; + clients[slot] = NULL; + } + else if (len > 0) { + // + // incomming data + // + struct modbus_data *mbindata = (struct modbus_data*) malloc(sizeof (struct modbus_data)); + + if (len > 0x10000) { + printf ("%s:%d out of bound inbuffer? len:%d max %d\n", __FILE__, __LINE__, len, 0x10000); + exit (1); + } + + // + // reset and copy data to default values + strncpy (mbindata->hostname, clients[slot]->GetRemoteAddr().c_str(), TEXT_LEN); + memcpy (mbindata->buffer, inbuffer, len); + mbindata->bufferlen = len; + mbindata->fc = 0; + mbindata->regcnt = -1; + mbindata->regstart = -1; + mbindata->unitid = 0; + mbindata->length = 0; + mbindata->transactionid = 0; + mbindata->protoolid = 0; + + Decode(mbindata); + gdk_threads_add_idle(cb_thread_network_data_add, mbindata); + + // + // fill in outdata + // + + // + // reply data - outbuffer + // + struct modbus_data *mboutdata = (struct modbus_data*) malloc(sizeof (struct modbus_data)); + strncpy (mboutdata->hostname, clients[slot]->GetRemoteAddr().c_str(), TEXT_LEN); + memset (mboutdata->buffer, 0x0, 0x10000); + mboutdata->bufferlen = 0; + mboutdata->fc = 0; + mboutdata->regcnt = -1; + mboutdata->regstart = -1; + mboutdata->unitid = 0; + mboutdata->length = 0; + mboutdata->transactionid = 0; + mboutdata->protoolid = 0; + if (mbindata->fc >= 1 && mbindata->fc <= 4) { + if (WorkerAndEncodeRead(mbindata, mboutdata)) { + clients[slot]->Write(mboutdata->buffer, mboutdata->bufferlen); + } + else { + char *txt = (char*)malloc(255); + snprintf (txt, 255, "error on processing message\n"); + gdk_threads_add_idle(cb_thread_network_text_add, txt); + } + } + else if (mbindata->fc == 5 || mbindata->fc == 6) { + if (WorkerAndEncodeWriteSingle(mbindata, mboutdata)) { + clients[slot]->Write(mboutdata->buffer, mboutdata->bufferlen); + } + else { + char *txt = (char*)malloc(255); + snprintf (txt, 255, "error on processing message\n"); + gdk_threads_add_idle(cb_thread_network_text_add, txt); + } + } + else if (mbindata->fc == 15 || mbindata->fc == 16) { + if (WorkerAndEncodeWriteMulti(mbindata, mboutdata)) { + clients[slot]->Write(mboutdata->buffer, mboutdata->bufferlen); + } + else { + char *txt = (char*)malloc(255); + snprintf (txt, 255, "error on processing message\n"); + gdk_threads_add_idle(cb_thread_network_text_add, txt); + } + } + gdk_threads_add_idle(cb_thread_network_data_add, mboutdata); + } + } + } + // unlock servermutex + cycletime = get_cycletime (&cycletimestamp); + s = "modbus cycletime: " + std::to_string(1000.0 * cycletime) + "ms"; + gdk_threads_add_idle(cb_thread_status, &s); + + g_mutex_unlock(&servermutex); + + // wait some time (10ms) + usleep (10000); + + g_mutex_lock(&servermutex); + keep_running = tcpserver.IsListen(); + g_mutex_unlock(&servermutex); + } + s = "modbus server stoped"; + gdk_threads_add_idle(cb_thread_status, &s); // not thread save we wait 100ms to be sure + usleep (10000); +} + + +// return 0 on error +int Modbus::WorkerAndEncodeRead(struct modbus_data *mbin, struct modbus_data *mbout) { + // + // to prevent race condition and invalid data: servermutex must be already locked + // buffer on outdata must be of size 0x10000 + uint16_t i16; + uint8_t i8; + int pos = 0; + int i; + int c; + int error = 0; + + // transaction + mbout->transactionid = mbin->transactionid; + i16 = htons((uint16_t)mbin->transactionid); + memcpy (mbout->buffer+pos, &i16, sizeof(i16)); + pos += sizeof (i16); + + // protocolid + mbout->protoolid = mbin->protoolid; + i16 = htons((uint16_t)mbin->protoolid); + memcpy (mbout->buffer+pos, &i16, sizeof(i16)); + pos += sizeof (i16); + + // length 3 + number of registers + mbout->length = 3; + if (mbin->fc == 1 || mbin->fc == 2) { + mbout->length += mbin->regcnt / 8; + if ((mbin->regcnt % 8) > 0) mbout->length += 1; + } + else if (mbin->fc == 4 || mbin->fc == 3) { + mbout->length += mbin->regcnt * 2; // 16bit = 2Bytes + } + if (mbout->length > 0x1000-6) { + printf ("outbuffer to small? mbout-length:%d\n", mbout->length); + return 0; + } + i16 = htons((uint16_t)mbout->length); + memcpy (mbout->buffer+pos, &i16, sizeof(i16)); + pos += sizeof (i16); + + // device id + mbout->unitid = mbin->unitid; + memcpy (mbout->buffer+pos, &mbout->unitid, sizeof(uint8_t)); + pos += sizeof (uint8_t); + + // fc + mbout->fc = mbin->fc; + memcpy (mbout->buffer+pos, &mbout->fc, sizeof(uint8_t)); + pos += sizeof (uint8_t); + + // length of data in bytes?????? + i8 = 0; + if (mbin->fc == 1 || mbin->fc == 2) { + i8 += mbin->regcnt / 8; + if ((mbin->regcnt % 8) > 0) i8 += 1; + } + else if (mbin->fc == 4 || mbin->fc == 3) { + i8 += mbin->regcnt * 2; // 16bit = 2Bytes + } + mbout->regcnt = i8; + memcpy (mbout->buffer+pos, &i8, sizeof(uint8_t)); + pos += sizeof (uint8_t); + + mbout->direction = 1; + + // + // fill in the buffer + if (mbin->fc == 1 || mbin->fc == 2) { + uint8_t u8 = 0; + for (c= 0, i = mbin->regstart; i < mbin->regstart + mbin->regcnt; i++, c++) { + if (c != 0 && c%8 == 0) { + memcpy (&mbout->buffer[pos], &u8, 1); + u8 = 0; + pos++; + } + + if (mbin->fc == 1) { + if (!mbarray[FC1][i].enabled) error = 1; // Error + mbarray[FC1][i].requested |= 1; + if (mbarray[FC1][i].value) u8 |= (1<<(c%8)); + } + else if (mbin->fc == 2) { + if (!mbarray[FC2][i].enabled) error = 1; // Error + mbarray[FC2][i].requested |= 1; + if (mbarray[FC2][i].value) u8 |= (1<<(c%8)); + } + else { + printf ("unknown fc code? fc:%d\n", mbin->fc); + error = 1; + } + } + memcpy (&mbout->buffer[pos], &u8, 1); + pos++; + } + else if (mbin->fc == 4 || mbin->fc == 3) { + for (i = mbin->regstart; i < mbin->regstart + mbin->regcnt; i++) { + if (mbin->fc == 3) { + if (!mbarray[FC3][i].enabled) error = 1; // return nothing + mbarray[FC3][i].requested |= 1; + i16 = htons(mbarray[FC3][i].value); + } + if (mbin->fc == 4) { + if (!mbarray[FC4][i].enabled) error = 1; // return nothing + mbarray[FC4][i].requested |= 1; + i16 = htons(mbarray[FC4][i].value); + } + memcpy (mbout->buffer+pos, &i16, sizeof(i16)); + pos += sizeof (i16); + } + } + + // + // inform the application about the modbus values change + if (onchangecallback != NULL && mbin->regcnt > 0) { + struct modbus_callback_data *mdata = (struct modbus_callback_data*) malloc(sizeof(struct modbus_callback_data)); + + mdata->r = (ModbusRegister *) malloc (sizeof (ModbusRegister) * mbin->regcnt); + int fc = mbin->fc; + if (fc == 5) fc = 1; + if (fc == 6) fc = 3; + if (fc == 15) fc = 1; + if (fc == 16) fc = 3; + + if (fc == 1) + memcpy (mdata->r, &mbarray[FC1][mbin->regstart], sizeof (ModbusRegister) * mbin->regcnt); + else if (fc == 2) + memcpy (mdata->r, &mbarray[FC2][mbin->regstart], sizeof (ModbusRegister) * mbin->regcnt); + else if (fc == 3) + memcpy (mdata->r, &mbarray[FC3][mbin->regstart], sizeof (ModbusRegister) * mbin->regcnt); + else if (fc == 4) + memcpy (mdata->r, &mbarray[FC4][mbin->regstart], sizeof (ModbusRegister) * mbin->regcnt); + + g_mutex_unlock(&servermutex); + mdata->fc = fc; + mdata->regstart = mbin->regstart; + mdata->count = mbin->regcnt; + gdk_threads_add_idle(onchangecallback, mdata); // not thread save we wait 100ms to be sure; + g_mutex_lock(&servermutex); + } + + mbout->bufferlen = pos; + + if (error) return 0; + else return 1; +} + + +int Modbus::WorkerAndEncodeWriteSingle(struct modbus_data *mbin, struct modbus_data *mbout) { + uint16_t i16; + int pos = 0; + int error = 0; + + // transaction + mbout->transactionid = mbin->transactionid; + i16 = htons((uint16_t)mbin->transactionid); + memcpy (mbout->buffer+pos, &i16, sizeof(i16)); + pos += sizeof (i16); + + // protocolid + mbout->protoolid = mbin->protoolid; + i16 = htons((uint16_t)mbin->protoolid); + memcpy (mbout->buffer+pos, &i16, sizeof(i16)); + pos += sizeof (i16); + + // length 3 + number of registers + mbout->length = 3; + mbout->length += mbin->regcnt * 2; // 16bit = 2Bytes + i16 = htons((uint16_t)mbout->length); + memcpy (mbout->buffer+pos, &i16, sizeof(i16)); + pos += sizeof (i16); + + // device id + mbout->unitid = mbin->unitid; + memcpy (mbout->buffer+pos, &mbout->unitid, sizeof(uint8_t)); + pos += sizeof (uint8_t); + + // fc + mbout->fc = mbin->fc; + memcpy (mbout->buffer+pos, &mbout->fc, sizeof(uint8_t)); + pos += sizeof (uint8_t); + + mbout->direction = 1; + + if (mbin->fc == 5) { + mbarray[FC1][mbin->regstart].requested |= 2; + if (mbarray[FC1][mbin->regstart].enabled) { + if (mbin->regcnt == 0xFF00) mbarray[FC1][mbin->regstart].value = 1; + else mbarray[FC1][mbin->regstart].value = 0; + } + else error = 1; + + // return register and value + i16 = htons((uint16_t)mbin->regstart); + memcpy (mbout->buffer+pos, &i16, sizeof(i16)); + pos += sizeof (i16); + + i16 = htons((uint16_t)mbin->regcnt); + memcpy (mbout->buffer+pos, &i16, sizeof(i16)); + pos += sizeof (i16); + } + if (mbin->fc == 6) { + mbarray[FC3][mbin->regstart].requested |= 2; + if (mbarray[FC3][mbin->regstart].enabled) + mbarray[FC3][mbin->regstart].value = mbin->regcnt; + else error = 1; + + // return register and value + i16 = htons((uint16_t)mbin->regstart); + memcpy (mbout->buffer+pos, &i16, sizeof(i16)); + pos += sizeof (i16); + + i16 = htons((uint16_t)mbin->regcnt); + memcpy (mbout->buffer+pos, &i16, sizeof(i16)); + pos += sizeof (i16); + } + + mbout->bufferlen = pos; + + // + // inform the application about the modbus values change + if (onchangecallback != NULL) { + struct modbus_callback_data *mdata = (struct modbus_callback_data*) malloc(sizeof(struct modbus_callback_data)); + mdata->r = (ModbusRegister*) malloc(sizeof(ModbusRegister)); + + int fc = mbin->fc; + if (fc == 5) fc = 1; + if (fc == 6) fc = 3; + if (fc == 15) fc = 1; + if (fc == 16) fc = 3; + *mdata->r = mbarray[fc-1][mbin->regstart]; + mdata->fc = fc; + mdata->regstart = mbin->regstart; + mdata->count = 1; + g_mutex_unlock(&servermutex); + gdk_threads_add_idle(onchangecallback, mdata); // not thread save we wait 100ms to be sure; + g_mutex_lock(&servermutex); + } + + if (error) return 0; + else return 1; +} + + +int Modbus::WorkerAndEncodeWriteMulti(struct modbus_data *mbin, struct modbus_data *mbout) { + uint16_t i16; + int pos = 0; + int i; + int c; + int error = 0; + + // transaction + mbout->transactionid = mbin->transactionid; + i16 = htons((uint16_t)mbin->transactionid); + memcpy (mbout->buffer+pos, &i16, sizeof(i16)); + pos += sizeof (i16); + + // protocolid + mbout->protoolid = mbin->protoolid; + i16 = htons((uint16_t)mbin->protoolid); + memcpy (mbout->buffer+pos, &i16, sizeof(i16)); + pos += sizeof (i16); + + // length 3 + number of registers + mbout->length = 3; + mbout->length += mbin->regcnt * 2; // 16bit = 2Bytes + i16 = htons((uint16_t)mbout->length); + memcpy (mbout->buffer+pos, &i16, sizeof(i16)); + pos += sizeof (i16); + + // device id + mbout->unitid = mbin->unitid; + memcpy (mbout->buffer+pos, &mbout->unitid, sizeof(uint8_t)); + pos += sizeof (uint8_t); + + // fc + mbout->fc = mbin->fc; + memcpy (mbout->buffer+pos, &mbout->fc, sizeof(uint8_t)); + pos += sizeof (uint8_t); + + mbout->direction = 1; + + if (mbin->fc == 15) { + for (i = 0; mbin->regstart+i < 0x10000 && i < mbin->regcnt; i++) { + mbarray[FC1][mbin->regstart+i].requested |= 2; + if (mbarray[FC1][mbin->regstart+i].enabled) { + if (mbin->buffer[13+(i/8)] & (1<<(i%8))) mbarray[FC1][mbin->regstart+i].value = 1; + else mbarray[FC1][mbin->regstart+i].value = 0; + } + else error = 1; + } + + // return register and value + i16 = htons((uint16_t)mbin->regstart); + memcpy (mbout->buffer+pos, &i16, sizeof(i16)); + pos += sizeof (i16); + + i16 = htons((uint16_t)mbin->regcnt); + memcpy (mbout->buffer+pos, &i16, sizeof(i16)); + pos += sizeof (i16); + } + if (mbin->fc == 16) { + for (c = 13, i = 0; i < mbin->regcnt && mbin->regstart+i < 0x10000; i++, c += sizeof(uint16_t)) { + memcpy (&i16, mbin->buffer+c, sizeof(i16)); + mbarray[FC3][mbin->regstart+i].requested |= 2; + if (mbarray[FC3][mbin->regstart+i].enabled) + mbarray[FC3][mbin->regstart+i].value = ntohs(i16); + else error = 1; + } + + // return register and value + i16 = htons((uint16_t)mbin->regstart); + memcpy (mbout->buffer+pos, &i16, sizeof(i16)); + pos += sizeof (i16); + + i16 = htons((uint16_t)mbin->regcnt); + memcpy (mbout->buffer+pos, &i16, sizeof(i16)); + pos += sizeof (i16); + } + + mbout->bufferlen = pos; + + // + // inform the application about the modbus values change + if (onchangecallback != NULL && mbin->regcnt > 0 && mbin->regstart > 0 && mbin->regstart < 0x10000) { + struct modbus_callback_data *mdata = (struct modbus_callback_data*) malloc(sizeof(struct modbus_callback_data)); + mdata->r = (ModbusRegister*) malloc (sizeof (ModbusRegister) * mbin->regcnt);; + + int fc = mbin->fc; + if (fc == 5) fc = 1; + if (fc == 6) fc = 3; + if (fc == 15) fc = 1; + if (fc == 16) fc = 3; + + if (mbin->regstart+mbin->regcnt > 0xFFFF) { + printf ("%s:%d ERROR: mbin->regstart+mbin->regcnt > 0xFFFF limit:%x \n", __FILE__, __LINE__, mbin->regstart+mbin->regcnt); + exit (1); + } + + if (fc == 1) + memcpy (mdata->r, &mbarray[FC1][mbin->regstart], sizeof (ModbusRegister) * mbin->regcnt); + else if (fc == 2) + memcpy (mdata->r, &mbarray[FC2][mbin->regstart], sizeof (ModbusRegister) * mbin->regcnt); + else if (fc == 3) + memcpy (mdata->r, &mbarray[FC3][mbin->regstart], sizeof (ModbusRegister) * mbin->regcnt); + else if (fc == 4) + memcpy (mdata->r, &mbarray[FC4][mbin->regstart], sizeof (ModbusRegister) * mbin->regcnt); + + g_mutex_unlock(&servermutex); + mdata->fc = fc; + mdata->regstart = mbin->regstart; + mdata->count = mbin->regcnt; + gdk_threads_add_idle(onchangecallback, mdata); // not thread save we wait 100ms to be sure; + g_mutex_lock(&servermutex); + } + + if (error) return 0; + else return 1; +} + + + +void Modbus::Decode(struct modbus_data *mbdata) { + // + uint16_t i16; + uint8_t i8; + int pos = 0; + + mbdata->fc = 0; + mbdata->regcnt = -1; + mbdata->regstart = -1; + mbdata->unitid = 0; + mbdata->length = 0; + mbdata->direction = 0; + mbdata->transactionid = 0; + mbdata->protoolid = 0; + mbdata->bytecnt = 0; + + if (mbdata->bufferlen < 8) return; + + // Transaction + memcpy (&i16, mbdata->buffer+pos, sizeof(i16)); + pos += sizeof(i16); + mbdata->transactionid = ntohs(i16); + + // protocol + memcpy (&i16, mbdata->buffer+pos, sizeof(i16)); + pos += sizeof(i16); + mbdata->protoolid = ntohs(i16); + + // length + memcpy (&i16, mbdata->buffer+pos, sizeof(i16)); + pos += sizeof(i16); + mbdata->length = ntohs(i16); + + // unitid + memcpy (&i8, mbdata->buffer+pos, sizeof(i8)); + pos += sizeof(i8); + mbdata->unitid = i8; + + // function code + memcpy (&i8, mbdata->buffer+pos, sizeof(i8)); + pos += sizeof(i8); + mbdata->fc = i8; + + // register + memcpy (&i16, mbdata->buffer+pos, sizeof(i16)); + pos += sizeof(i16); + mbdata->regstart = ntohs(i16); + + // number of registers + memcpy (&i16, mbdata->buffer+pos, sizeof(i16)); + pos += sizeof(i16); + mbdata->regcnt = ntohs(i16); + + // unitid + memcpy (&i8, mbdata->buffer+pos, sizeof(i8)); + pos += sizeof(i8); + mbdata->bytecnt = i8; +}; + +int Modbus::GetRegister(int fc, int regnum, ModbusRegister *r) { + r->enabled = false; + r->requested = 0; + r->updated = false; + r->value = 0; + + if (regnum < 0 || regnum >= 0x10000) return -1; + + g_mutex_lock(&servermutex); + + if (fc == 1) *r = mbarray[FC1][regnum]; + else if (fc == 2) *r = mbarray[FC2][regnum]; + else if (fc == 3) *r = mbarray[FC3][regnum]; + else if (fc == 4) *r = mbarray[FC4][regnum]; + + g_mutex_unlock(&servermutex); + + return 0; +}; + + +int Modbus::GetRegisters(int fc, int regnum, int count, ModbusRegister *r) { + r->enabled = false; + r->requested = 0; + r->updated = false; + r->value = 0; + + if (regnum < 0 || regnum >= 0x10000) return -1; + + g_mutex_lock(&servermutex); + + if (fc == 1) *r = mbarray[FC1][regnum]; + else if (fc == 2) *r = mbarray[FC2][regnum]; + else if (fc == 3) *r = mbarray[FC3][regnum]; + else if (fc == 4) *r = mbarray[FC4][regnum]; + + g_mutex_unlock(&servermutex); + + return 0; +}; + + +int Modbus::SetRegister(int fc, int regnum, ModbusRegister *r) { + if (regnum < 0 || regnum >= 0x10000) return -1; + + g_mutex_lock(&servermutex); + + if (fc == 1) mbarray[FC1][regnum] = *r; + else if (fc == 2) mbarray[FC2][regnum] = *r; + else if (fc == 3) mbarray[FC3][regnum] = *r; + else if (fc == 4) mbarray[FC4][regnum] = *r; + + g_mutex_unlock(&servermutex); + + return 0; +} + + +int Modbus::SetRegisters(int fc, int regnum, int count, ModbusRegister *r) { + int reg = regnum; + int regend = regnum+count; + + g_mutex_lock(&servermutex); + for (reg = regnum; reg < regend; regnum++) { + if (reg < 0 && reg >= 0x10000) { + g_mutex_unlock(&servermutex); + return -1; + } + + if (fc == 1) mbarray[FC1][reg] = *r; + else if (fc == 2) mbarray[FC2][reg] = *r; + else if (fc == 3) mbarray[FC3][reg] = *r; + else if (fc == 4) mbarray[FC4][reg] = *r; + r++; + } + g_mutex_unlock(&servermutex); + + return 0; +} + + +void Modbus::Enable(int fc, int regstart, int count, int onoff) { + int i; + if (fc < 1 || fc > 4) return; + if (regstart < 0) return; + fc--; + + g_mutex_lock(&servermutex); + for (i = 0; i < count && regstart < MODBUS_IOBUFFER; i++, regstart++) { + mbarray[fc][regstart].enabled = onoff; + } + g_mutex_unlock(&servermutex); +}; + + +void Modbus::RequestsClear() { + int fc, reg; + + g_mutex_lock(&servermutex); + for (fc = 0; fc < 4; fc++) for (reg = 0; reg < MODBUS_IOBUFFER; reg++) { + mbarray[fc][reg].requested = 0; + } + g_mutex_unlock(&servermutex); +}; + + +void Modbus::EnableAll(int onoff) { + int fc, reg; + + g_mutex_lock(&servermutex); + for (fc = 0; fc < 4; fc++) for (reg = 0; reg < MODBUS_IOBUFFER; reg++) { + mbarray[fc][reg].enabled = onoff; + } + g_mutex_unlock(&servermutex); +}; + + +void Modbus::SetRegValue(int fc, int regstart, int count, uint16_t *values) { + int reg; + + if (fc <= 0 || fc > 4) { + printf ("%s:%d fc(%d) is set out of range\n", __FILE__, __LINE__, fc); + return; + } + + if (regstart < 0) { + printf ("%s:%d regstart(%d) is set out of range\n", __FILE__, __LINE__, regstart); + return; + } + + if (regstart + count >= MODBUS_IOBUFFER || regstart < 0) { + printf ("%s:%d regstart(%d) or count(%d) are set out of range\n", __FILE__, __LINE__, regstart, count); + return; + } + + g_mutex_lock(&servermutex); + for (int i = 0, reg = regstart; i < count && reg < 0x10000; i++, reg++) { + mbarray[fc-1][reg].value = values[i]; + } + + // + // inform the application about the modbus values change + if (onchangecallback != NULL && count > 0 && regstart >= 0 && regstart < 0x10000) { + struct modbus_callback_data *mdata = (struct modbus_callback_data*) malloc(sizeof(struct modbus_callback_data)); + mdata->r = (ModbusRegister*) malloc (sizeof (ModbusRegister) * count); + + if (fc == 5) fc = 1; + if (fc == 6) fc = 3; + if (fc == 15) fc = 1; + if (fc == 16) fc = 3; + + if (regstart+count > 0xFFFF) { + printf ("%s:%d ERROR: regstart+count > 0xFFFF limit:%x \n", __FILE__, __LINE__, regstart+count); + exit (1); + } + + if (fc == 1) + memcpy (mdata->r, &mbarray[FC1][regstart], sizeof (ModbusRegister) * count); + else if (fc == 2) + memcpy (mdata->r, &mbarray[FC2][regstart], sizeof (ModbusRegister) * count); + else if (fc == 3) + memcpy (mdata->r, &mbarray[FC3][regstart], sizeof (ModbusRegister) * count); + else if (fc == 4) + memcpy (mdata->r, &mbarray[FC4][regstart], sizeof (ModbusRegister) * count); + + g_mutex_unlock(&servermutex); + mdata->fc = fc; + mdata->regstart = regstart; + mdata->count = count; + gdk_threads_add_idle(onchangecallback, mdata); // not thread save we wait 100ms to be sure; + g_mutex_lock(&servermutex); + } + +/* // debug del me + printf ("%s:%d %s regs (", __FILE__, __LINE__, __FUNCTION__); + std::string text; + text = ""; + if (fc == 5) fc = 1; + if (fc == 6) fc = 3; + if (fc == 15) fc = 1; + if (fc == 16) fc = 3; + for (int r = 0; r < count; r++) { + unsigned char c; + char hexnum[] = "0123456789ABCDEF"; + + c = *(((unsigned char *)&mbarray[fc-1][regstart+r])+0); + text += hexnum[c/16]; + text += hexnum[c%16]; + + c = *(((unsigned char *)&mbarray[fc-1][regstart+r])+1); + text += hexnum[c/16]; + text += hexnum[c%16]; + + text += ":"; + } + + printf ("%s)\n", text.c_str()); +*/ + g_mutex_unlock(&servermutex); +}; + + diff --git a/modbus.h b/modbus.h new file mode 100644 index 0000000..23071ff --- /dev/null +++ b/modbus.h @@ -0,0 +1,113 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// modbus.h is part of TestModbus-Server. +// +///////////////////////////////////////////////////////////////////////////////// + +#ifndef _MODBUS_H_ +#define _MODBUS_H_ + + +#include +#include +#include + +#include "tcp.h" +#include + + +enum { + FC1 = 0, + FC2, + FC3, + FC4, + FCCOUNT +}; + +#define TEXT_LEN 512 +#define MODBUS_MAXCLIENTS 5 +#define MODBUS_IOBUFFER 0x10000 + +struct modbus_data { + char hostname[TEXT_LEN]; + char buffer[0x10000]; + size_t bufferlen; + int transactionid; + int protoolid; + int length; + int unitid; + int direction; + int fc; + int regstart; + int regcnt; + int bytecnt; +}; + + +// return the cycletime in us +float get_cycletime(struct timeval *t); + +struct { + uint16_t value; + bool enabled; + int requested; // 1 .. Read 2 .. Write + bool updated; +} typedef ModbusRegister; + + +struct modbus_callback_data { + int fc; + int regstart; + int count; + ModbusRegister *r; +}; + + +class Modbus { +private: + TCP tcpserver; + int port; + TCP *clients[MODBUS_MAXCLIENTS]; + GThread *serverthread; + GMutex servermutex; + gboolean (*onchangecallback)(gpointer data); + ModbusRegister mbarray[FCCOUNT][MODBUS_IOBUFFER]; // for all 4 Registertypes .. + + char inbuffer[MODBUS_IOBUFFER]; + char outbuffer[MODBUS_IOBUFFER]; + + void Decode(struct modbus_data *mbdata); + // return 0 on error, 1 on no error + int WorkerAndEncodeRead(struct modbus_data *mbin, struct modbus_data *mbout); + int WorkerAndEncodeWriteSingle(struct modbus_data *mbin, struct modbus_data *mbout); + int WorkerAndEncodeWriteMulti(struct modbus_data *mbin, struct modbus_data *mbout); +public: + Modbus(); + ~Modbus(); + + void ServerThread(); + void SetCallback (gboolean (*callback_func)(gpointer data)); + int Start(int serverport); + void Stop(); + + int GetRegister(int fc, int regnum, ModbusRegister *r); + int GetRegisters(int fc, int regnum, int count, ModbusRegister *r); + + int SetRegister(int fc, int regnum, ModbusRegister *r); + int SetRegisters(int fc, int regnum, int count, ModbusRegister *r); + void CloseConnection(int slot); + void ReadData(gpointer pdata); + + void Enable(int fc, int regstart, int count, int onoff); + void EnableAll(int onoff); + void RequestsClear(); + void SetRegValue(int fc, int regstart, int count, uint16_t *values); + + int isRunning(); +}; + +#if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__) +#define usleep(_us_) Sleep(_us_/1000) +#endif + +#endif diff --git a/tcp.cc b/tcp.cc new file mode 100644 index 0000000..f72a4e0 --- /dev/null +++ b/tcp.cc @@ -0,0 +1,521 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// tcp.cc is part of TestModbus-Server. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "tcp.h" +#include +#if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__) +#else + #include /* close() */ +#endif +#include +#include /* memset() */ +#include + +using namespace std; + +int tcpinit; +// +// convert host and port to sockaddr_in6 +// +int dns_filladdr (string host, string port, int ai_family, struct sockaddr_storage *sAddr) { + struct addrinfo hints, *res; + int err; + + bzero (&hints, sizeof (struct addrinfo)); + hints.ai_family = ai_family; + hints.ai_socktype = SOCK_DGRAM; + + if ((err = getaddrinfo (host.c_str(), port.c_str(), &hints, &res)) < 0) { + fprintf (stdout, "dns_filladdr (getaddrinfo):%s\n", gai_strerror (err)); + return -1; + } + + memcpy (sAddr, res->ai_addr, res->ai_addrlen); + freeaddrinfo (res); + + return 1; +}; + + +// +// convert int to char* +// +char* itoa(char* buffer, int number, int size) { + snprintf (buffer, size, "%d", number); + return buffer; +} + + +TCP::~TCP() { + Close(); +} + +TCP::TCP() { + if (tcpinit == 0) { +#ifdef BUILD_WINDOWS + + WORD wVersionRequested; + WSADATA wsaData; + int err; + + /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */ + wVersionRequested = MAKEWORD(2, 2); + + err = WSAStartup(wVersionRequested, &wsaData); + if (err != 0) { + /* Tell the user that we could not find a usable */ + /* Winsock DLL. */ + printf("WSAStartup failed with error: %d\n", err); + return; + } +#endif + tcpinit = 1; + } + sock = 0; + writecnt = 0; + readcnt = 0; + islisten = 0; +}; + +TCP::TCP(int s) { + TCP(); + sock = s; +// memset (&localaddr, 0x0, sizeof(localaddr)); +// memset (&remoteaddr, 0x0, sizeof(remoteaddr)); + writecnt = 0; + readcnt = 0; + islisten = 0; +}; + +TCP::TCP(string h, string p) { + TCP(); + Connect (h,p); +}; + + +int TCP::Listen(int port) { + char buffer[NET_BUFFERSIZE]; + int err, i; + struct addrinfo hints, *res, *rp; + + if (sock > 0) Close(); +// FIXME: solution to get both (IPV4 and IPV6) to work at the same time? + bzero (&hints, sizeof (struct addrinfo)); + hints.ai_flags = AI_PASSIVE; +#if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__) + hints.ai_family = AF_INET; +#else + hints.ai_family = AF_INET6; +#endif + hints.ai_socktype = SOCK_STREAM; + if ((err = getaddrinfo (NULL, itoa(buffer, port, 32), &hints, &res)) != 0) { + return 0; + } + + // + // walk through all results until we could connect + // + for (rp = res; rp != NULL; rp = rp->ai_next) { + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (sock == -1) continue; + + i = 1; + if ((err = setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, (char*)&i, sizeof (i))) != 0) { + printf ("%s:%d setsockopt error\n", __FILE__, __LINE__); + } + + if ((err = bind (sock, rp->ai_addr, rp->ai_addrlen)) < 0) { + close (sock); + sock = -1; + continue; + } + if (listen (sock, 8) < 0) { // maximum of 8 connections at the time + close (sock); + sock = -1; + continue; + } + break; + } + + freeaddrinfo (res); + + if (rp == NULL) { + sock = -1; + return 0; + } + + islisten = 1; + + return 1; +}; + + +TCP* TCP::Accept() { + fd_set rfds; + struct timeval tv; + int retval, err, i; + SOCKET newsock; + struct sockaddr_storage cliaddr; + socklen_t cliaddr_len = sizeof(struct sockaddr_storage); + char host[NET_BUFFERSIZE]; + char port[NET_BUFFERSIZE]; + TCP *tcp = NULL; + + if (sock <= 0) return NULL; + + FD_ZERO(&rfds); + FD_SET(sock, &rfds); + tv.tv_sec = 0; + tv.tv_usec = 1000; + + retval = select (sock+1, &rfds, NULL, NULL, &tv); + if (retval == -1 && errno == EINTR) { + retval = 0; + } + else if (retval == -1) { + return NULL; + } + else if (retval) { +#if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__) + newsock = accept (sock, (struct sockaddr *) &cliaddr, (int*) &cliaddr_len); +#else + newsock = accept (sock, (struct sockaddr *) &cliaddr, &cliaddr_len); +#endif + if (newsock < 0) return NULL; + tcp = new TCP(); + tcp->SetSocket(newsock, &cliaddr, cliaddr_len); + + return tcp; + } + return NULL; +} + + +int TCP::Connect(string h, string p) { + int i = 0; + + remote_host = h; + remote_port = p; + + return Connect(); +}; + + +int TCP::Connect(string hostport, int defaultport) { + char buffer[32]; + int pos = hostport.rfind(':', hostport.length()); + if (pos == -1) { + remote_port = itoa (buffer, defaultport, 32); + remote_host = hostport; + } + else { + remote_port = hostport.substr (pos+1, hostport.length() - pos); + remote_host = hostport.substr (0, pos); + } + + return Connect(); +}; + + +int TCP::Connect() { + int err, s; + struct addrinfo hints, *res, *rp; + struct timeval timeout; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6 + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = 0; + hints.ai_protocol = 0; + + err = getaddrinfo(remote_host.c_str(), remote_port.c_str(), &hints, &res); + if (err != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); + return 0; + } + + // + // walk through all results until we could connect + // + for (rp = res; rp != NULL; rp = rp->ai_next) { + s = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + + // setup timeout to max 2 secs + timeout.tv_sec = 2; + timeout.tv_usec = 0; + setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout)); + + if (s == -1) continue; + if (connect(s, rp->ai_addr, rp->ai_addrlen) != -1) { + sock = s; + break; + } + close(s); + } + + freeaddrinfo(res); + + // + // connect not successfull + // + if (rp == NULL) return 0; + + writecnt = 0; + readcnt = 0; + + return 1; +}; + + +long int TCP::ReadPop (char *buffer, long int pktlen, long int bufferlen) { + memmove (buffer, buffer+pktlen, bufferlen-pktlen); + return bufferlen-pktlen; +} + + +long int TCP::Read(char *buffer, long int len) { + long int len_ = len; + + if (sock <= 0) return -2; + len_ = recv (sock, buffer, len, 0); + + if (len_ < 0 && errno == EAGAIN) len_ = 0; + else if (len_ < 0 && errno != EAGAIN) { +#ifdef BUILD_WINDOWS + printf ("%s ERROR:%s\n", __FUNCTION__, strerror (errno)); +#else + fprintf (stderr, "%s ERROR:%s\n", __FUNCTION__, strerror (errno)); +#endif + len_ = -2; + } + else if (len_ == 0) len_ = -1; + if (len_ < 0) Close(); + + readcnt += len_; + + return len_; +}; + + +long int TCP::ReadTimeout(char *buffer, long int len, int timeout) { + int data = IsData (timeout); + + if (data > 0) return Read (buffer, len); + else if (data < 0) return -1; + else return 0; +}; + + +////////////////////////////////////////////////////////// +// +// write data, generate no signal if some error occures +long int TCP::Write(char *buffer, long int len) { + int i; + int to = NET_MAX_RETRY; + + if (sock <= 0) return -1; + + do { + i = send (sock, buffer, len, MSG_NOSIGNAL); + } while (i == -1 && (to--) > 0 && errno == EINTR); + + if (i < 0) Close (); + writecnt += i; + + return i; +}; + + +void TCP::Close() { +#ifdef BUILD_WINDOWS + closesocket(sock); +#else + if (sock > 0) close (sock); +#endif + sock = -1; + islisten = false; +}; + + +void TCP::SetSocket(SOCKET s, struct sockaddr_storage *saddr, socklen_t saddrlen) { + char host[NET_HOSTLEN]; + char port[NET_PORTLEN]; + int err; + + if (sock > 0) Close(); + if (s > 0) { + sock = s; + + if (saddr != NULL) { + memcpy (&peeraddr, saddr, sizeof(peeraddr)); + if ((err = getnameinfo ((struct sockaddr*) saddr, saddrlen, host, NET_HOSTLEN, + port, NET_PORTLEN, NI_NUMERICHOST | NI_NUMERICSERV)) == 0) { + remote_host = host; + remote_port = port; + } else { + printf ("error: getnameinfo"); +/* if (err == EAI_AGAIN) printf ("EAI_AGAIN\n"); + if (err == EAI_BADFLAGS) printf ("EAI_BADFLAGS\n"); + if (err == EAI_FAIL) printf ("EAI_FAIL\n"); + if (err == EAI_FAMILY) printf ("EAI_FAMILY\n"); + if (err == EAI_MEMORY) printf ("EAI_MEMORY\n"); + if (err == EAI_NONAME) printf ("EAI_NONAME\n"); + if (err == EAI_OVERFLOW) printf ("EAI_OVERFLOW\n"); + if (err == EAI_SYSTEM) printf ("EAI_SYSTEM\n"); */ // windows seem to have different error codes + } + } + } + else sock = -1; +}; + + +int TCP::IsConnected() { + return (sock > 0); +}; + + +int TCP::IsData(int timeout) { + fd_set sockset; + struct timeval tval; + + if (sock <= 0) return -1; + + FD_ZERO (&sockset); + FD_SET (sock, &sockset); + tval.tv_sec = timeout / 1000; + tval.tv_usec = (timeout % 1000); + if ((select (sock + 1, &sockset, NULL, NULL, &tval)) != -1) { + if (FD_ISSET (sock, &sockset)) return 1; + return 0; + } + else { + if (errno == EBADF) sock = -1; + } + return 0; +}; + + +int TCP::WebGetFile (string url, char *buffer, int maxsize) { + char outdata[NET_BUFFERSIZE]; + char indata[NET_BUFFERSIZE]; + char host[NET_HOSTLEN]; + char port[NET_PORTLEN]; + int breakup, len, head, i, buffpos = 0; + + if (!IsConnected()) { + strncpy (host, WebGetURLHost (url).c_str(), NET_HOSTLEN); + strncpy (port, WebGetURLPort (url).c_str(), NET_PORTLEN); + Connect (host, port); + } + + if (!IsConnected()) return -1; + + // send http request + snprintf (outdata, NET_BUFFERSIZE, "GET %s HTTP/1.0\nUser-Agent: unknown \nHost: %s\n\n", WebGetURLFile (url).c_str(), host); + Write (outdata, strlen (outdata)); + + // wait for data start + i = breakup = 0; + head = 1; + while (!breakup && head && (len = ReadTimeout (indata, NET_BUFFERSIZE-1, 2000)) >= 0) { + indata[len] = 0; + while (i < len -4 && head && !breakup) { + if (indata[i] == 0x0d && indata [i+1] == 0x0a && indata [i+2] == 0x0d && indata [i+3] == 0x0a) { + head = 0; + i = i + 4; + buffpos = len-i; + if (buffpos > maxsize) buffpos = maxsize; + memcpy (buffer, indata+i, buffpos); + } + + else if (strncmp (indata+i, "Content-Length:", 15) == 0) { + i = i + 16; + } + + else if (strncmp (indata+i, "403 ", 4) == 0) breakup = 1; + else i++; + } + } + + // read data + while (!breakup && (len = ReadTimeout (indata, NET_BUFFERSIZE-1, 2000)) >= 0) { + i = len; + if (i > maxsize-buffpos) i = maxsize-buffpos; + if (i > 0) { + memcpy (buffer+buffpos, indata, i); + buffpos += i; + } + } + + return buffpos; +}; + +const string TCP::WebGetURLHost (string url) { + string result; + int posSServPort = 7; // begin server:port + int posEServPort = 0; // end Server:Port + int posPort = -1; // port Position + + posEServPort = url.find("/", 7); + result = url.substr (posSServPort, posEServPort-posSServPort); + posPort = result.find(":"); + if (posPort > 0) + result = url.substr (posSServPort, posPort); + + return result; +}; + +const string TCP::WebGetURLPort (string url) { + string result; + int posSServPort = 7; // begin server:port + int posEServPort = 0; // end Server:Port + int posPort = -1; // port Position + + posEServPort = url.find("/", 7); + result = url.substr (posSServPort, posEServPort-posSServPort); + posPort = result.find(":"); + if (posPort > 0) + result = result.substr (posPort+1); + else + result = "80"; + + return result; +}; + +const string TCP::WebGetURLFile (string url) { + string result; + int posEServPort = 0; // end Server:Port + + posEServPort = url.find("/", 7); + result = url.substr (posEServPort); + + return result; +}; + + +const string TCP::GetRemoteAddr() { + string ret = ""; + socklen_t addrlen = sizeof (peeraddr); + char host[256] = ""; + char port[256] = ""; + + if (getnameinfo ((struct sockaddr*)&peeraddr, addrlen, host, 255, port, 255, NI_NUMERICHOST | NI_NUMERICSERV) != 0) { + printf ("getnameinfo error: %s\n", strerror(errno)); + } + + ret = (string)host + ":" + (string)port; + + return ret; +}; + + +const string TCP::GetLocalAddr() { + string ret; + + return ret; +}; + + diff --git a/tcp.h b/tcp.h new file mode 100644 index 0000000..23ca6b9 --- /dev/null +++ b/tcp.h @@ -0,0 +1,123 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// tcp.h is part of TestModbus-Server. +// +///////////////////////////////////////////////////////////////////////////////// + +#ifndef _TCP_H_ +#define _TCP_H_ + +#if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__) + +#define WIN32_LEAN_AND_MEAN + +#define _NTDDI_VERSION_FROM_WIN32_WINNT2(ver) ver##0000 +#define _NTDDI_VERSION_FROM_WIN32_WINNT(ver) _NTDDI_VERSION_FROM_WIN32_WINNT2(ver) + +#ifndef _WIN32_WINNT +# define _WIN32_WINNT 0x501 +#endif +#ifndef NTDDI_VERSION +# define NTDDI_VERSION _NTDDI_VERSION_FROM_WIN32_WINNT(_WIN32_WINNT) +#endif + +// #include +#include +#include +#include +#include + +#define socklen_t size_t + +#ifndef bzero +#define bzero(__z__, __x__) memset (__z__, 0x0, __x__) +#endif + +#ifndef MSG_NOSIGNAL +# define MSG_NOSIGNAL 0 +#endif + +#else + +#include +#include +#include +#include +#include +#include + +#endif + +#include + +using namespace std; + +#define SOCKET int + +#define NET_HOSTLEN 256 +#define NET_PORTLEN 6 +#define NET_BUFFERSIZE 1024 +#define NET_MAX_RETRY 5 // retry to send data EINTR +#define NET_MAX_TIMEOUT 30000 // timeout in ms + + +/************************************************************************ + * + * global functions needed for networking + * + */ +int dns_filladdr (string host, string port, int ai_family, + struct sockaddr_storage *sAddr); +char *itoa(char* buffer, int number, int size); +void UDPTCPNetwork_Startup(); +extern int UDPTCPNetwork_init; + + +/************************************************************************ + * tcp related functions + */ +class TCP { +private: + SOCKET sock; + struct sockaddr_storage peeraddr; + string remote_host; + string remote_port; + int readcnt; + int writecnt; + int islisten; +public: + TCP(); + TCP(SOCKET s); + TCP(string h, string p); + TCP(string hostport, int defaultport); + ~TCP(); + + int Connect(); + int Connect(string h, string p); + int Connect(string hostport, int defaultport); + long int ReadPop (char *buffer, long int pktlen, long int bufferlen); + long int ReadTimeout(char *buffer, long int len, int timeout); + long int Read(char *buffer, long int len); + long int Write(char *buffer, long int len); + void Close(); + int IsConnected(); + int IsData(int timeout); // timeout in ms; + int IsListen() { return islisten; }; + + int Listen(int port); + TCP* Accept(); + + SOCKET GetSocket() { return sock; }; + void SetSocket(SOCKET s, struct sockaddr_storage *saddr, socklen_t saddrlen); + + const string GetRemoteAddr(); + const string GetLocalAddr(); + + const string WebGetURLHost (string url); + const string WebGetURLPort (string url); + const string WebGetURLFile (string url); + int WebGetFile (string url, char *buffer, int maxsize); +}; + +#endif + diff --git a/test-fc15.cc b/test-fc15.cc new file mode 100644 index 0000000..89ad133 --- /dev/null +++ b/test-fc15.cc @@ -0,0 +1,110 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// test-fc15.cc is part of TestModbus-Server. +// +///////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#define SIZE_BUFFER 4096 +int main (int argc, char **argv) { + unsigned char data[] = { 0x00,0x70,0x00,0x00 , 0x00,0x0B,0x01,0x10 , + 0x00,0x04,0x00,0x02 , 0x04,0xC0,0x02,0x7B , + 0xA5 }; + + char buffer[SIZE_BUFFER]; + int pos; + TCP tcp; + int port; + int i,c; + int regstart; + int regcnt; + uint8_t ui8; + uint16_t ui16; + float f; + + memset (buffer, 0x00, SIZE_BUFFER); + + if (argc < 3) { + printf("test-fc15 server port register values.[01010101]..\n"); + printf("test-fc15 localhost 502 16 1111100000001010000\n"); + return 0; + } + + port = atoi (argv[2]); + printf ("%d connecting to: %s:%d\n", argc, argv[1], port); + if (tcp.Connect(argv[1], port) != 1) { + printf ("could not connect.\n"); + return -1; + } + + if (argc == 3) { + tcp.Write((char*)data, sizeof(data)); + } + else if (argc > 4) { + regstart = atoi (argv[3]); + regcnt = strlen(argv[4]); + + printf ("writing %d floats, offset %d\n", regcnt, regstart); + + // construct modbus protocol + pos = 0; + + // transact id + ui16 = 0; + memcpy (buffer+pos, &ui16, sizeof(ui16)); + pos += sizeof(ui16); + + // proitocol id + ui16 = 0; + memcpy (buffer+pos, &ui16, sizeof(ui16)); + pos += sizeof(ui16); + + // lenght + ui16 = htons(4 + regcnt/8); + memcpy (buffer+pos, &ui16, sizeof(ui16)); + pos += sizeof(ui16); + + // device id + ui8 = 1; + memcpy (buffer+pos, &ui8, sizeof(ui8)); + pos += sizeof(ui8); + + // function code + ui8 = 15; + memcpy (buffer+pos, &ui8, sizeof(ui8)); + pos += sizeof(ui8); + + // starting address + ui16 = htons((uint16_t)regstart); + memcpy (buffer+pos, &ui16, sizeof(ui16)); + pos += sizeof(ui16); + + // number of registers + ui16 = htons((uint16_t)regcnt); + memcpy (buffer+pos, &ui16, sizeof(ui16)); + pos += sizeof(ui16); + + // number of bytes + ui8 = regcnt*4; + memcpy (buffer+pos, &ui8, sizeof(ui8)); + pos += sizeof(ui8); + + buffer[pos] = 0; + for (i = 0; i < regcnt; i++) { + if (i > 0 && i % 8 == 0) { + pos++; + buffer[pos] = 0; + } + if (argv[4][i] == '1') buffer[pos] |= (1 << i%8); + } + pos++; + + tcp.Write((char*)buffer, pos); + } + tcp.Close(); + + return 0; +}; diff --git a/test-fc16.cc b/test-fc16.cc new file mode 100644 index 0000000..7f93ef2 --- /dev/null +++ b/test-fc16.cc @@ -0,0 +1,115 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// test-fc16.cc is part of TestModbus-Server. +// +///////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#define SIZE_BUFFER 4096 +int main (int argc, char **argv) { + unsigned char data[] = { 0x00,0x70,0x00,0x00 , 0x00,0x0B,0x01,0x10 , + 0x00,0x04,0x00,0x02 , 0x04,0xC0,0x02,0x7B , + 0xA5 }; + + char buffer[SIZE_BUFFER]; + int pos; + TCP tcp; + int port; + int i,c; + int regstart; + int regcnt; + uint8_t ui8; + uint16_t ui16; + float f; + + memset (buffer, 0x00, SIZE_BUFFER); + + if (argc < 3) { + printf("test-fc16 server port register values...\n"); + printf("test-fc16 localhost 502 16 25.0 0.54 1000.5\n"); + return 0; + } + + port = atoi (argv[2]); + printf ("%d connecting to: %s:%d\n", argc, argv[1], port); + if (tcp.Connect(argv[1], port) != 1) { + printf ("could not connect.\n"); + return -1; + } + + if (argc == 3) { + tcp.Write((char*)data, sizeof(data)); + } + else if (argc > 4) { + regstart = atoi (argv[3]); + regcnt = argc-4; + + printf ("writing %d floats, offset %d\n", regcnt, regstart); + + // construct modbus protocol + pos = 0; + + // transact id + ui16 = 0; + memcpy (buffer+pos, &ui16, sizeof(ui16)); + pos += sizeof(ui16); + + // proitocol id + ui16 = 0; + memcpy (buffer+pos, &ui16, sizeof(ui16)); + pos += sizeof(ui16); + + // lenght + ui16 = htons(3 + regcnt*4); + memcpy (buffer+pos, &ui16, sizeof(ui16)); + pos += sizeof(ui16); + + // device id + ui8 = 1; + memcpy (buffer+pos, &ui8, sizeof(ui8)); + pos += sizeof(ui8); + + // function code + ui8 = 16; + memcpy (buffer+pos, &ui8, sizeof(ui8)); + pos += sizeof(ui8); + + // starting address + ui16 = htons((uint16_t)regstart); + memcpy (buffer+pos, &ui16, sizeof(ui16)); + pos += sizeof(ui16); + + // number of registers + ui16 = htons((uint16_t)regcnt*2); + memcpy (buffer+pos, &ui16, sizeof(ui16)); + pos += sizeof(ui16); + + // number of bytes + ui8 = regcnt*4; + memcpy (buffer+pos, &ui8, sizeof(ui8)); + pos += sizeof(ui8); + + for (i = 0; i < regcnt; i++) { + f = (float) atof(argv[i+4]); + printf ("i: %d string:%s f:%f\n", i, argv[i+4], f); + + memcpy (&ui16, &f, sizeof(ui16)); + ui16 = htons(ui16); + memcpy (buffer+pos, &ui16, sizeof(ui16)); + pos += sizeof(ui16); + + memcpy (&ui16, ((char*)&f +2), sizeof(ui16)); + ui16 = htons(ui16); + memcpy (buffer+pos, &ui16, sizeof(ui16)); + pos += sizeof(ui16); + } + + tcp.Write((char*)buffer, pos); + } + tcp.Close(); + + return 0; +}; diff --git a/testmodbus-server.png b/testmodbus-server.png new file mode 100644 index 0000000000000000000000000000000000000000..7d1bb00f256fbfcf2e9c80213951ded6369113ec GIT binary patch literal 4996 zcmZu#byQSczdkTDL%uM8^nggGC=$}8(h>^N-Q6G^g1|_rlt@d8FF6d2G$#oHh z1mQ$FC@N~JD=M=INLxFH!1`rqt+$I)MI8`grp@Di{n-*)kG*MGu4qOmr^=L z8!$f*{1d@Nrv5CUVCk{@uGRaErSBExk>zYep<#_pcX*oB;xF%$2v|x>Z1BZn_Y^jJw#HU&~{Z5SrOsJ;JkYDF`M)HH>uWbs-?{*iFT)X=UxY;cM@=L zQ2rSV#TqU0(ijKE?BjTRgEal`Uf)Gh1=X;9K0QBfkKkuUO6v)amjzgc8}a}9e(Ym~ zy<@jtw~qcekL=4mqm`DPM|_5le&=GNQQ57vLq4w~)y%2xr?4r=(_T-r;7}>od=^df zt2YD-t1?4oDpXO^-$7`H6U-b&UUuxbR$`ZDx_(c?cdu~M++A+<6Ap~_ftJHFa1>O04fBQ_f?f3wX092CPn~^ z5WA~A@`4~ij;jmDzev^>3=(>)YbX=05Hiz}3iW+F8URBK-pWSaif%40Hm=@~qNk08 zw~aNMpM$qOo2t5owqZCq9RyK5QCE_G=>PRsj+3@t?|E-qEN9%xS=KufT(vmd7;Ylv zveF{)rKO@t9;5Vi^O9#ScHZ5e>sxspd&3!=N+!$R#`Tc!+E!EwBmEV%>1kD4%$1vn zlxdtDXeiz{Xv5jAW~de}G5g0A5%{>?qpIw(Q-01ox5IBm!F*x@Wew8ZL$}9t_fHL2TL)r4 z;U$P=gk!Y4`OA$-naod@9)+Cnwi(0LDzk)-1(=*qgUczZmyHis>BQjUm9*p$!L5_A zZu`TckmcK=qq_Qx7grC|XjLbWBVu=!wT%BcGj_GKX@3K*jgr-ie@?yzS!Wf7`aS07 zzuH53aDJHr;q-47=a(pCo-fY7q{96LH+#=-9x;fi{oPal&S}RXwej}jg>0jLi&lPD z-VIJ$w4!To3Ew*~WED>d?a;o&gk&r(jwjr_{4}8T_c&~#6mlZvId9+2oNM%e;f6zu zw_OXH>D6iI=sxJxbKbl;F`HfM;o-5oJ&!JY^JeM!Dpk}&+DOIw_enDad3ef1ODZ04 zDH<3Uv`s{g;KQkhhlfK1%MHrUD_JHWg8116H?Q@mD7+7^J)f(9baGPHQjtT4;S}fRhi`qY zOF&=zIbI|?yY$)og4)<{gxX(a|m?>!5Vx%Rnn#^)t_RqmTei)b8TY@1M*Z{#_tE+Zw=+@R; zlP1q-G&u!Di6&c~g!>Y7$8%Zj`mQ5NMORn1X8TV9ozM$fK7%#EE%Wa`elS2Mo7HW7 zk%SbcJi`M6Yu_n~bo+e-OifL94-aihadxBbX9}XkoxZqFnS~tEB9TZ5d|Dcsl+)j< zKY9}A<`{EdzU*^2UG7gcnF$MnlvGtWdHgOf{w%j$UcjKlYvLpTXs+lC2W$YuEhxx^ zLZSRGuy?`?Q7!xXp4&5a*48QMe1@I_em*|RIyzK^x;$OH$2)D81dxQJq~(0GPhjAE zEv*~k;^OAU8CrKwy9*og>LFPB-jCkHw4Lqkq^&LI?TfR+O?78y{*qt{3JMw;8utMJ z+G^UUF#PV>8i&kj(DC+48U>`Iqr=7b)aY9K%m+L5kPw-&>gpSIqq)oz8eHn?>YS32 zuk|h(J$|HQW)j9LMP%J}dy$foQphaj>wan@$^cHDh?SK!=8(ZVUCM1i8kAAY^Xu5% zE{Z|`Fk04c+axL~>gD7+M(c$puNzGO<`lgagL37dp!4^NvE)5a`)c0Pq_{Yupom4=W$YsKsAoY<%3p%`G`5hQQj! zM!rbbP7mGt=s}+zX@c@LLQ~RZviPQ;sby<0S=ERY}>>0o2ja(Ix@L#>U;<-R9Vn zy^ynzE+loSLHXXSTbqaCyOqJrYJ#w#p`o;YZfGiW@b)9~&9j;a5)7H_E55?}O z@|!eLf({Bi?(FKKqNa`j<*v}cYRBOnA0Ka2=Sb`5=$L{+g%1qe!$@1crw2va=(CBa zGHV+VKi(1sthSWnw-E4SQEZt^uHds=!@60$^CP^l;QH%*LqiXmLjOqq9C|e_v}mZO zr*f+me`{6-fRSO6C)DJb9qR*HfVl82G~+VCaT zD}Epk$PXZWNHZ$=OhiQFk8Pjg0wgIVbyx+cYv1Rrmj+VP(C8`23w$fsM>-nt{pF4eEk~%(0zj=Xgb89PcWJFUs6ic7XD*J^`)_2RO!W45Y0U^+tdP6Ja z#S7e9OhaV76%}lVgnWbYe8!PUmu*l{XJ}eRa)B_@-_=3INv>xdk%UGy=Hcq1quF;9 zNk~cG1H?Xl`I7M<>(+Bj`k21Mty{P3ot(PBn(*b5lN0m)7uShMNEGz-X+1qX;rm2* zxDpZ)az(oNRaGNPtxCv_LWdR9JP+dcAal^d_q!zfDoy2p{Ni~aq?Vs;}@54a*R7#Tp~iDc!` z3hYGepX_fE40ZuZ=hDiBo0*w?h>#5FkUJTby?oFdSRNcr84+@Rv>t+BWlb*8%KN_f z^b)kC)a$IQ&vSE$4GV#!%+a5DoNW0%6--a5A#=z@Q*LsBo?|gprn(7IALM$H&*$)FjWFuE3iP zO0KJPjF>%eK1F0DnL&&I%wHYN>m5kvBMf%6RbvdRBCMKpq(!2*@xx4`DrG6-mE1wy z81a8R+55qcQWFk5SX;<4;{pFCT_SxB>_JXvouh4WujZ@p)YAgp0cn%CK zoyw&VQCV3zBkJtz3?ODlBz*hzYkoD3Ssc=1dOhWqT~NSrjgb*OZ0F)acH_p4>DgID zZko51m1NJKKM&MIN_AIMRjK&+hyxgxR#u{U^orXT7u$NT&j29@q_w-Z*D>@;EI?*O zkcph+X-<{p`Sj7z5z8PZ_>>wfL7;-9j&ElfefQb^z!Ltr5}~iJU;6Hyf~%`wL}Vlt z0zn`pB^7C&qEaCLOgAWZA=s4#yDPBTSP=H< z)c{xtK532`%oOUHnPFgOX9w!k1E~6qK{*+`q_&nC5UJX|dq_}@F&{p7wMBWTVuiKK zCz0af>1fkt@3M)Gj{MrUZ*lAD>cFu7>HhVvv$GaXPNYC6c+eDXX9w%gzI^$D&B#EM zKID}SI!eq6-{b=$lnFh%ySrg%v{Xw=i?HJacR>I!095q!all=0a&d`HKxZQ4Zw41I zK{(LnRJCewurwBnZ4Q|RTUt(*y^sT)oh$n^>E}<|!s234h?1K6MWJq4tAnGXXne>?>_aWR|L*m57LqCG55onVX*n&<$(Dgi#q88I!MGz2dh}y@M`Qwy4Vv00zyKsdw`OW#Kgq?hSjEjipwM4ZL)#Z{(i7NF5&ePzU+CY zSR;$T&(9Aw1O9gQV!JThcV|HZ7^l5$^v)5Q-G~eZBicUOO)R?c;N_KrN@!6B9co== ziaB$+caMaQ&pg?gf{3U+mNm4LP7(AeHy>YDYsh&@dU{dA;wU|+nZCint}m6w{U{Dq zKyqaH1aHK=gD@b@rDSBpRIOLHU0h2_N9fJ zKKynt#%q;)Y;4SZxsM7X9tI}t9v#J~agKQf?#!cO4-UKwi;7S{T~evsAb6J?C$)_a zGxOTVdAp&P7B#Nzx(~7?P!>6w89?`u($ZIrq!1c9?tZJS7p{-0?$0!-B>}df+yoymI?#PpRYFNg$-=D#DJU)> zq3gA{Q%X*bjJ?$_&^W09zki0$*RKG904pkTrFtATxqrirPfni7>HvB!iV!k|hs#0X za$zrD(#vp+^!KwD6%_?)7AGfDWC$1^*`DJ9PKevCUCCE+a^ibzY`h2PwS9Dy0*GxV zCg>P`^qK4?n4$z|dk+R0i%B-L1#Qn(^4Vd$*x~|vI`_#P$b<>ke@EBjd99AD_!9dZZ{2pDmaBJL9C_0F@ndIiZ>(4Zu&(qK zq(g&)96dcfb9F}iw*p)`YUUW}nVE-o)laLLb$$ZfhDrs_ZaO|GLKp~xj31~Z&1^}Z0XZEY zLN-R#ZDnP|LZ&TCi9pZ3O_@Cv=l+L=rh6#sb~3s(^iT6bxGu*#QhFri7uM3sO7Tx! zK+O8;oTeISd#I?W`uh5ovRij|-CK?qf(TY>Yis-F(S91fBssC43dTO*EQD?;FcfcNYV-l3eM*uRg57P_wU`rwf3Kp zP_-@D;J^S|=3vmI>f^`PET$wIo0}tK6l|3UBVv$1_${aDvd1g(Fjg)K{Nk&c8sY87$IHTwkYXY*rnc*2Mg3cKWsC|yUKo63 zIzHHqnm_xd4O`*?eTa==ZX=CW82j|e{Dmkm(!)jf@7=Q}6@Ewzhg%b|!#{<*>7@r` z4M%^xasn+a+1fDrw}pj;?gPeX-^zZ|5cl27>ge@NJQi_?W%p>-&C3Z!AHz<;diUnK ziy$}m(%3Oj1(5FIlag#F9K~TRkMG&Ov&#dSqeivg&)4^DMa9sTYnTddKqv6@{Xcq> z*;D(6zRGi^_P6KK_T>s|l9>~U+Kpu2RXzZnAslq%3^*2{tgL(>ulqN-i^MN5QS8U8 z5E$QG+~|@zl_BPwXJ+~%={tO2FftL0_#3HpThm`8;Q@A_Y*_?<6M;3n7EIR!qyz~7LKy~rjtpN3`d7txFTP0*=s890>x z2-H>A)usJ6!E1;@oh-iQ`Tv{OOU64mm{PObrxrM7ISTijyWa}@-9Z7C1r7ZqODt(SD-Ay& yjXB%-^#Oa4Yg%|3YY_VXzr*;S7aO7#C$cOX23-Y%l!f4J45Y5CsZ^z49`PR{Er7%T literal 0 HcmV?d00001 diff --git a/testmodbus-server.ui b/testmodbus-server.ui new file mode 100644 index 0000000..8010628 --- /dev/null +++ b/testmodbus-server.ui @@ -0,0 +1,1118 @@ + + + + + + + + + + + + + + + + WORD + + + DWORD + + + DWORD_SWAP + + + FLOAT + + + FLOAT_SWAP + + + BOOL + + + + + False + Add Variable or Flag + dialog + + + False + vertical + 2 + + + False + end + + + gtk-add + True + True + True + True + True + + + + True + True + 0 + + + + + gtk-close + True + True + True + True + True + + + + True + True + 1 + + + + + False + False + 0 + + + + + True + False + vertical + 10 + + + True + False + 9 + True + + + Coils +[Read/Write] + True + True + False + True + True + adddlg_FC2 + + + False + True + 0 + + + + + Digital In +[Read] + True + True + False + True + True + adddlg_FC1 + + + False + True + 1 + + + + + Holding Reg. +[Read/Write] + True + True + False + True + True + adddlg_FC1 + + + False + True + 2 + + + + + Input Reg. +[Read] + True + True + False + True + True + adddlg_FC1 + + + False + True + 3 + + + + + False + True + 0 + + + + + + True + False + 4 + 4 + 4 + 4 + 4 + 4 + + + True + False + end + Name : + + + 0 + 0 + + + + + True + True + + + 1 + 0 + + + + + True + False + end + Type : + + + 0 + 2 + + + + + True + False + end + Flag / Register : + + + 0 + 1 + + + + + True + False + end + Simmulation : + + + 0 + 3 + + + + + True + True + + + 1 + 1 + + + + + True + False + liststore1 + 0 + on + True + 0 + + + False + + + + + 1 + 2 + + + + + True + False + end + Value : + + + 0 + 4 + + + + + True + False + + + True + True + + + False + True + 0 + + + + + Change + True + True + False + True + + + False + True + 1 + + + + + 1 + 4 + + + + + True + False + 0 + True + + NONE + SAW;t=60;min=0;max=1000 + SIN;t=60;min=-100;max=100; + PULSE;ton=10;toff=60 + + + + True + + + + + 1 + 3 + + + + + False + False + 4 + 1 + + + + + True + True + 1 + + + + + + + False + 600 + 400 + testmodbus-server.png + True + + + + + + True + False + vertical + + + True + False + + + True + False + _Datei + True + + + True + False + + + gtk-new + True + False + True + True + + + + + + gtk-open + True + False + True + True + + + + + + gtk-save + True + False + True + True + + + + + + gtk-save-as + True + False + True + True + + + + + + True + False + + + + + gtk-quit + True + False + True + True + + + + + + + + + + True + False + _Hilfe + True + + + True + False + + + gtk-about + True + False + True + True + + + + + + + + + + False + True + 0 + + + + + True + True + True + + + 200 + True + False + vertical + + + True + False + Netzwerk + + + False + True + 0 + + + + + 200 + True + True + in + 200 + + + True + True + False + + + + + True + True + 1 + + + + + False + False + + + + + True + True + + + True + False + vertical + + + True + True + in + + + True + True + + + + + + + + True + True + 0 + + + + + True + False + 3 + + + Req. Clear + True + True + True + + + + False + False + 0 + + + + + Enable All + True + True + True + + + + False + False + 1 + + + + + Enable Values + True + True + True + + + + False + False + 2 + + + + + Disable All + True + True + True + + + + False + True + 4 + + + + + False + True + 4 + 1 + + + + + 1 + + + + + True + False + Registers + + + False + + + + + True + False + vertical + + + True + True + in + + + True + True + + + + + + + + True + True + 1 + + + + + True + False + 5 + + + Add + True + True + True + + + + False + False + 0 + + + + + Edit + True + True + True + + + + False + False + 1 + + + + + Delete + True + True + True + + + + False + False + 2 + + + + + False + True + 5 + end + 2 + + + + + 1 + + + + + True + False + Values + + + 1 + False + + + + + + True + False + True + True + + + Auto Add Values + True + True + False + True + + + 0 + 0 + + + + + + + + + + + + + + + + + + + + 2 + + + + + True + False + Config + + + 2 + False + + + + + True + True + + + + + True + True + 3 + 1 + + + + + True + False + 12 + + + True + False + Port: + + + False + True + end + -1 + + + + + True + False + 4 + .. + + + False + True + 0 + + + + + Start + True + True + True + + + + False + True + 4 + end + 1 + + + + + 0 + True + True + 92 + 6 + 6 + 502 + + + + + False + True + end + 2 + + + + + True + False + .. + + + False + True + 4 + + + + + False + True + 3 + 2 + + + + + + + + TestModbus-Server program 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. + + TestModbus-Server 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 this program. If not, see <http://www.gnu.org/licenses/>. + + + + 500 + 400 + False + dialog + + + False + vertical + 2 + + + False + end + + + + + + gtk-close + True + True + True + True + True + + + + True + True + 1 + + + + + False + False + 0 + + + + + True + False + vertical + + + True + False + True + + + True + False + + + + + + True + False + testmodbus-server.png + + + False + False + 1 + + + + + + + + False + True + 0 + + + + + True + False + vertical + + + True + False + 28 + TestModbus-Server + + + + + + + + False + True + 0 + + + + + True + False + version + + + + + + False + True + 14 + 1 + + + + + True + False + https://steffen.gulpe.de/modbus-tcpip + + + False + True + 2 + + + + + True + True + 7 + 1 + + + + + False + True + 0 + + + + + True + True + in + + + True + True + textbuffer1 + + + + + True + True + 2 + + + + + True + False + + + False + True + 3 + + + + + True + True + 1 + + + + + +