From 24fe75b8b153bd366b93d2d752e4e832a6caeec0 Mon Sep 17 00:00:00 2001 From: Sebastian Keller Date: Sun, 25 Nov 2018 15:35:02 +0100 Subject: [PATCH] release 1.7 --- .gitignore | 8 - MANIFEST.in | 8 + README.md | 71 +- RTOC.egg-info/PKG-INFO | 25 + RTOC.egg-info/SOURCES.txt | 194 ++++++ RTOC.egg-info/dependency_links.txt | 1 + RTOC.egg-info/requires.txt | 11 + RTOC.egg-info/top_level.txt | 1 + LoggerPlugin.py => RTOC/LoggerPlugin.py | 126 ++-- RTLogger.py => RTOC/RTLogger.py | 534 +++++++++++---- RTOC.py => RTOC/RTOC.py | 340 ++++++---- RTOC/__init__.py | 4 + RTOC/__main__.py | 136 ++++ {data => RTOC/data}/Actions.py | 68 +- {data => RTOC/data}/RTPlotActions.py | 45 +- {data => RTOC/data}/RTPlotWidget.py | 74 +-- {data => RTOC/data}/ScriptFunctions.py | 6 +- {data => RTOC/data}/__init__.py | 0 {data => RTOC/data}/define.py | 0 {data => RTOC/data}/eventWidget.py | 30 +- {data => RTOC/data}/icon.png | Bin {data => RTOC/data}/importCode.py | 0 {data => RTOC/data}/lib/__init__.py | 0 {data => RTOC/data}/lib/general_lib.py | 0 {data => RTOC/data}/lib/pyqt_customlib.py | 2 +- {data => RTOC/data}/loggerlib.py | 0 {data => RTOC/data}/scriptHelpWidget.py | 10 +- {data => RTOC/data}/scriptLibrary.py | 12 +- {data => RTOC/data}/scriptSubWidget.py | 57 +- {data => RTOC/data}/scriptWidget.py | 45 +- {data => RTOC/data}/signalEditWidget.py | 24 +- {data => RTOC/data}/signalWidget.py | 145 ++-- {data => RTOC/data}/styleMultiPlotGUI.py | 17 +- {data => RTOC/data}/stylePlotGUI.py | 103 +-- {data => RTOC/data}/ui/darkmode.html | 144 ++-- {data => RTOC/data}/ui/eventWidget.ui | 0 {data => RTOC/data}/ui/gridViewWidget.ui | 0 {data => RTOC/data}/ui/icons/blinking.png | Bin {data => RTOC/data}/ui/icons/clear.png | Bin {data => RTOC/data}/ui/icons/crosshair.png | Bin {data => RTOC/data}/ui/icons/cut.png | Bin .../data}/ui/icons/dark/Hmovetoolbar.png | Bin .../data}/ui/icons/dark/Hsepartoolbar.png | Bin .../data}/ui/icons/dark/Vmovetoolbar.png | Bin .../data}/ui/icons/dark/Vsepartoolbar.png | Bin {data => RTOC/data}/ui/icons/dark/add.png | Bin .../data}/ui/icons/dark/add_element.png | Bin .../ui/icons/dark/add_element_hovered.png | Bin .../data}/ui/icons/dark/add_hovered.png | Bin {data => RTOC/data}/ui/icons/dark/add_new.png | Bin .../data}/ui/icons/dark/add_new_hovered.png | Bin .../data}/ui/icons/dark/checkbox_checked.png | Bin .../icons/dark/checkbox_checked_hovered.png | Bin .../ui/icons/dark/checkbox_indeterminate.png | Bin .../dark/checkbox_indeterminate_hovered.png | Bin .../ui/icons/dark/checkbox_unchecked.png | Bin .../icons/dark/checkbox_unchecked_hovered.png | Bin .../data}/ui/icons/dark/close_window.png | Bin .../ui/icons/dark/close_window_hovered.png | Bin .../data}/ui/icons/dark/delete_profile.png | Bin .../ui/icons/dark/delete_profile_hovered.png | Bin .../data}/ui/icons/dark/down_arrow.png | Bin .../ui/icons/dark/down_arrow_disabled.png | Bin .../data}/ui/icons/dark/download.png | Bin {data => RTOC/data}/ui/icons/dark/edit.png | Bin .../data}/ui/icons/dark/edit_hovered.png | Bin {data => RTOC/data}/ui/icons/dark/file.png | Bin .../data}/ui/icons/dark/file_hovered.png | Bin {data => RTOC/data}/ui/icons/dark/filter.png | Bin .../data}/ui/icons/dark/filter_hovered.png | Bin {data => RTOC/data}/ui/icons/dark/image.png | Bin .../data}/ui/icons/dark/image_hovered.png | Bin .../data}/ui/icons/dark/left_arrow.png | Bin .../ui/icons/dark/left_arrow_disabled.png | Bin .../data}/ui/icons/dark/maximize_window.png | Bin .../ui/icons/dark/maximize_window_hovered.png | Bin {data => RTOC/data}/ui/icons/dark/menu.png | Bin .../data}/ui/icons/dark/menu_hovered.png | Bin {data => RTOC/data}/ui/icons/dark/merge.png | Bin .../data}/ui/icons/dark/merge_hovered.png | Bin .../data}/ui/icons/dark/minimize_window.png | Bin .../ui/icons/dark/minimize_window_hovered.png | Bin .../data}/ui/icons/dark/radio_checked.png | Bin .../ui/icons/dark/radio_checked_hovered.png | Bin .../data}/ui/icons/dark/radio_disabled.png | Bin .../data}/ui/icons/dark/radio_unchecked.png | Bin .../ui/icons/dark/radio_unchecked_hovered.png | Bin .../data}/ui/icons/dark/remove_element.png | Bin .../ui/icons/dark/remove_element_hovered.png | Bin .../data}/ui/icons/dark/restore_window.png | Bin .../ui/icons/dark/restore_window_hovered.png | Bin .../data}/ui/icons/dark/right_arrow.png | Bin .../ui/icons/dark/right_arrow_disabled.png | Bin {data => RTOC/data}/ui/icons/dark/save.png | Bin .../data}/ui/icons/dark/save_hovered.png | Bin .../data}/ui/icons/dark/sizegrip.png | Bin {data => RTOC/data}/ui/icons/dark/source.png | Bin .../data}/ui/icons/dark/source_hovered.png | Bin .../data}/ui/icons/dark/submitElement.png | Bin .../ui/icons/dark/submitElement_hovered.png | Bin {data => RTOC/data}/ui/icons/dark/undo.png | Bin .../data}/ui/icons/dark/undo_hovered.png | Bin {data => RTOC/data}/ui/icons/dark/undock.png | Bin .../data}/ui/icons/dark/up_arrow.png | Bin .../data}/ui/icons/dark/up_arrow_disabled.png | Bin {data => RTOC/data}/ui/icons/duplicate.png | Bin {data => RTOC/data}/ui/icons/events.png | Bin {data => RTOC/data}/ui/icons/exportCSV.png | Bin {data => RTOC/data}/ui/icons/graph.png | Bin {data => RTOC/data}/ui/icons/grid.png | Bin {data => RTOC/data}/ui/icons/help.png | Bin .../data}/ui/icons/hohe_prioritaet.png | Bin .../data}/ui/icons/hohe_prioritaet_grey.png | Bin .../data}/ui/icons/icons8-stornieren-480.png | Bin {data => RTOC/data}/ui/icons/invert.png | Bin {data => RTOC/data}/ui/icons/labels.png | Bin {data => RTOC/data}/ui/icons/legend.png | Bin {data => RTOC/data}/ui/icons/measure.png | Bin .../data}/ui/icons/mittlere_prioritaet.png | Bin .../ui/icons/mittlere_prioritaet_grey.png | Bin .../data}/ui/icons/niedrige_prioritaet.png | Bin .../ui/icons/niedrige_prioritaet_grey.png | Bin {data => RTOC/data}/ui/icons/open.png | Bin {data => RTOC/data}/ui/icons/pause.png | Bin {data => RTOC/data}/ui/icons/rename.png | Bin {data => RTOC/data}/ui/icons/repeat.png | Bin {data => RTOC/data}/ui/icons/repeat1.png | Bin {data => RTOC/data}/ui/icons/style.png | Bin {data => RTOC/data}/ui/icons/swap.png | Bin {data => RTOC/data}/ui/icons/x.png | Bin {data => RTOC/data}/ui/icons/xaxislabel.png | Bin {data => RTOC/data}/ui/icons/xtimebase.png | Bin {data => RTOC/data}/ui/icons/y.png | Bin {data => RTOC/data}/ui/lightmode.html | 140 ++-- {data => RTOC/data}/ui/messtoolDialog.ui | 0 {data => RTOC/data}/ui/plotToolsWidget.ui | 0 {data => RTOC/data}/ui/plotViewWidget.ui | 0 {data => RTOC/data}/ui/plotWidget.ui | 0 {data => RTOC/data}/ui/rtoc.ui | 21 + {data => RTOC/data}/ui/scriptHelpWidget.ui | 0 {data => RTOC/data}/ui/scriptSubWidget.ui | 0 {data => RTOC/data}/ui/scriptWidget.ui | 0 {data => RTOC/data}/ui/signalWidget.ui | 0 {data => RTOC/data}/ui/signalWidget2.ui | 0 {data => RTOC/data}/ui/stylePlotDialog.ui | 0 {data => RTOC/data}/ui/stylePlotDialog2.ui | 0 {data => RTOC/data}/ui/triggerWidget.ui | 0 RTOC/jsonsocket.py | 156 +++++ {lang => RTOC/lang}/en_en.qm | Bin 18679 -> 22092 bytes {lang => RTOC/lang}/en_en.ts | 624 +++++++++++++++--- {plugins => RTOC/plugins}/DPS5020.py | 9 +- {plugins => RTOC/plugins}/DPS5020/dps5020.ui | 0 RTOC/plugins/Deneb.py | 156 +++++ RTOC/plugins/Deneb/deneb.ui | 80 +++ .../Funktionsgenerator/gen_function.ui | 0 RTOC/plugins/Futtertrocknung.py | 128 ++++ {plugins => RTOC/plugins}/Generator.py | 38 +- {plugins => RTOC/plugins}/Generator2.py | 52 +- RTOC/plugins/Heliotherm.py | 258 ++++++++ RTOC/plugins/Heliotherm/heliotherm.ui | 80 +++ {plugins => RTOC/plugins}/HoldPeak VC820.py | 13 +- RTOC/plugins/NetWoRTOC.py | 240 +++++++ {plugins => RTOC/plugins}/OctoTouch.py | 9 +- .../plugins}/Octotouch/OctoprintApi.py | 8 +- .../__pycache__/OctoprintApi.cpython-36.pyc | Bin 0 -> 13145 bytes .../plugins}/Octotouch/octotouch.ui | 0 RTOC/plugins/Reflow/reflow.ui | 102 +++ RTOC/plugins/ReflowOfen.py | 138 ++++ RTOC/plugins/ReflowPlatte.py | 138 ++++ {plugins => RTOC/plugins}/System.py | 14 +- {plugins => RTOC/plugins}/System/system.ui | 0 {plugins => RTOC/plugins}/Template.py | 8 +- .../plugins}/Template/template.ui | 0 .../holdPeak_VC820/portSelectWidget.ui | 0 .../holdPeak_VC820/vc820py/.gitignore | 0 .../plugins}/holdPeak_VC820/vc820py/README.md | 0 .../holdPeak_VC820/vc820py/generate_test.py | 0 .../holdPeak_VC820/vc820py/raw2json.py | 0 .../holdPeak_VC820/vc820py/rawtime2csv.py | 0 .../holdPeak_VC820/vc820py/rawtime2json.py | 0 .../holdPeak_VC820/vc820py/rawtime2raw.py | 0 .../vc820py/read_from_serial.py | 0 .../holdPeak_VC820/vc820py/read_multiple.py | 0 .../vc820py/read_multiple_reader.py | 0 .../holdPeak_VC820/vc820py/read_reader.py | 0 .../plugins}/holdPeak_VC820/vc820py/reader.py | 0 .../holdPeak_VC820/vc820py/record_serial.py | 0 .../holdPeak_VC820/vc820py/replay_rawtime.py | 0 .../plugins}/holdPeak_VC820/vc820py/ut61b.py | 0 .../plugins}/holdPeak_VC820/vc820py/vc820.py | 0 .../netWoRTOC/__pycache__/gui.cpython-36.pyc | Bin 0 -> 4809 bytes .../__pycache__/networkscan.cpython-36.pyc | Bin 0 -> 2927 bytes RTOC/plugins/netWoRTOC/gui.py | 122 ++++ RTOC/plugins/netWoRTOC/networkscan.py | 132 ++++ RTOC/plugins/netWoRTOC/networtoc.ui | 371 +++++++++++ RTOC/plugins/netWoRTOC/repeat.png | Bin 0 -> 777 bytes RTOC/plugins/netWoRTOC/repeat1.png | Bin 0 -> 989 bytes RTOC/plugins/netWoRTOC/search.png | Bin 0 -> 8823 bytes RTOC/plugins/vc820py/-h | 1 + .../vc820py/__pycache__/vc820.cpython-36.pyc | Bin 0 -> 5723 bytes RTOC/telegramBot.py | 453 +++++++++++++ config.json | 32 - example_TCPClients/EspLibrary/rtocTCP.ino | 187 ------ .../PythonistaForIOS/LoggerPlugin.py | 200 ------ example_TCPClients/PythonistaForIOS/README.md | 4 - example_TCPClients/PythonistaForIOS/RTOC.py | 129 ---- example_TCPClients/PythonistaForIOS/gui.py | 22 - example_TCPClients/PythonistaForIOS/gui.pyui | 141 ---- .../PythonistaForIOS/jsonsocket.py | 145 ---- .../PythonistaForIOS/screenshot.PNG | Bin 31619 -> 0 bytes example_TCPClients/PythonistaForIOS/test.py | 0 example_scripts/XY-Plot_example.py | 4 - example_scripts/XY-Plot_example2.py | 3 - example_scripts/allTest.py | 37 -- example_scripts/combineTest.py | 5 - example_scripts/regelung_example.py | 11 - example_scripts/sinus_example.py | 3 - example_scripts/sinus_example2.py | 2 - example_scripts/sinus_multi_example.py | 6 - example_scripts/square_example.py | 4 - example_scripts/square_plot_example.py | 1 - .../square_plugin_function_example.py | 3 - example_scripts/square_sinus_example.py | 1 - example_scripts/square_trigger_example.py | 4 - example_scripts/square_trigger_example2.py | 2 - jsonsocket.py | 145 ---- plotStyles.json | 21 - plugins/NetWoRTOC.py | 114 ---- plugins/netWoRTOC/networtoc.ui | 96 --- run.sh | 2 +- screenshots/RTOC-schematik.png | Bin 0 -> 46835 bytes setup.cfg | 13 + setup.py | 161 +++++ setupStandalone.py | 25 + 234 files changed, 4891 insertions(+), 2364 deletions(-) delete mode 100644 .gitignore create mode 100644 MANIFEST.in create mode 100644 RTOC.egg-info/PKG-INFO create mode 100644 RTOC.egg-info/SOURCES.txt create mode 100644 RTOC.egg-info/dependency_links.txt create mode 100644 RTOC.egg-info/requires.txt create mode 100644 RTOC.egg-info/top_level.txt rename LoggerPlugin.py => RTOC/LoggerPlugin.py (61%) rename RTLogger.py => RTOC/RTLogger.py (57%) rename RTOC.py => RTOC/RTOC.py (61%) create mode 100644 RTOC/__init__.py create mode 100644 RTOC/__main__.py rename {data => RTOC/data}/Actions.py (80%) rename {data => RTOC/data}/RTPlotActions.py (92%) rename {data => RTOC/data}/RTPlotWidget.py (74%) rename {data => RTOC/data}/ScriptFunctions.py (94%) rename {data => RTOC/data}/__init__.py (100%) rename {data => RTOC/data}/define.py (100%) rename {data => RTOC/data}/eventWidget.py (68%) rename {data => RTOC/data}/icon.png (100%) rename {data => RTOC/data}/importCode.py (100%) rename {data => RTOC/data}/lib/__init__.py (100%) rename {data => RTOC/data}/lib/general_lib.py (100%) rename {data => RTOC/data}/lib/pyqt_customlib.py (99%) rename {data => RTOC/data}/loggerlib.py (100%) rename {data => RTOC/data}/scriptHelpWidget.py (84%) rename {data => RTOC/data}/scriptLibrary.py (96%) rename {data => RTOC/data}/scriptSubWidget.py (85%) rename {data => RTOC/data}/scriptWidget.py (78%) rename {data => RTOC/data}/signalEditWidget.py (93%) rename {data => RTOC/data}/signalWidget.py (72%) rename {data => RTOC/data}/styleMultiPlotGUI.py (75%) rename {data => RTOC/data}/stylePlotGUI.py (82%) rename {data => RTOC/data}/ui/darkmode.html (83%) rename {data => RTOC/data}/ui/eventWidget.ui (100%) rename {data => RTOC/data}/ui/gridViewWidget.ui (100%) rename {data => RTOC/data}/ui/icons/blinking.png (100%) rename {data => RTOC/data}/ui/icons/clear.png (100%) rename {data => RTOC/data}/ui/icons/crosshair.png (100%) rename {data => RTOC/data}/ui/icons/cut.png (100%) rename {data => RTOC/data}/ui/icons/dark/Hmovetoolbar.png (100%) rename {data => RTOC/data}/ui/icons/dark/Hsepartoolbar.png (100%) rename {data => RTOC/data}/ui/icons/dark/Vmovetoolbar.png (100%) rename {data => RTOC/data}/ui/icons/dark/Vsepartoolbar.png (100%) rename {data => RTOC/data}/ui/icons/dark/add.png (100%) rename {data => RTOC/data}/ui/icons/dark/add_element.png (100%) rename {data => RTOC/data}/ui/icons/dark/add_element_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/add_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/add_new.png (100%) rename {data => RTOC/data}/ui/icons/dark/add_new_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/checkbox_checked.png (100%) rename {data => RTOC/data}/ui/icons/dark/checkbox_checked_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/checkbox_indeterminate.png (100%) rename {data => RTOC/data}/ui/icons/dark/checkbox_indeterminate_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/checkbox_unchecked.png (100%) rename {data => RTOC/data}/ui/icons/dark/checkbox_unchecked_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/close_window.png (100%) rename {data => RTOC/data}/ui/icons/dark/close_window_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/delete_profile.png (100%) rename {data => RTOC/data}/ui/icons/dark/delete_profile_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/down_arrow.png (100%) rename {data => RTOC/data}/ui/icons/dark/down_arrow_disabled.png (100%) rename {data => RTOC/data}/ui/icons/dark/download.png (100%) rename {data => RTOC/data}/ui/icons/dark/edit.png (100%) rename {data => RTOC/data}/ui/icons/dark/edit_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/file.png (100%) rename {data => RTOC/data}/ui/icons/dark/file_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/filter.png (100%) rename {data => RTOC/data}/ui/icons/dark/filter_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/image.png (100%) rename {data => RTOC/data}/ui/icons/dark/image_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/left_arrow.png (100%) rename {data => RTOC/data}/ui/icons/dark/left_arrow_disabled.png (100%) rename {data => RTOC/data}/ui/icons/dark/maximize_window.png (100%) rename {data => RTOC/data}/ui/icons/dark/maximize_window_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/menu.png (100%) rename {data => RTOC/data}/ui/icons/dark/menu_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/merge.png (100%) rename {data => RTOC/data}/ui/icons/dark/merge_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/minimize_window.png (100%) rename {data => RTOC/data}/ui/icons/dark/minimize_window_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/radio_checked.png (100%) rename {data => RTOC/data}/ui/icons/dark/radio_checked_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/radio_disabled.png (100%) rename {data => RTOC/data}/ui/icons/dark/radio_unchecked.png (100%) rename {data => RTOC/data}/ui/icons/dark/radio_unchecked_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/remove_element.png (100%) rename {data => RTOC/data}/ui/icons/dark/remove_element_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/restore_window.png (100%) rename {data => RTOC/data}/ui/icons/dark/restore_window_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/right_arrow.png (100%) rename {data => RTOC/data}/ui/icons/dark/right_arrow_disabled.png (100%) rename {data => RTOC/data}/ui/icons/dark/save.png (100%) rename {data => RTOC/data}/ui/icons/dark/save_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/sizegrip.png (100%) rename {data => RTOC/data}/ui/icons/dark/source.png (100%) rename {data => RTOC/data}/ui/icons/dark/source_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/submitElement.png (100%) rename {data => RTOC/data}/ui/icons/dark/submitElement_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/undo.png (100%) rename {data => RTOC/data}/ui/icons/dark/undo_hovered.png (100%) rename {data => RTOC/data}/ui/icons/dark/undock.png (100%) rename {data => RTOC/data}/ui/icons/dark/up_arrow.png (100%) rename {data => RTOC/data}/ui/icons/dark/up_arrow_disabled.png (100%) rename {data => RTOC/data}/ui/icons/duplicate.png (100%) rename {data => RTOC/data}/ui/icons/events.png (100%) rename {data => RTOC/data}/ui/icons/exportCSV.png (100%) rename {data => RTOC/data}/ui/icons/graph.png (100%) rename {data => RTOC/data}/ui/icons/grid.png (100%) rename {data => RTOC/data}/ui/icons/help.png (100%) rename {data => RTOC/data}/ui/icons/hohe_prioritaet.png (100%) rename {data => RTOC/data}/ui/icons/hohe_prioritaet_grey.png (100%) rename {data => RTOC/data}/ui/icons/icons8-stornieren-480.png (100%) rename {data => RTOC/data}/ui/icons/invert.png (100%) rename {data => RTOC/data}/ui/icons/labels.png (100%) rename {data => RTOC/data}/ui/icons/legend.png (100%) rename {data => RTOC/data}/ui/icons/measure.png (100%) rename {data => RTOC/data}/ui/icons/mittlere_prioritaet.png (100%) rename {data => RTOC/data}/ui/icons/mittlere_prioritaet_grey.png (100%) rename {data => RTOC/data}/ui/icons/niedrige_prioritaet.png (100%) rename {data => RTOC/data}/ui/icons/niedrige_prioritaet_grey.png (100%) rename {data => RTOC/data}/ui/icons/open.png (100%) rename {data => RTOC/data}/ui/icons/pause.png (100%) rename {data => RTOC/data}/ui/icons/rename.png (100%) rename {data => RTOC/data}/ui/icons/repeat.png (100%) rename {data => RTOC/data}/ui/icons/repeat1.png (100%) rename {data => RTOC/data}/ui/icons/style.png (100%) rename {data => RTOC/data}/ui/icons/swap.png (100%) rename {data => RTOC/data}/ui/icons/x.png (100%) rename {data => RTOC/data}/ui/icons/xaxislabel.png (100%) rename {data => RTOC/data}/ui/icons/xtimebase.png (100%) rename {data => RTOC/data}/ui/icons/y.png (100%) rename {data => RTOC/data}/ui/lightmode.html (83%) rename {data => RTOC/data}/ui/messtoolDialog.ui (100%) rename {data => RTOC/data}/ui/plotToolsWidget.ui (100%) rename {data => RTOC/data}/ui/plotViewWidget.ui (100%) rename {data => RTOC/data}/ui/plotWidget.ui (100%) rename {data => RTOC/data}/ui/rtoc.ui (96%) rename {data => RTOC/data}/ui/scriptHelpWidget.ui (100%) rename {data => RTOC/data}/ui/scriptSubWidget.ui (100%) rename {data => RTOC/data}/ui/scriptWidget.ui (100%) rename {data => RTOC/data}/ui/signalWidget.ui (100%) rename {data => RTOC/data}/ui/signalWidget2.ui (100%) rename {data => RTOC/data}/ui/stylePlotDialog.ui (100%) rename {data => RTOC/data}/ui/stylePlotDialog2.ui (100%) rename {data => RTOC/data}/ui/triggerWidget.ui (100%) create mode 100644 RTOC/jsonsocket.py rename {lang => RTOC/lang}/en_en.qm (65%) rename {lang => RTOC/lang}/en_en.ts (68%) rename {plugins => RTOC/plugins}/DPS5020.py (96%) rename {plugins => RTOC/plugins}/DPS5020/dps5020.ui (100%) create mode 100644 RTOC/plugins/Deneb.py create mode 100644 RTOC/plugins/Deneb/deneb.ui rename {plugins => RTOC/plugins}/Funktionsgenerator/gen_function.ui (100%) create mode 100644 RTOC/plugins/Futtertrocknung.py rename {plugins => RTOC/plugins}/Generator.py (93%) rename {plugins => RTOC/plugins}/Generator2.py (88%) create mode 100644 RTOC/plugins/Heliotherm.py create mode 100644 RTOC/plugins/Heliotherm/heliotherm.ui rename {plugins => RTOC/plugins}/HoldPeak VC820.py (93%) create mode 100644 RTOC/plugins/NetWoRTOC.py rename {plugins => RTOC/plugins}/OctoTouch.py (93%) rename {plugins => RTOC/plugins}/Octotouch/OctoprintApi.py (99%) create mode 100644 RTOC/plugins/Octotouch/__pycache__/OctoprintApi.cpython-36.pyc rename {plugins => RTOC/plugins}/Octotouch/octotouch.ui (100%) create mode 100644 RTOC/plugins/Reflow/reflow.ui create mode 100644 RTOC/plugins/ReflowOfen.py create mode 100644 RTOC/plugins/ReflowPlatte.py rename {plugins => RTOC/plugins}/System.py (98%) rename {plugins => RTOC/plugins}/System/system.ui (100%) rename {plugins => RTOC/plugins}/Template.py (83%) rename {plugins => RTOC/plugins}/Template/template.ui (100%) rename {plugins => RTOC/plugins}/holdPeak_VC820/portSelectWidget.ui (100%) rename {plugins => RTOC/plugins}/holdPeak_VC820/vc820py/.gitignore (100%) rename {plugins => RTOC/plugins}/holdPeak_VC820/vc820py/README.md (100%) rename {plugins => RTOC/plugins}/holdPeak_VC820/vc820py/generate_test.py (100%) rename {plugins => RTOC/plugins}/holdPeak_VC820/vc820py/raw2json.py (100%) rename {plugins => RTOC/plugins}/holdPeak_VC820/vc820py/rawtime2csv.py (100%) rename {plugins => RTOC/plugins}/holdPeak_VC820/vc820py/rawtime2json.py (100%) rename {plugins => RTOC/plugins}/holdPeak_VC820/vc820py/rawtime2raw.py (100%) rename {plugins => RTOC/plugins}/holdPeak_VC820/vc820py/read_from_serial.py (100%) rename {plugins => RTOC/plugins}/holdPeak_VC820/vc820py/read_multiple.py (100%) rename {plugins => RTOC/plugins}/holdPeak_VC820/vc820py/read_multiple_reader.py (100%) rename {plugins => RTOC/plugins}/holdPeak_VC820/vc820py/read_reader.py (100%) rename {plugins => RTOC/plugins}/holdPeak_VC820/vc820py/reader.py (100%) rename {plugins => RTOC/plugins}/holdPeak_VC820/vc820py/record_serial.py (100%) rename {plugins => RTOC/plugins}/holdPeak_VC820/vc820py/replay_rawtime.py (100%) rename {plugins => RTOC/plugins}/holdPeak_VC820/vc820py/ut61b.py (100%) rename {plugins => RTOC/plugins}/holdPeak_VC820/vc820py/vc820.py (100%) create mode 100644 RTOC/plugins/netWoRTOC/__pycache__/gui.cpython-36.pyc create mode 100644 RTOC/plugins/netWoRTOC/__pycache__/networkscan.cpython-36.pyc create mode 100644 RTOC/plugins/netWoRTOC/gui.py create mode 100644 RTOC/plugins/netWoRTOC/networkscan.py create mode 100644 RTOC/plugins/netWoRTOC/networtoc.ui create mode 100644 RTOC/plugins/netWoRTOC/repeat.png create mode 100644 RTOC/plugins/netWoRTOC/repeat1.png create mode 100644 RTOC/plugins/netWoRTOC/search.png create mode 100644 RTOC/plugins/vc820py/-h create mode 100644 RTOC/plugins/vc820py/__pycache__/vc820.cpython-36.pyc create mode 100644 RTOC/telegramBot.py delete mode 100644 config.json delete mode 100644 example_TCPClients/EspLibrary/rtocTCP.ino delete mode 100644 example_TCPClients/PythonistaForIOS/LoggerPlugin.py delete mode 100644 example_TCPClients/PythonistaForIOS/README.md delete mode 100644 example_TCPClients/PythonistaForIOS/RTOC.py delete mode 100644 example_TCPClients/PythonistaForIOS/gui.py delete mode 100644 example_TCPClients/PythonistaForIOS/gui.pyui delete mode 100644 example_TCPClients/PythonistaForIOS/jsonsocket.py delete mode 100644 example_TCPClients/PythonistaForIOS/screenshot.PNG delete mode 100644 example_TCPClients/PythonistaForIOS/test.py delete mode 100644 example_scripts/XY-Plot_example.py delete mode 100644 example_scripts/XY-Plot_example2.py delete mode 100644 example_scripts/allTest.py delete mode 100644 example_scripts/combineTest.py delete mode 100644 example_scripts/regelung_example.py delete mode 100644 example_scripts/sinus_example.py delete mode 100644 example_scripts/sinus_example2.py delete mode 100644 example_scripts/sinus_multi_example.py delete mode 100644 example_scripts/square_example.py delete mode 100644 example_scripts/square_plot_example.py delete mode 100644 example_scripts/square_plugin_function_example.py delete mode 100644 example_scripts/square_sinus_example.py delete mode 100644 example_scripts/square_trigger_example.py delete mode 100644 example_scripts/square_trigger_example2.py delete mode 100644 jsonsocket.py delete mode 100644 plotStyles.json delete mode 100644 plugins/NetWoRTOC.py delete mode 100644 plugins/netWoRTOC/networtoc.ui create mode 100644 screenshots/RTOC-schematik.png create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 setupStandalone.py diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 41dde1b..0000000 --- a/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -__pycache__/ -data/__pycache__/ -data/lib/__pycache__/ -ProFiler.log -build/ -dist/ -setup.py -avbin64.dll \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..fd4c66d --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,8 @@ +include RTOC/* +include RTOC/data/* +include RTOC/data/ui/* +include RTOC/data/lib/* +include RTOC/data/ui/icons/* +include RTOC/data/ui/icons/dark/* +include RTOC/lang/* +recursive-include RTOC * diff --git a/README.md b/README.md index eabe773..accc492 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,62 @@ # RealTime OpenControl (RTOC) -### Version: 1.6 +### Version: 1.7 -Compatible with Windows and Linux +![Beispielschematik](screenshots/RTOC-schematik.png) +![Übersicht](screenshots/overview.png) -### Currently this README and the WIKI-documentation is only available in german. The software is available in english AND german, selectable in the menu. English translations for the documentation is following soon! +# Installation +## With pip3 (Recommended) +> pip3 install RTOC +> python3 -m RTOC [-s] [-r 'REMOTEADRESSE'] -Short description: RealTime OpenControl is a universal measurement, plot and control-software. It's purpose is to put measurements from different devices (for example 3d-printers, multimeters, power supplies, microcontroller,...) into one tool. Its fully expandable for every device with Python-Plugins and a running TCP-server. You can also control the devices (if your plugin has this functionality) with python-scripts, which you can write and run at runtime! This makes it also possible to plot everything else. There are some example-plugins and example-scripts included. It also offers an extended plotting-GUI with multiple plots, measure-tools, style-adjustments. -Just have a look at the screenshots and the wiki, download it, try it, and send me suggestions and bug-reports, if you find some! -Note: the english-translation is not perfect, some text isn't translated. This will be fixed sooner or later, depending on the feedback. +## From source +Klone RTOC in ein beliebiges Verzeichnis und wechsle in das Verzeichnis: -### Usecases: -- Home-automation -- temperature-sensor calibration -- system-monitor -- long-time-measurements -- just playing around with plots +RTOC mit GUI starten: +> python3 RTOC.py -You can download the python3-sourcecode and run it with `python3 RTOC.py` or you can download the build from here and run it without python (currently Windows only!) +RTOC nur Logger-Server starten: +> python3 RTOC.py -s -![Übersicht](screenshots/overview.png) - -RTOC starten: -> python3 RTOC.py -> Alternativ wird hier ein BUILD (inklusive Installer) bereitgestellt (bisher nur Windows!) +RTOC im Remote-Modus starten (NetWoRTOC-Plugin im Vordergrund): +> python3 RTOC-py -r 'REMOTEADRESSE' RealTime OpenControl ermöglicht eine geräteübergreifende Messaufzeichnung. Außerdem kann man mit dem integrierten Python-Skript-Editor auf die Messdaten und Geräte zugreifen und mit diesen interagieren. Somit lassen sich langsame Regelungen zwischen mehreren Geräten realisieren. -Z.B.: Temperaturmessung mit Multimeter (mit USB-Anschluss) und Regelung eines Heizelements auf eine Solltemperatur. Ideal zum Aufzeichnen, Testen und Optimieren von Regelungen. Eignet sich auch für Custom-HomeAutomation (z.B.: auf Raspberry Pi oder HomeServer) mit maximaler Flexibilität und Anpassbarkeit +Z.B.: Temperaturmessung mit Multimeter (mit USB-Anschluss) und Regelung eines Heizelements auf eine Solltemperatur. Ideal zum Aufzeichnen, Testen und Optimieren von Regelungen. + +Eignet sich auch für Custom-HomeAutomation (z.B.: auf Raspberry Pi oder HomeServer) mit maximaler Flexibilität und Anpassbarkeit. Dabei kann über TCP auf den RTOC-Server zugegriffen werden. Dadurch können auch Plugin-Funktionen ausgeführt und Parameter angepasst werden. +Somit lässt sich der Logger auch einfach als Python-Remote-Steuerung verwenden. Fast ebenso viele Funktionen bietet der integrierte Telegram-Bot. Die Einbindung neuer Geräte ist einfach möglich: - Als Python-Plugin für RTOC (v.a. für lokale Geräte) - Als TCP-Client (v.a. für Netzwerkgeräte) + - Die TCP-Schnittstelle stellt hierbei alle Funktionen bereit, die das RTOC-Backend bereitstellt (für weitere Infos siehe Wiki) +Steuerung der Geräte ist noch einfacher: +- Direkt in der GUI +- Remote über das Netzwerk bzw. Internet mit TCP +- Mit Telegram-Bot + ### Standart/Beispiel Plugins: - Funktionsgenerator: Erzeugt Sinus, Square, Sawtooth, Random, AC, DC - System: Zur Aufzeichnung vieler Systemvariablen (CPU, Memory, Network,...) - Octoprint: Aufzeichnung für 3D-Drucker - DPS5020: Netzgerät-Aufzeichnung und Steuerung (evtl. auch DPS5005, ...) - HoldPeak VC820: Multimeter Messaufzeichnung (wahrsch. auch andere VC820) -- NetWoRTOC: Datenaustausch zwischen mehreren RTOC's im Netzwerk +- NetWoRTOC: Steuerung und Datenaustausch zwischen mehreren RTOC's im Netzwerk -Die Oberfläche hat erweiterte Darstellungsoptionen und verschiedene Messtools für den Plot bereit. +Die Oberfläche hat erweiterte Darstellungsoptionen und stellt verschiedene Messtools für den Plot bereit. ## Funktionsübersicht - Plugins und TCP-Clients: - können Daten als Stream(=append) oder Plot(=replace) senden - können Events senden +- Remotesteuerung per TCP oder Telegram-Bot: + - Ermöglicht reine Serveranwendungen z.B. auf Raspberry Pi + - Kompletter Zugriff per Telegram-Bot - Skripte: - Multi-Tab Skript-Editor - Der Nutzer kann während der Laufzeit mit den Signalen und Plugins interagieren: @@ -66,15 +75,17 @@ Die Oberfläche hat erweiterte Darstellungsoptionen und verschiedene Messtools f ### Screenshots - siehe unten ## Python3 Paket-Abhängigkeiten -- sudo apt install python3-pyqt5 -- sudo apt install python3-pyqt5.qtsvg -- sudo apt install python3-pip -- pip3 install numpy -- pip3 install pyqtgraph -- pip3 install markdown2 -- pip3 install xlsxwriter -- pip3 install scipy -- pip3 install qtmodern +- numpy +- pyqt5 +- pyqtgraph +- markdown2 +- xlsxwriter +- scipy +- qtmodern +- python-telegram-bot +- matplotlib +- requests +- python-nmap Optional für Plugins: - pip3 install minimalmodbus (DPS5020) diff --git a/RTOC.egg-info/PKG-INFO b/RTOC.egg-info/PKG-INFO new file mode 100644 index 0000000..c5e51ec --- /dev/null +++ b/RTOC.egg-info/PKG-INFO @@ -0,0 +1,25 @@ +Metadata-Version: 1.2 +Name: RTOC +Version: 1.7 +Summary: RealTime OpenControl +Home-page: https://github.com/Haschtl/RealTimeOpenControl +Author: Sebastian Keller +Author-email: sebastiankeller@online.de +License: GNU +Description: RealTime OpenControl is a universal measurement, plot and control-software. + It's purpose is to put measurements from different devices (for example 3d-printers, multimeters, power supplies, microcontroller,...) into one tool. + Its fully expandable for every device with Python-Plugins and a running TCP-server. + You can also control the devices (if your plugin has this functionality) with python-scripts, which you can write and run at runtime! This makes it also possible to plot everything else. + There are some example-plugins and example-scripts included. + It also offers an extended plotting-GUI with multiple plots, measure-tools, style-adjustments. + +Platform: UNKNOWN +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Environment :: Other Environment +Classifier: Intended Audience :: Science/Research +Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) +Classifier: Operating System :: OS Independent +Classifier: Topic :: Scientific/Engineering :: Visualization +Classifier: Topic :: Software Development :: User Interfaces +Requires-Python: >=3 diff --git a/RTOC.egg-info/SOURCES.txt b/RTOC.egg-info/SOURCES.txt new file mode 100644 index 0000000..33ab984 --- /dev/null +++ b/RTOC.egg-info/SOURCES.txt @@ -0,0 +1,194 @@ +MANIFEST.in +README.md +setup.cfg +setup.py +RTOC/LoggerPlugin.py +RTOC/RTLogger.py +RTOC/RTOC.py +RTOC/__init__.py +RTOC/__main__.py +RTOC/jsonsocket.py +RTOC/telegramBot.py +RTOC.egg-info/PKG-INFO +RTOC.egg-info/SOURCES.txt +RTOC.egg-info/dependency_links.txt +RTOC.egg-info/requires.txt +RTOC.egg-info/top_level.txt +RTOC/data/Actions.py +RTOC/data/RTPlotActions.py +RTOC/data/RTPlotWidget.py +RTOC/data/ScriptFunctions.py +RTOC/data/__init__.py +RTOC/data/define.py +RTOC/data/eventWidget.py +RTOC/data/icon.png +RTOC/data/importCode.py +RTOC/data/loggerlib.py +RTOC/data/scriptHelpWidget.py +RTOC/data/scriptLibrary.py +RTOC/data/scriptSubWidget.py +RTOC/data/scriptWidget.py +RTOC/data/signalEditWidget.py +RTOC/data/signalWidget.py +RTOC/data/styleMultiPlotGUI.py +RTOC/data/stylePlotGUI.py +RTOC/data/lib/__init__.py +RTOC/data/lib/general_lib.py +RTOC/data/lib/pyqt_customlib.py +RTOC/data/ui/darkmode.html +RTOC/data/ui/eventWidget.ui +RTOC/data/ui/gridViewWidget.ui +RTOC/data/ui/lightmode.html +RTOC/data/ui/messtoolDialog.ui +RTOC/data/ui/plotToolsWidget.ui +RTOC/data/ui/plotViewWidget.ui +RTOC/data/ui/plotWidget.ui +RTOC/data/ui/rtoc.ui +RTOC/data/ui/scriptHelpWidget.ui +RTOC/data/ui/scriptSubWidget.ui +RTOC/data/ui/scriptWidget.ui +RTOC/data/ui/signalWidget.ui +RTOC/data/ui/signalWidget2.ui +RTOC/data/ui/stylePlotDialog.ui +RTOC/data/ui/stylePlotDialog2.ui +RTOC/data/ui/triggerWidget.ui +RTOC/data/ui/icons/blinking.png +RTOC/data/ui/icons/clear.png +RTOC/data/ui/icons/crosshair.png +RTOC/data/ui/icons/cut.png +RTOC/data/ui/icons/duplicate.png +RTOC/data/ui/icons/events.png +RTOC/data/ui/icons/exportCSV.png +RTOC/data/ui/icons/graph.png +RTOC/data/ui/icons/grid.png +RTOC/data/ui/icons/help.png +RTOC/data/ui/icons/hohe_prioritaet.png +RTOC/data/ui/icons/hohe_prioritaet_grey.png +RTOC/data/ui/icons/icons8-stornieren-480.png +RTOC/data/ui/icons/invert.png +RTOC/data/ui/icons/labels.png +RTOC/data/ui/icons/legend.png +RTOC/data/ui/icons/measure.png +RTOC/data/ui/icons/mittlere_prioritaet.png +RTOC/data/ui/icons/mittlere_prioritaet_grey.png +RTOC/data/ui/icons/niedrige_prioritaet.png +RTOC/data/ui/icons/niedrige_prioritaet_grey.png +RTOC/data/ui/icons/open.png +RTOC/data/ui/icons/pause.png +RTOC/data/ui/icons/rename.png +RTOC/data/ui/icons/repeat.png +RTOC/data/ui/icons/repeat1.png +RTOC/data/ui/icons/style.png +RTOC/data/ui/icons/swap.png +RTOC/data/ui/icons/x.png +RTOC/data/ui/icons/xaxislabel.png +RTOC/data/ui/icons/xtimebase.png +RTOC/data/ui/icons/y.png +RTOC/data/ui/icons/dark/Hmovetoolbar.png +RTOC/data/ui/icons/dark/Hsepartoolbar.png +RTOC/data/ui/icons/dark/Vmovetoolbar.png +RTOC/data/ui/icons/dark/Vsepartoolbar.png +RTOC/data/ui/icons/dark/add.png +RTOC/data/ui/icons/dark/add_element.png +RTOC/data/ui/icons/dark/add_element_hovered.png +RTOC/data/ui/icons/dark/add_hovered.png +RTOC/data/ui/icons/dark/add_new.png +RTOC/data/ui/icons/dark/add_new_hovered.png +RTOC/data/ui/icons/dark/checkbox_checked.png +RTOC/data/ui/icons/dark/checkbox_checked_hovered.png +RTOC/data/ui/icons/dark/checkbox_indeterminate.png +RTOC/data/ui/icons/dark/checkbox_indeterminate_hovered.png +RTOC/data/ui/icons/dark/checkbox_unchecked.png +RTOC/data/ui/icons/dark/checkbox_unchecked_hovered.png +RTOC/data/ui/icons/dark/close_window.png +RTOC/data/ui/icons/dark/close_window_hovered.png +RTOC/data/ui/icons/dark/delete_profile.png +RTOC/data/ui/icons/dark/delete_profile_hovered.png +RTOC/data/ui/icons/dark/down_arrow.png +RTOC/data/ui/icons/dark/down_arrow_disabled.png +RTOC/data/ui/icons/dark/download.png +RTOC/data/ui/icons/dark/edit.png +RTOC/data/ui/icons/dark/edit_hovered.png +RTOC/data/ui/icons/dark/file.png +RTOC/data/ui/icons/dark/file_hovered.png +RTOC/data/ui/icons/dark/filter.png +RTOC/data/ui/icons/dark/filter_hovered.png +RTOC/data/ui/icons/dark/image.png +RTOC/data/ui/icons/dark/image_hovered.png +RTOC/data/ui/icons/dark/left_arrow.png +RTOC/data/ui/icons/dark/left_arrow_disabled.png +RTOC/data/ui/icons/dark/maximize_window.png +RTOC/data/ui/icons/dark/maximize_window_hovered.png +RTOC/data/ui/icons/dark/menu.png +RTOC/data/ui/icons/dark/menu_hovered.png +RTOC/data/ui/icons/dark/merge.png +RTOC/data/ui/icons/dark/merge_hovered.png +RTOC/data/ui/icons/dark/minimize_window.png +RTOC/data/ui/icons/dark/minimize_window_hovered.png +RTOC/data/ui/icons/dark/radio_checked.png +RTOC/data/ui/icons/dark/radio_checked_hovered.png +RTOC/data/ui/icons/dark/radio_disabled.png +RTOC/data/ui/icons/dark/radio_unchecked.png +RTOC/data/ui/icons/dark/radio_unchecked_hovered.png +RTOC/data/ui/icons/dark/remove_element.png +RTOC/data/ui/icons/dark/remove_element_hovered.png +RTOC/data/ui/icons/dark/restore_window.png +RTOC/data/ui/icons/dark/restore_window_hovered.png +RTOC/data/ui/icons/dark/right_arrow.png +RTOC/data/ui/icons/dark/right_arrow_disabled.png +RTOC/data/ui/icons/dark/save.png +RTOC/data/ui/icons/dark/save_hovered.png +RTOC/data/ui/icons/dark/sizegrip.png +RTOC/data/ui/icons/dark/source.png +RTOC/data/ui/icons/dark/source_hovered.png +RTOC/data/ui/icons/dark/submitElement.png +RTOC/data/ui/icons/dark/submitElement_hovered.png +RTOC/data/ui/icons/dark/undo.png +RTOC/data/ui/icons/dark/undo_hovered.png +RTOC/data/ui/icons/dark/undock.png +RTOC/data/ui/icons/dark/up_arrow.png +RTOC/data/ui/icons/dark/up_arrow_disabled.png +RTOC/lang/en_en.qm +RTOC/lang/en_en.ts +RTOC/plugins/DPS5020.py +RTOC/plugins/Generator.py +RTOC/plugins/HoldPeak VC820.py +RTOC/plugins/NetWoRTOC.py +RTOC/plugins/OctoTouch.py +RTOC/plugins/System.py +RTOC/plugins/Template.py +RTOC/plugins/DPS5020/dps5020.ui +RTOC/plugins/Funktionsgenerator/gen_function.ui +RTOC/plugins/Octotouch/OctoprintApi.py +RTOC/plugins/Octotouch/octotouch.ui +RTOC/plugins/Octotouch/__pycache__/OctoprintApi.cpython-36.pyc +RTOC/plugins/System/system.ui +RTOC/plugins/Template/template.ui +RTOC/plugins/holdPeak_VC820/portSelectWidget.ui +RTOC/plugins/holdPeak_VC820/vc820py/.gitignore +RTOC/plugins/holdPeak_VC820/vc820py/README.md +RTOC/plugins/holdPeak_VC820/vc820py/generate_test.py +RTOC/plugins/holdPeak_VC820/vc820py/raw2json.py +RTOC/plugins/holdPeak_VC820/vc820py/rawtime2csv.py +RTOC/plugins/holdPeak_VC820/vc820py/rawtime2json.py +RTOC/plugins/holdPeak_VC820/vc820py/rawtime2raw.py +RTOC/plugins/holdPeak_VC820/vc820py/read_from_serial.py +RTOC/plugins/holdPeak_VC820/vc820py/read_multiple.py +RTOC/plugins/holdPeak_VC820/vc820py/read_multiple_reader.py +RTOC/plugins/holdPeak_VC820/vc820py/read_reader.py +RTOC/plugins/holdPeak_VC820/vc820py/reader.py +RTOC/plugins/holdPeak_VC820/vc820py/record_serial.py +RTOC/plugins/holdPeak_VC820/vc820py/replay_rawtime.py +RTOC/plugins/holdPeak_VC820/vc820py/ut61b.py +RTOC/plugins/holdPeak_VC820/vc820py/vc820.py +RTOC/plugins/holdPeak_VC820/vc820py/__pycache__/vc820.cpython-36.pyc +RTOC/plugins/netWoRTOC/gui.py +RTOC/plugins/netWoRTOC/networkscan.py +RTOC/plugins/netWoRTOC/networtoc.ui +RTOC/plugins/netWoRTOC/repeat.png +RTOC/plugins/netWoRTOC/repeat1.png +RTOC/plugins/netWoRTOC/search.png +RTOC/plugins/netWoRTOC/__pycache__/gui.cpython-36.pyc +RTOC/plugins/netWoRTOC/__pycache__/networkscan.cpython-36.pyc +RTOC/plugins/vc820py/-h +RTOC/plugins/vc820py/__pycache__/vc820.cpython-36.pyc \ No newline at end of file diff --git a/RTOC.egg-info/dependency_links.txt b/RTOC.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/RTOC.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/RTOC.egg-info/requires.txt b/RTOC.egg-info/requires.txt new file mode 100644 index 0000000..494071e --- /dev/null +++ b/RTOC.egg-info/requires.txt @@ -0,0 +1,11 @@ +numpy +pyqt5 +pyqtgraph +markdown2 +xlsxwriter +scipy +qtmodern +python-telegram-bot +matplotlib +requests +python-nmap diff --git a/RTOC.egg-info/top_level.txt b/RTOC.egg-info/top_level.txt new file mode 100644 index 0000000..f37ef9f --- /dev/null +++ b/RTOC.egg-info/top_level.txt @@ -0,0 +1 @@ +RTOC diff --git a/LoggerPlugin.py b/RTOC/LoggerPlugin.py similarity index 61% rename from LoggerPlugin.py rename to RTOC/LoggerPlugin.py index 5793626..4265e14 100644 --- a/LoggerPlugin.py +++ b/RTOC/LoggerPlugin.py @@ -1,18 +1,18 @@ -# from multiprocessing.connection import Client +# LoggerPlugin v1.6 import traceback -#import socket -#import json -import jsonsocket -import pickle import time +try: + from . import jsonsocket +except: + import jsonsocket + + class LoggerPlugin: - def __init__(self, stream=None, plot = None, event=None): + def __init__(self, stream=None, plot=None, event=None): # Plugin setup self.setDeviceName() - self.dataY = [0] # Array containing different data-streams - self.dataX = [None] - self.dataunits = [''] # Represents the unit of the current data + self.deviceName = "noDevice" self.datanames = [''] # Names for every data-stream self.__cb = stream self.__ev = event @@ -22,6 +22,7 @@ def __init__(self, stream=None, plot = None, event=None): self.run = False # False -> stops thread self.smallGUI = False self.xy = False + self.widget = None def stream(self, *args, **kwargs): if self.__cb: @@ -53,7 +54,7 @@ def plot(self, x=[], y=[], *args, **kwargs): else: print("No event connected") - def event(self, *args, **kwargs): # text="", dataname=None, devicename=None, xpos=None): + def event(self, *args, **kwargs): text = kwargs.get('text', "") dataname = kwargs.get('sname', None) devicename = kwargs.get('dname', None) @@ -71,30 +72,21 @@ def event(self, *args, **kwargs): # text="", dataname=None, devicename=None, xpo if idx == 4: priority = arg - if dataname == None: + if dataname is None: if len(self.datanames) != 0: dataname = self.datanames[0] else: dataname = "unknownEvent" - if devicename == None: + if devicename is None: devicename = self.deviceName if self.__ev: self.__ev(text, dataname, devicename, xpos, priority) else: print("No event connected") - # def createClient(self, address = 'localhost'): - # self.client = Client((address, 5056)) - def createTCPClient(self, address="localhost"): - # self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - # # Connect the socket to the port where the server is listening - # server_address = (socket.gethostname(), 5055) - # self.sock.connect(server_address) self.tcpaddress = address self.sock = jsonsocket.Client() - #self.sock.connect(address, 5055) - #client.close() def sendTCP(self, *args, **kwargs): dataY = kwargs.get('y', None) @@ -103,11 +95,16 @@ def sendTCP(self, *args, **kwargs): dataunits = kwargs.get('unit', None) dataX = kwargs.get('x', None) signals = kwargs.get('getSignal', None) + latest = kwargs.get('getLatest', None) events = kwargs.get('getEvent', None) signallist = kwargs.get('getSignalList', False) + eventlist = kwargs.get('getEventList', False) + pluginlist = kwargs.get('getPluginList', False) plot = kwargs.get('plot', False) event = kwargs.get('event', None) remove = kwargs.get('remove', None) + plugin = kwargs.get('plugin', None) + logger = kwargs.get('logger', None) for idx, arg in enumerate(args): if idx == 0: @@ -121,28 +118,36 @@ def sendTCP(self, *args, **kwargs): if idx == 4: dataX = arg - if dataX == None and dataY != None and not plot: + if dataX is None and dataY is not None and not plot: dataX = [time.time()]*len(dataY) dicti = {} - if dataY != None: - dicti['plot']=plot - dicti['y']=dataY - dicti['x']=dataX - dicti['sname']=datanames - dicti['dname']=devicename - dicti['unit']=dataunits + if dataY is not None: + dicti['plot'] = plot + dicti['y'] = dataY + dicti['x'] = dataX + dicti['sname'] = datanames + dicti['dname'] = devicename + dicti['unit'] = dataunits if signallist: - dicti['getSignallist']=True - if event != None: - dicti['event']=event - if events != None: - dicti['getEvent']=events - if signals != None: - dicti['getSignal']=signals - if remove != None: + dicti['getSignalList'] = True + if eventlist: + dicti['getEventList'] = True + if event is not None: + dicti['event'] = event + if events is not None: + dicti['getEvent'] = events + if signals is not None: + dicti['getSignal'] = signals + if latest is not None: + dicti['getLatest'] = latest + if remove is not None: dicti['remove'] = remove - - #data=pickle.dumps(list(dicti)) + if plugin is not None: + dicti['plugin'] = plugin + if logger is not None: + dicti['logger'] = logger + if pluginlist: + dicti['getPluginList'] = True if self.sock and self.run: try: @@ -153,45 +158,26 @@ def sendTCP(self, *args, **kwargs): return response except ConnectionRefusedError: print('TCP Connection refused') + try: + self.sock.close() + except: + pass return False except: tb = traceback.format_exc() print(tb) print("Error sending over TCP") + try: + self.sock.close() + except: + pass self.sock = jsonsocket.Client() return False else: print("Please createTCPClient first") + self.createTCPClient() return False - # def sendData(self, *args, **kwargs): - # dataY = kwargs.get('y', [1]) - # datanames = kwargs.get('sname', ["noName"]) - # devicename = kwargs.get('dname', "noDevice") - # dataunits = kwargs.get('unit', [""]) - # dataX = kwargs.get('x', [time.time()]*len(dataY)) - # for idx, arg in enumerate(args): - # if idx == 0: - # dataY = arg - # if idx == 1: - # datanames = arg - # if idx == 2: - # devicename = arg - # if idx == 3: - # dataunits = arg - # if idx == 4: - # dataX = arg - # - # if self.client and self.run: - # try: - # self.client.send([dataY, datanames, devicename, dataunits, dataX]) - # # print(self.client.recv()) - # except: - # tb = traceback.format_exc() - # print(tb) - # elif self.run: - # print("Please call 'self.createClient()' before running self.sendData()!") - def setDeviceName(self, devicename="noDevice"): self.devicename = devicename # Is shown in GUI @@ -200,7 +186,5 @@ def close(self): if self.widget: self.widget.hide() self.widget.close() - #if self.client: - # self.client.close() - #if self.sock: - #self.sock.close() + # if self.sock: + # self.sock.close() diff --git a/RTLogger.py b/RTOC/RTLogger.py similarity index 57% rename from RTLogger.py rename to RTOC/RTLogger.py index 39a6d44..bbf52c9 100644 --- a/RTLogger.py +++ b/RTOC/RTLogger.py @@ -1,4 +1,4 @@ -import sys +#self.addNewEventimport sys import os import time import traceback @@ -9,17 +9,81 @@ from threading import Thread from collections import deque import numpy as np +from PyQt5.QtCore import QCoreApplication +from PyQt5.QtCore import QObject +import sys -import plugins -import data.lib.general_lib as lib -from data.loggerlib import * -from data.ScriptFunctions import ScriptFunctions -import data.scriptLibrary as rtoc -import jsonsocket -from LoggerPlugin import LoggerPlugin - -class RTLogger(ScriptFunctions): - def __init__(self): +try: + from .data.lib import general_lib as lib + from .data import loggerlib as loggerlib + from .data.ScriptFunctions import ScriptFunctions + from .data import scriptLibrary as rtoc + from . import plugins + from . import jsonsocket + from .LoggerPlugin import LoggerPlugin + from .telegramBot import telegramBot +except ImportError: + from data.lib import general_lib as lib + from data import loggerlib as loggerlib + from data.ScriptFunctions import ScriptFunctions + from data import scriptLibrary as rtoc + import plugins + import jsonsocket + from LoggerPlugin import LoggerPlugin + from telegramBot import telegramBot + +userpath = os.path.expanduser('~/Documents/RTOC') +if not os.path.exists(userpath): + os.mkdir(userpath) +if not os.path.exists(userpath+'/devices'): + os.mkdir(userpath+'/devices') +sys.path.insert(0, userpath) +import devices + + +translate = QCoreApplication.translate + +defaultconfig = { + "language": "en", + "lastSessions": [], + "darkmode": True, + "scriptWidget": True, + "deviceWidget": True, + "signalsWidget": True, + "pluginsWidget": False, + "eventWidget": True, + "newSignalSymbols": True, + "plotLabelsEnabled": True, + "plotGridEnabled": True, + "grid": [ + True, + True, + 1.0 + ], + "plotLegendEnabled": False, + "blinkingIdentifier": False, + "signalStyles": [], + "defaultRecordLength": 5000, + "plotRate": 8, + "plotInverted": False, + "xTimeBase": True, + "timeAxis": True, + "systemTray": False, + "tcpserver": True, + "defaultScriptSampleTime": 10, + "lastScript": "", + "signalInactivityTimeout": 2, + "tcpPort": 5050, + "telegram_bot": False, + "telegram_name": "RTOC-Remote", + "telegram_token": "", + "telegram_eventlevel": 1, + "telegram_chat_ids": [], + "documentfolder": "" +} + +class RTLogger(ScriptFunctions, QObject): + def __init__(self, enableTCP=None): self.run = True self.config = {} self.load_config() @@ -27,45 +91,82 @@ def __init__(self): self.pluginFunctions = {} self.pluginParameters = {} self.pluginStatus = {} + self.starttime = time.time() self.maxLength = self.config["defaultRecordLength"] self.latestSignal = [] - self.devicenames = [] - for finder, name, ispkg in iter_namespace(plugins): - if name not in ["plugins.Template", "plugins.LoggerPlugin"]: - self.devicenames += [name] - + self.devicenames = {} + self.getDeviceList() self.signals = [] self.signalNames = [] self.signalUnits = [] self.signalIDs = [] self.events = [] - self.triggerExpressions = [] self.triggerValues = [] self.tcp = None + if enableTCP is not None: + self.config['tcpserver'] = enableTCP + self.toggleTcpServer(self.config['tcpserver']) self.clearSignals() self.callback = None self.newSignalCallback = None - #self.newSignal = None self.scriptExecutedCallback = None self.handleScriptCallback = None self.clearCallback = None self.newEventCallback = None - self.tcpclient = LoggerPlugin(None,None,None) + self.startDeviceCallback = None + self.stopDeviceCallback = None + self.tcpclient = LoggerPlugin(None, None, None) + + self.telegramBot = telegramBot(self) + self.toggleTelegramBot() + + def getDeviceList(self): + print("Default plugins:") + for finder, name, ispkg in loggerlib.iter_namespace(plugins): + namesplit = name.split('.') + print(namesplit[-1]) + if namesplit[-1] not in ["Template", "LoggerPlugin"]: + self.devicenames[namesplit[-1]] = name + self.pluginStatus[namesplit[-1]] = False + print('User plugins:') + for finder, name, ispkg in loggerlib.iter_namespace(devices): + namesplit = name.split('.') + print(namesplit[-1]) + if namesplit[-1] not in ["Template", "LoggerPlugin"]: + self.devicenames[namesplit[-1]] = name + self.pluginStatus[namesplit[-1]] = False + + def toggleTelegramBot(self, value=None): + if value is None: + value = self.config['telegram_bot'] + self.config['telegram_bot'] = value + if value: + ok = self.telegramBot.connect() + if not ok: + self.config['telegram_bot'] = False + self.telegramBot.stop() + else: + self.telegramBot.stop() - def toggleTcpServer(self, value = None): - if value == None: + def toggleTcpServer(self, value=None): + if value is None: value = self.config['tcpserver'] self.config['tcpserver'] = value if value: - self.tcpRunning = True - print("TCPServer gestartet") - self.tcp = jsonsocket.Server("0.0.0.0",self.config["tcpPort"]) - self.__tcpserver = Thread(target=self.tcpListener) - self.__tcpserver.start() + try: + self.tcp = jsonsocket.Server("0.0.0.0", self.config["tcpPort"]) + self.tcpRunning = True + self.__tcpserver = Thread(target=self.tcpListener) + self.__tcpserver.start() + print("TCPServer gestartet") + except OSError: + print("Port already in use. Cannot start TCP-Server") + self.tcpRunning = False + self.config['tcpserver'] = False else: self.tcpRunning = False print("TCPServer beendet") @@ -76,77 +177,171 @@ def sendTCP(self, hostname="localhost", *args, **kwargs): self.tcpclient.createTCPClient(hostname) self.tcpclient.sendTCP(*args, **kwargs) + def getThread(self): + return self.__tcpserver + def tcpListener(self): while self.tcpRunning: - ans = {'error':False} + ans = {'error': False} try: self.tcp.accept() msg = self.tcp.recv() if type(msg) == dict: if 'y' in msg.keys(): plot = msg.get('plot', False) - datasY = msg.get('y',[]) - datanames = msg.get('sname',[""]) - devicename = msg.get('dname',"noDevice") - unit = msg.get('unit',[""]) - datasX = msg.get('x',None) - if devicename == None: + datasY = msg.get('y', []) + datanames = msg.get('sname', [""]) + devicename = msg.get('dname', "noDevice") + unit = msg.get('unit', [""]) + datasX = msg.get('x', None) + if devicename is None: devicename = "noDevice" if plot: - self.plot(datasX, datasY, datanames,devicename, unit) + self.plot(datasX, datasY, datanames, devicename, unit) else: self.addDataCallback(datasY, datanames, devicename, unit, datasX, True) ans['sent'] = True - if 'getSignallist' in msg.keys(): - ans['signallist'] =self.signalNames + if 'getSignalList' in msg.keys(): + signalNames = self.signalNames + if ['RTOC', ''] in signalNames: + signalNames.pop(signalNames.index(['RTOC', ''])) + ans['signalList'] = signalNames + if 'getPluginList' in msg.keys(): + ans['pluginList'] = self.getPluginDict() if 'event' in msg.keys(): self.addNewEvent(*msg['event']) + if 'getEventList' in msg.keys(): + ans['events'] = {} + for name in self.signalNames: + sig = self.getEvents(self.getSignalId(name[0], name[1])) + ans['events'][".".join(name)] = [list(sig[0]), list(sig[1])] if 'getEvent' in msg.keys(): - ans['events']={} + ans['events'] = {} for device in msg['getEvent']: - dev=device.split('.') - sig = self.getEvents(self.getSignalId(dev[0],dev[1])) - ans['events'][device]= [list(sig[0]),list(sig[1])] + dev = device.split('.') + sig = self.getEvents(self.getSignalId(dev[0], dev[1])) + ans['events'][device] = [list(sig[0]), list(sig[1])] if 'getSignal' in msg.keys(): - ans['signals']={} + ans['signals'] = {} for device in msg['getSignal']: - dev=device.split('.') - sig = self.getSignal(self.getSignalId(dev[0],dev[1])) - ans['signals'][device]= [list(sig[0]),list(sig[1])] + dev = device.split('.') + sig = self.getSignal(self.getSignalId(dev[0], dev[1])) + unit = self.getSignalUnits(self.getSignalId(dev[0], dev[1])) + ans['signals'][device] = [list(sig[0]), list(sig[1]), unit] + if 'getLatest' in msg.keys(): + ans['latest'] = {} + for device in msg['getLatest']: + dev = device.split('.') + sig = self.getSignal(self.getSignalId(dev[0], dev[1])) + ans['latest'][device] = sig[1][-1] if 'remove' in msg.keys(): - ans['remove']='Not implemented' + ans['remove'] = 'Not implemented' + if 'plugin' in msg.keys(): + ans['plugin'] = self.handleTcpPlugins(msg['plugin']) + if 'logger' in msg.keys(): + ans['logger'] = self.handleTcpLogger(msg['logger']) for key in msg.keys(): if key not in ans.keys(): - ans[key]=msg[key] + ans[key] = msg[key] self.tcp.send(ans) except OSError: print("TCP Server idle") + except KeyboardInterrupt: + self.stop() except: tb = traceback.format_exc() print(tb) print("Error in TCP-Connection") - ans['error']=True - #self.tcp.send(ans) - - + ans['error'] = True + # self.tcp.send(ans) + + def handleTcpPlugins(self, pluginDicts): + if type(pluginDicts) == dict: + for plugin in pluginDicts.keys(): + if type(pluginDicts[plugin]) == dict: + for call in pluginDicts[plugin].keys(): + if call == "start" and type(pluginDicts[plugin][call]) == bool: + if pluginDicts[plugin][call]: + pluginDicts[plugin][call] = self.startPlugin(plugin, callback=None) + else: + pluginDicts[plugin][call] = self.stopPlugin(plugin) + elif '()' in call: + pluginDicts[plugin][call] = self.callPluginFunction( + plugin, call.replace('()', ''), *pluginDicts[plugin][call]) + else: + pluginDicts[plugin][call] = self.getPluginParameter( + plugin, call, pluginDicts[plugin][call]) + return pluginDicts + + def handleTcpLogger(self, loggerDict): + if type(loggerDict) == dict: + for call in loggerDict.keys(): + if call == 'clear': + if loggerDict[call] == 'all': + self.clear() + elif type(loggerDict[call]) == list: + for idx, sig in enumerate(loggerDict[call]): + id = self.getSignalId(*sig.split('.')) + if id != -1: + loggerDict[call][idx] = self.removeSignal(id) + else: + loggerDict[call][idx] = False + if call == 'resize': + if type(loggerDict[call]) == int: + self.resizeSignals(loggerDict[call]) + loggerDict[call] = True + if call == 'export': + if type(loggerDict[call]) == list: + if len(loggerDict[call]) <= 2: + self.exportData(*loggerDict[call]) + loggerDict[call] = True + if call == 'info': + loggerDict[call] = {} + loggerDict[call]['recordLength'] = self.maxLength + loggerDict[call]['signals'] = len(self.signals) + loggerDict[call]['recordLength'] = self.maxLength + loggerDict[call]['starttime'] = self.starttime + loggerDict[call]['telegram_token'] = self.config['telegram_token'] + loggerDict[call]['telegram_bot'] = self.config['telegram_bot'] + return loggerDict + + def getPluginDict(self): + dict = {} + for name in self.devicenames.keys(): + dict[name] = {} + dict[name]['functions'] = [] + dict[name]['parameters'] = [] + dict[name]['status'] = False + for fun in self.pluginFunctions.keys(): + if fun.startswith(name+".") and fun not in [name+".close", name+".loadGUI", name+".createTCPClient", name+".sendTCP", name+".plot", name+".setDeviceName", name+".event", name+".stream"]: + dict[name]['functions'].append(fun.replace(name+".", '')) + for fun in self.pluginParameters.keys(): + if fun.startswith(name+".") and fun not in [name+".deviceName", name+".close", name+".run", name+".smallGUI", name+".sock", name+".widget"]: + dict[name]['parameters'].append(fun.replace(name+".", '')) + for fun in self.pluginStatus.keys(): + if name == fun: + dict[name]['status'] = self.pluginStatus[fun] + return dict # Plugin functions ############################################################ - def startPlugin(self, name, callback=None): + def startPlugin(self, name, callback=None, remote=True): # Starts the specified plugin and connects callback if possible try: - name = 'plugins.' + name - if name in self.devicenames: - #self.plugins[name] = importlib.import_module(name) + if name in self.devicenames.keys(): + fullname = self.devicenames[name] if callback is None: self.pluginObjects[name] = importlib.import_module( - name).Plugin(self.addDataCallback, self.plot, self.addNewEvent) + fullname).Plugin(self.addDataCallback, self.plot, self.addNewEvent) else: self.pluginObjects[name] = importlib.import_module( - name).Plugin(callback, self.addNewEvent) + fullname).Plugin(callback, self.addNewEvent) self.analysePlugin(self.pluginObjects[name], name) - self.pluginStatus[name] = "OK" + self.pluginStatus[name] = True print("PLUGIN: " + name+' connected\n') - self.addNewEvent(text=self.tr("Plugin gestartet: ")+name.replace("plugins.",""),sname="", dname="RTOC", priority = 0) + #self.addNewEvent(text=translate('RTLogger', "Plugin gestartet: ") + + # name, sname="", dname="RTOC", priority=0) + if self.startDeviceCallback and remote: + self.startDeviceCallback(name) return True, "" else: print("PLUGIN not found: '"+str(name)+"'\n") @@ -158,22 +353,74 @@ def startPlugin(self, name, callback=None): print("PLUGIN FAILURE\nCould not load Plugin '"+str(name)+"'\n") return False, tb - def stopPlugin(self, name): + def getPlugin(self, name): + try: + if name in self.pluginObjects.keys(): + return self.pluginObjects[name] + else: + print("Plugin "+name+" not found or started") + return False + except: + tb = traceback.format_exc() + print(tb) + print("PLUGIN FAILURE\nCould not get Plugin '"+str(name)+"'\n") + return False + + def getPluginParameter(self, plugin, parameter, *args): + try: + print(args) + if parameter == "get" and type(parameter) == str: + if type(args[0]) == list: + rets = [] + for param in args[0]: + exec('self.ret=self.pluginObjects[plugin].'+str(param)) + rets.append(self.ret) + self.ret = rets + return self.ret + elif plugin in self.pluginObjects.keys(): + exec('self.pluginObjects[plugin].'+parameter+" = " + str(args[0])) + self.ret = args[0] + return self.ret + else: + print("Plugin "+plugin+" not found or started") + return False + except: + tb = traceback.format_exc() + print(tb) + print("PLUGIN FAILURE\nCould not get/set/call Plugin parameter/function'"+str(plugin)+"'\n") + return False + + def callPluginFunction(self, plugin, function, *args, **kwargs): + try: + if plugin in self.pluginObjects.keys() and type(function) == str: + exec('self.func = self.pluginObjects[plugin].'+function) + return self.func(*args, **kwargs) + else: + print("Plugin "+plugin+" not found or started") + return False + except: + tb = traceback.format_exc() + print(tb) + print("PLUGIN FAILURE\nCould not get/set/call Plugin parameter/function'"+str(plugin)+"'\n") + return False + + def stopPlugin(self, name, remote=True): # Stops the specified plugin try: - name = 'plugins.' + name if name in self.pluginObjects.keys(): #self.pluginObjects[name].run = False - self.pluginStatus[name] = "STOPPED" + self.pluginStatus[name] = False self.pluginObjects[name].close() print("PLUGIN: " + name+' disconnected\n') - self.addNewEvent(text=self.tr("Plugin gestoppt: ")+name.replace("plugins.",""),sname="", dname="RTOC", priority = 0) + #self.addNewEvent(text=translate('RTLogger', "Plugin gestoppt: ") + + # name, sname="", dname="RTOC", priority=0) # else: # print("Plugin "+name+" not loaded") + if self.stopDeviceCallback and remote: + self.stopDeviceCallback(name) return True except: tb = traceback.format_exc() - self.pluginStatus[name] = tb print(tb) print("PLUGIN FAILURE\nCould not stop Plugin '"+str(name)+"'\n") return False @@ -181,28 +428,28 @@ def stopPlugin(self, name): def stop(self): # Stops all plugins self.run = False - #self.serv.close() - #self.__tcpserver.run = False self.tcpRunning = False if self.tcp: self.tcp.close() print("TCPServer beendet") - # self.serv.close() - for name in self.devicenames: - self.stopPlugin(name.replace("plugins.", "")) + if self.config['telegram_bot']: + if self.config['telegram_eventlevel'] <= 1: + self.telegramBot.sendMessage(self.config['telegram_name'] + " wird beendet.") + self.telegramBot.stop() + for name in self.devicenames.keys(): + self.stopPlugin(name) def analysePlugin(self, object, name): # Adds public Plugin-functions and parameters to local attributes pluginFunctions and pluginParameters all = dir(object) - name = name.replace("plugins.", "") for element in all: if str(element)[0] != "_": if callable(getattr(object, element)): - self.pluginFunctions[name+"."+element] = "self.pluginObjects['plugins." + \ + self.pluginFunctions[name+"."+element] = "self.pluginObjects[" + \ name+"']."+getattr(object, element).__name__ else: self.pluginParameters[name+"." + - element] = "self.pluginObjects['plugins."+name+"']."+str(element) + element] = "self.pluginObjects["+name+"']."+str(element) # Signal functions ############################################################ @@ -224,7 +471,7 @@ def clear(self): def clearSignals(self, newLength=None): # Change the size of the recording - if newLength == None: + if newLength is None: newLength = self.maxLength else: self.maxLength = newLength @@ -236,7 +483,7 @@ def clearSignals(self, newLength=None): for _ in range(len(self.signalNames))] def resizeSignals(self, newLength=None): - if newLength == None: + if newLength is None: newLength = self.maxLength else: self.maxLength = newLength @@ -251,7 +498,7 @@ def resizeSignals(self, newLength=None): for idx in range(len(self.signalNames))] def removeSignal(self, id): - idx=self.signalIDs.index(id) + idx = self.signalIDs.index(id) if idx != -1: self.signalNames.pop(idx) self.signalIDs.pop(idx) @@ -262,7 +509,7 @@ def removeSignal(self, id): def getNewID(self): newID = 0 - while newID in self.signalIDs or newID==0: + while newID in self.signalIDs or newID == 0: newID += 1 return newID @@ -274,12 +521,13 @@ def __addNewSignal(self, dataY, dataunit, devicename, dataname, dataX=None, crea newID = self.getNewID() self.signalIDs.append(newID) self.signals += [[deque([], newLength), deque([], newLength)]] - self.events += [[deque([], newLength), deque([], newLength),deque([], self.maxLength)]] + self.events += [[deque([], newLength), deque([], newLength), deque([], self.maxLength)]] self.signalUnits += [deque([], newLength)] if self.newSignalCallback: #self.newSignal = [len(self.signalNames)-1, devicename, dataname, dataunit] self.newSignalCallback(newID, devicename, dataname, dataunit) - self.addNewEvent(text=self.tr("Signal-Stream hinzugefügt: ")+dataname+"."+devicename,sname="", dname="RTOC", priority = 0) + #self.addNewEvent(text=translate('RTLogger', "Signal-Stream hinzugefügt: ") + + # dataname+"."+devicename, sname="", dname="RTOC", priority=0) self.__addNewData(dataY, dataunit, devicename, dataname, dataX, createCallback) def __addNewData(self, dataY, dataunit, devicename, dataname, dataX=None, createCallback=True): @@ -316,7 +564,7 @@ def addNewEvent(self, *args, **kwargs): if idx == 4: priority = arg - if priority not in [0,1,2]: + if priority not in [0, 1, 2]: priority = 0 callback = False @@ -325,29 +573,35 @@ def addNewEvent(self, *args, **kwargs): self.signals += [[deque([], self.maxLength), deque([], self.maxLength)]] newID = self.getNewID() self.signalIDs.append(newID) - self.events += [[deque([], self.maxLength), deque([], self.maxLength), deque([], self.maxLength)]] + self.events += [[deque([], self.maxLength), + deque([], self.maxLength), deque([], self.maxLength)]] self.signalUnits += [deque([], self.maxLength)] self.signals[self.signalNames.index([devicename, dataname])][0].append(time.time()) self.signals[self.signalNames.index([devicename, dataname])][1].append(0) self.signalUnits[self.signalNames.index([devicename, dataname])].append("") callback = True idx = self.signalNames.index([devicename, dataname]) - if x == None: + if x is None: self.events[idx][0].append(time.time()) - x=time.time() + x = time.time() else: self.events[idx][0].append(float(x)) self.events[idx][1].append(strung) self.events[idx][2].append(priority) if self.newEventCallback: self.newEventCallback(x, strung, devicename, dataname, priority) - if self.newSignalCallback and callback and (devicename!="RTOC"): - #self.newSignal = [len(self.signalNames)-1, devicename, dataname, ""] + if self.newSignalCallback and callback and (devicename != "RTOC"): self.newSignalCallback(newID, devicename, dataname, "") # if self.callbackEvent: # self.callbackEvent() - - def tr(self, *args): + if self.config['telegram_bot']: + if priority >= self.config['telegram_eventlevel']: + ptext = ['_Information_\n', '*Warnung*\n', '*_Fehler_*\n'][priority] + self.telegramBot.sendMessage( + ptext+'Gerät: ' + devicename+'\nSignal: '+dataname+'\n'+strung) + + def tr(self, args): + print('not translated') return args def __plotNewSignal(self, x, y, dataunit, devicename, dataname, createCallback=False): @@ -355,19 +609,19 @@ def __plotNewSignal(self, x, y, dataunit, devicename, dataname, createCallback=F print("LOGGER: Adding signalplot: "+devicename+", "+dataname) newLength = self.maxLength self.signalNames += [[devicename, dataname]] - self.events += [[deque([], self.maxLength), deque([], self.maxLength),deque([], self.maxLength)]] + self.events += [[deque([], self.maxLength), deque([], self.maxLength), + deque([], self.maxLength)]] self.signalIDs.append(self.getNewID()) self.signals += [[deque([], newLength), deque([], newLength)]] self.signalUnits += [deque([], newLength)] self.__plotNewData(x, y, dataunit, devicename, dataname, createCallback) if self.newSignalCallback: - #self.newSignal = [self.signalIDs[-1], devicename, dataname, dataunit] self.newSignalCallback(self.signalIDs[-1], devicename, dataname, dataunit) - self.addNewEvent(text=self.tr("Signal-Plot hinzugefügt: ")+dataname+"."+devicename,sname="", dname="RTOC", priority = 0) + #self.addNewEvent(text=translate('RTLogger', "Signal-Plot hinzugefügt: ") + + # dataname+"."+devicename, sname="", dname="RTOC", priority=0) def __plotNewData(self, x, y, dataunit, devicename, dataname, createCallback=False): # Add new data to a signal-stream - # self.latestSignal.append([devicename,dataname]) self.latestSignal = [devicename, dataname] idx = self.signalNames.index([devicename, dataname]) self.signals[idx][0].clear() @@ -415,6 +669,7 @@ def getSignalId(self, devicename, dataname): # Callback functions ########################################################## + def addDataCallback(self, datasY=[], *args, **kwargs): datanames = kwargs.get('snames', [""]) devicename = kwargs.get('dname', "noDevice") @@ -436,7 +691,7 @@ def addDataCallback(self, datasY=[], *args, **kwargs): if type(datasY) == list: if datasX == [None]: datasX = [None]*len(datasY) - if callback == [""] or callback == None: + if callback == [""] or callback is None: callback = [""]*len(datasY) for idx, data in enumerate(datasY): if [devicename, datanames[idx]] in self.signalNames: @@ -463,7 +718,7 @@ def addData(self, data=0, *args, **kwargs): if idx == 3: createCallback = arg - #try: + # try: if type(data) == list: if len(data) == 2: if [devicename, dataname] in self.signalNames: @@ -481,10 +736,6 @@ def addData(self, data=0, *args, **kwargs): else: self.__addNewSignal(data, dataunit, devicename, dataname, None, createCallback) - # except: - # tb = traceback.format_exc() - # print(tb) - # print("SCRIPT FAILURE\nSignal not available") def plot(self, x=[], y=[], *args, **kwargs): dataname = kwargs.get('sname', "noName") @@ -504,7 +755,6 @@ def plot(self, x=[], y=[], *args, **kwargs): if y == []: y = x x = list(range(len(x))) - #try: if len(x) == len(y): if [devicename, dataname] in self.signalNames: self.__plotNewData(x, y, @@ -515,10 +765,6 @@ def plot(self, x=[], y=[], *args, **kwargs): dataunit, devicename, dataname, createCallback) else: print("Plotting aborted. len(x)!=len(y)") - # except: - # tb = traceback.format_exc() - # print(tb) - # print("SCRIPT FAILURE\nPlotting failed!") # Other functions ######################################################### @@ -531,7 +777,7 @@ def exportData(self, *args, **kwargs): if idx == 1: filetype = arg - if filename == None: + if filename is None: filename = self.generateFilename() if filetype == "xlsx": self.exportXLSX(filename) @@ -547,7 +793,7 @@ def generateFilename(self): minx.append(min(list(signal[0]))) minx = max(minx) now = time.strftime("%d_%m_%y_%H_%M", time.localtime(minx)) - return str(now) + return self.config['documentfolder']+"/"+str(now) def exportXLSX(self, filename): workbook = xlsxwriter.Workbook(filename+".xslx") @@ -599,9 +845,7 @@ def exportXLSX(self, filename): col += 1 row = 1 for data in self.signalUnits[idx]: - if np.isnan(data): - data = -1 - worksheet2.write(row, col, data) + worksheet2.write(row, col, str(data)) row += 1 workbook.close() @@ -635,15 +879,15 @@ def exportJSON(self, filename): with open(filename+".json", 'w') as fp: json.dump(jsonfile, fp, sort_keys=False, indent=4, separators=(',', ': ')) - def restoreJSON(self, filename="restore.json"): + def restoreJSON(self, filename=None): # try: + if filename == None: + filename = self.config['documentfolder']+"/restore.json" if os.path.exists(filename): with open(filename) as f: data = json.load(f) self.clear() self.maxLength = data["maxLength"] - #self.events[0]=deque(data["events"][0], self.maxLength) - #self.events[1]=deque(data["events"][1], self.maxLength) for signal in data["data"].keys(): name = signal.split(".") if len(data["data"][signal][0]) != 0: @@ -652,15 +896,14 @@ def restoreJSON(self, filename="restore.json"): for signal in data["events"].keys(): if signal != ".": name = signal.split(".") + if len(name) == 1: + name.append("") for idx, event in enumerate(data["events"][signal][0]): - self.addNewEvent(data["events"][signal][1][idx], name[1], name[0], event, data["events"][signal][2]) + self.addNewEvent(data["events"][signal][0][idx], name[1], + name[0], event, data["events"][signal][1]) return True else: return False - # except: - # tb = traceback.format_exc() - # print(tb) - # return False def exportCSV(self, filename): textfile = '' @@ -675,28 +918,47 @@ def exportCSV(self, filename): myfile.write(textfile) def exportSignal(self, filename, signal): - x = list(signal[0]) - y = list(signal[1]) with open(filename+".csv", 'w', newline='') as myfile: wr = csv.writer(myfile, quoting=csv.QUOTE_ALL) - wr.writerow(signal[0]) - wr.writerow(signal[1]) + wr.writerow(list(signal[0])) + wr.writerow(list(signal[1])) def load_config(self): self.lastEditedList = [] - with open("config.json", encoding="UTF-8") as jsonfile: - self.config = json.load(jsonfile, encoding="UTF-8") - newlist = [] - for path in self.config["lastSessions"]: - if os.path.exists(path): - newlist.append(path) - self.config["lastSessions"] = newlist - for lastpath in self.config["lastSessions"]: - self.lastEditedList.append(lastpath) - lib.logging('config loaded') + + userpath = os.path.expanduser('~/Documents/RTOC') + if not os.path.exists(userpath): + os.mkdir(userpath) + + if os.path.exists(userpath+"/config.json"): + try: + with open(userpath+"/config.json", encoding="UTF-8") as jsonfile: + self.config = json.load(jsonfile, encoding="UTF-8") + newlist = [] + #self.config['documentfolder'] = userpath + for path in self.config["lastSessions"]: + if os.path.exists(path): + newlist.append(path) + self.config["lastSessions"] = newlist + for lastpath in self.config["lastSessions"]: + self.lastEditedList.append(lastpath) + except: + print('Error loading config.json') + self.config = defaultconfig + else: + print('No config-file found.') + self.config = defaultconfig + + self.config['documentfolder'] = userpath + conf = dict(self.config) + conf['telegram_bot'] = False + with open(self.config['documentfolder']+"/config.json", 'w', encoding="utf-8") as fp: + json.dump(conf, fp, sort_keys=False, indent=4, separators=(',', ': ')) + + def save_config(self): - with open("config.json", 'w', encoding="utf-8") as fp: + with open(self.config['documentfolder']+"/config.json", 'w', encoding="utf-8") as fp: json.dump(self.config, fp, sort_keys=False, indent=4, separators=(',', ': ')) def getSignal(self, id): @@ -704,19 +966,19 @@ def getSignal(self, id): idx = self.signalIDs.index(id) return self.signals[idx] else: - return [[],[]] + return [[], []] def getEvents(self, id): if id in self.signalIDs: idx = self.signalIDs.index(id) return self.events[idx] else: - return [[],[],[]] + return [[], [], []] def getSignalUnits(self, id): if id in self.signalIDs: idx = self.signalIDs.index(id) - return self.signalUnits[idx] + return list(self.signalUnits[idx]) else: return [] @@ -725,16 +987,10 @@ def getSignalNames(self, id): idx = self.signalIDs.index(id) return self.signalNames[idx] else: - return [[],[]] - - + return [[], []] if __name__ == "__main__": kl = RTLogger() time.sleep(1) - kl.startPlugin('holdPeak_VC820') - # time.sleep(2) - kl.startPlugin('func_generator') - # time.sleep(2) - # kl.stopPlugin('func_generator') + kl.stop() diff --git a/RTOC.py b/RTOC/RTOC.py similarity index 61% rename from RTOC.py rename to RTOC/RTOC.py index c24b11d..731a6df 100644 --- a/RTOC.py +++ b/RTOC/RTOC.py @@ -6,16 +6,24 @@ from PyQt5 import uic from PyQt5 import QtWidgets, QtGui from functools import partial -import time import traceback import json +import getopt -import data.lib.pyqt_customlib as pyqtlib -import RTLogger -from data.scriptWidget import ScriptWidget -from data.eventWidget import EventWidget -from data.RTPlotWidget import RTPlotWidget -from data.Actions import Actions +try: + from . import RTLogger + from .data.lib import pyqt_customlib as pyqtlib + from .data.scriptWidget import ScriptWidget + from .data.eventWidget import EventWidget + from .data.RTPlotWidget import RTPlotWidget + from .data.Actions import Actions +except ImportError: + import RTLogger + from data.lib import pyqt_customlib as pyqtlib + from data.scriptWidget import ScriptWidget + from data.eventWidget import EventWidget + from data.RTPlotWidget import RTPlotWidget + from data.Actions import Actions if os.name == 'nt': import ctypes @@ -42,37 +50,35 @@ def _fromUtf8(s): LINECOLORS = ['r', 'g', 'b', 'c', 'm', 'y', 'w'] -class SubWindow(QtWidgets.QMainWindow):#, RTPlotWidget): +class SubWindow(QtWidgets.QMainWindow): def __init__(self, logger, selfself, idx, title="SubWindow"): - super(SubWindow, self).__init__()#logger, selfself, idx) + super(SubWindow, self).__init__() # logger, selfself, idx) self.widget = RTPlotWidget(logger, selfself, idx) - #super(RTPlotWidget, self).__init__(logger, selfself, idx) _DOCK_OPTS = QtGui.QMainWindow.AnimatedDocks _DOCK_OPTS |= QtGui.QMainWindow.AllowNestedDocks _DOCK_OPTS |= QtGui.QMainWindow.AllowTabbedDocks - self.self=selfself + self.self = selfself self.setDockOptions(_DOCK_OPTS) self.setCentralWidget(self.widget.widget) self.setWindowTitle(title) - app_icon = QtGui.QIcon("data/icon.png") + packagedir, file = os.path.split(os.path.realpath(__file__)) + app_icon = QtGui.QIcon(packagedir+"/data/icon.png") self.setWindowIcon(app_icon) self.show() self.signalObjects = self.widget.signalObjects self.treeWidget = self.widget.treeWidget - # def closeEvent(self, event, *args, **kwargs): - # super(SubWindow, self).closeEvent(event) def closeEvent(self, event, *args, **kwargs): - #print("Closing plot "+str(self.widget.id)) if self.widget.id == 0: self.widget.stop() else: for signalObject in self.widget.signalObjects: - #self.self.plotWidgets[0].addSignalRAW(signalObject) - self.self.plotWidgets[0].addSignal(signalObject.devicename, signalObject.signalname, signalObject.id, signalObject.unit) - while len(self.widget.signalObjects)!=0: + self.self.plotWidgets[0].addSignal( + signalObject.devicename, signalObject.signalname, signalObject.id, signalObject.unit) + while len(self.widget.signalObjects) != 0: signalObject = self.widget.signalObjects[0] - self.widget.deleteSignal(signalObject.id, signalObject.devicename, signalObject.signalname) + self.widget.deleteSignal( + signalObject.id, signalObject.devicename, signalObject.signalname) self.self.deletePlotWidget(self.widget.id) self.widget.stop() super(SubWindow, self).closeEvent(event) @@ -102,11 +108,11 @@ def addSignal(self, devicename, signalname, id, unit): class RTOC(QtWidgets.QMainWindow, Actions): def __init__(self): super(RTOC, self).__init__() - uic.loadUi("data/ui/rtoc.ui", self) - self.app_icon = QtGui.QIcon("data/icon.png") + packagedir, file = os.path.split(os.path.realpath(__file__)) + uic.loadUi(packagedir+"/data/ui/rtoc.ui", self) + self.app_icon = QtGui.QIcon(packagedir+"/data/icon.png") self.setWindowIcon(self.app_icon) self.tray_icon = QtWidgets.QSystemTrayIcon(self) - #self.tray_icon.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_ComputerIcon)) self.tray_icon.setIcon(self.app_icon) self.tray_icon.activated.connect(self.systemTrayClickAction) @@ -117,6 +123,8 @@ def __init__(self): self.logger.newSignalCallback = self.addNewSignal self.logger.callback = self.newDataCallback self.logger.clearCallback = self.clearData + self.logger.stopDeviceCallback = self.remoteDeviceStop + self.logger.startDeviceCallback = self.remoteDeviceStart self.config = self.logger.config self.loadPlotStyles() @@ -139,15 +147,13 @@ def __init__(self): self.pluginsWidget.hide() if not self.config["scriptWidget"]: self.scriptDockWidget.hide() - if not self.config["signalsWidget"]: - self.signalsWidget.hide() if not self.config["deviceWidget"]: self.deviceWidget.hide() if not self.config["eventWidget"]: self.eventWidgets.hide() - if not self.config["tcpserver"]: - self.TCPServerAction.setChecked(False) - + self.TCPServerAction.setChecked(self.config["tcpserver"]) + self.actionTelegramBot.setChecked(self.config["telegram_bot"]) + self.actionBotToken.setText(self.config['telegram_token']) self.newPlotWidget() self.updateLabels() @@ -156,15 +162,18 @@ def __init__(self): self.loadSession() def loadPlotStyles(self): - filename = "plotStyles.json" + filename = self.config['documentfolder']+"/plotStyles.json" if os.path.exists(filename): - with open(filename, encoding="UTF-8") as jsonfile: - self.plotStyles = json.load(jsonfile, encoding="UTF-8") + try: + with open(filename, encoding="UTF-8") as jsonfile: + self.plotStyles = json.load(jsonfile, encoding="UTF-8") + except: + self.plotStyles = {} else: self.plotStyles = {} def savePlotStyles(self): - with open("plotStyles.json", 'w', encoding="utf-8") as fp: + with open(self.config['documentfolder']+"/plotStyles.json", 'w', encoding="utf-8") as fp: json.dump(self.plotStyles, fp, sort_keys=False, indent=4, separators=(',', ': ')) def initToolBar(self): @@ -203,21 +212,17 @@ def initTrayIcon(self): self.TCPServerAction.setChecked(self.config["tcpserver"]) def initDeviceWidget(self): - for plugin in self.logger.devicenames: - #button = QtWidgets.QCheckBox() + for plugin in self.logger.devicenames.keys(): button = QtWidgets.QToolButton() - button.setText(plugin.replace("plugins.", "")) + button.setText(plugin) button.setCheckable(True) sizePolicy = QtGui.QSizePolicy( QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - # sizePolicy.setHeightForWidth( - # button.sizePolicy().hasHeightForWidth()) button.setSizePolicy(sizePolicy) - # button.setCheckable(True) button.clicked.connect(partial(self.toggleDevice, plugin, button)) self.deviceLayout.addWidget(button) @@ -242,34 +247,30 @@ def newPlotWidget(self): plotWidget = RTPlotWidget(self.logger, self, 0) self.plotWidgets.append(plotWidget) self.plotLayout.addWidget(self.plotWidgets[self.activePlotWidgetIndex].widget) - # plotWidget.show() self.plotWidgets[self.activePlotWidgetIndex].droppedTree.connect(self.signalsDropped) else: - #dockwidget = QtWidgets.QDockWidget("Plot "+str(self.activePlotWidgetIndex), self.plotWidgets[self.activePlotWidgetIndex]) - #dockwidget = QtWidgets.QDialog("Plot "+str(len(self.plotWidgets))) plotWidget = SubWindow(self.logger, self, self.activePlotWidgetIndex, "Graph "+str(len(self.plotWidgets)+1)) self.plotWidgets.append(plotWidget) - self.plotWidgets[self.activePlotWidgetIndex].widget.droppedTree.connect(self.signalsDropped) - + self.plotWidgets[self.activePlotWidgetIndex].widget.droppedTree.connect( + self.signalsDropped) def signalsDropped(self, dicti): for idx, item in enumerate(dicti["signalObjects"]): - self.moveSignal(dicti["oldWidget"],dicti["newWidget"],item) + self.moveSignal(dicti["oldWidget"], dicti["newWidget"], item) def moveSignal(self, oldIdx, newIdx, signalItem): if oldIdx != newIdx: - if signalItem.childCount()==0: - #print("Moving signal from "+str(oldIdx)+" to "+str(newIdx)) - signalObject = self.plotWidgets[oldIdx].treeWidget.itemWidget(signalItem,0) - signalLabel = self.plotWidgets[oldIdx].treeWidget.itemWidget(signalItem,1) - if signalObject != None and signalLabel != None: - #self.plotWidgets[newIdx].addSignalRAW(signalObject) - self.plotWidgets[oldIdx].deleteSignal(signalObject.id, signalObject.devicename, signalObject.signalname) - self.plotWidgets[newIdx].addSignal(signalObject.devicename, signalObject.signalname, signalObject.id, signalObject.unit) - self._drag_info = {"oldWidget":"", "newWidget":"", "signalObjects":[]} + if signalItem.childCount() == 0: + signalObject = self.plotWidgets[oldIdx].treeWidget.itemWidget(signalItem, 0) + signalLabel = self.plotWidgets[oldIdx].treeWidget.itemWidget(signalItem, 1) + if signalObject is not None and signalLabel is not None: + self.plotWidgets[oldIdx].deleteSignal( + signalObject.id, signalObject.devicename, signalObject.signalname) + self.plotWidgets[newIdx].addSignal( + signalObject.devicename, signalObject.signalname, signalObject.id, signalObject.unit) + self._drag_info = {"oldWidget": "", "newWidget": "", "signalObjects": []} else: - #print("Group moving: len "+str(signalItem.childCount())) while signalItem.childCount() != 0: self.moveSignal(oldIdx, newIdx, signalItem.child(0)) @@ -283,14 +284,25 @@ def deletePlotWidget(self, id): def updateLabels(self): self.maxLengthSpinBox.setValue(self.logger.maxLength) + def remoteDeviceStop(self, devicename): + items = (self.deviceLayout.itemAt(i) for i in range(self.deviceLayout.count())) + for w in items: + if w.widget().text() == devicename: + w.widget().setChecked(False) + + def remoteDeviceStart(self, devicename): + items = (self.deviceLayout.itemAt(i) for i in range(self.deviceLayout.count())) + for w in items: + if w.widget().text() == devicename: + w.widget().setChecked(True) + def toggleDevice(self, deviceName, button): if not button.isChecked(): - #print("Closing Device connection: "+deviceName) - ok = self.logger.stopPlugin(deviceName.replace("plugins.", "")) + ok = self.logger.stopPlugin(deviceName, False) button.setMenu(None) if ok: for idx in range(self.pluginsBox.count()): - if self.pluginsBox.itemText(idx) == deviceName.replace("plugins.", ""): + if self.pluginsBox.itemText(idx) == deviceName: self.pluginsBox.removeItem(idx) break if self.pluginsBox.count() == 0: @@ -298,17 +310,14 @@ def toggleDevice(self, deviceName, button): else: button.setChecked(True) else: - #print("Loading Device connection: "+deviceName) - ok, errors = self.logger.startPlugin(deviceName.replace("plugins.", "")) + ok, errors = self.logger.startPlugin(deviceName, None, False) if ok: try: invert_op = getattr(self.logger.pluginObjects[deviceName], "loadGUI", None) if callable(invert_op): widget = self.logger.pluginObjects[deviceName].loadGUI() if self.logger.pluginObjects[deviceName].smallGUI is None: - #window = QtWidgets.QMainWindow() - #window.setCentralWidget(widget) - widget.setWindowTitle(deviceName.replace("plugins.", "")) + widget.setWindowTitle(deviceName) widget.show() elif self.logger.pluginObjects[deviceName].smallGUI is True: button.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) @@ -317,23 +326,23 @@ def toggleDevice(self, deviceName, button): action.setDefaultWidget(widget) button.menu().addAction(action) else: - self.pluginsBox.addItem(widget, deviceName.replace("plugins.", "")) + self.pluginsBox.addItem(widget, deviceName) if not self.pluginsWidget.isVisible(): self.pluginsWidget.show() except: tb = traceback.format_exc() - pyqtlib.info_message(self.tr("Fehler"), self.tr("Fehler beim Laden der Geräte GUI\nBitte Code überprüfen."),tb) + pyqtlib.info_message(self.tr("Fehler"), self.tr( + "Fehler beim Laden der Geräte GUI\nBitte Code überprüfen."), tb) else: - pyqtlib.info_message(self.tr("Fehler"), self.tr("Fehler beim Laden des Geräts\nBitte stellen Sie sicher, dass das Gerät verbunden ist."),errors) + pyqtlib.info_message(self.tr("Fehler"), self.tr( + "Fehler beim Laden des Geräts\nBitte stellen Sie sicher, dass das Gerät verbunden ist."), errors) button.setChecked(False) self.scriptWidget.updateListWidget() def addNewSignal(self, id, devicename, signalname, dataunit): - #idx , devicename, signalname, dataunit = self.logger.newSignal self.plotWidgets[self.activePlotWidgetIndex].addSignal2.emit( devicename, signalname, id, dataunit) self.scriptWidget.updateListWidget() - #QtCore.QMetaObject.invokeMethod(self.plotWidgets[self.activePlotWidgetIndex], "addSignal", QtCore.Qt.QueuedConnection, devicename, signalname, id, dataunit) def newDataCallback(self): devicename, dataname = self.logger.latestSignal @@ -355,18 +364,18 @@ def loadSession(self, fileName="restore.json"): if ok: self.scriptWidget.openFile(self.config["lastScript"]) else: - self.scriptWidget.openScript("","neu") + self.scriptWidget.openScript("", "neu") def forceClose(self): self.forceQuit = True self.close() - def closeEvent(self, event):#, *args, **kwargs): + def closeEvent(self, event): # , *args, **kwargs): if self.systemTrayAction.isChecked() and not self.forceQuit: self.tray_icon.show() event.ignore() self.hide() - t=self.tr("läuft im Hintergrund weiter und zeichnet Messwerte auf") + t = self.tr("läuft im Hintergrund weiter und zeichnet Messwerte auf") self.tray_icon.showMessage( self.tr("RealTime OpenControl"), t, @@ -375,17 +384,17 @@ def closeEvent(self, event):#, *args, **kwargs): ) else: self.savePlotStyles() - if len(self.logger.signals)!=0: + if len(self.logger.signals) >= 0 and self.logger.signalNames != [['RTOC', '']]: ok = pyqtlib.tri_message( - self.tr("Speichern"), self.tr("Wollen Sie die aktuelle Sitzung speichern?"),"") + self.tr("Speichern"), self.tr("Wollen Sie die aktuelle Sitzung speichern?"), "") else: - ok=False - if ok != None: + ok = False + if ok is not None: if ok == True: self.logger.exportJSON("restore") elif ok == False: - if os.path.exists("restore.json"): - os.remove("restore.json") + if os.path.exists(self.config['documentfolder']+"/restore.json"): + os.remove(self.config['documentfolder']+"/restore.json") print('Goodbye') self.run = False self.saveSettings() @@ -400,12 +409,12 @@ def closeEvent(self, event):#, *args, **kwargs): def readSettings(self): self.settings = QtCore.QSettings('user', 'RTOC') - if not self.settings.value("geometry") == None: + if not self.settings.value("geometry") is None: self.restoreGeometry(self.settings.value("geometry", "")) - if not self.settings.value("windowState") == None: + if not self.settings.value("windowState") is None: self.restoreState(self.settings.value("windowState")) - if not self.settings.value("devicesGeometry") == None: + if not self.settings.value("devicesGeometry") is None: self.deviceWidget.resize(self.settings.value("devicesGeometry", "")) def saveSettings(self): @@ -425,34 +434,54 @@ def mousePressEvent(self, event): super(RTOC, self).mousePressEvent(event) -# def splashScreen(app): -# # Create and display the splash screen -# splash_pix = QtGui.QPixmap('data/splash.png') -# splash = QtWidgets.QSplashScreen(splash_pix, QtCore.Qt.WindowStaysOnTopHint) -# splash.setMask(splash_pix.mask()) -# #splash.show() -# #app.processEvents() -# -# splash.setEnabled(False) -# # splash = QSplashScreen(splash_pix) -# # adding progress bar -# progressBar = QtWidgets.QProgressBar(splash) -# progressBar.setStyleSheet("QProgressBar{border: 0px solid grey;border-radius: 20px;text-align: center; background-color: rgba(255, 255, 255, 0)} QProgressBar::chunk {background-color: rgba(31, 31, 31, 0.7);width: 10px;margin: 0px;}") -# progressBar.setMaximum(10) -# progressBar.setGeometry(0, splash_pix.height() -27, splash_pix.width(), 27) -# -# # splash.setMask(splash_pix.mask()) -# -# splash.show() -# #splash.showMessage("

RealTime OpenControl loading ...

\nv1.6", QtCore.Qt.AlignBottom | QtCore.Qt.AlignCenter, QtCore.Qt.black) +class RTOC_TCP(QtWidgets.QMainWindow): + def __init__(self): + super(RTOC_TCP, self).__init__() + packagedir, file = os.path.split(os.path.realpath(__file__)) + self.logger = RTLogger.RTLogger(True) + app_icon = QtGui.QIcon(packagedir+"/data/icon.png") + self.tray_icon = QtWidgets.QSystemTrayIcon(self) + self.tray_icon.setIcon(app_icon) + self.tray_icon.activated.connect(self.systemTrayClickAction) + + quit_action = QtWidgets.QAction("Beenden", self) + quit_action.triggered.connect(self.close) - # for i in range(1, 11): - # progressBar.setValue(i) - # t = time.time() - # while time.time() < t + 0.1: - # app.processEvents() - # - # return splash + tray_menu = QtWidgets.QMenu() + tray_menu.addAction(quit_action) + self.tray_icon.setContextMenu(tray_menu) + t = "TCP-Server gestartet\nLäuft im Hintergrund" + print(t) + self.tray_icon.show() + self.tray_icon.showMessage( + "RealTime OpenControl", + t, + app_icon, + 2000 + ) + + def systemTrayClickAction(self, reason): + if reason == QtWidgets.QSystemTrayIcon.Context: + print('clicked') + + def closeEvent(self, event): + if len(self.logger.signals) != 0: + ok = pyqtlib.tri_message( + self.tr("Speichern"), self.tr("Wollen Sie die aktuelle Sitzung speichern?"), "") + else: + ok = False + if ok is not None: + if ok == True: + self.logger.exportJSON("restore") + elif ok == False: + if os.path.exists(self.config['documentfolder']+"/restore.json"): + os.remove(self.config['documentfolder']+"/restore.json") + print('Goodbye') + # self.logger.save_config() + self.logger.stop() + super(RTOC_TCP, self).closeEvent(event) + else: + event.ignore() def setStyleSheet(app, myapp): @@ -467,18 +496,23 @@ def setStyleSheet(app, myapp): tb = traceback.format_exc() print(tb) print("New Style not installed") - with open("data/ui/darkmode.html", 'r') as myfile: + with open("/data/ui/darkmode.html", 'r') as myfile: stylesheet = myfile.read().replace('\n', '') app.setStyleSheet(stylesheet) return app, myapp def setLanguage(app): - with open("config.json", encoding="UTF-8") as jsonfile: - config = json.load(jsonfile, encoding="UTF-8") + try: + userpath = os.path.expanduser('~/Documents/RTOC') + with open(userpath+"/config.json", encoding="UTF-8") as jsonfile: + config = json.load(jsonfile, encoding="UTF-8") + except: + config={'language':'en'} if config['language'] == 'en': translator = QtCore.QTranslator() - translator.load("lang/en_en.qm") + packagedir, file = os.path.split(os.path.realpath(__file__)) + translator.load(packagedir+"/lang/en_en.qm") app.installTranslator(translator) # more info here: http://kuanyui.github.io/2014/09/03/pyqt-i18n/ # generate translationfile: % pylupdate5 RTOC.py -ts lang/de_de.ts @@ -487,25 +521,87 @@ def setLanguage(app): def main(): + opts, args = getopt.getopt(sys.argv[1:], "hsr:", ["remote="]) + if len(opts) == 0: + startRTOC() + else: + for opt, arg in opts: + if opt == '-h': + print( + 'RTOC.py [-h, -s] [-r ]\n -h: Hilfe\n-s: TCP-Server ohne GUI\n-r : TCP-Client zu RTOC-Server') + sys.exit() + elif opt == '-s': + runInBackground() + sys.exit(0) + elif opt in ("-r", "--remote"): + remotepath = arg + startRemoteRTOC(remotepath) + + +def runInBackground(): app = QtWidgets.QApplication(sys.argv) - with open("config.json", encoding="UTF-8") as jsonfile: - config = json.load(jsonfile, encoding="UTF-8") - if config['language'] == 'en': - print("English language selected") - translator = QtCore.QTranslator() - translator.load("lang/en_en.qm") - app.installTranslator(translator) - # more info here: http://kuanyui.github.io/2014/09/03/pyqt-i18n/ - # generate translationfile: % pylupdate5 RTOC.py -ts lang/de_de.ts - # compile translationfile: % lrelease-qt5 lang/de_de.ts - # use self.tr("TEXT TO TRANSLATE") in the code + myapp = RTOC_TCP() + app, myapp = setStyleSheet(app, myapp) + + app.exec_() + + +def startRemoteRTOC(remotepath): + app = QtWidgets.QApplication(sys.argv) + try: + userpath = os.path.expanduser('~/Documents/RTOC') + with open(userpath+"/config.json", encoding="UTF-8") as jsonfile: + config = json.load(jsonfile, encoding="UTF-8") + except: + config={'language':'en'} + if config['language'] == 'en': + print("English language selected") + translator = QtCore.QTranslator() + packagedir, file = os.path.split(os.path.realpath(__file__)) + translator.load(packagedir+"/lang/en_en.qm") + app.installTranslator(translator) myapp = RTOC() + myapp.config["tcpserver"] = True + + app, myapp = setStyleSheet(app, myapp) + print(remotepath) + myapp.show() + myapp.pluginsWidget.show() + myapp.scriptDockWidget.hide() + myapp.deviceWidget.hide() + myapp.eventWidgets.show() + button = QtWidgets.QPushButton() + button.setCheckable(True) + button.setChecked(True) + myapp.toggleDevice('NetWoRTOC', button) + myapp.logger.getPlugin('NetWoRTOC').start(remotepath) + myapp.logger.getPlugin('NetWoRTOC').widget.comboBox.setCurrentText(remotepath) + myapp.logger.getPlugin('NetWoRTOC').widget.streamButton.setChecked(True) + app.exec_() - # splash = splashScreen(app) + +def startRTOC(): + app = QtWidgets.QApplication(sys.argv) + try: + userpath = os.path.expanduser('~/Documents/RTOC') + with open(userpath+"/config.json", encoding="UTF-8") as jsonfile: + config = json.load(jsonfile, encoding="UTF-8") + except: + config={'language':'en'} + if config['language'] == 'en': + print("English language selected") + translator = QtCore.QTranslator() + packagedir, file = os.path.split(os.path.realpath(__file__)) + translator.load(packagedir+"/lang/en_en.qm") + app.installTranslator(translator) + # more info here: http://kuanyui.github.io/2014/09/03/pyqt-i18n/ + # generate translationfile: % pylupdate5 RTOC.py -ts lang/de_de.ts + # compile translationfile: % lrelease-qt5 lang/de_de.ts + # use self.tr("TEXT TO TRANSLATE") in the code + myapp = RTOC() app, myapp = setStyleSheet(app, myapp) myapp.show() - # splash.finish(myapp) app.exec_() diff --git a/RTOC/__init__.py b/RTOC/__init__.py new file mode 100644 index 0000000..cca099c --- /dev/null +++ b/RTOC/__init__.py @@ -0,0 +1,4 @@ +from .data import * + +name = "RTOC" +__version__ = "1.7" diff --git a/RTOC/__main__.py b/RTOC/__main__.py new file mode 100644 index 0000000..c88a644 --- /dev/null +++ b/RTOC/__main__.py @@ -0,0 +1,136 @@ +import sys +import os +from PyQt5 import QtCore +from PyQt5 import QtWidgets +import json +import getopt +import traceback + +try: + from .RTOC import RTOC, RTOC_TCP +except: + from RTOC import RTOC, RTOC_TCP + + +def main(): + opts, args = getopt.getopt(sys.argv[1:], "hsr:", ["remote="]) + if len(opts) == 0: + startRTOC() + else: + for opt, arg in opts: + if opt == '-h': + print( + 'RTOC.py [-h, -s] [-r ]\n -h: Hilfe\n-s: TCP-Server ohne GUI\n-r : TCP-Client zu RTOC-Server') + sys.exit() + elif opt == '-s': + runInBackground() + sys.exit(0) + elif opt in ("-r", "--remote"): + remotepath = arg + startRemoteRTOC(remotepath) + +def setStyleSheet(app, myapp): + try: + import qtmodern.styles + import qtmodern.windows + qtmodern.styles.dark(app) + #mw = qtmodern.windows.ModernWindow(myapp) + mw = myapp + return app, mw + except ImportError: + tb = traceback.format_exc() + print(tb) + print("New Style not installed") + with open("/data/ui/darkmode.html", 'r') as myfile: + stylesheet = myfile.read().replace('\n', '') + app.setStyleSheet(stylesheet) + return app, myapp + + +def setLanguage(app): + try: + userpath = os.path.expanduser('~/Documents/RTOC') + with open(userpath+"/config.json", encoding="UTF-8") as jsonfile: + config = json.load(jsonfile, encoding="UTF-8") + except: + config={'language':'en'} + if config['language'] == 'en': + translator = QtCore.QTranslator() + packagedir, file = os.path.split(os.path.realpath(__file__)) + translator.load(packagedir+"/lang/en_en.qm") + app.installTranslator(translator) + # more info here: http://kuanyui.github.io/2014/09/03/pyqt-i18n/ + # generate translationfile: % pylupdate5 RTOC.py -ts lang/de_de.ts + # compile translationfile: % lrelease-qt5 lang/de_de.ts + # use self.tr("TEXT TO TRANSLATE") in the code + +def runInBackground(): + app = QtWidgets.QApplication(sys.argv) + myapp = RTOC_TCP() + app, myapp = setStyleSheet(app, myapp) + + app.exec_() + + +def startRemoteRTOC(remotepath): + app = QtWidgets.QApplication(sys.argv) + try: + userpath = os.path.expanduser('~/Documents/RTOC') + with open(userpath+"/config.json", encoding="UTF-8") as jsonfile: + config = json.load(jsonfile, encoding="UTF-8") + except: + config={'language':'en'} + if config['language'] == 'en': + print("English language selected") + translator = QtCore.QTranslator() + packagedir, file = os.path.split(os.path.realpath(__file__)) + translator.load(packagedir+"/lang/en_en.qm") + app.installTranslator(translator) + myapp = RTOC() + myapp.config["tcpserver"] = True + + app, myapp = setStyleSheet(app, myapp) + print(remotepath) + myapp.show() + myapp.pluginsWidget.show() + myapp.scriptDockWidget.hide() + myapp.deviceWidget.hide() + myapp.eventWidgets.show() + button = QtWidgets.QPushButton() + button.setCheckable(True) + button.setChecked(True) + myapp.toggleDevice('NetWoRTOC', button) + myapp.logger.getPlugin('NetWoRTOC').start(remotepath) + myapp.logger.getPlugin('NetWoRTOC').widget.comboBox.setCurrentText(remotepath) + myapp.logger.getPlugin('NetWoRTOC').widget.streamButton.setChecked(True) + app.exec_() + + +def startRTOC(): + app = QtWidgets.QApplication(sys.argv) + try: + userpath = os.path.expanduser('~/Documents/RTOC') + with open(userpath+"/config.json", encoding="UTF-8") as jsonfile: + config = json.load(jsonfile, encoding="UTF-8") + except: + config={'language':'en'} + if config['language'] == 'en': + print("English language selected") + translator = QtCore.QTranslator() + packagedir, file = os.path.split(os.path.realpath(__file__)) + translator.load(packagedir+"/lang/en_en.qm") + app.installTranslator(translator) + # more info here: http://kuanyui.github.io/2014/09/03/pyqt-i18n/ + # generate translationfile: % pylupdate5 RTOC.py -ts lang/de_de.ts + # compile translationfile: % lrelease-qt5 lang/de_de.ts + # use self.tr("TEXT TO TRANSLATE") in the code + myapp = RTOC() + app, myapp = setStyleSheet(app, myapp) + + myapp.show() + app.exec_() + + +if __name__ == '__main__': + main() + sys.exit() diff --git a/data/Actions.py b/RTOC/data/Actions.py similarity index 80% rename from data/Actions.py rename to RTOC/data/Actions.py index 7f56e7c..a8e32d1 100644 --- a/data/Actions.py +++ b/RTOC/data/Actions.py @@ -3,7 +3,8 @@ from PyQt5 import QtWidgets from functools import partial -import data.lib.pyqt_customlib as pyqtlib +from .lib import pyqt_customlib as pyqtlib + class Actions: def connectButtons(self): @@ -17,6 +18,8 @@ def connectButtons(self): self.importDataAction.triggered.connect(self.importDataTriggered) self.exportDataAction.triggered.connect(self.exportDataTriggered) self.TCPServerAction.triggered.connect(self.toggleTcpServer) + self.actionTelegramBot.triggered.connect(self.toggleTelegramBot) + self.actionBotToken.triggered.connect(self.setBotToken) self.systemTrayAction.triggered.connect(self.toggleSystemTray) @@ -26,47 +29,50 @@ def connectButtons(self): self.scriptWidgetToggle.triggered.connect(self.toggleScriptWidget) self.eventWidgetToggle.triggered.connect(self.toggleEventWidget) - self.actionDeutsch.triggered.connect(partial(self.toggleLanguage, "de", self.actionDeutsch, [self.actionEnglish], False)) - self.actionEnglish.triggered.connect(partial(self.toggleLanguage, "en", self.actionEnglish, [self.actionDeutsch], False)) - if self.config['language']=='de': + self.actionDeutsch.triggered.connect( + partial(self.toggleLanguage, "de", self.actionDeutsch, [self.actionEnglish], False)) + self.actionEnglish.triggered.connect( + partial(self.toggleLanguage, "en", self.actionEnglish, [self.actionDeutsch], False)) + if self.config['language'] == 'de': self.toggleLanguage("de", self.actionDeutsch, [self.actionEnglish], True) else: - self.toggleLanguage( "en", self.actionEnglish, [self.actionDeutsch], True) + self.toggleLanguage("en", self.actionEnglish, [self.actionDeutsch], True) self.helpAction.triggered.connect(self.showHelpWebsite) self.aboutAction.triggered.connect(self.showAboutMessage) self.searchEdit.textChanged.connect(self.filterDevices) - # def toggleTcpServer(self): - # self.logger.toggleTcpServer(self.TCPServerAction.isChecked()) - def toggleTcpServer(self): if self.config["tcpserver"]: self.config["tcpserver"] = False - #self.logger.toggleTcpServer(False) - #self.TCPServerAction.setChecked(False) else: self.config["tcpserver"] = True self.logger.toggleTcpServer(self.config["tcpserver"]) self.TCPServerAction.setChecked(self.config["tcpserver"]) + def toggleTelegramBot(self): + if self.config["telegram_bot"]: + self.config["telegram_bot"] = False + else: + self.config["telegram_bot"] = True + self.logger.toggleTelegramBot(self.config["telegram_bot"]) + self.actionTelegramBot.setChecked(self.config["telegram_bot"]) + + def setBotToken(self): + ans, ok = pyqtlib.text_message( + self, "Bot Token eingeben", 'Bitte erzeugen sie in Telegram mit "Botfather" einen Bot,\n generiere einen Bot und füge dessen Token hier ein', self.config['telegram_token']) + if ok: + self.logger.telegramBot.setToken(ans) + self.actionBotToken.setText(self.config['telegram_token']) + def toggleSystemTray(self): if self.config["systemTray"]: self.config["systemTray"] = False - #self.systemTrayAction.setChecked(True) else: self.config["systemTray"] = True self.systemTrayAction.setChecked(self.config["systemTray"]) - # def trayToggleSystemTray(self): - # if self.config["systemTray"]: - # self.config["systemTray"] = False - # self.systemTrayAction.setChecked(False) - # else: - # self.config["systemTray"] = True - # self.systemTrayAction.setChecked(True) - def systemTrayClickAction(self, reason): if reason == QtWidgets.QSystemTrayIcon.Context: self.tcp_action.setChecked(self.config['tcpserver']) @@ -74,12 +80,6 @@ def systemTrayClickAction(self, reason): elif reason == QtWidgets.QSystemTrayIcon.DoubleClick: self.tray_icon.hide() self.show() - # elif reason == QtWidgets.QSystemTrayIcon.Trigger: - # print("triggered") - # elif reason == QtWidgets.QSystemTrayIcon.MiddleClick: - # print("middleclick") - # else: - # print("unknown reason: "+str(reason)) def resizeLogger(self): newLength = self.maxLengthSpinBox.value() @@ -92,7 +92,7 @@ def clearDataAction(self): self.logger.clear() def loadSessionTriggered(self): - dir_path = os.path.dirname(os.path.realpath(__file__)) + dir_path = self.config['documentfolder'] fileBrowser = QtWidgets.QFileDialog(self) fileBrowser.setDirectory(dir_path) fileBrowser.setNameFilters(["Json (*.json)"]) @@ -106,7 +106,7 @@ def loadSessionTriggered(self): self.loadSession(fileName) def saveSessionTriggered(self): - dir_path = os.path.dirname(os.path.realpath(__file__)) + dir_path = self.config['documentfolder'] fileBrowser = QtWidgets.QFileDialog(self) fileBrowser.setDirectory(dir_path) fileBrowser.setNameFilters( @@ -114,7 +114,6 @@ def saveSessionTriggered(self): fileBrowser.selectNameFilter("") fname, mask = fileBrowser.getSaveFileName( self, self.tr("Session speichern"), dir_path, "JSON-Datei (*.json)") - # if fileBrowser.exec_(): if fname: fileName = fname if mask == 'JSON-Datei (*.json)': @@ -132,7 +131,7 @@ def importData(self, filename): i) for i in data[idx+1]], "data"+str(int(idx/2)), filename.split(".")[0].split("/")[-1], "") def exportDataTriggered(self): - dir_path = os.path.dirname(os.path.realpath(__file__)) + dir_path = self.config['documentfolder'] fileBrowser = QtWidgets.QFileDialog(self) fileBrowser.setDirectory(dir_path) fileBrowser.setNameFilters( @@ -187,21 +186,21 @@ def toggleScriptWidget(self): self.config["scriptWidget"] = False def importDataTriggered(self): - dir_path = os.path.dirname(os.path.realpath(__file__)) + dir_path = self.config['documentfolder'] fileBrowser = QtWidgets.QFileDialog(self) fileBrowser.setDirectory(dir_path) fileBrowser.setNameFilters(["CSV (*.csv)"]) fileBrowser.selectNameFilter("") fname, mask = fileBrowser.getOpenFileName( self, "Export", dir_path, "CSV (*.csv)") - # if fileBrowser.exec_(): if fname: fileName = fname if mask == "CSV (*.csv)": self.importData(fileName) def showAboutMessage(self): - pyqtlib.info_message(self.tr("Über"), "RealTime OpenControl 1.6", self.tr("RealTime OpenControl (RTOC) ist eine freie OpenSource Software unter der BSD-3-Lizenz.\n\nAlle Symbole werden unter der 'Creative Commons Attribution-NoDerivs 3.0 Unported' Lizenz bereitgestellt von icons8 (https://icons8.de)\n\nCopyright (C) 2018 Sebastian Keller")) + pyqtlib.info_message(self.tr("Über"), "RealTime OpenControl 1.7", self.tr( + "RealTime OpenControl (RTOC) ist eine freie OpenSource Software unter der BSD-3-Lizenz.\n\nAlle Symbole werden unter der 'Creative Commons Attribution-NoDerivs 3.0 Unported' Lizenz bereitgestellt von icons8 (https://icons8.de)\n\nCopyright (C) 2018 Sebastian Keller")) def showHelpWebsite(self): url = "https://git.kellerbase.de/haschtl/kellerlogger/wikis/RealTime-OpenControl-(RTOC)" @@ -216,11 +215,12 @@ def filterDevices(self, text): else: child.hide() - def toggleLanguage(self, newlang, button, otherbuttons, force = False): + def toggleLanguage(self, newlang, button, otherbuttons, force=False): button.setChecked(True) if newlang != self.config["language"] or force == True: for b in otherbuttons: b.setChecked(False) self.config["language"] = newlang if force == False: - pyqtlib.info_message(self.tr("Sprache geändert"), self.tr("Bitte Programm neustarten"), "") + pyqtlib.info_message(self.tr("Sprache geändert"), + self.tr("Bitte Programm neustarten"), "") diff --git a/data/RTPlotActions.py b/RTOC/data/RTPlotActions.py similarity index 92% rename from data/RTPlotActions.py rename to RTOC/data/RTPlotActions.py index 4a3f70f..da0fa32 100644 --- a/data/RTPlotActions.py +++ b/RTOC/data/RTPlotActions.py @@ -1,12 +1,14 @@ import pyqtgraph as pg -# import pyqtgraph.exporters from PyQt5 import QtCore from PyQt5 import QtGui from PyQt5 import QtWidgets from PyQt5 import uic import time -import data.lib.pyqt_customlib as pyqtlib -from data.styleMultiPlotGUI import plotMultiStyler +from .lib import pyqt_customlib as pyqtlib +from .styleMultiPlotGUI import plotMultiStyler +import os + +packagedir, file = os.path.split(os.path.realpath(__file__)) class RTPlotActions: @@ -28,12 +30,13 @@ def initPlotWidget(self): self.legend.anchor((1, 0), (1, 0), (-10, 10)) self.plot.showGrid(x=self.grid[0], y=self.grid[1], alpha=self.grid[2]) - self.plotMouseLabel = pg.TextItem("", color=(200, 200, 200), fill=(200, 200, 200, 50), html=None) # , + self.plotMouseLabel = pg.TextItem("", color=( + 200, 200, 200), fill=(200, 200, 200, 50), html=None) # , self.plot.addItem(self.plotMouseLabel) def initPlotViewWidget(self): self.plotViewWidget = QtWidgets.QWidget() - uic.loadUi("data/ui/plotViewWidget.ui", self.plotViewWidget) + uic.loadUi(packagedir+"/ui/plotViewWidget.ui", self.plotViewWidget) self.plotViewButton.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) self.plotViewButton.setMenu(QtWidgets.QMenu(self.plotViewButton)) action = QtWidgets.QWidgetAction(self.plotViewButton) @@ -56,7 +59,7 @@ def initPlotViewWidget(self): self.plotViewWidget.timeAxisButton.clicked.connect(self.toggleAxisStyle) self.gridViewWidget = QtWidgets.QWidget() - uic.loadUi("data/ui/gridViewWidget.ui", self.gridViewWidget) + uic.loadUi(packagedir+"/ui/gridViewWidget.ui", self.gridViewWidget) self.plotViewWidget.gridButton.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) self.plotViewWidget.gridButton.setMenu(QtWidgets.QMenu(self.plotViewWidget.gridButton)) action = QtWidgets.QWidgetAction(self.plotViewWidget.gridButton) @@ -64,7 +67,7 @@ def initPlotViewWidget(self): self.plotViewWidget.gridButton.setStyleSheet( "QToolButton:checked, QToolButton:pressed,QToolButton::menu-button:pressed { background-color: #76797C;border: 1px solid #76797C;padding: 5px;}") self.plotViewWidget.gridButton.menu().addAction(action) - #self.plotViewWidget.gridButton.clicked.connect(lambda: self.plotViewWidget.gridButton.menu().popup( + # self.plotViewWidget.gridButton.clicked.connect(lambda: self.plotViewWidget.gridButton.menu().popup( # self.plotViewWidget.gridButton.mapToGlobal(QtCore.QPoint(0, 35)))) self.gridViewWidget.xCheckbox.clicked.connect(self.gridXAction) @@ -88,7 +91,7 @@ def gridAlphaAction(self): def updateGrid(self): self.plot.showGrid(x=self.grid[0], y=self.grid[1], alpha=self.grid[2]) - self.config["grid"]= self.grid + self.config["grid"] = self.grid def toggleInverted(self): if self.plotViewWidget.invertPlotButton.isChecked(): @@ -134,7 +137,7 @@ def toggleAxisStyle(self): def initPlotToolsWidget(self): self.plotToolsWidget = QtWidgets.QWidget() - uic.loadUi("data/ui/plotToolsWidget.ui", self.plotToolsWidget) + uic.loadUi(packagedir+"/ui/plotToolsWidget.ui", self.plotToolsWidget) sizePolicy = QtGui.QSizePolicy( QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) @@ -178,7 +181,8 @@ def initCrosshair(self): self.vLine = pg.InfiniteLine(angle=90, movable=False) self.hLine = pg.InfiniteLine(angle=0, movable=False) #self.CrossHairLabel = pg.LabelItem(justify='right') - self.CrossHairLabel = pg.TextItem("", color=(200, 200, 200), fill=(200, 200, 200, 50), html=None) # , + self.CrossHairLabel = pg.TextItem("", color=( + 200, 200, 200), fill=(200, 200, 200, 50), html=None) # , self.plot.addItem(self.vLine, ignoreBounds=True) self.plot.addItem(self.hLine, ignoreBounds=True) self.plot.addItem(self.CrossHairLabel) @@ -198,7 +202,7 @@ def initMeasureTool(self): self.measureWidget = QtWidgets.QDialog() self.measureWidget.setWindowFlags( QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint) - uic.loadUi("data/ui/messtoolDialog.ui", self.measureWidget) + uic.loadUi(packagedir+"/ui/messtoolDialog.ui", self.measureWidget) self.measureWidget.setStyleSheet(self.styleSheet().replace( "background-color: rgba(49, 54, 59, .9)", "background-color: rgba(34, 36, 38, 0.9)")) self.measureWidget.mousePressEvent = self.mousePressEvents @@ -324,7 +328,6 @@ def stylePlots(self): def mousePressEvents(self, event): self.measureWidget.oldPos = event.globalPos() - def mouseMoveEvents(self, event): delta = QtCore.QPoint(event.globalPos() - self.measureWidget.oldPos) self.measureWidget.move(self.measureWidget.x() + delta.x(), @@ -345,13 +348,13 @@ def mouseMoved(self, evt): self.hLine.setPos(mousePoint.y()) def filterDevices(self, tex): - tex=tex+';' - tex = tex.replace('; ',';') + tex = tex+';' + tex = tex.replace('; ', ';') for item in self.signalTreeWidgetItems: - sig = self.treeWidget.itemWidget(item,0) + sig = self.treeWidget.itemWidget(item, 0) found = False for text in tex.split(';'): - #print(text) + # print(text) if (text != "" or tex == ';') and found == False: if text.lower() in sig.text().lower() or tex == ";": item.setHidden(False) @@ -362,13 +365,3 @@ def filterDevices(self, tex): item.setHidden(True) sig.hidden = True sig.toggleSignal() - - # for sig in self.signalObjects: - # if text.lower() in sig.text().lower() or text == "": - # sig.show() - # sig.label.show() - # else: - # sig.hide() - # sig.label.hide() - # sig.resize(sig.sizeHint().width(), sig.minimumHeight()) - # sig.label.resize(sig.label.sizeHint().width(), sig.label.minimumHeight()) diff --git a/data/RTPlotWidget.py b/RTOC/data/RTPlotWidget.py similarity index 74% rename from data/RTPlotWidget.py rename to RTOC/data/RTPlotWidget.py index e3d080d..8612aea 100644 --- a/data/RTPlotWidget.py +++ b/RTOC/data/RTPlotWidget.py @@ -4,23 +4,25 @@ import time from functools import partial -from data.RTPlotActions import RTPlotActions -from data.signalWidget import SignalWidget -import data.define as define +from .RTPlotActions import RTPlotActions +from .signalWidget import SignalWidget +from . import define as define +import os class RTPlotWidget(QtWidgets.QWidget, RTPlotActions): droppedTree = QtCore.pyqtSignal(dict) addSignal2 = QtCore.pyqtSignal(str, str, int, str) + def __init__(self, logger, selfself, id): super(RTPlotWidget, self).__init__() - #print("creating new plot widget") - uic.loadUi("data/ui/plotWidget.ui", self) + packagedir, file = os.path.split(os.path.realpath(__file__)) + uic.loadUi(packagedir+"/ui/plotWidget.ui", self) self.setAcceptDrops(True) self.setObjectName("RTPlotWidget"+str(id)) self.self = selfself self.id = id self.logger = logger - self.config= self.self.config + self.config = self.self.config self.plotStyles = self.self.plotStyles self.active = True self.lastActive = time.time() @@ -35,10 +37,10 @@ def __init__(self, logger, selfself, id): self.updatePlotTimer.start(int(1/self.updatePlotSamplerate*1000)) self.xTimeBase = True self.globalXOffset = 0 - self.grid=self.config["grid"] + self.grid = self.config["grid"] self.mouseX = 0 self.mouseY = 0 - self.lastSignalClick = [0,0] + self.lastSignalClick = [0, 0] self.addSignal2.connect(self.addSignal, QtCore.Qt.QueuedConnection) self.signalHeight = define.signalButtonHeight @@ -79,17 +81,14 @@ def __init__(self, logger, selfself, id): self.togglePlotGrid() def clear(self): - # for p in self.signalObjects: while len(self.signalObjects) > 0: p = self.signalObjects[0] - # self.plot.getPlotItem().removeItem(p.plot) p.remove() self.signalNames = [] self.signalObjects = [] def updatePlot(self): - # if self.active: if self.self.isVisible(): for signal in self.signalObjects: signal.updatePlot() @@ -114,26 +113,18 @@ def getSignalIdx(self, id): def removeSignal(self, id, devicename, signalname): idx = self.getSignalIdx(id) popped = self.signalObjects.pop(idx) - #self.removeSignal(idx, devicename, signalname) self.treeWidget.removeItemWidget(self.signalTreeWidgetItems[idx], 0) self.treeWidget.removeItemWidget(self.signalTreeWidgetItems[idx], 1) self.deviceTreeWidgetItems[self.devices.index( devicename)].removeChild(self.signalTreeWidgetItems[idx]) if self.deviceTreeWidgetItems[self.devices.index(devicename)].childCount() == 0: - #print("Deleting Devicegroup") self.treeWidget.removeItemWidget( self.deviceTreeWidgetItems[self.devices.index(devicename)], 0) self.treeWidget.takeTopLevelItem(self.devices.index(devicename)) self.deviceTreeWidgetItems.pop(self.devices.index(devicename)) self.devices.pop(self.devices.index(devicename)) self.signalTreeWidgetItems.pop(idx) - - #for idx, sig in enumerate(self.signalObjects): - # sig.id = idx - # self.signalListWidget.clear() - # self.triggerWidget.triggerSignals.clear() - # self.updateListWidget() self.countLabel.setText(self.tr("Signale: ")+str(len(self.signalObjects))) def addSignalRAW(self, signalObject): @@ -145,27 +136,19 @@ def addSignalRAW(self, signalObject): self.signalObjects[idx].labelItem.hide() devicename = signalObject.devicename - #self.legend.addItem(self.signalObjects[idx].plot,signalname+" ["+unit+"]") - # self.signalListWidget.addItem(devicename+"."+signalname) - # self.triggerWidget.triggerSignals.addItem(devicename+"."+signalname) - # self.triggerSignals.findItems(devicename+"."+signalname).setSelected(True) self.signalTreeWidgetItems.append(QtWidgets.QTreeWidgetItem()) self.signalObjects[idx].setMinimumHeight(self.signalHeight) self.signalObjects[idx].setMaximumHeight(self.signalHeight) - # item.setSizeHint(sizeHint) if devicename not in self.devices: self.devices.append(devicename) treeWidgetItem = QtWidgets.QTreeWidgetItem() - #treeWidgetItem.setText(0, devicename) button = QtWidgets.QPushButton(devicename) button.setCheckable(True) button.setChecked(True) self.deviceTreeWidgetItems.append(treeWidgetItem) self.treeWidget.addTopLevelItem(treeWidgetItem) - self.treeWidget.setItemWidget(treeWidgetItem,0, button) + self.treeWidget.setItemWidget(treeWidgetItem, 0, button) button.clicked.connect(partial(self.toggleDevice, button, treeWidgetItem)) - #self.treeWidget.setItemWidget(treeWidgetItem, 0, item2) - #child = QtWidgets.QTreeWidgetItem() self.signalTreeWidgetItems[idx].setMinimumHeight(0) self.deviceTreeWidgetItems[self.devices.index( devicename)].addChild(self.signalTreeWidgetItems[idx]) @@ -173,13 +156,11 @@ def addSignalRAW(self, signalObject): self.treeWidget.setItemWidget( self.signalTreeWidgetItems[idx], 1, self.signalObjects[idx].label) self.deviceTreeWidgetItems[self.devices.index(devicename)].setExpanded(True) - # self.treeWidget.setItemWidget(item, 0, item2)# self.signalObjects[idx]) - # self.listWidget.addWidget(self.signalObjects[idx]) self.countLabel.setText(self.tr("Signale: ")+str(len(self.signalObjects))) def signalClickedAction(self, devicename, signalname): r = 0.001 - if abs(self.mouseX-self.lastSignalClick[0])= 2 and len(x)==len(y): + if len(y) >= 2 and len(x) == len(y): dx = np.diff(x) dy = np.diff(y) - return x[:-1], np.divide(dy,dx) + return x[:-1], np.divide(dy, dx) else: - return [0],[0] + return [0], [0] + def norm(x, y, *args, **kwargs): normGain = kwargs.get('max', 1) diff --git a/data/scriptSubWidget.py b/RTOC/data/scriptSubWidget.py similarity index 85% rename from data/scriptSubWidget.py rename to RTOC/data/scriptSubWidget.py index 398af55..e0c0965 100644 --- a/data/scriptSubWidget.py +++ b/RTOC/data/scriptSubWidget.py @@ -5,9 +5,9 @@ import time import traceback from threading import Thread -from data.importCode import importCode +from .importCode import importCode import os -import data.lib.pyqt_customlib as pyqtlib +from .lib import pyqt_customlib as pyqtlib class AnimationThread(QtCore.QThread): @@ -18,7 +18,6 @@ def __init__(self): QtCore.QThread.__init__(self) self.thread_must_be_run = True self.mutex = QtCore.QMutex() - # print " thread init done " def stop_this_thread(self): self.thread_must_be_run = False @@ -27,30 +26,19 @@ def error(self): self.errorRequest.emit() def run(self): - # while self.thread_must_be_run == True : - self.mutex.lock() # locking may not be necessary - - # Here we emit a signal whenever we want to update window - # contents. The other thread will handle this signal - # so that the method process_blinkRequest() will - # be called. - - # self.emit( SIGNAL( "blinkRequest" ) ) + self.mutex.lock() self.blinkRequest.emit(True) self.mutex.unlock() time.sleep(0.05) self.blinkRequest.emit(False) - # sleep( 1.000 ) # Delay of one second. - # QThread.sleep( 1 ) # An alternative way to sleep. - - # print " run method ends " class Callback(QtCore.QThread): received = QtCore.pyqtSignal(bool, str) + def __init__(self, strung=""): super(Callback, self).__init__() self.ok = True - self.text="" + self.text = "" def setText(self, ok, strung): self.ok = ok @@ -58,13 +46,14 @@ def setText(self, ok, strung): self.run() def run(self): - # print(received) self.received.emit(self.ok, self.text) + class ScriptSubWidget(QtWidgets.QWidget): def __init__(self, logger, scriptstr, filepath=""): super(ScriptSubWidget, self).__init__() - uic.loadUi("data/ui/scriptSubWidget.ui", self) + packagedir, file = os.path.split(os.path.realpath(__file__)) + uic.loadUi(packagedir+"/ui/scriptSubWidget.ui", self) self.infoEdit.hide() @@ -94,7 +83,7 @@ def __init__(self, logger, scriptstr, filepath=""): self.scriptEdit.modificationChanged.connect(self.scriptChangedAction) - self.animation_thread = AnimationThread() + self.animation_thread = AnimationThread() self.animation_thread.blinkRequest.connect(self.blinkButton) self.animation_thread.errorRequest.connect(self.errorButton) @@ -119,7 +108,8 @@ def initTrigger(self): action = QtWidgets.QWidgetAction(self.startScriptButton) self.triggerWidget = QtWidgets.QWidget() - uic.loadUi("data/ui/triggerWidget.ui", self.triggerWidget) + packagedir, file = os.path.split(os.path.realpath(__file__)) + uic.loadUi(packagedir+"/ui/triggerWidget.ui", self.triggerWidget) action.setDefaultWidget(self.triggerWidget) self.startScriptButton.menu().addAction(action) self.startScriptButton.clicked.connect(self.toggleScript) @@ -128,7 +118,8 @@ def initTrigger(self): self.triggerWidget.sampleRadio.toggled.connect(self.enableSampleRateTrigger) self.triggerWidget.signalRadio.toggled.connect(self.enableSignalTrigger) - self.startScriptButton.setStyleSheet("QToolButton:checked, QToolButton:pressed,QToolButton::menu-button:pressed {background-color: #31363b;}") + self.startScriptButton.setStyleSheet( + "QToolButton:checked, QToolButton:pressed,QToolButton::menu-button:pressed {background-color: #31363b;}") self.triggerWidget.triggerSignals.clear() for element in self.logger.signalNames: @@ -162,7 +153,6 @@ def saveScript(self): fileBrowser.selectNameFilter("") self.filepath, mask = fileBrowser.getSaveFileName( self, self.tr("Skript speichern unter"), self.filepath, "Python (*.py)") - # if fileBrowser.exec_(): if self.filepath: fileName = self.filepath text = self.scriptEdit.toPlainText() @@ -170,7 +160,7 @@ def saveScript(self): text = file.write(text) self.scriptEdit.document().setModified(False) self.saved = True - self.logger.config["lastScript"]=fileName + self.logger.config["lastScript"] = fileName if self.modifiedCallback: head, tail = os.path.split(fileName) self.modifiedCallback(False, tail) @@ -193,10 +183,8 @@ def initScript(self): except: return False, scriptStr + errors + "\nERROR in Initialization" else: - #self.scriptStr = "" - #self.scriptStr = scriptStr self.scriptEnabled = False - errors = scriptStr + errors+ "\nERROR in Code-Import" + errors = scriptStr + errors + "\nERROR in Code-Import" return False, errors def checkScript(self, scriptStr): @@ -205,7 +193,7 @@ def checkScript(self, scriptStr): return True, "" except: tb = traceback.format_exc() - tb = tb+"\nSYNTAX ERROR in script" + tb = tb+"\nSYNTAX ERROR in script" return False, tb def singleRunScript(self): @@ -238,7 +226,6 @@ def enableSignalTrigger(self): def updateTriggerSamplerate(self): self.scriptSamplerate = self.triggerWidget.triggerSamplerateSpinBox.value() - #self.config["defaultScriptSampleTime"] = self.logger.scriptSamplerate # text cursor functions def getScriptEditCursor(self): @@ -272,7 +259,7 @@ def blinkButton(self, value): self.startScriptButton.setStyleSheet("QToolButton{background-color: #31363b}") def errorButton(self): - if self.startScriptButton.isChecked() and self.startScriptButton.styleSheet != "QToolButton{background-color: #8c2020}": + if self.startScriptButton.isChecked() and self.startScriptButton.styleSheet != "QToolButton{background-color: #8c2020}": self.startScriptButton.setStyleSheet("QToolButton{background-color: #8c2020}") def handleScript(self, devicename, dataname): @@ -296,7 +283,8 @@ def executeScript(self): except: tb = traceback.format_exc() self.checkScript(self.logger.generateCode(self.scriptStr)) - scriptConverted = "Script converted:\n\n"+self.logger.generateCode(self.scriptStr)+"\n\n" + scriptConverted = "Script converted:\n\n" + \ + self.logger.generateCode(self.scriptStr)+"\n\n" self.executedCallback(False, scriptConverted + tb) def samplerateTriggeredThread(self): @@ -315,13 +303,14 @@ def samplerateTriggeredThread(self): else: time.sleep(1) - def closeEvent(self, event):#, *args, **kwargs): + def closeEvent(self, event): if self.saved: self.run = False super(ScriptSubWidget, self).closeEvent(event) else: - ok = pyqtlib.alert_message(self.tr("Warnung"),self.tr("Wollen sie die Datei speichern?"), "", "",self.tr("Ja"), self.tr("Nein")) - if ok != None: + ok = pyqtlib.alert_message(self.tr("Warnung"), self.tr( + "Wollen sie die Datei speichern?"), "", "", self.tr("Ja"), self.tr("Nein")) + if ok is not None: if ok == True: self.saveScript() self.run = False diff --git a/data/scriptWidget.py b/RTOC/data/scriptWidget.py similarity index 78% rename from data/scriptWidget.py rename to RTOC/data/scriptWidget.py index 340ccef..fd48aa4 100644 --- a/data/scriptWidget.py +++ b/RTOC/data/scriptWidget.py @@ -1,17 +1,17 @@ import os -from PyQt5 import QtGui from PyQt5 import QtWidgets from PyQt5 import uic from PyQt5 import QtCore -import time -from data.scriptHelpWidget import ScriptHelpWidget -from data.scriptSubWidget import ScriptSubWidget +from .scriptHelpWidget import ScriptHelpWidget +from .scriptSubWidget import ScriptSubWidget + class ScriptWidget(QtWidgets.QWidget): def __init__(self, logger): super(ScriptWidget, self).__init__() - uic.loadUi("data/ui/scriptWidget.ui", self) + packagedir, file = os.path.split(os.path.realpath(__file__)) + uic.loadUi(packagedir+"/ui/scriptWidget.ui", self) self.logger = logger self.scripts = [] @@ -31,15 +31,17 @@ def openFile(self, fileName): with open(fileName, "r") as file: text = file.read() head, tail = os.path.split(fileName) - self.openScript(text,tail, fileName) - self.logger.config["lastScript"]=fileName + self.openScript(text, tail, fileName) + self.logger.config["lastScript"] = fileName except: - self.openScript(self.tr("Fehler beim laden der Datei ")+fileName,self.tr("Fehler"), "") - self.logger.config["lastScript"]="" + self.openScript(self.tr("Fehler beim laden der Datei ") + + fileName, self.tr("Fehler"), "") + self.logger.config["lastScript"] = "" else: - self.openScript(self.tr("Datei ")+fileName+ self.tr(" nicht gefunden"),self.tr("Fehler"), "") + self.openScript(self.tr("Datei ")+fileName + + self.tr(" nicht gefunden"), self.tr("Fehler"), "") - def openScript(self, scriptstr = "", name = "", filepath = ""): + def openScript(self, scriptstr="", name="", filepath=""): self.scripts.append(ScriptSubWidget(self.logger, scriptstr, filepath)) self.scripts[-1].modifiedCallback = self.scriptModifiedCallback self.tabWidget.insertTab(len(self.scripts)-1, self.scripts[len(self.scripts)-1], name) @@ -48,13 +50,13 @@ def openScript(self, scriptstr = "", name = "", filepath = ""): def scriptModifiedCallback(self, edited, newtext=None): if edited: text = self.tabWidget.tabText(self.activeScript)+"*" - self.tabWidget.setTabText(self.activeScript,text) + self.tabWidget.setTabText(self.activeScript, text) else: - if newtext!=None: - text=newtext + if newtext != None: + text = newtext else: text = self.tabWidget.tabText(self.activeScript)[:-1] - self.tabWidget.setTabText(self.activeScript,text) + self.tabWidget.setTabText(self.activeScript, text) def closeScript(self, idx): if idx < len(self.scripts): @@ -62,7 +64,7 @@ def closeScript(self, idx): if self.scripts[idx].run == False: self.tabWidget.removeTab(idx) self.scripts.pop(idx) - if len(self.scripts)==0: + if len(self.scripts) == 0: self.openScript("", self.tr("Unbenannt")) def changeActiveScript(self): @@ -77,7 +79,8 @@ def saveScriptAction(self): self.scripts[self.activeScript].saveScript() def loadScriptAction(self): - dir_path = os.path.dirname(os.path.realpath(__file__)) + dir_path = self.config['documentfolder'] + dir_path = self.config['documentfolder'] fileBrowser = QtWidgets.QFileDialog(self) fileBrowser.setDirectory(dir_path) fileBrowser.setNameFilters(["Python (*.py)"]) @@ -92,8 +95,8 @@ def loadScriptAction(self): with open(fileName, "r") as file: text = file.read() head, tail = os.path.split(fileName) - self.openScript(text,tail, fileName) - self.logger.config["lastScript"]=fileName + self.openScript(text, tail, fileName) + self.logger.config["lastScript"] = fileName def toggleHelpAction(self): if self.helpButton.isChecked(): @@ -132,7 +135,7 @@ def clear(self): def closeEvent(self, event): for script in self.scripts: - #script.run=False + # script.run=False script.close() if script.run == True: - break; + break diff --git a/data/signalEditWidget.py b/RTOC/data/signalEditWidget.py similarity index 93% rename from data/signalEditWidget.py rename to RTOC/data/signalEditWidget.py index 070e717..51ee08f 100644 --- a/data/signalEditWidget.py +++ b/RTOC/data/signalEditWidget.py @@ -3,17 +3,17 @@ import time import os from collections import deque -import pyqtgraph as pg import numpy as np -import data.lib.pyqt_customlib as pyqtlib -from data.stylePlotGUI import plotStyler +from .lib import pyqt_customlib as pyqtlib +from .stylePlotGUI import plotStyler class SignalEditWidget(QtWidgets.QWidget): def __init__(self, selfself, id, plotWidget): super(SignalEditWidget, self).__init__() - uic.loadUi("data/ui/signalWidget2.ui", self) + packagedir, file = os.path.split(os.path.realpath(__file__)) + uic.loadUi(packagedir+"/ui/signalWidget2.ui", self) self.self = selfself self.id = id self.plotWidget = plotWidget @@ -73,9 +73,9 @@ def toggleEvents(self): event.hide() event.vLine.hide() - def editPlotStyle(self): - dialog = plotStyler(self.self.plot, "Stil von "+self.self.devicename+"."+self.self.signalname+" anpassen") + dialog = plotStyler(self.self.plot, "Stil von "+self.self.devicename + + "."+self.self.signalname+" anpassen") if dialog.exec_(): symbol = dialog.symbol brush = dialog.brush @@ -83,13 +83,15 @@ def editPlotStyle(self): if type(symbol[key]) not in [int, float, str]: symbol[key] = str(symbol[key]) for key in brush.keys(): - if brush[key] == None: + if brush[key] is None: pass elif type(brush[key]) not in [int, float, str]: brush[key] = str(brush[key]) - self.plotWidget.plotStyles[str(self.self.devicename)+"."+str(self.self.signalname)]={} - self.plotWidget.plotStyles[str(self.self.devicename)+"."+str(self.self.signalname)]["symbol"]=symbol - self.plotWidget.plotStyles[str(self.self.devicename)+"."+str(self.self.signalname)]["brush"]=brush + self.plotWidget.plotStyles[str(self.self.devicename)+"."+str(self.self.signalname)] = {} + self.plotWidget.plotStyles[str(self.self.devicename) + + "."+str(self.self.signalname)]["symbol"] = symbol + self.plotWidget.plotStyles[str(self.self.devicename) + + "."+str(self.self.signalname)]["brush"] = brush self.self.labelItem.setColor(symbol["color"]) @@ -138,7 +140,7 @@ def cutSignal(self): self.self.updatePlot() def exportSignal(self): - dir_path = os.path.dirname(os.path.realpath(__file__)) + dir_path = self.config['documentfolder'] fileBrowser = QtWidgets.QFileDialog(self) fileBrowser.setDirectory(dir_path) fileBrowser.setNameFilters( diff --git a/data/signalWidget.py b/RTOC/data/signalWidget.py similarity index 72% rename from data/signalWidget.py rename to RTOC/data/signalWidget.py index 0cd67b0..726ec47 100644 --- a/data/signalWidget.py +++ b/RTOC/data/signalWidget.py @@ -6,10 +6,11 @@ import time from functools import partial -import data.lib.general_lib as lib -from data.signalEditWidget import SignalEditWidget -import data.define as define -from data.stylePlotGUI import setStyle as setPlotStyle +from .lib import general_lib as lib +from .signalEditWidget import SignalEditWidget +from . import define as define +from .stylePlotGUI import setStyle as setPlotStyle + def mouseClickEventPlotCurveItem(self, ev): if ev.button() != QtCore.Qt.LeftButton: @@ -17,6 +18,7 @@ def mouseClickEventPlotCurveItem(self, ev): ev.accept() self.sigClicked.emit(self) + class MyLabel(QtWidgets.QLabel): def __init__(self, text): super().__init__(text) @@ -43,23 +45,11 @@ def stop_this_thread(self): self.thread_must_be_run = False def run(self): - # while self.thread_must_be_run == True : - self.mutex.lock() # locking may not be necessary - - # Here we emit a signal whenever we want to update window - # contents. The other thread will handle this signal - # so that the method process_window_update_request() will - # be called. - - # self.emit( SIGNAL( "window_update_request" ) ) + self.mutex.lock() self.window_update_request.emit(True) self.mutex.unlock() time.sleep(define.blinkingIdentifier) self.window_update_request.emit(False) - # sleep( 1.000 ) # Delay of one second. - # QThread.sleep( 1 ) # An alternative way to sleep. - - # print " run method ends " class AnimatedWidget(QtGui.QWidget): @@ -113,12 +103,12 @@ def __init__(self, plotWidget, logger, devicename, signalname, id, unit): self.active = True self.showEvents = True self.signalTimeOut = define.signalTimeOut - self.style={} + self.style = {} self.xTimeBase = self.plotWidget.xTimeBase self.events = [] self.eventItems = [] - self.animation_thread = AnimationThread() + self.animation_thread = AnimationThread() self.animation_thread.window_update_request.connect(self.blinkLabel) self.initLabel() @@ -129,42 +119,41 @@ def __init__(self, plotWidget, logger, devicename, signalname, id, unit): self.plot = pg.PlotDataItem(x=[], y=[], name=self.legendName) # REMOVE THIS PART IF ITS TOO SLOW - self.display_text= pg.TextItem(text='',color=(200, 200, 200), fill=(200, 200, 200, 50),anchor=(1,1)) + self.display_text = pg.TextItem(text='', color=( + 200, 200, 200), fill=(200, 200, 200, 50), anchor=(1, 1)) self.display_text.hide() self.plotWidget.plot.addItem(self.display_text) self.plotWidget.plot.scene().sigMouseMoved.connect(self.onMove) - - #self.plot.setClickable(True) self.plotWidget.legend.addItem(self.plot, self.legendName) symbol, brush = self.findStyle() self.plot.curve.setClickable(True) - self.plot.curve.sigClicked.connect(partial(self.plotWidget.signalClickedAction, self.devicename, self.signalname)) + self.plot.curve.sigClicked.connect( + partial(self.plotWidget.signalClickedAction, self.devicename, self.signalname)) setPlotStyle(self.plot, symbol, brush) - #self.plot.setAlpha(symbol["alpha"], True) - #randcolor=(random.randint(50, 255), random.randint(50, 255), random.randint(50, 255)) - self.labelItem = pg.TextItem(devicename+"."+signalname, color=symbol["color"], html=None, anchor=(0,0.5)) + self.labelItem = pg.TextItem(devicename+"."+signalname, + color=symbol["color"], html=None, anchor=(0, 0.5)) self.labelItem.setPos(0, 0) self.initMenu() self.editWidget.xTimeBaseButton.setChecked(self.xTimeBase) - self.editWidget.labelButton.setChecked(self.plotWidget.plotViewWidget.labelButton.isChecked()) + self.editWidget.labelButton.setChecked( + self.plotWidget.plotViewWidget.labelButton.isChecked()) if self.plotWidget.plotViewWidget.labelButton.isChecked(): self.labelItem.hide() self.editWidget.toggleLabel() - #self.plot.sigPointsClicked.connect(self.signalClickedAction) self.signalModifications = [0, 0, 1, 1] def onMove(self, pos): act_pos = self.plot.mapFromScene(pos) p1 = self.plot.scatter.pointsAt(act_pos) - if len(p1)!=0: + if len(p1) != 0: x, y = p1[0].pos() print(x) - self.display_text.setText(self.devicename+'.'+self.signalname+'\nx=%f\ny=%f'%(x,y)) - self.display_text.setPos(x,y) + self.display_text.setText(self.devicename+'.'+self.signalname+'\nx=%f\ny=%f' % (x, y)) + self.display_text.setPos(x, y) self.display_text.show() else: self.display_text.hide() @@ -178,10 +167,10 @@ def findStyle(self): else: symbol = {} brush = {} - r = lambda: random.randint(0,255) - symbol["color"]='#%02X%02X%02X' % (r(),r(),r()) - symbol["width"]=define.defaultLineWidth - #pen = pg.mkPen((random.randint(0, 99), 100)) + + def r(): return random.randint(0, 255) + symbol["color"] = '#%02X%02X%02X' % (r(), r(), r()) + symbol["width"] = define.defaultLineWidth return symbol, brush def initMenu(self): @@ -193,13 +182,8 @@ def initMenu(self): self.menu().addAction(action) def newDataIncoming(self): - # self.updatePlot() - #self.label.setStyleSheet("background-color: rgb(25, 98, 115)") - # if self.plotWidget.plotViewWidget.blinkingButton.isChecked(): self.animation_thread.start() - # self.label.anim() - # self.anim.start() def blinkLabel(self, value): if value is True: @@ -208,16 +192,7 @@ def blinkLabel(self, value): self.label.setStyleSheet("background-color: #232629") def initLabel(self): - # self.label = AnimatedWidget() - # hl = QtGui.QVBoxLayout() - # self.labelx = QtWidgets.QLabel("") - # hl.addWidget(self.labelx) - # self.label.setLayout(hl) - self.label = MyLabel("Summer") - #font = self.label.font() - # font.setPointSize(35) - # self.label.setFont(font) - # hbox.addWidget(self.label) + self.label = MyLabel("") self.anim = QtCore.QPropertyAnimation(self.label, b"color") self.anim.setDuration(1) @@ -230,57 +205,58 @@ def rename(self, name): self.signalname = name self.labelItem.setText(name) + def createToolTip(self, id): + maxduration = self.calcDuration(list(self.logger.getSignal(id)[0])) + duration = self.logger.getSignal(id)[0][-1]-self.logger.getSignal(id)[0][0] + try: + line1 = time.strftime("%H:%M:%S", time.gmtime(int(duration))) + \ + "/~"+time.strftime("%H:%M:%S", time.gmtime(int(maxduration))) + line2 = str( + len(list(self.logger.getSignal(id)[0])))+"/"+str(self.logger.maxLength) + count = 20 + if len(self.logger.getSignal(id)[0]) <= count: + count = len(self.logger.getSignal(id)[0]) + if count > 1: + meaner = list(self.logger.getSignal(id)[0])[-count:] + diff = 0 + for idx, m in enumerate(meaner[:-1]): + diff += meaner[idx+1]-m + if diff != 0: + line3 = str(round((len(meaner)-1)/diff, 2))+" Hz" + else: + line3 = "? Hz" + else: + line3 = "? Hz" + return line1+"\n"+line2 + "\n" + line3 + except: + return "Tooltip failed" + def updatePlot(self): current_time = time.time() - #self.editWidget.deleteButton.setEnabled(False) if len(self.logger.signals) > 0: if len(self.logger.getSignal(self.id)[1]) > 0: current_signal = self.logger.getSignal(self.id)[1][-1] current_unit = self.logger.getSignalUnits(self.id)[-1] self.setText(self.signalname) self.label.setText(str(round(current_signal, 5))+" "+str(current_unit)) - maxduration = self.calcDuration(list(self.logger.getSignal(self.id)[0])) - duration = self.logger.getSignal(self.id)[0][-1]-self.logger.getSignal(self.id)[0][0] - try: - line1 = time.strftime("%H:%M:%S", time.gmtime(int(duration))) + \ - "/~"+time.strftime("%H:%M:%S", time.gmtime(int(maxduration))) - line2 = str( - len(list(self.logger.getSignal(self.id)[0])))+"/"+str(self.logger.maxLength) - count = 20 - if len(self.logger.getSignal(self.id)[0]) <= count: - count = len(self.logger.getSignal(self.id)[0]) - if count > 1: - meaner = list(self.logger.getSignal(self.id)[0])[-count:] - diff = 0 - for idx, m in enumerate(meaner[:-1]): - diff += meaner[idx+1]-m - if diff!=0: - line3 = str(round((len(meaner)-1)/diff, 2))+" Hz" - else: - line3 = "? Hz" - else: - line3 = "? Hz" - self.setToolTip(line1+"\n"+line2 + "\n" + line3) - except: - print("Tooltip failed") + self.setToolTip(self.createToolTip(self.id)) for idx, event in enumerate(self.logger.getEvents(self.id)[0]): evtext = self.logger.getEvents(self.id)[1][idx] if [event, evtext] not in self.events: self.events.append([event, evtext]) - eventItem = pg.TextItem(evtext, color=(200, 200, 200), html=None,anchor=(0,0.5)) + eventItem = pg.TextItem(str(evtext), color=( + 200, 200, 200), html=None, anchor=(0, 0.5)) eventItem.setPos(event-current_time, 0) eventItem.vLine = pg.InfiniteLine(angle=90, movable=False) self.eventItems.append(eventItem) self.plotWidget.plot.addItem(eventItem) - #self.eventItems[idx].vLine = pg.InfiniteLine(angle=90, movable=False) self.plotWidget.plot.addItem(self.eventItems[idx].vLine, ignoreBounds=True) if not self.showEvents: self.eventItems[idx].hide() self.eventItems[idx].vLine.hide() - #if self.id < len(list(self.logger.signals)): clock = list(self.logger.getSignal(self.id)[0]) if len(clock) > 0: lastTimestamp = self.plotWidget.lastUpdate - clock[-1] @@ -288,9 +264,6 @@ def updatePlot(self): self.label.setStyleSheet("background-color: rgb(113, 100, 29)") elif lastTimestamp < self.signalTimeOut and self.label.styleSheet() == "background-color: rgb(113, 100, 29)": self.label.setStyleSheet("background-color: #232629") - #elif self.label.styleSheet() == "background-color: rgb(25, 98, 115)": - # self.label.setStyleSheet("background-color: #232629") - # Workaround for XY plot, because Xdata is not clock if self.active: offsetX = self.signalModifications[0] offsetY = self.signalModifications[1] @@ -303,7 +276,6 @@ def updatePlot(self): else: ctime = 0 - self.plotWidget.globalXOffset if clock[len(clock)-1] <= current_time+10000 and clock[len(clock)-1] > current_time-10000: - #self.plotWidget.lastUpdate = clock[len(clock)-1] for idx2 in range(len(clock)): clock[idx2] = scaleX*(clock[idx2]-ctime+offsetX) data = list(self.logger.getSignal(self.id)[1]) @@ -335,12 +307,7 @@ def updatePlot(self): for idx, eventItem in enumerate(self.eventItems): pos = scaleX*(self.events[idx][0]-ctime+offsetX) eventItem.setPos(pos, 0) - # eventItem.updateTextPos() self.eventItems[idx].vLine.setPos(pos) - # try: - # self.editWidget.deleteButton.setEnabled(True) - # except: - # print("Deletebutton cannot be disabled, because it does not exist") def toggleSignal(self): @@ -365,8 +332,6 @@ def toggleSignal(self): event.vLine.hide() def remove(self, cb=True, total=True): - # if len(self.logger.getSignalUnits(self.id))==0: - # self.logger.getSignalUnits(self.id).append("") self.plotWidget.legend.removeItem(self.legendName) if total: idx = self.logger.removeSignal(self.id) @@ -389,7 +354,7 @@ def closeEvent(self, event, *args, **kwargs): super(SignalWidget, self).closeEvent(event) def calcDuration(self, x): - if len(x)>2: + if len(x) > 2: dt = x[-1]-x[0] l = len(x) maxlen = self.logger.maxLength diff --git a/data/styleMultiPlotGUI.py b/RTOC/data/styleMultiPlotGUI.py similarity index 75% rename from data/styleMultiPlotGUI.py rename to RTOC/data/styleMultiPlotGUI.py index a0b2c60..9b164a0 100644 --- a/data/styleMultiPlotGUI.py +++ b/RTOC/data/styleMultiPlotGUI.py @@ -1,15 +1,15 @@ -from PyQt5 import QtCore from PyQt5 import uic -from PyQt5 import QtWidgets, QtGui -import pyqtgraph as pg -from functools import partial +from PyQt5 import QtWidgets +import os + +from .stylePlotGUI import plotStyler -from data.stylePlotGUI import plotStyler class plotMultiStyler(QtWidgets.QDialog): - def __init__(self, signalnames, plots=[], logger=None): # + (filepath, known_values) + def __init__(self, signalnames, plots=[], logger=None): super(plotMultiStyler, self).__init__() - uic.loadUi("data/ui/stylePlotDialog.ui", self) + packagedir, file = os.path.split(os.path.realpath(__file__)) + uic.loadUi(packagedir+"/ui/stylePlotDialog.ui", self) self.setCallbacks() self.logger = logger @@ -24,7 +24,7 @@ def __init__(self, signalnames, plots=[], logger=None): # + (filepath, known_va text = ".".join(signal) self.listWidget.addItem(text) - #self.show() + # self.show() def setCallbacks(self): self.cancelButton.clicked.connect(self.close) @@ -41,5 +41,4 @@ def styleSelected(self): signalname = selectedSignal.text() idx = self.logger.getSignalId(signalname.split(".")[0], signalname.split(".")[1]) if idx != -1: - #symbol, brush = self.styler.getStyle() self.styler.setStyleAction(self.plots[idx]) diff --git a/data/stylePlotGUI.py b/RTOC/data/stylePlotGUI.py similarity index 82% rename from data/stylePlotGUI.py rename to RTOC/data/stylePlotGUI.py index bb7e296..e602b3e 100644 --- a/data/stylePlotGUI.py +++ b/RTOC/data/stylePlotGUI.py @@ -2,13 +2,16 @@ from PyQt5 import uic from PyQt5 import QtWidgets, QtGui import pyqtgraph as pg +import os + +from . import define as define -import data.define as define class plotStyler(QtWidgets.QDialog): - def __init__(self, plot=None, title="", stylesheet=""): # + (filepath, known_values) + def __init__(self, plot=None, title="", stylesheet=""): super(plotStyler, self).__init__() - uic.loadUi("data/ui/stylePlotDialog2.ui", self) + packagedir, file = os.path.split(os.path.realpath(__file__)) + uic.loadUi(packagedir+"/ui/stylePlotDialog2.ui", self) self.setWindowTitle(title) self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) self.setStyleSheet(stylesheet) @@ -22,11 +25,11 @@ def __init__(self, plot=None, title="", stylesheet=""): # + (filepath, known_va self.fillColor = None self.brush = {} self.symbol = {} - self.colorDialog= QtGui.QColorDialog() + self.colorDialog = QtGui.QColorDialog() self.colorDialog.hide() self.colorLayout.addWidget(self.colorDialog) self.colorDialog.colorSelected.connect(self.setColor) - if plot != None: + if plot is not None: symbol, brush = getStyle(plot) self.setGUIStyle(symbol, brush) @@ -53,7 +56,6 @@ def setShadowColor(self): self.resize(1100, 500) self.shadowColorButton.setStyleSheet("background-color:" + self.shadowColor) - def setColor(self): color = self.colorDialog.currentColor().name() stylesheet = "background-color:" + color @@ -109,8 +111,6 @@ def setSymbolPenColor(self): def setFillLevel(self): self.fillLevel = self.fillColorSpinBox.value() - #self.fillColor = QtGui.QColorDialog.getColor() - #self.fillColorButton.setStyleSheet("background-color:" + self.fillColor.name()) def deleteFill(self): self.fillLevel = None @@ -119,7 +119,7 @@ def deleteFill(self): self.fillColorButton.setStyleSheet("") def setStyleAction(self, plot=None): - plot=self.plot + plot = self.plot symbol, brush = self.getGUIStyle() setStyle(plot, symbol, brush) self.symbol, self.brush = getStyle(plot) @@ -170,7 +170,7 @@ def setGUIStyle(self, symbol, brush): self.lineColorButton.setStyleSheet("background-color:"+str(self.lineColor)) elif type(self.lineColor) == tuple: self.lineColorButton.setStyleSheet("background-color:"+str(self.lineColor)) - elif self.shadowColor != None: + elif self.shadowColor is not None: self.lineColorButton.setStyleSheet("background-color:"+str(self.lineColor.name())) if "shadowColor" in symbol.keys(): @@ -179,8 +179,9 @@ def setGUIStyle(self, symbol, brush): self.shadowColorButton.setStyleSheet("background-color:"+str(self.shadowColor)) elif type(self.shadowColor) == tuple: self.shadowColorButton.setStyleSheet("background-color:"+str(self.shadowColor)) - elif self.shadowColor != None: - self.shadowColorButton.setStyleSheet("background-color:"+str(self.shadowColor.name())) + elif self.shadowColor is not None: + self.shadowColorButton.setStyleSheet( + "background-color:"+str(self.shadowColor.name())) if "style" in brush.keys(): style = brush["style"] @@ -208,8 +209,9 @@ def setGUIStyle(self, symbol, brush): self.symbolColorButton.setStyleSheet("background-color:"+str(self.symbolColor)) elif type(self.symbolColor) == tuple: self.symbolColorButton.setStyleSheet("background-color:"+str(self.symbolColor)) - elif self.shadowColor != None: - self.symbolColorButton.setStyleSheet("background-color:"+str(self.symbolColor.name())) + elif self.shadowColor is not None: + self.symbolColorButton.setStyleSheet( + "background-color:"+str(self.symbolColor.name())) if "fillBrush" in brush.keys(): self.fillColor = brush["fillBrush"] @@ -217,20 +219,23 @@ def setGUIStyle(self, symbol, brush): self.fillColorButton.setStyleSheet("background-color:"+str(self.fillColor)) elif type(self.fillColor) == tuple: self.fillColorButton.setStyleSheet("background-color:"+str(self.fillColor)) - elif self.shadowColor != None: + elif self.shadowColor is not None: self.fillColorButton.setStyleSheet("background-color:"+str(self.fillColor.name())) if "pen" in brush.keys(): self.symbolPenColor = brush["pen"] if type(self.symbolPenColor) == str: - self.symbolPenColorButton.setStyleSheet("background-color:"+str(self.symbolPenColor)) + self.symbolPenColorButton.setStyleSheet( + "background-color:"+str(self.symbolPenColor)) elif type(self.symbolPenColor) == tuple: - self.symbolPenColorButton.setStyleSheet("background-color:"+str(self.symbolPenColor)) - elif self.shadowColor != None: - self.symbolPenColorButton.setStyleSheet("background-color:"+str(self.symbolPenColor.name())) + self.symbolPenColorButton.setStyleSheet( + "background-color:"+str(self.symbolPenColor)) + elif self.shadowColor is not None: + self.symbolPenColorButton.setStyleSheet( + "background-color:"+str(self.symbolPenColor.name())) if "fillLevel" in brush.keys(): - if brush["fillLevel"] != None: + if brush["fillLevel"] is not None: self.fillLevel = brush["fillLevel"] self.fillColorSpinBox.setValue(self.fillLevel) @@ -238,11 +243,11 @@ def getGUIStyle(self): symbol = {} brush = {} style = self.lineStyleComboBox.currentText() - if style in ["Linie", "Line"]: + if style in ["Linie", "Line"]: symbol["style"] = QtCore.Qt.SolidLine elif style in ["Punkte (P)", "Dots"]: symbol["style"] = QtCore.Qt.DotLine - elif style in ["Striche (S)","Stroke"]: + elif style in ["Striche (S)", "Stroke"]: symbol["style"] = QtCore.Qt.DashLine elif style in ["P S", "Dot Stroke"]: symbol["style"] = QtCore.Qt.DashDotLine @@ -250,11 +255,11 @@ def getGUIStyle(self): symbol["style"] = QtCore.Qt.DashDotDotLine style = self.shadowStyleComboBox.currentText() - if style in ["Linie", "Line"]: + if style in ["Linie", "Line"]: symbol["style"] = QtCore.Qt.SolidLine elif style in ["Punkte (P)", "Dots"]: symbol["style"] = QtCore.Qt.DotLine - elif style in ["Striche (S)","Stroke"]: + elif style in ["Striche (S)", "Stroke"]: symbol["style"] = QtCore.Qt.DashLine elif style in ["P S", "Dot Stroke"]: symbol["style"] = QtCore.Qt.DashDotLine @@ -264,7 +269,7 @@ def getGUIStyle(self): symbol["alpha"] = self.alphaSpinBox.value()/100 symbol["width"] = self.lineWidthSpinBox.value() symbol["shadowWidth"] = self.shadowWidthSpinBox.value() - if self.lineColor != None: + if self.lineColor is not None: symbol["color"] = self.lineColor symbol["shadowColor"] = self.shadowColor @@ -287,20 +292,18 @@ def getGUIStyle(self): brush["size"] = self.symbolSizeSpinBox.value() - if self.symbolColor != None: + if self.symbolColor is not None: brush["color"] = self.symbolColor - if self.symbolPenColor != None: + if self.symbolPenColor is not None: brush["pen"] = self.symbolPenColor - #if self.fillLevel != None: brush["fillLevel"] = self.fillLevel - - #if self.fillColor != None: brush["fillBrush"] = self.fillColor return symbol, brush + def getStyle(plot): symbol = {} brush = {} @@ -311,7 +314,7 @@ def getStyle(plot): symbol["width"] = opts['pen'].width() symbol["color"] = opts['pen'].color().name() symbol["alpha"] = opts["alphaHint"] - if opts['shadowPen'] != None: + if opts['shadowPen'] is not None: symbol["shadowColor"] = opts['shadowPen'].color().name() symbol["shadowWidth"] = opts['shadowPen'].width() symbol["shadowStyle"] = opts['shadowPen'].style() @@ -319,33 +322,31 @@ def getStyle(plot): symbol["shadowColor"] = None symbol["shadowWidth"] = 0 symbol["shadowStyle"] = 0 - #'shadowPen': None, - brush["fillLevel"] =opts['fillLevel'] - if opts['fillBrush'] != None: + # 'shadowPen': None, + brush["fillLevel"] = opts['fillLevel'] + if opts['fillBrush'] is not None: if type(opts['symbolBrush']) == tuple: - brush["fillBrush"] =opts['fillBrush'] + brush["fillBrush"] = opts['fillBrush'] else: - brush["fillBrush"] =opts['fillBrush'].color().name() - #'fillBrush': None, + brush["fillBrush"] = opts['fillBrush'].color().name() + # 'fillBrush': None, - brush["style"]=opts['symbol'] - brush["size"]=opts['symbolSize'] + brush["style"] = opts['symbol'] + brush["size"] = opts['symbolSize'] if type(opts['symbolBrush']) == tuple: - brush["color"]=opts['symbolBrush'] + brush["color"] = opts['symbolBrush'] else: - brush["color"]=opts['symbolBrush'].color().name() + brush["color"] = opts['symbolBrush'].color().name() if type(opts['symbolPen']) == tuple: - brush["pen"]=opts['symbolPen'] + brush["pen"] = opts['symbolPen'] else: - brush["pen"]=opts['symbolPen'].color().name() - #'symbolBrush': (50, 50, 150), + brush["pen"] = opts['symbolPen'].color().name() return symbol, brush def setStyle(plot, symbol={}, brush={}): if symbol != {}: - #pen = pg.mkPen(symbol) if "width" not in symbol.keys(): symbol["width"] = define.defaultLineWidth if "style" not in symbol.keys(): @@ -354,10 +355,12 @@ def setStyle(plot, symbol={}, brush={}): symbol["shadowWidth"] = None if "shadowStyle" not in symbol.keys(): symbol["shadowStyle"] = None - pen = pg.mkPen(color=symbol["color"], width = int(symbol["width"]),style=int(symbol["style"])) + pen = pg.mkPen(color=symbol["color"], width=int( + symbol["width"]), style=int(symbol["style"])) plot.setPen(pen) - if symbol["shadowWidth"] != None and symbol["shadowStyle"] != None: - plot.setShadowPen(color=symbol["shadowColor"], width = int(symbol["shadowWidth"]), style=int(symbol["shadowStyle"]), cosmetic=True) + if symbol["shadowWidth"] is not None and symbol["shadowStyle"] is not None: + plot.setShadowPen(color=symbol["shadowColor"], width=int( + symbol["shadowWidth"]), style=int(symbol["shadowStyle"]), cosmetic=True) else: plot.setShadowPen(None) if "alpha" in symbol.keys(): @@ -370,15 +373,13 @@ def setStyle(plot, symbol={}, brush={}): else: plot.setSymbol(None) elif key == "color": - c=brush["color"] + c = brush["color"] plot.setSymbolBrush(c) elif key == "size": - #plot.setSymbolBrush(brush["size"]) plot.setSymbolSize(brush["size"]) elif key == "fillLevel": plot.setFillLevel(brush["fillLevel"]) elif key == "fillBrush": plot.setFillBrush(brush["fillBrush"]) elif key == "pen": - #plot.setSymbolBrush(brush["size"]) plot.setSymbolPen(brush["pen"]) diff --git a/data/ui/darkmode.html b/RTOC/data/ui/darkmode.html similarity index 83% rename from data/ui/darkmode.html rename to RTOC/data/ui/darkmode.html index 565df81..5f79ab4 100644 --- a/data/ui/darkmode.html +++ b/RTOC/data/ui/darkmode.html @@ -45,7 +45,7 @@ QGroupBox::indicator:unchecked:pressed { border: none; - image: url(data/ui/icons/dark/checkbox_unchecked_hovered.png); + image: url(/data/ui/icons/dark/checkbox_unchecked_hovered.png); } QGroupBox::indicator:checked:hover, @@ -53,17 +53,17 @@ QGroupBox::indicator:checked:pressed { border: none; - image: url(data/ui/icons/dark/checkbox_checked_hovered.png); + image: url(/data/ui/icons/dark/checkbox_checked_hovered.png); } QGroupBox::indicator:checked:disabled { - image: url(data/ui/icons/dark/checkbox_checked_disabled.png); + image: url(/data/ui/icons/dark/checkbox_checked_disabled.png); } QGroupBox::indicator:unchecked:disabled { - image: url(data/ui/icons/dark/checkbox_unchecked_disabled.png); + image: url(/data/ui/icons/dark/checkbox_unchecked_disabled.png); } QRadioButton @@ -86,7 +86,7 @@ QRadioButton::indicator:unchecked { - image: url(data/ui/icons/dark/radio_unchecked.png); + image: url(/data/ui/icons/dark/radio_unchecked.png); } @@ -96,14 +96,14 @@ { border: none; outline: none; - image: url(data/ui/icons/dark/radio_unchecked_hovered.png); + image: url(/data/ui/icons/dark/radio_unchecked_hovered.png); } QRadioButton::indicator:checked { border: none; outline: none; - image: url(data/ui/icons/dark/radio_checked.png); + image: url(/data/ui/icons/dark/radio_checked.png); } QRadioButton::indicator:checked:hover, @@ -112,18 +112,18 @@ { border: none; outline: none; - image: url(data/ui/icons/dark/radio_checked_hovered.png); + image: url(/data/ui/icons/dark/radio_checked_hovered.png); } QRadioButton::indicator:checked:disabled { outline: none; - image: url(data/ui/icons/dark/radio_disabled.png); + image: url(/data/ui/icons/dark/radio_disabled.png); } QRadioButton::indicator:unchecked:disabled { - image: url(data/ui/icons/dark/radio_disabled.png); + image: url(/data/ui/icons/dark/radio_disabled.png); } @@ -191,41 +191,41 @@ /* non-exclusive indicator = check box style indicator (see QActionGroup::setExclusive) */ QMenu::indicator:non-exclusive:unchecked { - image: url(data/ui/icons/dark/checkbox_unchecked.png); + image: url(/data/ui/icons/dark/checkbox_unchecked.png); } QMenu::indicator:non-exclusive:unchecked:selected { - image: url(data/ui/icons/dark/checkbox_unchecked_hovered.png); + image: url(/data/ui/icons/dark/checkbox_unchecked_hovered.png); } QMenu::indicator:non-exclusive:checked { - image: url(data/ui/icons/dark/checkbox_checked.png); + image: url(/data/ui/icons/dark/checkbox_checked.png); } QMenu::indicator:non-exclusive:checked:selected { - image: url(data/ui/icons/dark/checkbox_checked_hovered.png); + image: url(/data/ui/icons/dark/checkbox_checked_hovered.png); } /* exclusive indicator = radio button style indicator (see QActionGroup::setExclusive) */ QMenu::indicator:exclusive:unchecked { - image: url(data/ui/icons/dark/radio_unchecked.png); + image: url(/data/ui/icons/dark/radio_unchecked.png); } QMenu::indicator:exclusive:unchecked:selected { - image: url(data/ui/icons/dark/radio_unchecked_hovered.png); + image: url(/data/ui/icons/dark/radio_unchecked_hovered.png); } QMenu::indicator:exclusive:checked { - image: url(data/ui/icons/dark/radio_checked.png); + image: url(/data/ui/icons/dark/radio_checked.png); } QMenu::indicator:exclusive:checked:selected { - image: url(data/ui/icons/dark/radio_checked_hovered.png); + image: url(/data/ui/icons/dark/radio_checked_hovered.png); } QMenu::right-arrow { margin: 5px; - image: url(data/ui/icons/dark/right_arrow.png) + image: url(/data/ui/icons/dark/right_arrow.png) } @@ -308,7 +308,7 @@ QScrollBar::add-line:horizontal { margin: 0px 3px 0px 3px; - border-image: url(data/ui/icons/dark/right_arrow_disabled.png); + border-image: url(/data/ui/icons/dark/right_arrow_disabled.png); width: 10px; height: 10px; subcontrol-position: right; @@ -318,7 +318,7 @@ QScrollBar::sub-line:horizontal { margin: 0px 3px 0px 3px; - border-image: url(data/ui/icons/dark/left_arrow_disabled.png); + border-image: url(/data/ui/icons/dark/left_arrow_disabled.png); height: 10px; width: 10px; subcontrol-position: left; @@ -327,7 +327,7 @@ QScrollBar::add-line:horizontal:hover,QScrollBar::add-line:horizontal:on { - border-image: url(data/ui/icons/dark/right_arrow.png); + border-image: url(/data/ui/icons/dark/right_arrow.png); height: 10px; width: 10px; subcontrol-position: right; @@ -337,7 +337,7 @@ QScrollBar::sub-line:horizontal:hover, QScrollBar::sub-line:horizontal:on { - border-image: url(data/ui/icons/dark/left_arrow.png); + border-image: url(/data/ui/icons/dark/left_arrow.png); height: 10px; width: 10px; subcontrol-position: left; @@ -374,7 +374,7 @@ QScrollBar::sub-line:vertical { margin: 3px 0px 3px 0px; - border-image: url(data/ui/icons/dark/up_arrow_disabled.png); + border-image: url(/data/ui/icons/dark/up_arrow_disabled.png); height: 10px; width: 10px; subcontrol-position: top; @@ -384,7 +384,7 @@ QScrollBar::add-line:vertical { margin: 3px 0px 3px 0px; - border-image: url(data/ui/icons/dark/down_arrow_disabled.png); + border-image: url(/data/ui/icons/dark/down_arrow_disabled.png); height: 10px; width: 10px; subcontrol-position: bottom; @@ -394,7 +394,7 @@ QScrollBar::sub-line:vertical:hover,QScrollBar::sub-line:vertical:on { - border-image: url(data/ui/icons/dark/up_arrow.png); + border-image: url(/data/ui/icons/dark/up_arrow.png); height: 10px; width: 10px; subcontrol-position: top; @@ -404,7 +404,7 @@ QScrollBar::add-line:vertical:hover, QScrollBar::add-line:vertical:on { - border-image: url(data/ui/icons/dark/down_arrow.png); + border-image: url(/data/ui/icons/dark/down_arrow.png); height: 10px; width: 10px; subcontrol-position: bottom; @@ -446,7 +446,7 @@ } QSizeGrip { - image: url(data/ui/icons/dark/sizegrip.png); + image: url(/data/ui/icons/dark/sizegrip.png); width: 12px; height: 12px; } @@ -507,16 +507,16 @@ } QToolBar::handle:horizontal { - image: url(data/ui/icons/dark/Hmovetoolbar.png); + image: url(/data/ui/icons/dark/Hmovetoolbar.png); } QToolBar::handle:vertical { - image: url(data/ui/icons/dark/Vmovetoolbar.png); + image: url(/data/ui/icons/dark/Vmovetoolbar.png); } QToolBar::separator:horizontal { - image: url(data/ui/icons/dark/Hsepartoolbar.png); + image: url(/data/ui/icons/dark/Hsepartoolbar.png); } QToolBar::separator:vertical { - image: url(data/ui/icons/dark/Vsepartoolbar.png); + image: url(/data/ui/icons/dark/Vsepartoolbar.png); } QToolButton#qt_toolbar_ext_button { background: #58595a @@ -620,13 +620,13 @@ QComboBox::down-arrow { - image: url(data/ui/icons/dark/down_arrow_disabled.png); + image: url(/data/ui/icons/dark/down_arrow_disabled.png); } QComboBox::down-arrow:on, QComboBox::down-arrow:hover, QComboBox::down-arrow:focus { - image: url(data/ui/icons/dark/down_arrow.png); + image: url(/data/ui/icons/dark/down_arrow.png); } QAbstractSpinBox { @@ -653,37 +653,37 @@ } QAbstractSpinBox::up-arrow,QAbstractSpinBox::up-arrow:off { - image: url(data/ui/icons/dark/up_arrow_disabled.png); + image: url(/data/ui/icons/dark/up_arrow_disabled.png); width: 10px; height: 10px; } QAbstractSpinBox::up-arrow:disabled { - image: url(data/ui/icons/dark/up_arrow_disabled.png); + image: url(/data/ui/icons/dark/up_arrow_disabled.png); width: 10px; height: 10px; } QAbstractSpinBox::up-arrow:hover { - image: url(data/ui/icons/dark/up_arrow.png); + image: url(/data/ui/icons/dark/up_arrow.png); } QAbstractSpinBox::down-arrow,QAbstractSpinBox::down-arrow:off { - image: url(data/ui/icons/dark/down_arrow_disabled.png); + image: url(/data/ui/icons/dark/down_arrow_disabled.png); width: 10px; height: 10px; } QAbstractSpinBox::down-arrow:disabled { - image: url(data/ui/icons/dark/down_arrow_disabled.png); + image: url(/data/ui/icons/dark/down_arrow_disabled.png); width: 10px; height: 10px; } QAbstractSpinBox::down-arrow:hover { - image: url(data/ui/icons/dark/down_arrow.png); + image: url(/data/ui/icons/dark/down_arrow.png); } @@ -718,18 +718,18 @@ } QTabBar::close-button { - image: url(data/ui/icons/dark/close.png); + image: url(/data/ui/icons/dark/close.png); background: transparent; } QTabBar::close-button:hover { - image: url(data/ui/icons/dark/close-hover.png); + image: url(/data/ui/icons/dark/close-hover.png); background: transparent; } QTabBar::close-button:pressed { - image: url(data/ui/icons/dark/close-pressed.png); + image: url(/data/ui/icons/dark/close-pressed.png); background: transparent; } @@ -839,27 +839,27 @@ } QTabBar QToolButton::right-arrow:enabled { - image: url(data/ui/icons/dark/right_arrow.png); + image: url(/data/ui/icons/dark/right_arrow.png); } QTabBar QToolButton::left-arrow:enabled { - image: url(data/ui/icons/dark/left_arrow.png); + image: url(/data/ui/icons/dark/left_arrow.png); } QTabBar QToolButton::right-arrow:disabled { - image: url(data/ui/icons/dark/right_arrow_disabled.png); + image: url(/data/ui/icons/dark/right_arrow_disabled.png); } QTabBar QToolButton::left-arrow:disabled { - image: url(data/ui/icons/dark/left_arrow_disabled.png); + image: url(/data/ui/icons/dark/left_arrow_disabled.png); } QDockWidget { background: #31363b; border: 1px solid #403F3F; - titlebar-close-icon: url(data/ui/icons/dark/close_window.png); - titlebar-normal-icon: url(data/ui/icons/dark/undock.png); + titlebar-close-icon: url(/data/ui/icons/dark/close_window.png); + titlebar-normal-icon: url(/data/ui/icons/dark/undock.png); } QDockWidget::close-button, QDockWidget::float-button { @@ -885,39 +885,39 @@ QTreeView:branch:selected, QTreeView:branch:hover { - background: url(data/ui/icons/dark/transparent.png); + background: url(/data/ui/icons/dark/transparent.png); } QTreeView::branch:has-siblings:!adjoins-item { - border-image: url(data/ui/icons/dark/transparent.png); + border-image: url(/data/ui/icons/dark/transparent.png); } QTreeView::branch:has-siblings:adjoins-item { - border-image: url(data/ui/icons/dark/transparent.png); + border-image: url(/data/ui/icons/dark/transparent.png); } QTreeView::branch:!has-children:!has-siblings:adjoins-item { - border-image: url(data/ui/icons/dark/transparent.png); + border-image: url(/data/ui/icons/dark/transparent.png); } QTreeView::branch:has-children:!has-siblings:closed, QTreeView::branch:closed:has-children:has-siblings { - image: url(data/ui/icons/dark/right_arrow.png); + image: url(/data/ui/icons/dark/right_arrow.png); } QTreeView::branch:open:has-children:!has-siblings, QTreeView::branch:open:has-children:has-siblings { - image: url(data/ui/icons/dark/down_arrow.png); + image: url(/data/ui/icons/dark/down_arrow.png); } QTreeView::branch:has-children:!has-siblings:closed:hover, QTreeView::branch:closed:has-children:has-siblings:hover { - image: url(data/ui/icons/dark/right_arrow_disabled.png); + image: url(/data/ui/icons/dark/right_arrow_disabled.png); } QTreeView::branch:open:has-children:!has-siblings:hover, QTreeView::branch:open:has-children:has-siblings:hover { - image: url(data/ui/icons/dark/down_arrow_disabled.png); + image: url(/data/ui/icons/dark/down_arrow_disabled.png); } QListView::item:!selected:hover, QTreeView::item:!selected:hover { @@ -1000,7 +1000,7 @@ /* the subcontrol below is used only in the InstantPopup or DelayedPopup mode */ QToolButton::menu-indicator { - image: url(data/ui/icons/dark/down_arrow.png); + image: url(/data/ui/icons/dark/down_arrow.png); top: -7px; left: -2px; /* shift it a bit */ } @@ -1015,7 +1015,7 @@ } QToolButton::menu-arrow { - image: url(data/ui/icons/dark/down_arrow.png); + image: url(/data/ui/icons/dark/down_arrow.png); } QToolButton::menu-arrow:open { @@ -1100,11 +1100,11 @@ /* style the sort indicator */ QHeaderView::down-arrow { - image: url(data/ui/icons/dark/down_arrow.png); + image: url(/data/ui/icons/dark/down_arrow.png); } QHeaderView::up-arrow { - image: url(data/ui/icons/dark/up_arrow.png); + image: url(/data/ui/icons/dark/up_arrow.png); } @@ -1211,13 +1211,13 @@ QDateEdit::down-arrow:enabled { - image: url(data/ui/icons/dark/down_arrow_disabled.png); + image: url(/data/ui/icons/dark/down_arrow_disabled.png); } QDateEdit::down-arrow:on, QDateEdit::down-arrow:hover, QDateEdit::down-arrow:focus { - image: url(data/ui/icons/dark/down_arrow.png); + image: url(/data/ui/icons/dark/down_arrow.png); } QLineEdit:disabled @@ -1261,39 +1261,39 @@ } QCheckBox::indicator:unchecked { - image: url(data/ui/icons/dark/checkbox_unchecked.png); + image: url(/data/ui/icons/dark/checkbox_unchecked.png); } QCheckBox::indicator:unchecked:hover { - image: url(data/ui/icons/dark/checkbox_unchecked_hovered.png); + image: url(/data/ui/icons/dark/checkbox_unchecked_hovered.png); } QCheckBox::indicator:unchecked:pressed { - image: url(data/ui/icons/dark/checkbox_checked_hovered.png); + image: url(/data/ui/icons/dark/checkbox_checked_hovered.png); } QCheckBox::indicator:checked { - image: url(data/ui/icons/dark/checkbox_checked.png); + image: url(/data/ui/icons/dark/checkbox_checked.png); } QCheckBox::indicator:checked:hover { - image: url(data/ui/icons/dark/checkbox_checked_hovered.png); + image: url(/data/ui/icons/dark/checkbox_checked_hovered.png); } QCheckBox::indicator:checked:pressed { - image: url(data/ui/icons/dark/checkbox_unchecked_hovered.png); + image: url(/data/ui/icons/dark/checkbox_unchecked_hovered.png); } QCheckBox::indicator:indeterminate { - image: url(data/ui/icons/dark/checkbox_indeterminate.png); + image: url(/data/ui/icons/dark/checkbox_indeterminate.png); } QCheckBox::indicator:indeterminate:hover { - image: url(data/ui/icons/dark/checkbox_indeterminate_hovered.png); + image: url(/data/ui/icons/dark/checkbox_indeterminate_hovered.png); } QCheckBox::indicator:indeterminate:pressed { - image: url(data/ui/icons/dark/checkbox_indeterminate_pressed.png); + image: url(/data/ui/icons/dark/checkbox_indeterminate_pressed.png); } QMessageBox { diff --git a/data/ui/eventWidget.ui b/RTOC/data/ui/eventWidget.ui similarity index 100% rename from data/ui/eventWidget.ui rename to RTOC/data/ui/eventWidget.ui diff --git a/data/ui/gridViewWidget.ui b/RTOC/data/ui/gridViewWidget.ui similarity index 100% rename from data/ui/gridViewWidget.ui rename to RTOC/data/ui/gridViewWidget.ui diff --git a/data/ui/icons/blinking.png b/RTOC/data/ui/icons/blinking.png similarity index 100% rename from data/ui/icons/blinking.png rename to RTOC/data/ui/icons/blinking.png diff --git a/data/ui/icons/clear.png b/RTOC/data/ui/icons/clear.png similarity index 100% rename from data/ui/icons/clear.png rename to RTOC/data/ui/icons/clear.png diff --git a/data/ui/icons/crosshair.png b/RTOC/data/ui/icons/crosshair.png similarity index 100% rename from data/ui/icons/crosshair.png rename to RTOC/data/ui/icons/crosshair.png diff --git a/data/ui/icons/cut.png b/RTOC/data/ui/icons/cut.png similarity index 100% rename from data/ui/icons/cut.png rename to RTOC/data/ui/icons/cut.png diff --git a/data/ui/icons/dark/Hmovetoolbar.png b/RTOC/data/ui/icons/dark/Hmovetoolbar.png similarity index 100% rename from data/ui/icons/dark/Hmovetoolbar.png rename to RTOC/data/ui/icons/dark/Hmovetoolbar.png diff --git a/data/ui/icons/dark/Hsepartoolbar.png b/RTOC/data/ui/icons/dark/Hsepartoolbar.png similarity index 100% rename from data/ui/icons/dark/Hsepartoolbar.png rename to RTOC/data/ui/icons/dark/Hsepartoolbar.png diff --git a/data/ui/icons/dark/Vmovetoolbar.png b/RTOC/data/ui/icons/dark/Vmovetoolbar.png similarity index 100% rename from data/ui/icons/dark/Vmovetoolbar.png rename to RTOC/data/ui/icons/dark/Vmovetoolbar.png diff --git a/data/ui/icons/dark/Vsepartoolbar.png b/RTOC/data/ui/icons/dark/Vsepartoolbar.png similarity index 100% rename from data/ui/icons/dark/Vsepartoolbar.png rename to RTOC/data/ui/icons/dark/Vsepartoolbar.png diff --git a/data/ui/icons/dark/add.png b/RTOC/data/ui/icons/dark/add.png similarity index 100% rename from data/ui/icons/dark/add.png rename to RTOC/data/ui/icons/dark/add.png diff --git a/data/ui/icons/dark/add_element.png b/RTOC/data/ui/icons/dark/add_element.png similarity index 100% rename from data/ui/icons/dark/add_element.png rename to RTOC/data/ui/icons/dark/add_element.png diff --git a/data/ui/icons/dark/add_element_hovered.png b/RTOC/data/ui/icons/dark/add_element_hovered.png similarity index 100% rename from data/ui/icons/dark/add_element_hovered.png rename to RTOC/data/ui/icons/dark/add_element_hovered.png diff --git a/data/ui/icons/dark/add_hovered.png b/RTOC/data/ui/icons/dark/add_hovered.png similarity index 100% rename from data/ui/icons/dark/add_hovered.png rename to RTOC/data/ui/icons/dark/add_hovered.png diff --git a/data/ui/icons/dark/add_new.png b/RTOC/data/ui/icons/dark/add_new.png similarity index 100% rename from data/ui/icons/dark/add_new.png rename to RTOC/data/ui/icons/dark/add_new.png diff --git a/data/ui/icons/dark/add_new_hovered.png b/RTOC/data/ui/icons/dark/add_new_hovered.png similarity index 100% rename from data/ui/icons/dark/add_new_hovered.png rename to RTOC/data/ui/icons/dark/add_new_hovered.png diff --git a/data/ui/icons/dark/checkbox_checked.png b/RTOC/data/ui/icons/dark/checkbox_checked.png similarity index 100% rename from data/ui/icons/dark/checkbox_checked.png rename to RTOC/data/ui/icons/dark/checkbox_checked.png diff --git a/data/ui/icons/dark/checkbox_checked_hovered.png b/RTOC/data/ui/icons/dark/checkbox_checked_hovered.png similarity index 100% rename from data/ui/icons/dark/checkbox_checked_hovered.png rename to RTOC/data/ui/icons/dark/checkbox_checked_hovered.png diff --git a/data/ui/icons/dark/checkbox_indeterminate.png b/RTOC/data/ui/icons/dark/checkbox_indeterminate.png similarity index 100% rename from data/ui/icons/dark/checkbox_indeterminate.png rename to RTOC/data/ui/icons/dark/checkbox_indeterminate.png diff --git a/data/ui/icons/dark/checkbox_indeterminate_hovered.png b/RTOC/data/ui/icons/dark/checkbox_indeterminate_hovered.png similarity index 100% rename from data/ui/icons/dark/checkbox_indeterminate_hovered.png rename to RTOC/data/ui/icons/dark/checkbox_indeterminate_hovered.png diff --git a/data/ui/icons/dark/checkbox_unchecked.png b/RTOC/data/ui/icons/dark/checkbox_unchecked.png similarity index 100% rename from data/ui/icons/dark/checkbox_unchecked.png rename to RTOC/data/ui/icons/dark/checkbox_unchecked.png diff --git a/data/ui/icons/dark/checkbox_unchecked_hovered.png b/RTOC/data/ui/icons/dark/checkbox_unchecked_hovered.png similarity index 100% rename from data/ui/icons/dark/checkbox_unchecked_hovered.png rename to RTOC/data/ui/icons/dark/checkbox_unchecked_hovered.png diff --git a/data/ui/icons/dark/close_window.png b/RTOC/data/ui/icons/dark/close_window.png similarity index 100% rename from data/ui/icons/dark/close_window.png rename to RTOC/data/ui/icons/dark/close_window.png diff --git a/data/ui/icons/dark/close_window_hovered.png b/RTOC/data/ui/icons/dark/close_window_hovered.png similarity index 100% rename from data/ui/icons/dark/close_window_hovered.png rename to RTOC/data/ui/icons/dark/close_window_hovered.png diff --git a/data/ui/icons/dark/delete_profile.png b/RTOC/data/ui/icons/dark/delete_profile.png similarity index 100% rename from data/ui/icons/dark/delete_profile.png rename to RTOC/data/ui/icons/dark/delete_profile.png diff --git a/data/ui/icons/dark/delete_profile_hovered.png b/RTOC/data/ui/icons/dark/delete_profile_hovered.png similarity index 100% rename from data/ui/icons/dark/delete_profile_hovered.png rename to RTOC/data/ui/icons/dark/delete_profile_hovered.png diff --git a/data/ui/icons/dark/down_arrow.png b/RTOC/data/ui/icons/dark/down_arrow.png similarity index 100% rename from data/ui/icons/dark/down_arrow.png rename to RTOC/data/ui/icons/dark/down_arrow.png diff --git a/data/ui/icons/dark/down_arrow_disabled.png b/RTOC/data/ui/icons/dark/down_arrow_disabled.png similarity index 100% rename from data/ui/icons/dark/down_arrow_disabled.png rename to RTOC/data/ui/icons/dark/down_arrow_disabled.png diff --git a/data/ui/icons/dark/download.png b/RTOC/data/ui/icons/dark/download.png similarity index 100% rename from data/ui/icons/dark/download.png rename to RTOC/data/ui/icons/dark/download.png diff --git a/data/ui/icons/dark/edit.png b/RTOC/data/ui/icons/dark/edit.png similarity index 100% rename from data/ui/icons/dark/edit.png rename to RTOC/data/ui/icons/dark/edit.png diff --git a/data/ui/icons/dark/edit_hovered.png b/RTOC/data/ui/icons/dark/edit_hovered.png similarity index 100% rename from data/ui/icons/dark/edit_hovered.png rename to RTOC/data/ui/icons/dark/edit_hovered.png diff --git a/data/ui/icons/dark/file.png b/RTOC/data/ui/icons/dark/file.png similarity index 100% rename from data/ui/icons/dark/file.png rename to RTOC/data/ui/icons/dark/file.png diff --git a/data/ui/icons/dark/file_hovered.png b/RTOC/data/ui/icons/dark/file_hovered.png similarity index 100% rename from data/ui/icons/dark/file_hovered.png rename to RTOC/data/ui/icons/dark/file_hovered.png diff --git a/data/ui/icons/dark/filter.png b/RTOC/data/ui/icons/dark/filter.png similarity index 100% rename from data/ui/icons/dark/filter.png rename to RTOC/data/ui/icons/dark/filter.png diff --git a/data/ui/icons/dark/filter_hovered.png b/RTOC/data/ui/icons/dark/filter_hovered.png similarity index 100% rename from data/ui/icons/dark/filter_hovered.png rename to RTOC/data/ui/icons/dark/filter_hovered.png diff --git a/data/ui/icons/dark/image.png b/RTOC/data/ui/icons/dark/image.png similarity index 100% rename from data/ui/icons/dark/image.png rename to RTOC/data/ui/icons/dark/image.png diff --git a/data/ui/icons/dark/image_hovered.png b/RTOC/data/ui/icons/dark/image_hovered.png similarity index 100% rename from data/ui/icons/dark/image_hovered.png rename to RTOC/data/ui/icons/dark/image_hovered.png diff --git a/data/ui/icons/dark/left_arrow.png b/RTOC/data/ui/icons/dark/left_arrow.png similarity index 100% rename from data/ui/icons/dark/left_arrow.png rename to RTOC/data/ui/icons/dark/left_arrow.png diff --git a/data/ui/icons/dark/left_arrow_disabled.png b/RTOC/data/ui/icons/dark/left_arrow_disabled.png similarity index 100% rename from data/ui/icons/dark/left_arrow_disabled.png rename to RTOC/data/ui/icons/dark/left_arrow_disabled.png diff --git a/data/ui/icons/dark/maximize_window.png b/RTOC/data/ui/icons/dark/maximize_window.png similarity index 100% rename from data/ui/icons/dark/maximize_window.png rename to RTOC/data/ui/icons/dark/maximize_window.png diff --git a/data/ui/icons/dark/maximize_window_hovered.png b/RTOC/data/ui/icons/dark/maximize_window_hovered.png similarity index 100% rename from data/ui/icons/dark/maximize_window_hovered.png rename to RTOC/data/ui/icons/dark/maximize_window_hovered.png diff --git a/data/ui/icons/dark/menu.png b/RTOC/data/ui/icons/dark/menu.png similarity index 100% rename from data/ui/icons/dark/menu.png rename to RTOC/data/ui/icons/dark/menu.png diff --git a/data/ui/icons/dark/menu_hovered.png b/RTOC/data/ui/icons/dark/menu_hovered.png similarity index 100% rename from data/ui/icons/dark/menu_hovered.png rename to RTOC/data/ui/icons/dark/menu_hovered.png diff --git a/data/ui/icons/dark/merge.png b/RTOC/data/ui/icons/dark/merge.png similarity index 100% rename from data/ui/icons/dark/merge.png rename to RTOC/data/ui/icons/dark/merge.png diff --git a/data/ui/icons/dark/merge_hovered.png b/RTOC/data/ui/icons/dark/merge_hovered.png similarity index 100% rename from data/ui/icons/dark/merge_hovered.png rename to RTOC/data/ui/icons/dark/merge_hovered.png diff --git a/data/ui/icons/dark/minimize_window.png b/RTOC/data/ui/icons/dark/minimize_window.png similarity index 100% rename from data/ui/icons/dark/minimize_window.png rename to RTOC/data/ui/icons/dark/minimize_window.png diff --git a/data/ui/icons/dark/minimize_window_hovered.png b/RTOC/data/ui/icons/dark/minimize_window_hovered.png similarity index 100% rename from data/ui/icons/dark/minimize_window_hovered.png rename to RTOC/data/ui/icons/dark/minimize_window_hovered.png diff --git a/data/ui/icons/dark/radio_checked.png b/RTOC/data/ui/icons/dark/radio_checked.png similarity index 100% rename from data/ui/icons/dark/radio_checked.png rename to RTOC/data/ui/icons/dark/radio_checked.png diff --git a/data/ui/icons/dark/radio_checked_hovered.png b/RTOC/data/ui/icons/dark/radio_checked_hovered.png similarity index 100% rename from data/ui/icons/dark/radio_checked_hovered.png rename to RTOC/data/ui/icons/dark/radio_checked_hovered.png diff --git a/data/ui/icons/dark/radio_disabled.png b/RTOC/data/ui/icons/dark/radio_disabled.png similarity index 100% rename from data/ui/icons/dark/radio_disabled.png rename to RTOC/data/ui/icons/dark/radio_disabled.png diff --git a/data/ui/icons/dark/radio_unchecked.png b/RTOC/data/ui/icons/dark/radio_unchecked.png similarity index 100% rename from data/ui/icons/dark/radio_unchecked.png rename to RTOC/data/ui/icons/dark/radio_unchecked.png diff --git a/data/ui/icons/dark/radio_unchecked_hovered.png b/RTOC/data/ui/icons/dark/radio_unchecked_hovered.png similarity index 100% rename from data/ui/icons/dark/radio_unchecked_hovered.png rename to RTOC/data/ui/icons/dark/radio_unchecked_hovered.png diff --git a/data/ui/icons/dark/remove_element.png b/RTOC/data/ui/icons/dark/remove_element.png similarity index 100% rename from data/ui/icons/dark/remove_element.png rename to RTOC/data/ui/icons/dark/remove_element.png diff --git a/data/ui/icons/dark/remove_element_hovered.png b/RTOC/data/ui/icons/dark/remove_element_hovered.png similarity index 100% rename from data/ui/icons/dark/remove_element_hovered.png rename to RTOC/data/ui/icons/dark/remove_element_hovered.png diff --git a/data/ui/icons/dark/restore_window.png b/RTOC/data/ui/icons/dark/restore_window.png similarity index 100% rename from data/ui/icons/dark/restore_window.png rename to RTOC/data/ui/icons/dark/restore_window.png diff --git a/data/ui/icons/dark/restore_window_hovered.png b/RTOC/data/ui/icons/dark/restore_window_hovered.png similarity index 100% rename from data/ui/icons/dark/restore_window_hovered.png rename to RTOC/data/ui/icons/dark/restore_window_hovered.png diff --git a/data/ui/icons/dark/right_arrow.png b/RTOC/data/ui/icons/dark/right_arrow.png similarity index 100% rename from data/ui/icons/dark/right_arrow.png rename to RTOC/data/ui/icons/dark/right_arrow.png diff --git a/data/ui/icons/dark/right_arrow_disabled.png b/RTOC/data/ui/icons/dark/right_arrow_disabled.png similarity index 100% rename from data/ui/icons/dark/right_arrow_disabled.png rename to RTOC/data/ui/icons/dark/right_arrow_disabled.png diff --git a/data/ui/icons/dark/save.png b/RTOC/data/ui/icons/dark/save.png similarity index 100% rename from data/ui/icons/dark/save.png rename to RTOC/data/ui/icons/dark/save.png diff --git a/data/ui/icons/dark/save_hovered.png b/RTOC/data/ui/icons/dark/save_hovered.png similarity index 100% rename from data/ui/icons/dark/save_hovered.png rename to RTOC/data/ui/icons/dark/save_hovered.png diff --git a/data/ui/icons/dark/sizegrip.png b/RTOC/data/ui/icons/dark/sizegrip.png similarity index 100% rename from data/ui/icons/dark/sizegrip.png rename to RTOC/data/ui/icons/dark/sizegrip.png diff --git a/data/ui/icons/dark/source.png b/RTOC/data/ui/icons/dark/source.png similarity index 100% rename from data/ui/icons/dark/source.png rename to RTOC/data/ui/icons/dark/source.png diff --git a/data/ui/icons/dark/source_hovered.png b/RTOC/data/ui/icons/dark/source_hovered.png similarity index 100% rename from data/ui/icons/dark/source_hovered.png rename to RTOC/data/ui/icons/dark/source_hovered.png diff --git a/data/ui/icons/dark/submitElement.png b/RTOC/data/ui/icons/dark/submitElement.png similarity index 100% rename from data/ui/icons/dark/submitElement.png rename to RTOC/data/ui/icons/dark/submitElement.png diff --git a/data/ui/icons/dark/submitElement_hovered.png b/RTOC/data/ui/icons/dark/submitElement_hovered.png similarity index 100% rename from data/ui/icons/dark/submitElement_hovered.png rename to RTOC/data/ui/icons/dark/submitElement_hovered.png diff --git a/data/ui/icons/dark/undo.png b/RTOC/data/ui/icons/dark/undo.png similarity index 100% rename from data/ui/icons/dark/undo.png rename to RTOC/data/ui/icons/dark/undo.png diff --git a/data/ui/icons/dark/undo_hovered.png b/RTOC/data/ui/icons/dark/undo_hovered.png similarity index 100% rename from data/ui/icons/dark/undo_hovered.png rename to RTOC/data/ui/icons/dark/undo_hovered.png diff --git a/data/ui/icons/dark/undock.png b/RTOC/data/ui/icons/dark/undock.png similarity index 100% rename from data/ui/icons/dark/undock.png rename to RTOC/data/ui/icons/dark/undock.png diff --git a/data/ui/icons/dark/up_arrow.png b/RTOC/data/ui/icons/dark/up_arrow.png similarity index 100% rename from data/ui/icons/dark/up_arrow.png rename to RTOC/data/ui/icons/dark/up_arrow.png diff --git a/data/ui/icons/dark/up_arrow_disabled.png b/RTOC/data/ui/icons/dark/up_arrow_disabled.png similarity index 100% rename from data/ui/icons/dark/up_arrow_disabled.png rename to RTOC/data/ui/icons/dark/up_arrow_disabled.png diff --git a/data/ui/icons/duplicate.png b/RTOC/data/ui/icons/duplicate.png similarity index 100% rename from data/ui/icons/duplicate.png rename to RTOC/data/ui/icons/duplicate.png diff --git a/data/ui/icons/events.png b/RTOC/data/ui/icons/events.png similarity index 100% rename from data/ui/icons/events.png rename to RTOC/data/ui/icons/events.png diff --git a/data/ui/icons/exportCSV.png b/RTOC/data/ui/icons/exportCSV.png similarity index 100% rename from data/ui/icons/exportCSV.png rename to RTOC/data/ui/icons/exportCSV.png diff --git a/data/ui/icons/graph.png b/RTOC/data/ui/icons/graph.png similarity index 100% rename from data/ui/icons/graph.png rename to RTOC/data/ui/icons/graph.png diff --git a/data/ui/icons/grid.png b/RTOC/data/ui/icons/grid.png similarity index 100% rename from data/ui/icons/grid.png rename to RTOC/data/ui/icons/grid.png diff --git a/data/ui/icons/help.png b/RTOC/data/ui/icons/help.png similarity index 100% rename from data/ui/icons/help.png rename to RTOC/data/ui/icons/help.png diff --git a/data/ui/icons/hohe_prioritaet.png b/RTOC/data/ui/icons/hohe_prioritaet.png similarity index 100% rename from data/ui/icons/hohe_prioritaet.png rename to RTOC/data/ui/icons/hohe_prioritaet.png diff --git a/data/ui/icons/hohe_prioritaet_grey.png b/RTOC/data/ui/icons/hohe_prioritaet_grey.png similarity index 100% rename from data/ui/icons/hohe_prioritaet_grey.png rename to RTOC/data/ui/icons/hohe_prioritaet_grey.png diff --git a/data/ui/icons/icons8-stornieren-480.png b/RTOC/data/ui/icons/icons8-stornieren-480.png similarity index 100% rename from data/ui/icons/icons8-stornieren-480.png rename to RTOC/data/ui/icons/icons8-stornieren-480.png diff --git a/data/ui/icons/invert.png b/RTOC/data/ui/icons/invert.png similarity index 100% rename from data/ui/icons/invert.png rename to RTOC/data/ui/icons/invert.png diff --git a/data/ui/icons/labels.png b/RTOC/data/ui/icons/labels.png similarity index 100% rename from data/ui/icons/labels.png rename to RTOC/data/ui/icons/labels.png diff --git a/data/ui/icons/legend.png b/RTOC/data/ui/icons/legend.png similarity index 100% rename from data/ui/icons/legend.png rename to RTOC/data/ui/icons/legend.png diff --git a/data/ui/icons/measure.png b/RTOC/data/ui/icons/measure.png similarity index 100% rename from data/ui/icons/measure.png rename to RTOC/data/ui/icons/measure.png diff --git a/data/ui/icons/mittlere_prioritaet.png b/RTOC/data/ui/icons/mittlere_prioritaet.png similarity index 100% rename from data/ui/icons/mittlere_prioritaet.png rename to RTOC/data/ui/icons/mittlere_prioritaet.png diff --git a/data/ui/icons/mittlere_prioritaet_grey.png b/RTOC/data/ui/icons/mittlere_prioritaet_grey.png similarity index 100% rename from data/ui/icons/mittlere_prioritaet_grey.png rename to RTOC/data/ui/icons/mittlere_prioritaet_grey.png diff --git a/data/ui/icons/niedrige_prioritaet.png b/RTOC/data/ui/icons/niedrige_prioritaet.png similarity index 100% rename from data/ui/icons/niedrige_prioritaet.png rename to RTOC/data/ui/icons/niedrige_prioritaet.png diff --git a/data/ui/icons/niedrige_prioritaet_grey.png b/RTOC/data/ui/icons/niedrige_prioritaet_grey.png similarity index 100% rename from data/ui/icons/niedrige_prioritaet_grey.png rename to RTOC/data/ui/icons/niedrige_prioritaet_grey.png diff --git a/data/ui/icons/open.png b/RTOC/data/ui/icons/open.png similarity index 100% rename from data/ui/icons/open.png rename to RTOC/data/ui/icons/open.png diff --git a/data/ui/icons/pause.png b/RTOC/data/ui/icons/pause.png similarity index 100% rename from data/ui/icons/pause.png rename to RTOC/data/ui/icons/pause.png diff --git a/data/ui/icons/rename.png b/RTOC/data/ui/icons/rename.png similarity index 100% rename from data/ui/icons/rename.png rename to RTOC/data/ui/icons/rename.png diff --git a/data/ui/icons/repeat.png b/RTOC/data/ui/icons/repeat.png similarity index 100% rename from data/ui/icons/repeat.png rename to RTOC/data/ui/icons/repeat.png diff --git a/data/ui/icons/repeat1.png b/RTOC/data/ui/icons/repeat1.png similarity index 100% rename from data/ui/icons/repeat1.png rename to RTOC/data/ui/icons/repeat1.png diff --git a/data/ui/icons/style.png b/RTOC/data/ui/icons/style.png similarity index 100% rename from data/ui/icons/style.png rename to RTOC/data/ui/icons/style.png diff --git a/data/ui/icons/swap.png b/RTOC/data/ui/icons/swap.png similarity index 100% rename from data/ui/icons/swap.png rename to RTOC/data/ui/icons/swap.png diff --git a/data/ui/icons/x.png b/RTOC/data/ui/icons/x.png similarity index 100% rename from data/ui/icons/x.png rename to RTOC/data/ui/icons/x.png diff --git a/data/ui/icons/xaxislabel.png b/RTOC/data/ui/icons/xaxislabel.png similarity index 100% rename from data/ui/icons/xaxislabel.png rename to RTOC/data/ui/icons/xaxislabel.png diff --git a/data/ui/icons/xtimebase.png b/RTOC/data/ui/icons/xtimebase.png similarity index 100% rename from data/ui/icons/xtimebase.png rename to RTOC/data/ui/icons/xtimebase.png diff --git a/data/ui/icons/y.png b/RTOC/data/ui/icons/y.png similarity index 100% rename from data/ui/icons/y.png rename to RTOC/data/ui/icons/y.png diff --git a/data/ui/lightmode.html b/RTOC/data/ui/lightmode.html similarity index 83% rename from data/ui/lightmode.html rename to RTOC/data/ui/lightmode.html index 40992ad..8b0fbad 100644 --- a/data/ui/lightmode.html +++ b/RTOC/data/ui/lightmode.html @@ -45,7 +45,7 @@ QGroupBox::indicator:unchecked:pressed { border: none; - image: url(data/ui/icons/light/checkbox_unchecked_hovered.png); + image: url(/data/ui/icons/light/checkbox_unchecked_hovered.png); } QGroupBox::indicator:checked:hover, @@ -53,17 +53,17 @@ QGroupBox::indicator:checked:pressed { border: none; - image: url(data/ui/icons/light/checkbox_checked_hovered.png); + image: url(/data/ui/icons/light/checkbox_checked_hovered.png); } QGroupBox::indicator:checked:disabled { - image: url(data/ui/icons/light/checkbox_checked_disabled.png); + image: url(/data/ui/icons/light/checkbox_checked_disabled.png); } QGroupBox::indicator:unchecked:disabled { - image: url(data/ui/icons/light/checkbox_unchecked_disabled.png); + image: url(/data/ui/icons/light/checkbox_unchecked_disabled.png); } QRadioButton @@ -86,7 +86,7 @@ QRadioButton::indicator:unchecked { - image: url(data/ui/icons/light/radio_unchecked.png); + image: url(/data/ui/icons/light/radio_unchecked.png); } @@ -96,14 +96,14 @@ { border: none; outline: none; - image: url(data/ui/icons/light/radio_unchecked_hovered.png); + image: url(/data/ui/icons/light/radio_unchecked_hovered.png); } QRadioButton::indicator:checked { border: none; outline: none; - image: url(data/ui/icons/light/radio_checked.png); + image: url(/data/ui/icons/light/radio_checked.png); } QRadioButton::indicator:checked:hover, @@ -112,18 +112,18 @@ { border: none; outline: none; - image: url(data/ui/icons/light/radio_checked_hovered.png); + image: url(/data/ui/icons/light/radio_checked_hovered.png); } QRadioButton::indicator:checked:disabled { outline: none; - image: url(data/ui/icons/light/radio_disabled.png); + image: url(/data/ui/icons/light/radio_disabled.png); } QRadioButton::indicator:unchecked:disabled { - image: url(data/ui/icons/light/radio_disabled.png); + image: url(/data/ui/icons/light/radio_disabled.png); } @@ -191,41 +191,41 @@ /* non-exclusive indicator = check box style indicator (see QActionGroup::setExclusive) */ QMenu::indicator:non-exclusive:unchecked { - image: url(data/ui/icons/checkbox_unchecked.png); + image: url(/data/ui/icons/checkbox_unchecked.png); } QMenu::indicator:non-exclusive:unchecked:selected { - image: url(data/ui/icons/checkbox_unchecked_hovered.png); + image: url(/data/ui/icons/checkbox_unchecked_hovered.png); } QMenu::indicator:non-exclusive:checked { - image: url(data/ui/icons/checkbox_checked.png); + image: url(/data/ui/icons/checkbox_checked.png); } QMenu::indicator:non-exclusive:checked:selected { - image: url(data/ui/icons/checkbox_checked_hovered.png); + image: url(/data/ui/icons/checkbox_checked_hovered.png); } /* exclusive indicator = radio button style indicator (see QActionGroup::setExclusive) */ QMenu::indicator:exclusive:unchecked { - image: url(data/ui/icons/light/radio_unchecked.png); + image: url(/data/ui/icons/light/radio_unchecked.png); } QMenu::indicator:exclusive:unchecked:selected { - image: url(data/ui/icons/light/radio_unchecked_hovered.png); + image: url(/data/ui/icons/light/radio_unchecked_hovered.png); } QMenu::indicator:exclusive:checked { - image: url(data/ui/icons/light/radio_checked.png); + image: url(/data/ui/icons/light/radio_checked.png); } QMenu::indicator:exclusive:checked:selected { - image: url(data/ui/icons/light/radio_checked_hovered.png); + image: url(/data/ui/icons/light/radio_checked_hovered.png); } QMenu::right-arrow { margin: 5px; - image: url(data/ui/icons/light/right_arrow.png) + image: url(/data/ui/icons/light/right_arrow.png) } @@ -308,7 +308,7 @@ QScrollBar::add-line:horizontal { margin: 0px 3px 0px 3px; - border-image: url(data/ui/icons/light/right_arrow_disabled.png); + border-image: url(/data/ui/icons/light/right_arrow_disabled.png); width: 10px; height: 10px; subcontrol-position: right; @@ -318,7 +318,7 @@ QScrollBar::sub-line:horizontal { margin: 0px 3px 0px 3px; - border-image: url(data/ui/icons/light/left_arrow_disabled.png); + border-image: url(/data/ui/icons/light/left_arrow_disabled.png); height: 10px; width: 10px; subcontrol-position: left; @@ -327,7 +327,7 @@ QScrollBar::add-line:horizontal:hover,QScrollBar::add-line:horizontal:on { - border-image: url(data/ui/icons/light/right_arrow.png); + border-image: url(/data/ui/icons/light/right_arrow.png); height: 10px; width: 10px; subcontrol-position: right; @@ -337,7 +337,7 @@ QScrollBar::sub-line:horizontal:hover, QScrollBar::sub-line:horizontal:on { - border-image: url(data/ui/icons/light/left_arrow.png); + border-image: url(/data/ui/icons/light/left_arrow.png); height: 10px; width: 10px; subcontrol-position: left; @@ -374,7 +374,7 @@ QScrollBar::sub-line:vertical { margin: 3px 0px 3px 0px; - border-image: url(data/ui/icons/light/up_arrow_disabled.png); + border-image: url(/data/ui/icons/light/up_arrow_disabled.png); height: 10px; width: 10px; subcontrol-position: top; @@ -384,7 +384,7 @@ QScrollBar::add-line:vertical { margin: 3px 0px 3px 0px; - border-image: url(data/ui/icons/light/down_arrow_disabled.png); + border-image: url(/data/ui/icons/light/down_arrow_disabled.png); height: 10px; width: 10px; subcontrol-position: bottom; @@ -394,7 +394,7 @@ QScrollBar::sub-line:vertical:hover,QScrollBar::sub-line:vertical:on { - border-image: url(data/ui/icons/light/up_arrow.png); + border-image: url(/data/ui/icons/light/up_arrow.png); height: 10px; width: 10px; subcontrol-position: top; @@ -404,7 +404,7 @@ QScrollBar::add-line:vertical:hover, QScrollBar::add-line:vertical:on { - border-image: url(data/ui/icons/light/down_arrow.png); + border-image: url(/data/ui/icons/light/down_arrow.png); height: 10px; width: 10px; subcontrol-position: bottom; @@ -446,7 +446,7 @@ } QSizeGrip { - image: url(data/ui/icons/light/sizegrip.png); + image: url(/data/ui/icons/light/sizegrip.png); width: 12px; height: 12px; } @@ -507,16 +507,16 @@ } QToolBar::handle:horizontal { - image: url(data/ui/icons/light/Hmovetoolbar.png); + image: url(/data/ui/icons/light/Hmovetoolbar.png); } QToolBar::handle:vertical { - image: url(data/ui/icons/light/Vmovetoolbar.png); + image: url(/data/ui/icons/light/Vmovetoolbar.png); } QToolBar::separator:horizontal { - image: url(data/ui/icons/light/Hsepartoolbar.png); + image: url(/data/ui/icons/light/Hsepartoolbar.png); } QToolBar::separator:vertical { - image: url(data/ui/icons/light/Vsepartoolbar.png); + image: url(/data/ui/icons/light/Vsepartoolbar.png); } QToolButton#qt_toolbar_ext_button { background: #58595a @@ -611,13 +611,13 @@ QComboBox::down-arrow { - image: url(data/ui/icons/light/down_arrow_disabled.png); + image: url(/data/ui/icons/light/down_arrow_disabled.png); } QComboBox::down-arrow:on, QComboBox::down-arrow:hover, QComboBox::down-arrow:focus { - image: url(data/ui/icons/light/down_arrow.png); + image: url(/data/ui/icons/light/down_arrow.png); } QAbstractSpinBox { @@ -644,25 +644,25 @@ } QAbstractSpinBox::up-arrow,QAbstractSpinBox::up-arrow:disabled,QAbstractSpinBox::up-arrow:off { - image: url(data/ui/icons/light/up_arrow_disabled2.png); + image: url(/data/ui/icons/light/up_arrow_disabled2.png); width: 10px; height: 10px; } QAbstractSpinBox::up-arrow:hover { - image: url(data/ui/icons/light/up_arrow.png); + image: url(/data/ui/icons/light/up_arrow.png); } QAbstractSpinBox::down-arrow,QAbstractSpinBox::down-arrow:disabled,QAbstractSpinBox::down-arrow:off { - image: url(data/ui/icons/light/down_arrow_disabled.png); + image: url(/data/ui/icons/light/down_arrow_disabled.png); width: 10px; height: 10px; } QAbstractSpinBox::down-arrow:hover { - image: url(data/ui/icons/light/down_arrow.png); + image: url(/data/ui/icons/light/down_arrow.png); } @@ -697,18 +697,18 @@ } QTabBar::close-button { - image: url(data/ui/icons/light/close.png); + image: url(/data/ui/icons/light/close.png); background: transparent; } QTabBar::close-button:hover { - image: url(data/ui/icons/light/close-hover.png); + image: url(/data/ui/icons/light/close-hover.png); background: transparent; } QTabBar::close-button:pressed { - image: url(data/ui/icons/light/close-pressed.png); + image: url(/data/ui/icons/light/close-pressed.png); background: transparent; } @@ -818,27 +818,27 @@ } QTabBar QToolButton::right-arrow:enabled { - image: url(data/ui/icons/light/right_arrow.png); + image: url(/data/ui/icons/light/right_arrow.png); } QTabBar QToolButton::left-arrow:enabled { - image: url(data/ui/icons/light/left_arrow.png); + image: url(/data/ui/icons/light/left_arrow.png); } QTabBar QToolButton::right-arrow:disabled { - image: url(data/ui/icons/light/right_arrow_disabled.png); + image: url(/data/ui/icons/light/right_arrow_disabled.png); } QTabBar QToolButton::left-arrow:disabled { - image: url(data/ui/icons/light/left_arrow_disabled.png); + image: url(/data/ui/icons/light/left_arrow_disabled.png); } QDockWidget { background: #dddddd; border: 1px solid #403F3F; - titlebar-close-icon: url(data/ui/icons/light/close.png); - titlebar-normal-icon: url(data/ui/icons/light/undock.png); + titlebar-close-icon: url(/data/ui/icons/light/close.png); + titlebar-normal-icon: url(/data/ui/icons/light/undock.png); } QDockWidget::close-button, QDockWidget::float-button { @@ -864,39 +864,39 @@ QTreeView:branch:selected, QTreeView:branch:hover { - background: url(data/ui/icons/light/transparent.png); + background: url(/data/ui/icons/light/transparent.png); } QTreeView::branch:has-siblings:!adjoins-item { - border-image: url(data/ui/icons/light/transparent.png); + border-image: url(/data/ui/icons/light/transparent.png); } QTreeView::branch:has-siblings:adjoins-item { - border-image: url(data/ui/icons/light/transparent.png); + border-image: url(/data/ui/icons/light/transparent.png); } QTreeView::branch:!has-children:!has-siblings:adjoins-item { - border-image: url(data/ui/icons/light/transparent.png); + border-image: url(/data/ui/icons/light/transparent.png); } QTreeView::branch:has-children:!has-siblings:closed, QTreeView::branch:closed:has-children:has-siblings { - image: url(data/ui/icons/light/branch_closed.png); + image: url(/data/ui/icons/light/branch_closed.png); } QTreeView::branch:open:has-children:!has-siblings, QTreeView::branch:open:has-children:has-siblings { - image: url(data/ui/icons/light/branch_open.png); + image: url(/data/ui/icons/light/branch_open.png); } QTreeView::branch:has-children:!has-siblings:closed:hover, QTreeView::branch:closed:has-children:has-siblings:hover { - image: url(data/ui/icons/light/branch_closed-on.png); + image: url(/data/ui/icons/light/branch_closed-on.png); } QTreeView::branch:open:has-children:!has-siblings:hover, QTreeView::branch:open:has-children:has-siblings:hover { - image: url(data/ui/icons/light/branch_open-on.png); + image: url(/data/ui/icons/light/branch_open-on.png); } QListView::item:!selected:hover, QTreeView::item:!selected:hover { @@ -979,7 +979,7 @@ /* the subcontrol below is used only in the InstantPopup or DelayedPopup mode */ QToolButton::menu-indicator { - image: url(data/ui/icons/light/down_arrow.png); + image: url(/data/ui/icons/light/down_arrow.png); top: -7px; left: -2px; /* shift it a bit */ } @@ -994,7 +994,7 @@ } QToolButton::menu-arrow { - image: url(data/ui/icons/light/down_arrow.png); + image: url(/data/ui/icons/light/down_arrow.png); } QToolButton::menu-arrow:open { @@ -1079,11 +1079,11 @@ /* style the sort indicator */ QHeaderView::down-arrow { - image: url(data/ui/icons/light/down_arrow.png); + image: url(/data/ui/icons/light/down_arrow.png); } QHeaderView::up-arrow { - image: url(data/ui/icons/light/up_arrow.png); + image: url(/data/ui/icons/light/up_arrow.png); } @@ -1190,13 +1190,13 @@ QDateEdit::down-arrow:enabled { - image: url(data/ui/icons/light/down_arrow_disabled.png); + image: url(/data/ui/icons/light/down_arrow_disabled.png); } QDateEdit::down-arrow:on, QDateEdit::down-arrow:hover, QDateEdit::down-arrow:focus { - image: url(data/ui/icons/light/down_arrow.png); + image: url(/data/ui/icons/light/down_arrow.png); } QLineEdit:disabled @@ -1247,39 +1247,39 @@ } QCheckBox::indicator:unchecked { - image: url(data/ui/icons/light/checkbox_unchecked.png); + image: url(/data/ui/icons/light/checkbox_unchecked.png); } QCheckBox::indicator:unchecked:hover { - image: url(data/ui/icons/light/checkbox_unchecked_hovered.png); + image: url(/data/ui/icons/light/checkbox_unchecked_hovered.png); } QCheckBox::indicator:unchecked:pressed { - image: url(data/ui/icons/light/checkbox_checked_hovered.png); + image: url(/data/ui/icons/light/checkbox_checked_hovered.png); } QCheckBox::indicator:checked { - image: url(data/ui/icons/light/checkbox_checked.png); + image: url(/data/ui/icons/light/checkbox_checked.png); } QCheckBox::indicator:checked:hover { - image: url(data/ui/icons/light/checkbox_checked_hovered.png); + image: url(/data/ui/icons/light/checkbox_checked_hovered.png); } QCheckBox::indicator:checked:pressed { - image: url(data/ui/icons/light/checkbox_unchecked_hovered.png); + image: url(/data/ui/icons/light/checkbox_unchecked_hovered.png); } QCheckBox::indicator:indeterminate { - image: url(data/ui/icons/light/checkbox_indeterminate.png); + image: url(/data/ui/icons/light/checkbox_indeterminate.png); } QCheckBox::indicator:indeterminate:hover { - image: url(data/ui/icons/light/checkbox_indeterminate_hovered.png); + image: url(/data/ui/icons/light/checkbox_indeterminate_hovered.png); } QCheckBox::indicator:indeterminate:pressed { - image: url(data/ui/icons/light/checkbox_indeterminate_pressed.png); + image: url(/data/ui/icons/light/checkbox_indeterminate_pressed.png); } QMessageBox { diff --git a/data/ui/messtoolDialog.ui b/RTOC/data/ui/messtoolDialog.ui similarity index 100% rename from data/ui/messtoolDialog.ui rename to RTOC/data/ui/messtoolDialog.ui diff --git a/data/ui/plotToolsWidget.ui b/RTOC/data/ui/plotToolsWidget.ui similarity index 100% rename from data/ui/plotToolsWidget.ui rename to RTOC/data/ui/plotToolsWidget.ui diff --git a/data/ui/plotViewWidget.ui b/RTOC/data/ui/plotViewWidget.ui similarity index 100% rename from data/ui/plotViewWidget.ui rename to RTOC/data/ui/plotViewWidget.ui diff --git a/data/ui/plotWidget.ui b/RTOC/data/ui/plotWidget.ui similarity index 100% rename from data/ui/plotWidget.ui rename to RTOC/data/ui/plotWidget.ui diff --git a/data/ui/rtoc.ui b/RTOC/data/ui/rtoc.ui similarity index 96% rename from data/ui/rtoc.ui rename to RTOC/data/ui/rtoc.ui index 9b81412..ca581fd 100644 --- a/data/ui/rtoc.ui +++ b/RTOC/data/ui/rtoc.ui @@ -358,6 +358,13 @@ &Datei + + + Telegram-Bot + + + + @@ -365,6 +372,7 @@ + @@ -658,6 +666,19 @@ Events + + + Bot-Token eingeben + + + + + true + + + Aktiviert + + diff --git a/data/ui/scriptHelpWidget.ui b/RTOC/data/ui/scriptHelpWidget.ui similarity index 100% rename from data/ui/scriptHelpWidget.ui rename to RTOC/data/ui/scriptHelpWidget.ui diff --git a/data/ui/scriptSubWidget.ui b/RTOC/data/ui/scriptSubWidget.ui similarity index 100% rename from data/ui/scriptSubWidget.ui rename to RTOC/data/ui/scriptSubWidget.ui diff --git a/data/ui/scriptWidget.ui b/RTOC/data/ui/scriptWidget.ui similarity index 100% rename from data/ui/scriptWidget.ui rename to RTOC/data/ui/scriptWidget.ui diff --git a/data/ui/signalWidget.ui b/RTOC/data/ui/signalWidget.ui similarity index 100% rename from data/ui/signalWidget.ui rename to RTOC/data/ui/signalWidget.ui diff --git a/data/ui/signalWidget2.ui b/RTOC/data/ui/signalWidget2.ui similarity index 100% rename from data/ui/signalWidget2.ui rename to RTOC/data/ui/signalWidget2.ui diff --git a/data/ui/stylePlotDialog.ui b/RTOC/data/ui/stylePlotDialog.ui similarity index 100% rename from data/ui/stylePlotDialog.ui rename to RTOC/data/ui/stylePlotDialog.ui diff --git a/data/ui/stylePlotDialog2.ui b/RTOC/data/ui/stylePlotDialog2.ui similarity index 100% rename from data/ui/stylePlotDialog2.ui rename to RTOC/data/ui/stylePlotDialog2.ui diff --git a/data/ui/triggerWidget.ui b/RTOC/data/ui/triggerWidget.ui similarity index 100% rename from data/ui/triggerWidget.ui rename to RTOC/data/ui/triggerWidget.ui diff --git a/RTOC/jsonsocket.py b/RTOC/jsonsocket.py new file mode 100644 index 0000000..3734b45 --- /dev/null +++ b/RTOC/jsonsocket.py @@ -0,0 +1,156 @@ +# file:jsonsocket.py +# https://github.com/mdebbar/jsonsocket +import json +import socket +import traceback + + +class Server(object): + """ + A JSON socket server used to communicate with a JSON socket client. All the + data is serialized in JSON. How to use it: + + server = Server(host, port) + while True: + server.accept() + data = server.recv() + # shortcut: data = server.accept().recv() + server.send({'status': 'ok'}) + """ + + backlog = 5 + client = None + + def __init__(self, host, port): + self.socket = socket.socket() + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket.setblocking(0) + self.socket.settimeout(5.0) + self.socket.bind((host, port)) + self.socket.listen(self.backlog) + + def __del__(self): + self.close() + + def accept(self): + # if a client is already connected, disconnect it + if self.client: + self.client.close() + self.client, self.client_addr = self.socket.accept() + return self + + def send(self, data): + if not self.client: + raise Exception('Cannot send data, no client is connected') + _send(self.client, data) + return self + + def recv(self): + if not self.client: + raise Exception('Cannot receive data, no client is connected') + return _recv(self.client) + + def close(self): + if self.client: + self.client.close() + self.client = None + if self.socket: + self.socket.close() + self.socket = None + + +class Client(object): + """ + A JSON socket client used to communicate with a JSON socket server. All the + data is serialized in JSON. How to use it: + + data = { + 'name': 'Patrick Jane', + 'age': 45, + 'children': ['Susie', 'Mike', 'Philip'] + } + client = Client() + client.connect(host, port) + client.send(data) + response = client.recv() + # or in one line: + response = Client().connect(host, port).send(data).recv() + """ + + socket = None + + def __del__(self): + self.close() + + def connect(self, host, port): + self.socket = socket.socket() + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket.setblocking(0) + self.socket.settimeout(5.0) + self.socket.connect((host, port)) + return self + + def send(self, data): + if not self.socket: + raise Exception('You have to connect first before sending data') + _send(self.socket, data) + return self + + def recv(self): + if not self.socket: + raise Exception('You have to connect first before receiving data') + return _recv(self.socket) + + def recv_and_close(self): + data = self.recv() + self.close() + return data + + def close(self): + if self.socket: + self.socket.close() + self.socket = None + +# helper functions ## + + +def _send(socket, data): + try: + serialized = json.dumps(data) + # serialized=pickle.dumps(list(data)) + except (TypeError, ValueError): + raise Exception('You can only send JSON-serializable data') + # send the length of the serialized data first + b = '%d\n' % len(serialized) + socket.send(b.encode()) + # send the serialized data + socket.sendall(serialized.encode()) + + +def _recv(socket): + # read the length of the data, letter by letter until we reach EOL + length_str = '' + char = socket.recv(1).decode() + while char != '\n': + length_str += char + char = socket.recv(1).decode() + total = int(length_str) + # use a memoryview to receive the data chunk by chunk efficiently + view = memoryview(bytearray(total)) + next_offset = 0 + while total - next_offset > 0: + recv_size = socket.recv_into(view[next_offset:], total - next_offset) + next_offset += recv_size + try: + deserialized = json.loads(view.tobytes()) + return deserialized + except (TypeError, ValueError): + # raise Exception('Data received was not in JSON format') + try: + deserialized = json.loads(view.tobytes().decode('utf-8')) + return deserialized + except (TypeError, ValueError): + tb = traceback.format_exc() + print("JSON SOCKET ERROR, Data received was not in JSON format") + print(tb) + return {} diff --git a/lang/en_en.qm b/RTOC/lang/en_en.qm similarity index 65% rename from lang/en_en.qm rename to RTOC/lang/en_en.qm index 1f7e3347a4b032306a5d07f2aad39c369ff6640f..d4bb90f9c70cae9e0a51c3840e9d8aefe7486e86 100644 GIT binary patch delta 4710 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 BofrTB delta 2165 zcmaJ?eN(? zjhqO{4y~TEnp%D|i_#I%P(qPNi*%KbqnmE7ZR^Lbx}@PgLVwh$-81LTGrzgN-}^rA z^FHsr?e8;p-(d8lhUbR0U4N(g)6pZ3EZKUvXF8D}646+q$wuI>h{Ql(EDlULvW*uF zYyDujxKjSz%d4@z&hJaif_Fmr&#OYwv+I0!S~AGC4dUc^Mx_tyah=9Q0df2m8&|CN z$Nm|{*WSMYbC>GIO{>~*uxJqDLXGw3784C=GhW(X3zUubjZH)oHwn>w*FbEeF#cvZ z61xQJH#`EB3zK`lLVH?;v_wB7juY&yP+j>%$Ze`33cM)1+bH1tBH`m&6GY7rzDUEq zNjBkX4D2B2uZuAoqlf~Y5^IJ*&cJB#U<2GW#V$6^4u($*V&j~CRPeoctVkw`C=!o9 zOf=CVUbG?6q#F{y>?kTZBL(Ouh%8S?!+k;6SCYy)ro#p$()#nsXwPp3aq<{xNA3_P zTP`(R*aN??Go{1TJK>9)()mySg@bw0)gv(=&??QvYMssPMkjKf9P{Lf0Us z?3DetrXb-va^TA#ZYh+5GGOe`aye4CO%&ZE&#CE!V*BNlCG{BRnevOzJV5K}G8UKhsyMWyyCED;c*Y&nN;5l57*t0AQCZRJEb&dbjz7iN@U zTtk$LS!o#GSCqa3$P@RZazDZVxA!RD-?frX;%#+QB??VSQq#sgIQACcL^#l z)YfHTNVwtJTi@1U60@{zYk_>Dw!3RF%w0E#(Mz<0VX%}Esx=v1@U}&3YJL?7d$eZF z1VuJ!r>~*%h*<4TWEm2FVwznz4g`O1%Dtc?kYK_OVDg8i7p%Z&#ETm(Sev~)%0=P<47EAmd}BprPZ7?eJ;)wnUmTujXvS#jT-jHJTO<4IAMjm=A&;n zLRq)DHKZ5HPBovLbrM!sW$sLcB?3M$U-qPn{-!nNzC&r)pXtEkx5G0(@p11V(HZ2a z^wEs`vw8W?zTgQC+wF}A_hY3g1$p`Ixelkz(~W0qq{XwwXP7rBYO~Q}if#8sj(6~$ zFDIV#$P-Mu1@ALAWs?ivHe6P+JbVe#!#CiNd3nWlhtp!W6}hurZkyX`!Pt_RF@J8p z-EMO+c+=1rnn!t5Om=dRlPo~nK*)S5z~`p{ANf(h{DK0+(|;Z?j4~0*j$qjc96b;| zAAu}bKZlPE*N<6_%yihD*?H0PP_yNEhjUf2-L~{tMRTM57x_m0Bz#6RmTpvUL-b|; lGe__bIWpZYTlTX5pT%pQs&by31)*MU#wk^J4H_93=D!6uLSg^_ diff --git a/lang/en_en.ts b/RTOC/lang/en_en.ts similarity index 68% rename from lang/en_en.ts rename to RTOC/lang/en_en.ts index 90f61f9..c6f7a08 100644 --- a/lang/en_en.ts +++ b/RTOC/lang/en_en.ts @@ -4,47 +4,47 @@ Actions - + Warnung Warning - + Wollen Sie wirklich alle Daten löschen? Do you really want to delete all data? - + (Unwiederrufbar) (Data will be lost) - + Session laden Load session - + Session speichern Save session - + Excel-Tabelle (*.xlsx) Excel-Table (*.xlsx) - + CSV-Datei (*.csv) CSV-File (*.csv) - + Export Export - + Excel-Tabelle (*.xlsx);;CSV-Datei (*.csv) Excel-Table (*.xlsx);;CSV-File (*.csv) @@ -63,12 +63,12 @@ Copyright (C) 2018 Sebastian Keller - + Sprache geändert Language changed - + Bitte Programm neustarten Please restart this software @@ -154,7 +154,7 @@ Copyright (C) 2018 Sebastian Keller EventWidget - + %H:%M:%S %d.%m.%Y %H:%M:%S %Y.%m.%d @@ -162,7 +162,7 @@ Copyright (C) 2018 Sebastian Keller Form - + Form @@ -351,11 +351,6 @@ Copyright (C) 2018 Sebastian Keller plot([y],sname="noName",dname="noDevice",unit="") # X-Werte = range(len(y)) - - - plotLine(text="", sname="noName", dname="noDevice", x=clock) - - {ans} = sendTCP(hostname = "localhost", *args, **kwargs) @@ -456,11 +451,6 @@ Copyright (C) 2018 Sebastian Keller Y-Werte : Gerät.Signal.y Y-Values : device.signal.y - - - clock - - Start @@ -654,12 +644,12 @@ Copyright (C) 2018 Sebastian Keller till - + Zeitpunkt Timepoint - + Inhalt Content @@ -669,12 +659,12 @@ Copyright (C) 2018 Sebastian Keller Device - + Signal Signal - + Filter Filter @@ -683,6 +673,11 @@ Copyright (C) 2018 Sebastian Keller Lösche alle Signale Delete all Events + + + event(text="", sname="noName", dname="noDevice", x=clock, priority=0) + + MainWindow @@ -732,7 +727,7 @@ Copyright (C) 2018 Sebastian Keller Devices with plugins will be shown here - + &Plugins Plugins @@ -752,72 +747,72 @@ Copyright (C) 2018 Sebastian Keller File - + Unterfenster-Darstellung verwalten Arrange View - + &Fenster View - + &Hilfe Help - + Skripte Scripts - + toolBar - + &Daten exportieren Export data - + Bitte nutze RECHTSKLICK auf Plotbereich Please use RIGHTCLICK in plot - + &Beenden Close - + &Plot anpassen Style plot - + Plot-Darstellung Plot-Style - + &Plot exportieren Export plot - + Fenster View - + &Signale Signals - + Signale-Unterfenster Signal-Subwindow @@ -827,57 +822,57 @@ Copyright (C) 2018 Sebastian Keller Devices-Subwindow - + Plugins-Unterfenster Plugin-Subwindow - + S&kript Scripts - + Skripte-Unterfenster Script-Subwindow - + &Skript laden Load script - + Skript aus Datei laden Load script from file - + S&kript speichern Save script - + Skript in Datei speichern Save script to file - + S&ession speichern Save session - + Zuletzt geladen Latest sessions - + Session &laden Load session - + &Hilfe zu KellerLogger Help @@ -887,52 +882,270 @@ Copyright (C) 2018 Sebastian Keller About RTOC - + Daten &importieren Import data - + Minimize to SystemTray - + TCP - Server - + Multiproessing - Listener - + Sprache Language - + Deutsch German - + English English - + Events Events + + + Telegram-Bot + Telegram-Bot + + + + Bot-Token eingeben + Enter Bot-Token + + + + Aktiviert + Activated + + + + NetWoRTOC + + + NetWoRTOC + + + + + Verbinden + Connect + + + + Suchen + Search + + + + RTOC-Server + + + + + 127.0.0.1 + + + + + Aufzeichnungsdauer: + Recordlength: + + + + Ändere die Aufzeichnungsdauer + Change the recordlength of remote-RTOC-Server + + + + Messwerte + Values + + + + Lösche alle Signale + Delete all signals from remote RTOC-Server + + + + Signale + Signals + + + + Samplerate: + + + + + Alle auswählen + Select all + + + + Geräte + Devices + + + + Geräte Funktionen und Parameter + Device functions and paramters + + + + Signal löschen + Delete signal + + + + RTOC-Netzwerksuche + Search RTOC-Servers + + + + Möchten Sie wirklich das Netzwerk nach RTOC-Servern durchsuchen? + Do you really want to search your local network for RTOC-Servers? + + + + Dieser Vorgang wird einige Zeit in Anspruch nehmen + This will take some time + + + + Sucht... + Searching... + + + + Fertig + Finished + + + + RTOC-Suche abgeschlossen + Searching for RTOC-Servers finished + + + + Server gefunden. + server found. + + + + Warnung + Warning + + + + Möchten sie wirklich alle Daten am RTOC-Server löschen? + Do you really want to delete all data from this RTOC-Server? + + + + (Unwiederrufbar) + (Data will be lost) + + + + Remote-Funktion ausführen + Call remote function + + + + an Host + at host + + + + ausführen. + execution. + + + + Funktionsparameter + Functionparameters + + + + Fehler + Failure + + + + Funktionsparameter sind nicht gültig + Submitted functionsparameters are not valid + + + + Bitte geben Sie gültige Parameter an + Please enter valid parameters + + + + Remote-Parameter ändern + change remote-parameter + + + + ändern. + change. + + + + Wert ungültig + Value not valid + + + + Bitte geben Sie einen gültigen Wert an + Please enter a valid value + + + + Beenden + Close + + + + Bitte warten + Please wait + + + + NetWoRTOC sucht gerade nach RTOC-Servern + NetWoRTOC is searching for RTOC-Servers + + + + Bitte warten bis der Vorgang abgeschlossen ist. + Please wait. + RTLogger - + Plugin gestartet: - Plugin startet: + Plugin startet: @@ -940,9 +1153,9 @@ Copyright (C) 2018 Sebastian Keller Plugin stopped: - + Signal-Stream hinzugefügt: - Signal-Stream added: + Signal-Stream added: @@ -950,72 +1163,90 @@ Copyright (C) 2018 Sebastian Keller Signal-Plot added: - + Plugin gestoppt: - Plugin gestoppt: + Plugin stopped: - + Signal-Plot hinzugefügt: - Signal-Plot added: + Signal-Plot added: + + + + Signale + Signals RTOC - + Anzeigen Show - + Beenden Close - + Im Hintergrund laufen Run in background - + TCP Server - + Fehler Failure - + Fehler beim Laden der Geräte GUI Bitte Code überprüfen. Loading device GUI failed. - + Fehler beim Laden des Geräts Bitte stellen Sie sicher, dass das Gerät verbunden ist. Loading device failed. Please make sure the device is connected. - + RealTime OpenControl - + läuft im Hintergrund weiter und zeichnet Messwerte auf is running in background and collecting data - + Speichern Save - + + Wollen Sie die aktuelle Sitzung speichern? + Do you want to save the current session? + + + + RTOC_TCP + + + Speichern + Save + + + Wollen Sie die aktuelle Sitzung speichern? Do you want to save the current session? @@ -1023,12 +1254,12 @@ Bitte stellen Sie sicher, dass das Gerät verbunden ist. RTPlotActions - + Signale Signals - + Vergangene Zeit Elapsed time @@ -1036,7 +1267,7 @@ Bitte stellen Sie sicher, dass das Gerät verbunden ist. RTPlotWidget - + Signale: Signals: @@ -1049,32 +1280,32 @@ Bitte stellen Sie sicher, dass das Gerät verbunden ist. Loading file failed - + Fehler Failure - + Datei File - + nicht gefunden not found - + Unbenannt Unknown - + Skript laden Load script - + Schließen Close @@ -1082,61 +1313,69 @@ Bitte stellen Sie sicher, dass das Gerät verbunden ist. SignalEditWidget - + Info Info - + Wähle zuerst das Schneide-Tool aus Please select Cutting-Tool first - + Du musst zuerst deine Schnittbereich festlegen Please set your Cut-Area first - + CSV-Datei (*.csv) CSV-File (*.csv) - + Export Export - + Umbenennen Rename - + Bitte gib einen neuen Namen an Please enter a new signalname - + Achtung Warning - + Daten werden dauerhaft geändert Signal will be changed permanently - + Ja Yes - + Nein No + + a + + + Signal-Plot hinzugefügt: + Signal-Plot added: + + layout @@ -1348,4 +1587,189 @@ Bitte stellen Sie sicher, dass das Gerät verbunden ist. Signals: 0 + + self.rtoc + + + Plugin gestartet: + Plugin startet: + + + + Plugin gestoppt: + Plugin gestoppt: + + + + Signal-Stream hinzugefügt: + Signal-Stream added: + + + + Signal-Plot hinzugefügt: + Signal-Plot added: + + + + telegram + + + Event-Benachrichtigung festlegen + Event-Notifications + + + + Letzte Messwerte + Latest Values + + + + Signale + Signals + + + + Geräte + Devices + + + + Einstellung angepasst + Settings updated + + + + Signale löschen + Delete signals + + + + Aufzeichnungsdauer ändern + Change recordlength + + + + <-- Zurück + <-- Back + + + + Aufzeichnungsdauer geändert + Recordlength was changed + + + + Fehlerhafte Eingabe + No valid message + + + + Gerät beenden + Stop device + + + + Gerät starten + Start device + + + + Gerätefehler + Device-failure + + + + Funktionen + Functions + + + + Parameter + Parameters + + + + Wenn dein Text aussieht, wie eine JSON, dann kannst du damit später alles machen + You reached the end of this telegram-bot. Well done. Lets get back to the menu ... + + + + Was soll ich dazu sagen ... + Oh no ... + + + + Zeit [s] + Time [s] + + + + Derzeitige Aufzeichnungsdauer: + Current recordlength: + + + + Funktionen + Functions + + + + Bitte gib Parameter an, falls benötigt + Please enter parameters, if needed + + + + Bitte gib einen neuen Wert an. +Derzeitiger Wert: + Please enter a new value. +Current Value: + + + + Fehler + Failure + + + + Hauptmenü + Mainmenu + + + + Alle Benachrichtigungen + All notifications + + + + Warnungen + Warnings + + + + Nur Fehlermeldungen + Only errors + + + + Keine Benachrichtigung + No notifications + + + + <- Zurück + <- Back + + + + Wähle eine Benachrichtigungsstufe aus. Derzeitige Stufe: + + Select a notification level. Current level: + + + + + Keine Messwerte vorhanden + No measurements recorded yet + + diff --git a/plugins/DPS5020.py b/RTOC/plugins/DPS5020.py similarity index 96% rename from plugins/DPS5020.py rename to RTOC/plugins/DPS5020.py index b09faf2..7792eca 100644 --- a/plugins/DPS5020.py +++ b/RTOC/plugins/DPS5020.py @@ -1,8 +1,12 @@ -from LoggerPlugin import LoggerPlugin +try: + from LoggerPlugin import LoggerPlugin +except ImportError: + from ..LoggerPlugin import LoggerPlugin import sys from threading import Thread, Lock import traceback +import os from PyQt5 import uic from PyQt5 import QtWidgets @@ -80,7 +84,8 @@ def updateT(self): def loadGUI(self): self.widget = QtWidgets.QWidget() - uic.loadUi("plugins/DPS5020/dps5020.ui", self.widget) + packagedir, file = os.path.split(os.path.realpath(__file__)) + uic.loadUi(packagedir+"/DPS5020/dps5020.ui", self.widget) # self.setCallbacks() self.widget.pushButton.clicked.connect(self.__openPortCallback) self.widget.currBox.editingFinished.connect(self.setCurrAction) diff --git a/plugins/DPS5020/dps5020.ui b/RTOC/plugins/DPS5020/dps5020.ui similarity index 100% rename from plugins/DPS5020/dps5020.ui rename to RTOC/plugins/DPS5020/dps5020.ui diff --git a/RTOC/plugins/Deneb.py b/RTOC/plugins/Deneb.py new file mode 100644 index 0000000..6bc4eb7 --- /dev/null +++ b/RTOC/plugins/Deneb.py @@ -0,0 +1,156 @@ +try: + from LoggerPlugin import LoggerPlugin +except ImportError: + from ..LoggerPlugin import LoggerPlugin + +import time +from threading import Thread +import traceback +import requests +from PyQt5 import uic +from PyQt5 import QtWidgets +import socket +import threading +import os + +devicename = "Deneb" + +socket_timeout = 3 +HOST, PORT = "192.168.178.71", 1991 + + +def communicate(data, host, port): + global response + try: + response = 'error: received no response' + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(socket_timeout) + sock.connect((host, port)) + sock.sendall(bytes(data + "\n", "utf-8")) + + response = str(sock.recv(1024), "utf-8") + except ConnectionRefusedError: + response = 'error: connection refused by remote host' + except socket.timeout: + response = 'error: connection timed out' + finally: + sock.close() + return 1 + + +def C_client(data, host=HOST, port=PORT): + start_t = time.time() + request_thread = threading.Thread(target=communicate, args=(data, host, port)) + request_thread.start() + while time.time()-start_t < socket_timeout*1.1: + time.sleep(0.001) + if not request_thread.isAlive(): + return response + return "error: connection timed out" + +############################# DO NOT EDIT FROM HERE ################################################ + + +class Plugin(LoggerPlugin): + def __init__(self, stream=None, plot= None, event=None): + # Plugin setup + super(Plugin, self).__init__(stream, plot, event) + self.setDeviceName(devicename) + self.smallGUI = True + + # Data-logger thread + self.run = False # False -> stops thread + self.__updater = Thread(target=self.updateT) # Actualize data + # self.updater.start() + + self.__base_address = "" + self.samplerate = 1 + self.temp_des = 0 + self.__s = requests.Session() + + # THIS IS YOUR THREAD + def updateT(self): + diff = 0 + while self.run: + if diff < 1/self.samplerate: + time.sleep(1/self.samplerate-diff) + start_time = time.time() + name, y = self.deneb_get_all() + name0, y0, devname0 = [],[],"Engine0" + name1, y1, devname1 = [],[],"Engine1" + nameX, yX, devnameX = [],[], "Deneb" + for idx, n in enumerate(name): + if "engine0" in n: + name0.append(n.replace("engine0:","")) + y0.append(y[idx]) + elif "engine1" in n: + name1.append(n.replace("engine1:","")) + y1.append(y[idx]) + else: + nameX.append(n) + yX.append(y[idx]) + if len(y0)>0: + self.stream(y0, name0, devname0, [""]*len(y0)) + if len(y1)>0: + self.stream(y1, name1, devname1, [""]*len(y1)) + if len(yX)>0: + self.stream(yX, nameX, devnameX, [""]*len(yX)) + + diff = (time.time() - start_time) + + def loadGUI(self): + self.widget = QtWidgets.QWidget() + packagedir, file = os.path.split(os.path.realpath(__file__)) + uic.loadUi(packagedir+"/Deneb/deneb.ui", self.widget) + # self.setCallbacks() + self.widget.pushButton.clicked.connect(self.__openConnectionCallback) + self.widget.samplerateSpinBox.valueChanged.connect(self.__changeSamplerate) + self.widget.comboBox.setCurrentText(HOST) + self.__openConnectionCallback() + return self.widget + + def __openConnectionCallback(self): + if self.run: + self.run = False + self.widget.pushButton.setText("Verbinden") + self.__base_address = "" + else: + address = self.widget.comboBox.currentText() + self.__base_address = "http://"+address+"/" + try: + self.deneb_get_all() + ok = True + except: + ok = False + if ok: + self.run = True + self.__updater = Thread(target=self.updateT) + self.__updater.start() + self.widget.pushButton.setText("Beenden") + else: + self.__base_address = "" + self.run = False + self.widget.pushButton.setText("Fehler") + + def deneb_get_all(self): + instring = C_client("parameter-dump;") + data_blocks = instring.split(";") + names = [] + y_values = [] + for d in data_blocks[:-1]: + try: + y_values.append(float(d.split("=")[1])) + names.append(d.split("=")[0]) + except IndexError: + #print(" ### failed to split this string: "+str(d)) + pass + return names, y_values + + def __changeSamplerate(self): + self.samplerate = self.widget.samplerateSpinBox.value() + + +if __name__ == "__main__": + standalone = Plugin() + standalone.setup() diff --git a/RTOC/plugins/Deneb/deneb.ui b/RTOC/plugins/Deneb/deneb.ui new file mode 100644 index 0000000..a01bada --- /dev/null +++ b/RTOC/plugins/Deneb/deneb.ui @@ -0,0 +1,80 @@ + + + Form + + + + 0 + 0 + 224 + 162 + + + + Form + + + + + + + + true + + + + 192.168.178.71 + + + + + + + + Weblink + + + + + + + Hz + + + 2 + + + 0.010000000000000 + + + 99999999.989999994635582 + + + 1.000000000000000 + + + 1.000000000000000 + + + + + + + Samplerate + + + + + + + + + Verbinden + + + + + + + + diff --git a/plugins/Funktionsgenerator/gen_function.ui b/RTOC/plugins/Funktionsgenerator/gen_function.ui similarity index 100% rename from plugins/Funktionsgenerator/gen_function.ui rename to RTOC/plugins/Funktionsgenerator/gen_function.ui diff --git a/RTOC/plugins/Futtertrocknung.py b/RTOC/plugins/Futtertrocknung.py new file mode 100644 index 0000000..66b54a0 --- /dev/null +++ b/RTOC/plugins/Futtertrocknung.py @@ -0,0 +1,128 @@ +try: + from LoggerPlugin import LoggerPlugin +except ImportError: + from ..LoggerPlugin import LoggerPlugin + +import time +from threading import Thread +import Adafruit_DHT +import time +import board +import busio +import adafruit_ccs811 + +devicename = "Futtertrocknung" + +dht22 = Adafruit_DHT.DHT22 +# css811: sudo nano /boot/config.txt for i2c baudrate +i2c = busio.I2C(board.SCL, board.SDA) +ccs1 = adafruit_ccs811.CCS811(i2c) +ccs2 = adafruit_ccs811.CCS811(i2c, 0x5B) + + +class Plugin(LoggerPlugin): + def __init__(self, stream=None, plot= None, event=None): + super(Plugin, self).__init__(stream, plot, event) + self.setDeviceName(devicename) + + self.run = True + self.samplerate = 1 # Function frequency in Hz (1/sec) + self.datanames = ['in2coolCO2', 'in2coolTVOC', 'out2coolCO2', 'out2coolTVOC', 'in2coolTemp', 'in2coolHumid', 'out2coolTemp', 'out2coolHumid'] + self.dataunits = ['ppm', 'ppm','ppm','ppm','°C','%','°C','%'] + self.data = [0,0,0,0,0,0,0,0] + + ccs1t = Thread(target=self.getCCS1Data) + ccs1t.start() + ccs2t = Thread(target=self.getCCS2Data) + ccs2t.start() + + dht22_1 = Thread(target=self.getDHT22_1) + dht22_1.start() + dht22_2 = Thread(target=self.getDHT22_2) + dht22_2.start() + + self.__updater = Thread(target=self.__updateT) # Actualize data + self.__updater.start() + + def __updateT(self): + diff = 0 + self.gen_start = time.time() + while self.run: # All should be inside of this while-loop, because self.run == False should stops this plugin + if diff < 1/self.samplerate: + time.sleep(1/self.samplerate-diff) + start_time = time.time() + devname='Futtertrocknung' + #self.getControllerData() + self.stream(self.data, self.datanames, devname, self.dataunits) + diff = (time.time() - start_time) + + def getCCS1Data(self): + # Wait for the sensor to be ready and calibrate the thermistor + while not ccs1.data_ready: + pass + temp = ccs1.temperature + ccs1.temp_offset = temp - 25.0 + + while self.run: + time.sleep(1/self.samplerate) + try: + self.data[0] = ccs1.eco2 + self.data[1] = ccs1.tvoc + #temp2 = ccs1.temperature + print('reading') + if self.data[0]>2000: + print('event') + self.event('CO2 Gehalt hoch', sname="CO2", dname="Futtertrocknung", priority=2) + except: + print("Error reading CCS811 [1]") + + def getCCS2Data(self): + # Wait for the sensor to be ready and calibrate the thermistor + while not ccs2.data_ready: + pass + temp = ccs2.temperature + ccs2.temp_offset = temp - 25.0 + + while self.run: + time.sleep(1/self.samplerate) + try: + self.data[2] = ccs2.eco2 + self.data[3] = ccs2.tvoc + #temp2 = ccs2.temperature + except: + print("Error reading CCS811 [2]") + + def getDHT22_1(self): + pin = 27 + while self.run: + time.sleep(1/self.samplerate) + humidity, temperature = Adafruit_DHT.read_retry(dht22, pin) + self.data[4] = temperature + self.data[5] = humidity + + def getDHT22_2(self): + pin = 17 + while self.run: + time.sleep(1/self.samplerate) + humidity, temperature = Adafruit_DHT.read_retry(dht22, pin) + self.data[6] = temperature + self.data[7] = humidity + + def getControllerData(self): + self.data['fanVelocity'] = '?' + self.data['fanMode'] = "Druck" + self.data['active'] = False + self.data['fan2heuPressure'] = '?' + self.data['fan2heuVelocity'] = '?' + self.data['fan2heuPressureDes'] = 0 + self.data['fan2heuVelocityDes'] = 0 + self.data['fanManualDes'] = 0 + + def setActive(self, active = True): + pass + + def setMode(self, mode = 0): # manuell, druck, durchfluss + pass + + def setDesired(self, mode, value): + pass diff --git a/plugins/Generator.py b/RTOC/plugins/Generator.py similarity index 93% rename from plugins/Generator.py rename to RTOC/plugins/Generator.py index 5952b84..e0fbd1e 100644 --- a/plugins/Generator.py +++ b/RTOC/plugins/Generator.py @@ -1,4 +1,7 @@ -from LoggerPlugin import LoggerPlugin +try: + from LoggerPlugin import LoggerPlugin +except ImportError: + from ..LoggerPlugin import LoggerPlugin import time import math @@ -6,6 +9,7 @@ from threading import Thread from PyQt5 import uic from PyQt5 import QtWidgets +import os devicename = "Generator" @@ -24,6 +28,7 @@ def __init__(self, stream=None, plot= None, event=None): self.offset = 0 self.phase = 0 self.__olddata = [0] + self.dataY=[0] # Data-logger thread self.run = True # False -> stops thread @@ -55,7 +60,8 @@ def __updateT(self): # loadGUI needs to return a QWidget, which will be implemented into the Plugins-Area def loadGUI(self): self.widget = QtWidgets.QWidget() - uic.loadUi("plugins/Funktionsgenerator/gen_function.ui", self.widget) + packagedir, file = os.path.split(os.path.realpath(__file__)) + uic.loadUi(packagedir+"/Funktionsgenerator/gen_function.ui", self.widget) self.setCallbacks() self.setLabels() return self.widget @@ -67,15 +73,15 @@ def __square(self): self.gen_start = time.time() if time.time() - self.gen_start >= 0.5/self.gen_freq: if self.dataY[0] != self.gen_level: - self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + self.stream(self.dataY, self.datanames, self.devicename, [""]) self.dataY[0] = self.gen_level + self.offset - self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + self.stream(self.dataY, self.datanames, self.devicename, [""]) else: if self.dataY[0] != 0: - self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + self.stream(self.dataY, self.datanames, self.devicename, [""]) self.dataY[0] = 0 + self.offset - self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + self.stream(self.dataY, self.datanames, self.devicename, [""]) def __sawtooth(self): if time.time() - self.gen_start >= 1/self.gen_freq: @@ -83,41 +89,41 @@ def __sawtooth(self): if self.dataY[0] != 0: #self.dataY[0] += self.gen_level*(self.gen_freq)/self.samplerate self.dataY[0] = self.gen_level - self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + self.stream(self.dataY, self.datanames, self.devicename, [""]) self.dataY[0] = 0 + self.offset - self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + self.stream(self.dataY, self.datanames, self.devicename, [""]) else: self.dataY[0] = self.gen_level * \ ((time.time() - self.gen_start*self.gen_freq)) + self.offset - self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + self.stream(self.dataY, self.datanames, self.devicename, [""]) def __sinus(self): self.dataY[0] = self.gen_level * \ math.sin((time.time()*self.gen_freq)*(2*math.pi) + self.phase) + self.offset - self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + self.stream(self.dataY, self.datanames, self.devicename, [""]) def __noise(self): self.dataY[0] = random.uniform(0, self.gen_level) + self.offset - self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + self.stream(self.dataY, self.datanames, self.devicename, [""]) def __ac(self): if time.time() - self.gen_start >= 1/self.gen_freq: self.gen_start = time.time() if time.time() - self.gen_start >= 0.5/self.gen_freq: if self.dataY[0] != self.gen_level: - self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + self.stream(self.dataY, self.datanames, self.devicename, [""]) self.dataY[0] = self.gen_level + self.offset - self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + self.stream(self.dataY, self.datanames, self.devicename, [""]) else: if self.dataY[0] != 0: - self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + self.stream(self.dataY, self.datanames, self.devicename, [""]) self.dataY[0] = -self.gen_level - self.offset - self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + self.stream(self.dataY, self.datanames, self.devicename, [""]) def __dc(self): self.dataY[0] = self.gen_level + self.offset - self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + self.stream(self.dataY, self.datanames, self.devicename, [""]) def setCallbacks(self): #self.connect(self.widget.samplerate, SIGNAL("valueChanged()",self.changeSamplerate)) diff --git a/plugins/Generator2.py b/RTOC/plugins/Generator2.py similarity index 88% rename from plugins/Generator2.py rename to RTOC/plugins/Generator2.py index c46af8f..5fc4b71 100644 --- a/plugins/Generator2.py +++ b/RTOC/plugins/Generator2.py @@ -1,4 +1,7 @@ -from LoggerPlugin import LoggerPlugin +try: + from LoggerPlugin import LoggerPlugin +except ImportError: + from ..LoggerPlugin import LoggerPlugin import time import math @@ -6,6 +9,7 @@ from threading import Thread from PyQt5 import uic from PyQt5 import QtWidgets +import os devicename = "Generator2" @@ -24,7 +28,7 @@ def __init__(self, stream=None, plot= None, event=None): self.offset = 0 self.phase = 0 self.__olddata = [0] - + self.dataY=[0] # Data-logger thread self.run = True # False -> stops thread self.__updater = Thread(target=self.__updateT) # Actualize data @@ -57,7 +61,8 @@ def __updateT(self): # loadGUI needs to return a QWidget, which will be implemented into the Plugins-Area def loadGUI(self): self.widget = QtWidgets.QWidget() - uic.loadUi("plugins/Funktionsgenerator/gen_function.ui", self.widget) + packagedir, file = os.path.split(os.path.realpath(__file__)) + uic.loadUi(packagedir+"/Funktionsgenerator/gen_function.ui", self.widget) self.setCallbacks() self.setLabels() return self.widget @@ -69,56 +74,56 @@ def __square(self): self.gen_start = time.time() if time.time() - self.gen_start >= 0.5/self.gen_freq: if self.dataY[0] != self.gen_level: - self.sendTCP(self.dataY, self.datanames, self.devicename, self.dataunits) + self.sendTCP(self.dataY, self.datanames, self.devicename, [""]) self.dataY[0] = self.gen_level + self.offset - self.sendTCP(self.dataY, self.datanames, self.devicename, self.dataunits) + self.sendTCP(self.dataY, self.datanames, self.devicename, [""]) else: if self.dataY[0] != 0: - self.sendTCP(self.dataY, self.datanames, self.devicename, self.dataunits) + self.sendTCP(self.dataY, self.datanames, self.devicename, [""]) self.dataY[0] = 0 + self.offset - self.sendTCP(self.dataY, self.datanames, self.devicename, self.dataunits) + self.sendTCP(self.dataY, self.datanames, self.devicename, [""]) def __sawtooth(self): if time.time() - self.gen_start >= 1/self.gen_freq: self.gen_start = time.time() if self.dataY[0] != 0: self.dataY[0] = self.gen_level - self.sendTCP(self.dataY, self.datanames, self.devicename, self.dataunits) + self.sendTCP(self.dataY, self.datanames, self.devicename, [""]) self.dataY[0] = 0 + self.offset - self.sendTCP(self.dataY, self.datanames, self.devicename, self.dataunits) + self.sendTCP(self.dataY, self.datanames, self.devicename, [""]) else: self.dataY[0] = self.gen_level * \ ((time.time() - self.gen_start*self.gen_freq)) + self.offset - self.sendTCP(self.dataY, self.datanames, self.devicename, self.dataunits) + self.sendTCP(self.dataY, self.datanames, self.devicename, [""]) def __sinus(self): self.dataY[0] = self.gen_level * \ math.sin((time.time()*self.gen_freq)*(2*math.pi) + self.phase) + self.offset - self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + self.stream(self.dataY, self.datanames, self.devicename, [""]) def __noise(self): self.dataY[0] = random.uniform(0, self.gen_level) + self.offset - self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + self.stream(self.dataY, self.datanames, self.devicename, [""]) def __ac(self): if time.time() - self.gen_start >= 1/self.gen_freq: self.gen_start = time.time() if time.time() - self.gen_start >= 0.5/self.gen_freq: if self.dataY[0] != self.gen_level: - self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + self.stream(self.dataY, self.datanames, self.devicename, [""]) self.dataY[0] = self.gen_level + self.offset - self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + self.stream(self.dataY, self.datanames, self.devicename, [""]) else: if self.dataY[0] != 0: - self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + self.stream(self.dataY, self.datanames, self.devicename, [""]) self.dataY[0] = -self.gen_level - self.offset - self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + self.stream(self.dataY, self.datanames, self.devicename, [""]) def __dc(self): self.dataY[0] = self.gen_level + self.offset - self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + self.stream(self.dataY, self.datanames, self.devicename, [""]) def setCallbacks(self): #self.connect(self.widget.samplerate, SIGNAL("valueChanged()",self.changeSamplerate)) @@ -164,8 +169,13 @@ def setLabels(self): self.widget.phase.setValue(self.phase) -#if __name__ == "__main__": - #standalone = Plugin() +if __name__ == "__main__": + standalone = Plugin() #standalone.sendData([4]) - #standalone.sendTCP([4]) - #standalone.run = False + ans = standalone.sendTCP(plugin={'Generator':{'get':['gen_freq']}}) + print(ans) + ans = standalone.sendTCP(plugin={'Generator':{'gen_freq':10}}) + print(ans) + ans = standalone.sendTCP(plugin={'Generator':{'setLabels()':[]}}) + print(ans) + standalone.run = False diff --git a/RTOC/plugins/Heliotherm.py b/RTOC/plugins/Heliotherm.py new file mode 100644 index 0000000..5096b28 --- /dev/null +++ b/RTOC/plugins/Heliotherm.py @@ -0,0 +1,258 @@ +try: + from LoggerPlugin import LoggerPlugin +except ImportError: + from ..LoggerPlugin import LoggerPlugin +import time +from threading import Thread +import traceback +import requests +from PyQt5 import uic +from PyQt5 import QtWidgets +import socket +import threading +import os + +devicename = "Heliotherm" + +HOST = "192.168.178.72" + +from pymodbus3.client.sync import ModbusTcpClient +from pyModbusTCP.client import ModbusClient + + +############################# DO NOT EDIT FROM HERE ################################################ + +mappingWrite = [ +[100, 'Betriebsart', 1, ''], +[101, 'HKR Soll_Raum', 10, '°C'], +[102, 'HKR Soll', 10, '°C'], +[103, 'HKR Soll aktiv', 1, ''], +[104, 'RLT min Kuehlen', 10, '°C'], +[105, 'WW Normaltemperatur', 10, '°C'], +[106, 'WW Minimaltemperatur', 10, '°C'], +[107, 'MKR1 Betriebsart', 1, ''], +[108, 'MKR1 Soll_Raum', 10, '°C'], +[109, 'MKR1 Soll', 10, '°C'], +[110, 'MKR1 Soll aktiv', 1, ''], +[111, 'MKR1 Kuehlen RLT min.', 10, '°C'], +[112, 'MKR2 Betriebsart', 1, ''], +[113, 'MKR2 Soll_Raum', 10, '°C'], +[114, 'MKR2 Soll', 10, '°C'], +[115, 'MKR2 Soll aktiv', 1, ''], +[116, 'MKR2 Kuehlen RLT min.', 10, '°C'], +[117, 'PV Anf', 1, ''], +[118, 'Unbekannt', 1, ''], +[119, 'Unbekannt', 1, ''], +[120, 'Unbekannt', 1, ''], +[121, 'Unbekannt', 1, ''], +[122, 'Unbekannt', 1, ''], +[123, 'Unbekannt', 1, ''], +[124, 'Unbekannt', 1, ''], +[125, 'Leistungsaufnahmevorgabe', 1, 'W'], +[126, 'Verdichterdrehzahlvorgabe', 1, '%°'], +[127, 'Ext. Anf', 1, ''], +[128, 'Entstoeren', 1, ''], +[129, 'Aussentemperatur Wert', 10, '°C'], +[130, 'Aussentemperatur aktiv', 1, ''], +[131, 'Puffertemperatur Wert', 10, '°C'], +[132, 'Puffertemperatur aktiv', 1, ''], +[133, 'Brauchwassertemperatur Wert', 10, '°C'], +[134, 'Brauchwassertemperatur aktiv', 1, ''], +[135, 'Unbekannt', 10, '°C'], +[136, 'Unbekannt', 10, '°C'], +[137, 'Unbekannt', 10, '°C'], +[138, 'Unbekannt', 10, '°C'], +[139, 'Unbekannt', 10, '°C'], +[140, 'Unbekannt', 10, '°C'], +[141, 'Unbekannt', 10, '°C'], +[142, 'Unbekannt', 10, '°C'], +[143, 'Unbekannt', 10, '°C'], +[144, 'Unbekannt', 10, '°C'], +[145, 'Unbekannt', 10, '°C'], +[146, 'Unbekannt', 10, '°C'], +] + +mappingRead = [ +[10, 'Temperatur Aussen', 10, '°C'], +[11, 'Temperatur Brauchwasser', 10, '°C'], +[12, 'Temperatur Vorlauf', 10, '°C'], +[13, 'Temperatur Ruecklauf', 10, '°C'], +[14, 'Temperatur Pufferspeicher', 10, '°C'], +[15, 'Temperatur EQ_Eintritt', 10, '°C'], +[16, 'Temperatur EQ_Austritt', 10, '°C'], +[17, 'Temperatur Sauggas', 10, '°C'], +[18, 'Temperatur Verdampfung', 10, '°C'], +[19, 'Temperatur Kondensation', 10, '°C'], +[20, 'Temperatur Heissgas', 10, '°C'], +[21, 'Niederdruck', 10, 'Bar'], +[22, 'Hochdruck', 10, 'Bar'], +[23, 'Heizkreispumpe', 1, ''], +[24, 'Pufferladepumpe', 1, ''], +[25, 'Verdichter', 1, ''], +[26, 'Stoerung', 1, ''], +[27, 'Vierwegeventil Luft', 1, ''], +[28, 'WMZ_Durchfluss', 10, 'l/min'], +[29, 'n-Soll Verdichter', 1, '%°'], +[30, 'COP', 10, ''], +[31, 'Temperatur Frischwasser', 10, '°C'], +[32, 'EVU Sperre', 1, ''], +[33, 'Aussentemperatur verzoegert', 10, '°C'], +[34, 'HKR verzoegert', 10, '°C'], +[35, 'MKR1_Solltemperatur', 10, '°C'], +[36, 'MKR2_Solltemperatur', 10, '°C'], +[37, 'EQ-Ventilator', 1, ''], +[38, 'WW-Vorrat', 1, ''], +[39, 'Kühlen UMV passiv', 1, ''], +[40, 'Expansionsventil', 1, '%°'], +[41, 'Verdichteranforderung', 1, ''], +[42, 'Betriebsstunden im WW-Betrieb', 1, 'h'], +[43, 'Unbekannt', 1, ''], +[44, 'Betriebsstunden im HZG-Betrieb', 1, 'h'], +[45, 'Unbekannt', 1, ''], +[46, 'Unbekannt', 1, ''], +[47, 'Unbekannt', 1, ''], +[48, 'Unbekannt', 1, ''], +[49, 'Unbekannt', 1, ''], +[50, 'Unbekannt', 1, ''], +[51, 'Unbekannt', 1, ''], +[52, 'Unbekannt', 1, ''], +[53, 'Unbekannt', 1, ''], +[54, 'Unbekannt', 1, ''], +[55, 'Unbekannt', 1, ''], +[56, 'Unbekannt', 1, ''], +[57, 'Unbekannt', 1, ''], +[58, 'Unbekannt', 1, ''], +[59, 'Unbekannt', 1, ''], +[60, 'WMZ_Heizung', 1, 'kW/h'], +[61, 'Unbekannt', 1, ''], +[62, 'Stromz_Heizung', 1, 'kW/h'], +[63, 'Unbekannt', 1, ''], +[64, 'WMZ_Brauchwasser', 1, 'kW/h'], +[65, 'Unbekannt', 1, ''], +[66, 'Stromz_Brauchwasser', 1, 'kW/h'], +[67, 'Unbekannt', 1, ''], +[68, 'Stromz_Gesamt', 1, 'kW/h'], +[69, 'Unbekannt', 1, ''], +[70, 'Stromz_Leistung', 1, 'W'], +[71, 'Unbekannt', 1, ''], +[72, 'WMZ_Gesamt', 1, 'kW/h'], +[73, 'Unbekannt', 1, ''], +[74, 'WMZ_Leistung', 1, 'kW'], +[75, 'Unbekannt', 1, ''], + +] + +class Plugin(LoggerPlugin): + def __init__(self, stream=None, plot= None, event=None): + # Plugin setup + super(Plugin, self).__init__(stream, plot, event) + self.setDeviceName(devicename) + self.smallGUI = True + + # Data-logger thread + self.run = False # False -> stops thread + self.__updater = Thread(target=self.updateT) # Actualize data + # self.updater.start() + + self.__base_address = "" + self.samplerate = 0.2 + self.temp_des = 0 + self.__s = requests.Session() + + # THIS IS YOUR THREAD + def updateT(self): + diff = 0 + while self.run: + if diff < 1/self.samplerate: + time.sleep(1/self.samplerate-diff) + start_time = time.time() + y, name, units = self.helio_get() + if y is not None: + self.stream(y, name, 'Heliotherm', units) + + diff = (time.time() - start_time) + + def loadGUI(self): + self.widget = QtWidgets.QWidget() + packagedir, file = os.path.split(os.path.realpath(__file__)) + uic.loadUi(packagedir+"/Deneb/deneb.ui", self.widget) + # self.setCallbacks() + self.widget.pushButton.clicked.connect(self.__openConnectionCallback) + self.widget.samplerateSpinBox.valueChanged.connect(self.__changeSamplerate) + self.widget.comboBox.setCurrentText(HOST) + self.__openConnectionCallback() + return self.widget + + def __openConnectionCallback(self): + if self.run: + self.run = False + self.widget.pushButton.setText("Verbinden") + self.__base_address = "" + else: + address = self.widget.comboBox.currentText() + self.__base_address = address + self.c = ModbusClient(host=self.__base_address, port=502, auto_open=True, auto_close=True) + self.c.timeout(10) + #self.helio_get() + self.run = True + self.__updater = Thread(target=self.updateT) + self.__updater.start() + self.widget.pushButton.setText("Beenden") + + def start(self, address): + if self.run: + self.run = False + self.__base_address = "" + else: + self.__base_address = address + self.c = ModbusClient(host=self.__base_address, port=502, auto_open=True, auto_close=True) + self.c.timeout(10) + #self.helio_get() + self.run = True + self.__updater = Thread(target=self.updateT) + self.__updater.start() + + def helio_get(self): + #client = ModbusTcpClient(self.__base_address) + #client.write_coil(1, True) + #result = client.read_coils(0,1) + resultWrite = self.c.read_holding_registers(100, 47) + resultRead = self.c.read_input_registers(10,65) + for idx, d in enumerate(resultRead): + if d>=2 **16/2: + resultRead[idx] = 2 **16 - d + for idx, d in enumerate(resultWrite): + if d>=2 **16/2: + resultWrite[idx] = 2 **16 - d + if resultWrite is not None and resultRead is not None: + y = [] + units = [] + snames = [] + for idx, value in enumerate(resultWrite): + if mappingWrite[idx][1]=='Unbekannt': + #mappingWrite[idx][1] = str(mappingWrite[idx][0]) + pass + else: + snames.append(mappingWrite[idx][1]) + y.append(resultWrite[idx]/mappingWrite[idx][2]) + units.append(mappingWrite[idx][3]) + for idx, value in enumerate(resultRead): + if mappingRead[idx][1]=='Unbekannt': + #mappingRead[idx][1] = str(mappingRead[idx][0]) + pass + else: + snames.append(mappingRead[idx][1]) + y.append(resultRead[idx]/mappingRead[idx][2]) + units.append(mappingRead[idx][3]) + return y, snames, units + else: + self.widget.pushButton.setText("Fehler") + return None, None, None + + def __changeSamplerate(self): + self.samplerate = self.widget.samplerateSpinBox.value() + + +if __name__ == "__main__": + standalone = Plugin() + standalone.setup() diff --git a/RTOC/plugins/Heliotherm/heliotherm.ui b/RTOC/plugins/Heliotherm/heliotherm.ui new file mode 100644 index 0000000..3cc4da5 --- /dev/null +++ b/RTOC/plugins/Heliotherm/heliotherm.ui @@ -0,0 +1,80 @@ + + + Form + + + + 0 + 0 + 224 + 162 + + + + Form + + + + + + + + true + + + + 192.168.178.72 + + + + + + + + Weblink + + + + + + + Hz + + + 2 + + + 0.010000000000000 + + + 99999999.989999994635582 + + + 1.000000000000000 + + + 1.000000000000000 + + + + + + + Samplerate + + + + + + + + + Verbinden + + + + + + + + diff --git a/plugins/HoldPeak VC820.py b/RTOC/plugins/HoldPeak VC820.py similarity index 93% rename from plugins/HoldPeak VC820.py rename to RTOC/plugins/HoldPeak VC820.py index e09a2de..1beb23b 100644 --- a/plugins/HoldPeak VC820.py +++ b/RTOC/plugins/HoldPeak VC820.py @@ -1,10 +1,14 @@ -from LoggerPlugin import LoggerPlugin +try: + from LoggerPlugin import LoggerPlugin +except ImportError: + from ..LoggerPlugin import LoggerPlugin from plugins.holdPeak_VC820.vc820py.vc820 import MultimeterMessage import serial import sys from threading import Thread import traceback +import os from PyQt5 import uic from PyQt5 import QtWidgets @@ -58,8 +62,6 @@ def __openPort(self, portname="COM7"): def updateT(self): while self.run: valid, value, unit = self.get_data() - self.dataY[0] = value - self.dataunits[0] = unit if unit == "V": datanames = ["Spannung"] elif unit == "A": @@ -75,11 +77,12 @@ def updateT(self): else: datanames = [unit] if valid: - self.stream(self.dataY, datanames, self.devicename, self.dataunits) + self.stream(value, datanames, self.devicename, unit) def loadGUI(self): self.widget = QtWidgets.QWidget() - uic.loadUi("plugins/holdPeak_VC820/portSelectWidget.ui", self.widget) + packagedir, file = os.path.split(os.path.realpath(__file__)) + uic.loadUi(packagedir+"/holdPeak_VC820/portSelectWidget.ui", self.widget) # self.setCallbacks() self.widget.pushButton.clicked.connect(self.__openPortCallback) self.__openPortCallback() diff --git a/RTOC/plugins/NetWoRTOC.py b/RTOC/plugins/NetWoRTOC.py new file mode 100644 index 0000000..3dcf6f7 --- /dev/null +++ b/RTOC/plugins/NetWoRTOC.py @@ -0,0 +1,240 @@ +try: + from LoggerPlugin import LoggerPlugin +except ImportError: + from ..LoggerPlugin import LoggerPlugin + +import time +from threading import Thread +import traceback + +from plugins.netWoRTOC.gui import GUI +import data.lib.pyqt_customlib as pyqtlib +from PyQt5.QtCore import QCoreApplication + +translate = QCoreApplication.translate +devicename = "NetWoRTOC" +HOST = "127.0.0.1" + + +class Plugin(LoggerPlugin): + def __init__(self, stream=None, plot= None, event=None): + # Plugin setup + super(Plugin, self).__init__(stream, plot, event) + self.setDeviceName(devicename) + self.smallGUI = False + + # Data-logger thread + self.run = False # False -> stops thread + self.__updater = Thread(target=self.updateT) # Actualize data + # self.updater.start() + + self.samplerate = 1 + self.__siglist = [] + self.__pluglist = [] + self.__eventlist = {} + self.maxLength = 0 + + # THIS IS YOUR THREAD + def updateT(self): + diff = 0 + while self.run: + if diff < 1/self.samplerate: + time.sleep(1/self.samplerate-diff) + start_time = time.time() + ans = self.sendTCP(getSignalList= True, getPluginList = True, logger={'info': True}, getEventList= True) + if ans: + if 'signalList' in ans.keys(): + if ans['signalList'] != self.__siglist: + self.__siglist = ans['signalList'] + self.updateList() + if 'pluginList' in ans.keys(): + if ans['pluginList'] != self.__pluglist: + self.__pluglist = ans['pluginList'] + self.widget.updateDevices.emit(self.__pluglist) + if self.widget.streamButton.isChecked(): + self.plotSignals() + if 'logger' in ans.keys(): + maxLength = ans['logger']['info']['recordLength'] + if maxLength != self.maxLength: + self.maxLength = maxLength + self.widget.maxLengthSpinBox.setValue(self.maxLength) + if 'events' in ans.keys(): + if ans['events'] != self.__eventlist: + #self.widget.updateDevices.emit(self.__eventlist) + self.updateEvents(ans['events']) + diff = time.time() - start_time + + def plotSignals(self): + if self.widget.checkBox.isChecked(): + selection = self.__siglist + selection = [".".join(i) for i in selection] + else: + selection = [] + for o in self.widget.listWidget.selectedItems(): + selection.append(o.text()) + if selection != []: + ans = self.sendTCP(getSignal= selection) + if ans != False: + if 'signals' in ans.keys(): + for sig in ans['signals'].keys(): + signame = sig.split('.') + s = ans['signals'][sig] + if s[2] != []: + u = s[2][-1] + else: + u = '' + self.plot(s[0],s[1],sname = signame[1], dname=signame[0]+"_Remote", unit=u) + + def toggleDevice(self, plugin, button): + state = button.isChecked() + ans = self.sendTCP(plugin={plugin: {'start':state}}) + print(ans) + + def updateList(self): + t = [] + for o in self.widget.listWidget.selectedItems(): + t.append(o.text()) + self.widget.listWidget.clear() + for sig in self.__siglist: + self.widget.listWidget.addItem('.'.join(sig)) + for idx in range(self.widget.listWidget.count()): + sig = self.widget.listWidget.item(idx) + if sig.text() in t: + self.widget.listWidget.item(idx).setSelected(True) + + def updateEvents(self, newEventList): + for dev in newEventList.keys(): + if dev not in self.__eventlist.keys(): + self.__eventlist[dev]=[[],[]] + for idx, ev in enumerate(newEventList[dev][0]): + if ev not in self.__eventlist[dev][0]: + device = dev.split('.') + self.event(x = ev, text = newEventList[dev][1][idx], sname=device[1], dname=device[0]+"_Remote", priority=0) + self.__eventlist = newEventList + + def loadGUI(self): + self.widget = GUI(self) + self.widget.connectButton.clicked.connect(self.__openConnectionCallback) + self.widget.doubleSpinBox.valueChanged.connect(self.__changeSamplerate) + self.widget.singleButton.clicked.connect(self.plotSignals) + self.widget.pluginCallWidget.itemClicked.connect(self.__callPluginFunction) + self.widget.clearButton.clicked.connect(self.__clear) + self.widget.maxLengthSpinBox.editingFinished.connect(self.__resizeLogger) + return self.widget + + def __resizeLogger(self): + ans = self.sendTCP(logger={'resize':self.widget.maxLengthSpinBox.value()}) + if ans: + self.maxLength = self.widget.maxLengthSpinBox.value() + def __clear(self): + ok = pyqtlib.alert_message(translate('NetWoRTOC','Warnung'), translate('NetWoRTOC','Möchten sie wirklich alle Daten am RTOC-Server löschen?'), translate('NetWoRTOC',"(Unwiederrufbar)")) + if ok: + ans = self.sendTCP(logger={'clear': 'all'}) + print(ans) + + def __callPluginFunction(self, strung): + strung = strung.text() + a = strung.split('.') + if len(a) == 2: + plugin = a[0] + function = a[1] + + if function.endswith('()'): + text, ok = pyqtlib.text_message(self.widget, translate('NetWoRTOC','Remote-Funktion ausführen'), strung+translate('NetWoRTOC'," an Host ")+self.tcpaddress+translate('NetWoRTOC'," ausführen."), translate('NetWoRTOC','Funktionsparameter')) + if ok: + self.par = [] + try: + exec('self.par = ['+text+"]") + ans = self.sendTCP(plugin={plugin: {function:self.par}}) + print(ans) + except: + tb = traceback.format_exc() + print(tb) + pyqtlib.info_message(translate('NetWoRTOC','Fehler'), translate('NetWoRTOC','Funktionsparameter sind nicht gültig'), translate('NetWoRTOC',"Bitte geben Sie gültige Parameter an")) + else: + ans = self.sendTCP(plugin={plugin: {'get':[function]}}) + text, ok = pyqtlib.text_message(self.widget, translate('NetWoRTOC','Remote-Parameter ändern'), strung+translate('NetWoRTOC'," an Host ")+self.tcpaddress+translate('NetWoRTOC'," ändern."), str(ans['plugin'][plugin]['get'][0])) + if ok: + self.par = [] + try: + exec('self.par = '+text) + ans = self.sendTCP(plugin={plugin: {function:self.par}}) + print(ans) + except: + tb = traceback.format_exc() + print(tb) + pyqtlib.info_message(translate('NetWoRTOC','Fehler'), translate('NetWoRTOC','Wert ungültig'), translate('NetWoRTOC',"Bitte geben Sie einen gültigen Wert an")) + else: + print(a) + # self.widget.pluginCallWidget.clearSelection() + + def __openConnectionCallback(self): + if self.run: + self.run = False + self.widget.connectButton.setText(translate('NetWoRTOC',"Verbinden")) + self.__clearAll() + else: + address = self.widget.comboBox.currentText() + self.createTCPClient(address) + self.run = True + try: + ok = self.sendTCP() + if ok != False: + ok = True + except: + ok = False + if ok: + self.run = True + self.__updater = Thread(target=self.updateT) + self.__updater.start() + self.widget.connectButton.setText(translate('NetWoRTOC',"Beenden")) + else: + self.__base_address = "" + self.run = False + self.widget.connectButton.setText(translate('NetWoRTOC',"Fehler")) + self.__clearAll() + self.enableGUI(self.run) + + def __clearAll(self): + for i in reversed(range(self.widget.deviceLayout.count())): + self.widget.deviceLayout.itemAt(i).widget().setParent(None) + self.widget.listWidget.clear() + self.widget.pluginCallWidget.clear() + self.__siglist = [] + self.__pluglist = [] + self.maxLength = 0 + self.widget.maxLengthSpinBox.setValue(self.maxLength) + self.widget.streamButton.setChecked(False) + + def enableGUI(self, value = True): + self.widget.groupBox_2.setEnabled(value) + self.widget.groupBox.setEnabled(value) + self.widget.groupBox_3.setEnabled(value) + self.widget.maxLengthSpinBox.setEnabled(value) + self.widget.clearButton.setEnabled(value) + + def start(self, address): + if self.run: + self.run = False + else: + self.createTCPClient(address) + self.run = True + try: + ok = self.sendTCP() + if ok != False: + ok = True + except: + ok = False + if ok: + self.run = True + self.__updater = Thread(target=self.updateT) + self.__updater.start() + self.widget.connectButton.setText(translate('NetWoRTOC',"Beenden")) + else: + self.__base_address = "" + self.run = False + self.widget.connectButton.setText(translate('NetWoRTOC',"Fehler")) + + + def __changeSamplerate(self): + self.samplerate = self.widget.doubleSpinBox.value() diff --git a/plugins/OctoTouch.py b/RTOC/plugins/OctoTouch.py similarity index 93% rename from plugins/OctoTouch.py rename to RTOC/plugins/OctoTouch.py index 0339178..9f53271 100644 --- a/plugins/OctoTouch.py +++ b/RTOC/plugins/OctoTouch.py @@ -1,9 +1,13 @@ -from LoggerPlugin import LoggerPlugin +try: + from LoggerPlugin import LoggerPlugin +except ImportError: + from ..LoggerPlugin import LoggerPlugin import time from threading import Thread from PyQt5 import uic from PyQt5 import QtWidgets +import os from plugins.Octotouch.OctoprintApi import OctoprintAPI @@ -49,7 +53,8 @@ def updateT(self): def loadGUI(self): self.widget = QtWidgets.QWidget() - uic.loadUi("plugins/Octotouch/octotouch.ui", self.widget) + packagedir, file = os.path.split(os.path.realpath(__file__)) + uic.loadUi(packagedir+"/Octotouch/octotouch.ui", self.widget) # self.setCallbacks() self.widget.pushButton.clicked.connect(self.__openConnectionCallback) self.widget.spinBox0.valueChanged.connect(self.__setTempDes0) diff --git a/plugins/Octotouch/OctoprintApi.py b/RTOC/plugins/Octotouch/OctoprintApi.py similarity index 99% rename from plugins/Octotouch/OctoprintApi.py rename to RTOC/plugins/Octotouch/OctoprintApi.py index 58f4419..7f98bdb 100644 --- a/plugins/Octotouch/OctoprintApi.py +++ b/RTOC/plugins/Octotouch/OctoprintApi.py @@ -306,14 +306,14 @@ def move(self, **kwargs): else: self.send_gcode("G91") code = "" - if x != None: + if x is not None: code = code+" X"+str(x) - if y != None: + if y is not None: code = code+" Y"+str(y) - if z != None: + if z is not None: code = code+" Z"+str(z) if code != "": - if feedrate != None: + if feedrate is not None: code = code+" F"+str(feedrate) return self.send_gcode("G1"+code) return "No direction specified" diff --git a/RTOC/plugins/Octotouch/__pycache__/OctoprintApi.cpython-36.pyc b/RTOC/plugins/Octotouch/__pycache__/OctoprintApi.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2158e577e30d790614daf623181dd8fe3120a189 GIT binary patch literal 13145 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 literal 0 HcmV?d00001 diff --git a/plugins/Octotouch/octotouch.ui b/RTOC/plugins/Octotouch/octotouch.ui similarity index 100% rename from plugins/Octotouch/octotouch.ui rename to RTOC/plugins/Octotouch/octotouch.ui diff --git a/RTOC/plugins/Reflow/reflow.ui b/RTOC/plugins/Reflow/reflow.ui new file mode 100644 index 0000000..1d777c4 --- /dev/null +++ b/RTOC/plugins/Reflow/reflow.ui @@ -0,0 +1,102 @@ + + + Form + + + + 0 + 0 + 224 + 162 + + + + Form + + + + + + + + true + + + + reflowofen + + + + + reflowplatte + + + + + + + + Solltemp: + + + + + + + Weblink + + + + + + + °C + + + 270 + + + + + + + Hz + + + 2 + + + 0.010000000000000 + + + 99999999.989999994635582 + + + 1.000000000000000 + + + 1.000000000000000 + + + + + + + Samplerate + + + + + + + + + Verbinden + + + + + + + + diff --git a/RTOC/plugins/ReflowOfen.py b/RTOC/plugins/ReflowOfen.py new file mode 100644 index 0000000..61a78d8 --- /dev/null +++ b/RTOC/plugins/ReflowOfen.py @@ -0,0 +1,138 @@ +try: + from LoggerPlugin import LoggerPlugin +except ImportError: + from ..LoggerPlugin import LoggerPlugin + +import os +import time +from threading import Thread +import traceback +import requests +from PyQt5 import uic +from PyQt5 import QtWidgets +import os + +devicename = "ReflowOfen" + + +class Plugin(LoggerPlugin): + def __init__(self, stream=None, plot= None, event=None): + # Plugin setup + super(Plugin, self).__init__(stream, plot, event) + self.setDeviceName(devicename) + self.smallGUI = True + + self.dataY = [0, 0] + self.datanames = ["Temperatur", "SollTemp"] + self.dataunits = ["°C", "°C"] + + self.__base_address = "" + self.samplerate = 1 + self.temp_des = 0 + self.__s = requests.Session() + + # Data-logger thread + self.run = False # False -> stops thread + self.__updater = Thread(target=self.updateT) # Actualize data + # self.updater.start() + + # THIS IS YOUR THREAD + def updateT(self): + diff = 0 + while self.run: + if diff < 1/self.samplerate: + time.sleep(1/self.samplerate-diff) + start_time = time.time() + valid, values = self.__get_data() + if valid: + self.dataY = values + self.temp_des = values[1] + self.widget.spinBox.setValue(self.temp_des) + self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + + diff = (time.time() - start_time) + + def loadGUI(self): + self.widget = QtWidgets.QWidget() + packagedir, file = os.path.split(os.path.realpath(__file__)) + uic.loadUi(packagedir+"/Reflow/reflow.ui", self.widget) + # self.setCallbacks() + self.widget.pushButton.clicked.connect(self.__openConnectionCallback) + self.widget.spinBox.valueChanged.connect(self.__setTempDes) + self.widget.samplerateSpinBox.valueChanged.connect(self.__changeSamplerate) + self.widget.comboBox.setCurrentText("reflowofen") + self.__openConnectionCallback() + return self.widget + + def __openConnection(self, address): + ok, test = self.__get("getTemp") + return ok + + def __openConnectionCallback(self): + if self.run: + self.run = False + self.widget.pushButton.setText("Verbinden") + self.__base_address = "" + else: + address = self.widget.comboBox.currentText() + self.__base_address = "http://"+address+"/" + if self.__openConnection(address): + self.run = True + self.__updater.start() + self.widget.pushButton.setText("Beenden") + else: + self.__base_address = "" + self.run = False + self.widget.pushButton.setText("Fehler") + + def __get_data(self): + ok, temp = self.__get("getTemp") + ok2, tempDes = self.__get("getTempDes") + if temp != "<70": + temp = float(temp) + else: + temp = 0 + tempDes = float(tempDes) + return ok and ok2, [temp, tempDes] + + def __post(self, data, adress="", noerror=204): + try: + r = self.__s.post(self.__base_address + adress, json=data) + if r.status_code != noerror: + raise Exception( + "Error: {code} - {content}".format(code=r.status_code, content=r.content.decode('utf-8'))) + return True, r + except: + tb = traceback.format_exc() + print(tb) + print("TRACEBACK HAS BEEN IGNORED. HTTP-POST FAILED") + return False, "Error posting "+str(adress) + return False, r + + def __get(self, path=""): + try: + r = self.__s.get(self.__base_address + str(path)) + data = r.content.decode('utf-8') + #io = StringIO(data) + io = data + #io = json.load(io) + #io = json.loads(data) + return True, io + except: + tb = traceback.format_exc() + print(tb) + print("TRACEBACK HAS BEEN IGNORED. HTTP-GET FAILED") + return False, "Error getting "+str(path) + + def __changeSamplerate(self): + self.samplerate = self.widget.samplerateSpinBox.value() + + def __setTempDes(self): + if self.temp_des != self.widget.spinBox.value(): + self.temp_des = self.widget.spinBox.value() + self.__get("?manual=1&temp_des="+str(self.temp_des)) + + +if __name__ == "__main__": + standalone = Plugin() + standalone.setup() diff --git a/RTOC/plugins/ReflowPlatte.py b/RTOC/plugins/ReflowPlatte.py new file mode 100644 index 0000000..8cf3cd2 --- /dev/null +++ b/RTOC/plugins/ReflowPlatte.py @@ -0,0 +1,138 @@ +try: + from LoggerPlugin import LoggerPlugin +except ImportError: + from ..LoggerPlugin import LoggerPlugin + +import time +from threading import Thread +import traceback +import requests + +from PyQt5 import uic +from PyQt5 import QtWidgets +import os + +devicename = "ReflowPlatte" + + +class Plugin(LoggerPlugin): + def __init__(self, stream=None, plot= None, event=None): + # Plugin setup + super(Plugin, self).__init__(stream, plot, event) + self.setDeviceName(devicename) + self.smallGUI = True + + self.dataY = [0, 0] + self.datanames = ["Temperatur", "SollTemp"] + self.dataunits = ["°C", "°C"] + + self.__base_address = "" + self.samplerate = 1 + self.temp_des = 0 + self.__s = requests.Session() + + # Data-logger thread + self.run = False # False -> stops thread + self.__updater = Thread(target=self.updateT) # Actualize data + # self.updater.start() + + # THIS IS YOUR THREAD + def updateT(self): + diff = 0 + while self.run: + if diff < 1/self.samplerate: + time.sleep(1/self.samplerate-diff) + start_time = time.time() + valid, values = self.__get_data() + if valid: + self.dataY = values + self.temp_des = values[1] + self.widget.spinBox.setValue(self.temp_des) + self.stream(self.dataY, self.datanames, self.devicename, self.dataunits) + + diff = (time.time() - start_time) + + def loadGUI(self): + self.widget = QtWidgets.QWidget() + packagedir, file = os.path.split(os.path.realpath(__file__)) + uic.loadUi(packagedir+"/Reflow/reflow.ui", self.widget) + # self.setCallbacks() + self.widget.pushButton.clicked.connect(self.__openConnectionCallback) + self.widget.spinBox.valueChanged.connect(self.__setTempDes) + self.widget.samplerateSpinBox.valueChanged.connect(self.__changeSamplerate) + self.widget.comboBox.setCurrentText("reflowplatte") + self.__openConnectionCallback() + return self.widget + + def __openConnection(self, address): + ok, test = self.__get("getTemp") + return ok + + def __openConnectionCallback(self): + if self.run: + self.run = False + self.widget.pushButton.setText("Verbinden") + self.__base_address = "" + else: + address = self.widget.comboBox.currentText() + self.__base_address = "http://"+address+"/" + if self.__openConnection(address): + self.run = True + self.__updater.start() + self.widget.pushButton.setText("Beenden") + else: + self.__base_address = "" + self.run = False + self.widget.pushButton.setText("Fehler") + + def __get_data(self): + ok, temp = self.__get("getTemp") + ok2, tempDes = self.__get("getTempDes") + if temp != "<70": + temp = float(temp) + else: + temp = 0 + tempDes = float(tempDes) + return ok and ok2, [temp, tempDes] + + def __post(self, data, adress="", noerror=204): + try: + r = self.__s.post(self.__base_address + adress, json=data) + if r.status_code != noerror: + raise Exception( + "Error: {code} - {content}".format(code=r.status_code, content=r.content.decode('utf-8'))) + return True, r + except: + tb = traceback.format_exc() + # print(tb) + print("TRACEBACK HAS BEEN IGNORED. HTTP-POST FAILED") + return False, "Error posting "+str(adress) + return False, r + + def __get(self, path=""): + try: + r = self.__s.get(self.__base_address + str(path)) + data = r.content.decode('utf-8') + #io = StringIO(data) + io = data + #io = json.load(io) + #io = json.loads(data) + return True, io + except: + tb = traceback.format_exc() + # print(tb) + print("TRACEBACK HAS BEEN IGNORED. HTTP-GET FAILED") + return False, "Error getting "+str(path) + + def __changeSamplerate(self): + self.samplerate = self.widget.samplerateSpinBox.value() + + def __setTempDes(self): + if self.temp_des != self.widget.spinBox.value(): + self.temp_des = self.widget.spinBox.value() + self.__get("?manual=1&temp_des="+str(self.temp_des)) + + +if __name__ == "__main__": + standalone = Plugin() + standalone.setup() diff --git a/plugins/System.py b/RTOC/plugins/System.py similarity index 98% rename from plugins/System.py rename to RTOC/plugins/System.py index 371c79d..fd356d8 100644 --- a/plugins/System.py +++ b/RTOC/plugins/System.py @@ -1,5 +1,8 @@ from __future__ import print_function -from LoggerPlugin import LoggerPlugin +try: + from LoggerPlugin import LoggerPlugin +except ImportError: + from ..LoggerPlugin import LoggerPlugin from threading import Thread from PyQt5 import uic @@ -39,7 +42,7 @@ def __init__(self, name, stream, updateDEF = None, staticDEF=None): self.__dict2QTable(self.staticDEF()) def update(self): - if self.run != None: + if self.run is not None: if self.updateDEF: d = self.updateDEF() self.__dict2QTable(d) @@ -69,7 +72,7 @@ def toggleUpdate(self, checkbox_state): self.run = True def __dict2QTable(self, data): - if data != None: + if data is not None: for r, key in enumerate(data.keys()): if self.table.rowCount()<=r: self.table.insertRow(r) @@ -118,7 +121,8 @@ def getData(self): def loadGUI(self): self.widget = QtWidgets.QWidget() - uic.loadUi("plugins/System/system.ui", self.widget) + packagedir, file = os.path.split(os.path.realpath(__file__)) + uic.loadUi(packagedir+"/System/system.ui", self.widget) #self.setCallbacks() self.tables = [] self.tables.append(toggleTable('Disk Partitions', self.stream, getDiskPartitions,None)) @@ -511,7 +515,7 @@ def setStyleSheet(app, myapp): #tb = traceback.format_exc() #print(tb) print("New Style not installed") - with open("data/ui/darkmode.html", 'r') as myfile: + with open("/data/ui/darkmode.html", 'r') as myfile: stylesheet = myfile.read().replace('\n', '') app.setStyleSheet(stylesheet) return app, myapp diff --git a/plugins/System/system.ui b/RTOC/plugins/System/system.ui similarity index 100% rename from plugins/System/system.ui rename to RTOC/plugins/System/system.ui diff --git a/plugins/Template.py b/RTOC/plugins/Template.py similarity index 83% rename from plugins/Template.py rename to RTOC/plugins/Template.py index 6b425cf..5520e12 100644 --- a/plugins/Template.py +++ b/RTOC/plugins/Template.py @@ -1,4 +1,7 @@ -from LoggerPlugin import LoggerPlugin +try: + from LoggerPlugin import LoggerPlugin +except ImportError: + from ..LoggerPlugin import LoggerPlugin import os import sys @@ -32,7 +35,8 @@ def updateT(self): def loadGUI(self): # Only needed for Plugin-GUI self.widget = QtWidgets.QWidget() - uic.loadUi("plugins/Template/template.ui", self.widget) + packagedir, file = os.path.split(os.path.realpath(__file__)) + uic.loadUi(packagedir+"/Template/template.ui", self.widget) return self.widget diff --git a/plugins/Template/template.ui b/RTOC/plugins/Template/template.ui similarity index 100% rename from plugins/Template/template.ui rename to RTOC/plugins/Template/template.ui diff --git a/plugins/holdPeak_VC820/portSelectWidget.ui b/RTOC/plugins/holdPeak_VC820/portSelectWidget.ui similarity index 100% rename from plugins/holdPeak_VC820/portSelectWidget.ui rename to RTOC/plugins/holdPeak_VC820/portSelectWidget.ui diff --git a/plugins/holdPeak_VC820/vc820py/.gitignore b/RTOC/plugins/holdPeak_VC820/vc820py/.gitignore similarity index 100% rename from plugins/holdPeak_VC820/vc820py/.gitignore rename to RTOC/plugins/holdPeak_VC820/vc820py/.gitignore diff --git a/plugins/holdPeak_VC820/vc820py/README.md b/RTOC/plugins/holdPeak_VC820/vc820py/README.md similarity index 100% rename from plugins/holdPeak_VC820/vc820py/README.md rename to RTOC/plugins/holdPeak_VC820/vc820py/README.md diff --git a/plugins/holdPeak_VC820/vc820py/generate_test.py b/RTOC/plugins/holdPeak_VC820/vc820py/generate_test.py similarity index 100% rename from plugins/holdPeak_VC820/vc820py/generate_test.py rename to RTOC/plugins/holdPeak_VC820/vc820py/generate_test.py diff --git a/plugins/holdPeak_VC820/vc820py/raw2json.py b/RTOC/plugins/holdPeak_VC820/vc820py/raw2json.py similarity index 100% rename from plugins/holdPeak_VC820/vc820py/raw2json.py rename to RTOC/plugins/holdPeak_VC820/vc820py/raw2json.py diff --git a/plugins/holdPeak_VC820/vc820py/rawtime2csv.py b/RTOC/plugins/holdPeak_VC820/vc820py/rawtime2csv.py similarity index 100% rename from plugins/holdPeak_VC820/vc820py/rawtime2csv.py rename to RTOC/plugins/holdPeak_VC820/vc820py/rawtime2csv.py diff --git a/plugins/holdPeak_VC820/vc820py/rawtime2json.py b/RTOC/plugins/holdPeak_VC820/vc820py/rawtime2json.py similarity index 100% rename from plugins/holdPeak_VC820/vc820py/rawtime2json.py rename to RTOC/plugins/holdPeak_VC820/vc820py/rawtime2json.py diff --git a/plugins/holdPeak_VC820/vc820py/rawtime2raw.py b/RTOC/plugins/holdPeak_VC820/vc820py/rawtime2raw.py similarity index 100% rename from plugins/holdPeak_VC820/vc820py/rawtime2raw.py rename to RTOC/plugins/holdPeak_VC820/vc820py/rawtime2raw.py diff --git a/plugins/holdPeak_VC820/vc820py/read_from_serial.py b/RTOC/plugins/holdPeak_VC820/vc820py/read_from_serial.py similarity index 100% rename from plugins/holdPeak_VC820/vc820py/read_from_serial.py rename to RTOC/plugins/holdPeak_VC820/vc820py/read_from_serial.py diff --git a/plugins/holdPeak_VC820/vc820py/read_multiple.py b/RTOC/plugins/holdPeak_VC820/vc820py/read_multiple.py similarity index 100% rename from plugins/holdPeak_VC820/vc820py/read_multiple.py rename to RTOC/plugins/holdPeak_VC820/vc820py/read_multiple.py diff --git a/plugins/holdPeak_VC820/vc820py/read_multiple_reader.py b/RTOC/plugins/holdPeak_VC820/vc820py/read_multiple_reader.py similarity index 100% rename from plugins/holdPeak_VC820/vc820py/read_multiple_reader.py rename to RTOC/plugins/holdPeak_VC820/vc820py/read_multiple_reader.py diff --git a/plugins/holdPeak_VC820/vc820py/read_reader.py b/RTOC/plugins/holdPeak_VC820/vc820py/read_reader.py similarity index 100% rename from plugins/holdPeak_VC820/vc820py/read_reader.py rename to RTOC/plugins/holdPeak_VC820/vc820py/read_reader.py diff --git a/plugins/holdPeak_VC820/vc820py/reader.py b/RTOC/plugins/holdPeak_VC820/vc820py/reader.py similarity index 100% rename from plugins/holdPeak_VC820/vc820py/reader.py rename to RTOC/plugins/holdPeak_VC820/vc820py/reader.py diff --git a/plugins/holdPeak_VC820/vc820py/record_serial.py b/RTOC/plugins/holdPeak_VC820/vc820py/record_serial.py similarity index 100% rename from plugins/holdPeak_VC820/vc820py/record_serial.py rename to RTOC/plugins/holdPeak_VC820/vc820py/record_serial.py diff --git a/plugins/holdPeak_VC820/vc820py/replay_rawtime.py b/RTOC/plugins/holdPeak_VC820/vc820py/replay_rawtime.py similarity index 100% rename from plugins/holdPeak_VC820/vc820py/replay_rawtime.py rename to RTOC/plugins/holdPeak_VC820/vc820py/replay_rawtime.py diff --git a/plugins/holdPeak_VC820/vc820py/ut61b.py b/RTOC/plugins/holdPeak_VC820/vc820py/ut61b.py similarity index 100% rename from plugins/holdPeak_VC820/vc820py/ut61b.py rename to RTOC/plugins/holdPeak_VC820/vc820py/ut61b.py diff --git a/plugins/holdPeak_VC820/vc820py/vc820.py b/RTOC/plugins/holdPeak_VC820/vc820py/vc820.py similarity index 100% rename from plugins/holdPeak_VC820/vc820py/vc820.py rename to RTOC/plugins/holdPeak_VC820/vc820py/vc820.py diff --git a/RTOC/plugins/netWoRTOC/__pycache__/gui.cpython-36.pyc b/RTOC/plugins/netWoRTOC/__pycache__/gui.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8347eb288e7b77c5a09a39a830a1a9dd0f310404 GIT binary patch literal 4809 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 literal 0 HcmV?d00001 diff --git a/RTOC/plugins/netWoRTOC/__pycache__/networkscan.cpython-36.pyc b/RTOC/plugins/netWoRTOC/__pycache__/networkscan.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d51ce34ceebb29d293bd81ef3cb157e21ee228a2 GIT binary patch literal 2927 zcmZ8jO>Y~=8J?N_kCmORmb)k6^UQY8HcEznDQD{?6M8+yrHbJDH76sRxl^UhKflO^VzcW2(++4q^} zeTJ_rF9(19_kVx&=Q?9wv4zWq|8unB?=X@{p0ak;^ENm4qAg5swXONQkk%vCwxul{ z^p3nBJ?THPV#;j*e*~A)KQtbOG++s&zY4;q7kv%aM(*8tn zHs(EdY|$Fq?4ZFJ{eB7WeY9;R1=e*&w}TMZMiy=*gFWM4%{!{o4|~~8bZ~WXM8(B0 zEsOc6ip!xIR3VywyxF#MyraprTa+>zmQ7FFSso9x$O`Sr_@luvO|`WbmpUkhJGsib zaZ%`?`!MeAN8Juh=6K9*J7)2AZN8%RHg3JQ`HQ#S(jv+K$kZ|*dGg%d`|sTRAWw&T z$)NaP5SQDT8G-k4ruK_&XVA)zv`b$VE1#fTgJE8s3*ieNR#*Y|d4s!Ltzf20({#~3 zSZD_Sm*JGGM~$Ni>;a=4kTzip&z>@AU1Ads-YRmjiwy>DuFioLW$v;QF3rw@_K9KWn+>YMZymem(_ujerX0-A8-5YOf zubT}9aktb<0G9#>oqnvHZkiQwQ)pf&luw14flf7Wt7{03`bSZchqx_>Z_HWc0j~oS zbqRMT|AXn$*U&`>I%ot;@gJCyfrPx|Q-A_$@(EE-Tmq$^X7B*X)2F_D>QAf^BqiX8 zuv7Ni)1E(OkNK{Uj&vXSr8{O*zJWHe4=;{wlu~`-jGdlG@E)Yw?C|;0m6r6Tg34^- z97HcKXvNS4^QG7M*^t0ih471&k3M)Kf*um6m`r24GU_xhbYnjzJ?mS(8;;yRquSoJLgkF4KZnQS&2+NN>C zLG2WInv~kfRWc}5h$!v6JB)`Qxay#?5r8C0Mf}z7Zl?MjS zk!ik(wI^&CVfvU1rIrR?NN-5haFr4>?8<~UA~ zb(QTl8?4#%9&bNT?dtd=Jmxs;{o!-EzI^TEF%|?C{uvDFrx;RK$ryZ|f~PG|5}Z;h zQz(D{l$q70x=w*6_cH*NDgct*Xqf*R^9AL{I0>vU7&PFk4UC=q4am>8p4dtEN zEWNP{aE1DPX-OBc02(6hRkI+OPtQPF@zlb+KNH+LkVyJpQQK7C#+MtCRdo9tda&*Xd1u6!kmF zAc{r-q@Oe#*)+KkE~o;%LfwLCo>#Pg6`4Zv=;Sm4T4hB=Z_25R56d*!sVGMEP$hS| z8lB=%Wb)TmT9j>%z88+>sY!l;a%a5ruk|xIOylcxLKK%_pg`5n^4I-(@Pdz|eBTd( P3#3ji=4_-2W#az_$?TO^ literal 0 HcmV?d00001 diff --git a/RTOC/plugins/netWoRTOC/gui.py b/RTOC/plugins/netWoRTOC/gui.py new file mode 100644 index 0000000..d0c5a18 --- /dev/null +++ b/RTOC/plugins/netWoRTOC/gui.py @@ -0,0 +1,122 @@ +from PyQt5 import uic +from PyQt5 import QtWidgets +from PyQt5 import QtCore +from PyQt5 import QtGui +from functools import partial +import os +from threading import Thread + +import plugins.netWoRTOC.networkscan as networkscan +import data.lib.pyqt_customlib as pyqtlib +from PyQt5.QtCore import QCoreApplication + +import nmap +import socket + +translate = QCoreApplication.translate +HOST = "127.0.0.1" + +class GUI(QtWidgets.QWidget): + updateDevices = QtCore.pyqtSignal(dict) + updateHostListCallback = QtCore.pyqtSignal() + + def __init__(self, selfself): + super(GUI, self).__init__() + packagedir, file = os.path.split(os.path.realpath(__file__)) + uic.loadUi(packagedir+"/networtoc.ui", self) + # self.setCallbacks() + self.self = selfself + #self.connectButton.clicked.connect(self.self.__openConnectionCallback) + #self.doubleSpinBox.valueChanged.connect(self.self.__changeSamplerate) + self.hostlist = [HOST] + self.comboBox.setCurrentText(HOST) + #self.singleButton.clicked.connect(self.self.plotSignals) + # self.widget.updateDevices = QtCore.pyqtSignal() + self.updateDevices.connect(self.updateDeviceList, QtCore.Qt.QueuedConnection) + self.updateHostListCallback.connect(self.updateHostList, QtCore.Qt.QueuedConnection) + #self.__openConnectionCallback() + self.searchButton.clicked.connect(self.getRTOCServerList) + + self.listWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.listWidget.customContextMenuRequested.connect(self.listItemRightClicked) + + self.checkBox.stateChanged.connect(self.toggleCheckAll) + + def toggleCheckAll(self, state): + for idx in range(self.listWidget.count()): + self.listWidget.item(idx).setSelected(state) + + def listItemRightClicked(self, QPos): + self.listMenu= QtGui.QMenu() + menu_item = self.listMenu.addAction(translate('NetWoRTOC',"Signal löschen")) + menu_item.triggered.connect(self.menuItemClicked) + parentPosition = self.listWidget.mapToGlobal(QtCore.QPoint(0, 0)) + self.listMenu.move(parentPosition + QPos) + self.listMenu.show() + + def menuItemClicked(self): + currentItemName=str(self.listWidget.currentItem().text() ) + ans = self.self.sendTCP(logger={'clear': [currentItemName]}) + + def updateDeviceList(self, dict): + for i in reversed(range(self.deviceLayout.count())): + self.deviceLayout.itemAt(i).widget().setParent(None) + self.pluginCallWidget.clear() + for sig in dict.keys(): + button = QtWidgets.QToolButton() + button.setText(sig) + button.setCheckable(True) + if dict[sig]['status'] == True: + button.setChecked(True) + for element in dict[sig]['functions']: + self.pluginCallWidget.addItem(sig+"."+element+'()') + for element in dict[sig]['parameters']: + self.pluginCallWidget.addItem(sig+"."+element) + elif dict[sig]['status'] != False: + button.setToolTip(str(dict[sig]['status'])) + sizePolicy = QtGui.QSizePolicy( + QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + button.setSizePolicy(sizePolicy) + + # button.setCheckable(True) + button.clicked.connect(partial(self.self.toggleDevice, sig, button)) + self.deviceLayout.addWidget(button) #self.widget.listWidget.addItem('.'.join(sig)) + + def getRTOCServerList(self): + if self.searchButton.text() != translate('NetWoRTOC','Sucht...'): + ok = pyqtlib.alert_message(translate('NetWoRTOC',"RTOC-Netzwerksuche"), translate('NetWoRTOC',"Möchten Sie wirklich das Netzwerk nach RTOC-Servern durchsuchen?"), translate('NetWoRTOC',"Dieser Vorgang wird einige Zeit in Anspruch nehmen")) + if ok: + t = Thread(target=self.searchThread) + t.start() + self.searchButton.setText(translate('NetWoRTOC','Sucht...')) + else: + pyqtlib.info_message(translate('NetWoRTOC',"Bitte warten"), translate('NetWoRTOC',"NetWoRTOC sucht gerade nach RTOC-Servern"), translate('NetWoRTOC',"Bitte warten bis der Vorgang abgeschlossen ist.")) + + def updateHostList(self): + self.comboBox.clear() + for item in self.hostlist: + self.comboBox.addItem(item) + pyqtlib.info_message(translate('NetWoRTOC',"Fertig"), translate('NetWoRTOC',"RTOC-Suche abgeschlossen"), str(len(self.hostlist)-1)+translate('NetWoRTOC'," Server gefunden.")) + self.searchButton.setText(translate('NetWoRTOC','Suchen')) + + def searchThread(self): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + ip = s.getsockname()[0] + s.close() + ip_parts = ip.split('.') + base_ip = ip_parts[0] + '.' + ip_parts[1] + '.' + ip_parts[2] + '.' + nm = nmap.PortScanner() + ans = nm.scan(base_ip+'0-255','5050') + for ip in ans['scan'].keys(): + if ans['scan'][ip]['tcp'][5050]['state'] != 'closed': + if len(ans['scan'][ip]['hostnames'])>0: + for hostname in ans['scan'][ip]['hostnames']: + if hostname['name'] != '': + self.hostlist.append(hostname['name']) + else: + self.hostlist.append(ip) + self.hostlist.append(HOST) + self.updateHostListCallback.emit() diff --git a/RTOC/plugins/netWoRTOC/networkscan.py b/RTOC/plugins/netWoRTOC/networkscan.py new file mode 100644 index 0000000..fef357f --- /dev/null +++ b/RTOC/plugins/netWoRTOC/networkscan.py @@ -0,0 +1,132 @@ +import os +import socket +import multiprocessing +import subprocess +import os + + +def pinger(job_q, results_q): + """ + Do Ping + :param job_q: + :param results_q: + :return: + """ + DEVNULL = open(os.devnull, 'w') + while True: + + ip = job_q.get() + + if ip is None: + break + + try: + #subprocess.check_call(['ping', '-c1', ip], + # stdout=DEVNULL) + subprocess.check_call(['ping', ip], + stdout=DEVNULL) + results_q.put(ip) + except: + pass + +def get_my_ip(): + """ + Find my IP address + :return: + """ + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + ip = s.getsockname()[0] + s.close() + return ip + + +def map_network(pool_size=255): + """ + Maps the network + :param pool_size: amount of parallel ping processes + :return: list of valid ip addresses + """ + + ip_list = list() + + # get my IP and compose a base like 192.168.1.xxx + ip_parts = get_my_ip().split('.') + base_ip = ip_parts[0] + '.' + ip_parts[1] + '.' + ip_parts[2] + '.' + print(base_ip) + # prepare the jobs queue + jobs = multiprocessing.Queue() + results = multiprocessing.Queue() + + pool = [multiprocessing.Process(target=pinger, args=(jobs, results)) for i in range(pool_size)] + + for p in pool: + p.start() + + # cue hte ping processes + for i in range(1, 255): + jobs.put(base_ip + '{0}'.format(i)) + + for p in pool: + jobs.put(None) + + for p in pool: + p.join() + + # collect he results + while not results.empty(): + ip = results.get() + ip_list.append(ip) + + return ip_list + +import socket +from contextlib import closing + +def check_socket(host, port): + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: + sock.settimeout(1) + if sock.connect_ex((host, port)) == 0: + return True + else: + return False + +def check_if_up(ip_address): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(0.4) + try: + with closing(sock): + sock.connect((str(ip_address), 135)) + return True + except socket.error: + return False + +def map_port(pool_size=255, port=5050): + ip_list = list() + + # get my IP and compose a base like 192.168.1.xxx + ip_parts = get_my_ip().split('.') + base_ip = ip_parts[0] + '.' + ip_parts[1] + '.' + ip_parts[2] + '.' + print(base_ip) + + for i in range(pool_size): + address = base_ip+str(i) + if check_if_up(address): + print(address+ " ...") + if check_socket(address, port): + print('Port '+str(port)+ ' is open.') + ip_list.append(address) + else: + print('Port '+str(port)+ ' is closed.') + return ip_list + +if __name__ == '__main__': + + print('Mapping...') + lst = map_network() + print(lst) + portlst = [] + for l in lst: + if check_socket(l,5050): + portlst.append(l) + print(portlst) diff --git a/RTOC/plugins/netWoRTOC/networtoc.ui b/RTOC/plugins/netWoRTOC/networtoc.ui new file mode 100644 index 0000000..dc4797e --- /dev/null +++ b/RTOC/plugins/netWoRTOC/networtoc.ui @@ -0,0 +1,371 @@ + + + NetWoRTOC + + + + 0 + 0 + 321 + 533 + + + + NetWoRTOC + + + + + + 0 + + + + + Verbinden + + + + + + + Suchen + + + + search.pngsearch.png + + + + + + + RTOC-Server + + + + + + + true + + + + 127.0.0.1 + + + + + + + + + + 0 + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + Aufzeichnungsdauer: + + + + + + + Ändere die Aufzeichnungsdauer + + + Ändere die Aufzeichnungsdauer + + + Messwerte + + + 10000000 + + + 0 + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + Lösche alle Signale + + + Lösche alle Signale + + + + + + + ../../data/ui/icons/clear.png../../data/ui/icons/clear.png + + + + 25 + 25 + + + + + + + + + + Qt::Vertical + + + + true + + + Signale + + + false + + + + 0 + + + 0 + + + 7 + + + 0 + + + 0 + + + + + + + Samplerate: + + + + + + + 1.000000000000000 + + + + + + + + + + + repeat.pngrepeat.png + + + + 25 + 25 + + + + true + + + + + + + + + + + repeat1.pngrepeat1.png + + + + 25 + 25 + + + + + + + + + + true + + + QAbstractItemView::MultiSelection + + + true + + + + + + + Alle auswählen + + + true + + + + + + + + Geräte + + + + 0 + + + 0 + + + 7 + + + 0 + + + 0 + + + + + true + + + + + 0 + 0 + 295 + 85 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + + + Qt::Vertical + + + + 20 + 82 + + + + + + + + + + + + + Geräte Funktionen und Parameter + + + + 0 + + + 0 + + + 7 + + + 0 + + + 0 + + + + + true + + + QAbstractItemView::NoSelection + + + true + + + + + + + + + + + + diff --git a/RTOC/plugins/netWoRTOC/repeat.png b/RTOC/plugins/netWoRTOC/repeat.png new file mode 100644 index 0000000000000000000000000000000000000000..87f2e84959566e2ddf27466dfccbb743c408797f GIT binary patch literal 777 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGojKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qt-3{3MpT^vIy;@;l581&kJr{&?kpASz~Z#c#8 ztTHh4R7S0z@p9Cje{H!OhC@8!CiL%PV`_4gq2Xd@4ehALUR_C#0 zGJd-x(LG^asYDS=TI785tT)FMtlwAtY)mXWH{%mC^V=PTPubhNj+{z5yZG2Nc?lgs z?QYY4iTJvN9WHBhuGOe6|8sy@B`I~|vw3?yG4oICn0j=USKiM?c2AHN-Jeg{<SqPlJ!ll#xGCcE?np?x?wpajFr5tIhLq<2J&*c>DYe=id>^9 zIQx0^&&KJelYBdG$k?Ce-xDg9?RDf+?o|1T-8$E%bUY1u%C3~UahZ$G`M95rK<{Y^ zp8mM%kHZ#_WN-PBKMs4qtkNxi4!EBJnPQQCioXUJoh z@BScl_R6n7ZrrAuQQ!NOKdfFW;Qqkf*x0zbX!l;`?+wn(hjy}j114VvPgg&ebxsLQ E06}kN?*IS* literal 0 HcmV?d00001 diff --git a/RTOC/plugins/netWoRTOC/repeat1.png b/RTOC/plugins/netWoRTOC/repeat1.png new file mode 100644 index 0000000000000000000000000000000000000000..100c78cadd5150d527ad0ea72c41e37dec9638b7 GIT binary patch literal 989 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGojKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qt-49rEIE{-7;ac^%s<_QN1v^;zxDBX8pUhTUh ziYEjgsKjvUF|J{{drn^4ulM-l6JOn`uBb<YIo8 zCxyzcaA!mw@|*GBaoPsvwE8KkJjd?%r*JYG7Ee^=IkZPSm6O@v_2x}1#?SU>c^f1h z0}7~}DWCk%dx3eJ#4Z-A$5Ru%7wC)eL^8#0eA8(7GeG2&{U5i;Ia0=d+!iX#V+y~O zb?1Zh(xwWon9S~thx0|aA88gW^*+YGKTxk@`GKj|-WdNm#x8Iq_<`5+H|BqiwS$@7 zVeRoh-S>T|{3X)`)cJC2_u=*XIsZS2p1=J3B9OMYTPA<(PDIYyP^0Y}^ZAI={n z-E$!-l4eW&*j;t^`s?Vu{{pJT`fmm(chmyyH@lK^EZOV|N0NLM%SK+)*&k;>EzEH~ zn`V>d+^Ya`%$)EW71&t? zGq_?r1%B@-{m~1I5g;q%`0Td(7DlY=R64$IH2ZUGqh^7sqs_gg z56%1h8nrneZQRDQzpw_Rw0|>DllwBDQ6D9)^X&h4LuAJ!g>Spde*EV?_IT>U`NunI zjQ*7NoLp`tH*ZxEXY!|2^K>5axIGDvjXcD3%-HQ~>{b@J?&wuJ^WvELkF33#W3Joy z_+h~3y*pU?+Pu$Jlyb^&FQ4`KrfPx8ovdr!-U@w!S@$-vERtB7R=0`eUf;y5cgIyf vg!P1Nw%2LQPv+mtvahED3~rphd)GeelDO+UnJQ~wmSpgB^>bP0l+XkK{Nl$` literal 0 HcmV?d00001 diff --git a/RTOC/plugins/netWoRTOC/search.png b/RTOC/plugins/netWoRTOC/search.png new file mode 100644 index 0000000000000000000000000000000000000000..bda66030a5c290cc90db35f5153067eadcbefa94 GIT binary patch literal 8823 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'?C_e{)?O_g'=@Ue{'?@Uk~'=@Pg~'>C_o+?BWg}!?BW`p'=G]g~/>E[au*7K^e{'?C_`u+?G_g}+>BWiu(0BWk'?K^a'=M[g(5BWg}-;AU`p/=C^c~+>A_bw-;C^jw#?K^c)5BWau 0E[bw#>E[c!5C_g})5BWg%;I_g#>M[au(0C_c%;E[i)5O^c~!?C^bw#>O]c/>G]hu \ No newline at end of file diff --git a/RTOC/plugins/vc820py/__pycache__/vc820.cpython-36.pyc b/RTOC/plugins/vc820py/__pycache__/vc820.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e6b3140d65b1397249ef8c93cea409a55e812a3b GIT binary patch literal 5723 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 dcZ<8C1OZ)Ed0!McDl{twVESr`BS literal 0 HcmV?d00001 diff --git a/RTOC/telegramBot.py b/RTOC/telegramBot.py new file mode 100644 index 0000000..9bcf8a1 --- /dev/null +++ b/RTOC/telegramBot.py @@ -0,0 +1,453 @@ +import time +import logging +from telegram.ext import CommandHandler, MessageHandler, Filters, Updater +from telegram import KeyboardButton, ReplyKeyboardMarkup, ChatAction, Bot, ParseMode +from threading import Thread +from io import BytesIO +import matplotlib.pyplot as plt + +from PyQt5.QtCore import QCoreApplication +from PyQt5.QtCore import QObject +translate = QCoreApplication.translate + +messageIntervall = 1 + + +def first_lower(s): + if len(s) == 0: + return s + else: + return s[0].lower() + s[1:] + + +class bufferMessage(): + def __init__(self, config): + self.config = config + self.lastbot = None + self.lastchat = None + self.running = True + self.buffer = '' + self.start() + + def start(self): + try: + self.bot = Bot(self.config['telegram_token']) + #Thread.__init__(self) + except: + print('Starting Telegram-Bot failed, maybe wrong telegram-token') + print(self.config['telegram_token']) + + def send_long_message(self, message_text='Empty message', chat_id=None): + self.lastchat = chat_id + if chat_id not in self.config['telegram_chat_ids'] and chat_id is not None: + self.config['telegram_chat_ids'].append(chat_id) + for id in self.config['telegram_chat_ids']: + self.bot.send_message(chat_id=id, text=message_text, parse_mode=ParseMode.MARKDOWN) + + +class telegramBot(QObject): + def __init__(self, logger): + self.logger = logger + self.config = self.logger.config + self.menuCommands = [translate('telegram', 'Event-Benachrichtigung festlegen'), translate( + 'telegram', 'Letzte Messwerte'), translate('telegram', 'Signale'), translate('telegram', 'Geräte')] + self.mode = {} + + self.servername = self.config['telegram_name'] + self.token = self.config['telegram_token'] + self.eventlevel = self.config['telegram_eventlevel'] + + self.sender = bufferMessage(self.config) + # self.sender.setName('buffer_sender') + self.updater = None + self.current_plugin = {} + self.current_call = {} + + def setToken(self, token): + self.token = token + self.config['telegram_token'] = token + + def sendMessage(self, message): + if self.sender.running: + t = Thread(target=self.sender.send_long_message,args=(str(message),)) + t.start() + return True + else: + return False + + def connect(self): + idler = Thread(target=self.connectThread) + idler.start() + return True + + def connectThread(self): + try: + self.updater = Updater(token=self.token) + self.dispatcher = self.updater.dispatcher + logging.basicConfig( + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO) + + echo_handler = MessageHandler(Filters.text, self.textHandler) + self.dispatcher.add_handler(echo_handler) + self.updater.start_polling() + print('Telegram-Server successfully started!') + self.updater.idle() + if self.config['telegram_eventlevel'] <= 1: + self.sender.send_long_message(self.servername+' wurde gestartet') + return True + except: + return False + + def stop(self): + print('Telegram-Server stopped!') + self.sender.running = False + if self.updater: + self.updater.stop() + +#################### Action Handler ################################################# + + def addCmd(self, name, command): # Create CommandHandler and add to dispatcher + def cmd(bot, update): + bot.send_chat_action(chat_id=update.message.chat_id, action=ChatAction.TYPING) + command(bot, update) + handler = CommandHandler(name, cmd) + self.dispatcher.add_handler(handler) + + +# SHELL HANDLER + def textHandler(self, bot, update): + if update.message.chat_id not in self.config['telegram_chat_ids'] and update.message.chat_id is not None: + self.config['telegram_chat_ids'].append(update.message.chat_id) + if update.message.chat_id not in self.mode.keys(): + self.mode[update.message.chat_id] = 'menu' + self.current_plugin[update.message.chat_id] = None + self.current_call[update.message.chat_id] = None + + strung = update.message.text + if self.mode[update.message.chat_id] == "adjustEventNotification": + if strung in self.adjustEventNotificationCommands: + i = self.adjustEventNotificationCommands.index(strung) + if i <= 3: + self.config['telegram_eventlevel'] = i + bot.send_message(chat_id=update.message.chat_id, + text=translate('telegram', 'Einstellung angepasst')) + self.mode[update.message.chat_id] = '' + self.menuHandler(bot, update) + elif self.mode[update.message.chat_id] == "menu": + if strung == translate('telegram', 'Event-Benachrichtigung festlegen'): + self.mode[update.message.chat_id] = "adjustEventNotification" + self.adjustEventNotificationHandler(bot, update) + elif strung == translate('telegram', 'Letzte Messwerte'): + self.sendLatest(bot, update.message.chat_id) + self.menuHandler(bot, update) + elif strung == translate('telegram', 'Geräte'): + self.mode[update.message.chat_id] = "plugins" + self.pluginsHandler(bot, update) + elif strung == translate('telegram', "Signale"): + self.mode[update.message.chat_id] = "signals" + self.signalsHandler(bot, update) + else: + self.menuHandler(bot, update) + elif self.mode[update.message.chat_id] == 'signals': + if strung == translate('telegram', 'Signale löschen'): + self.logger.clear() + self.menuHandler(bot, update) + elif strung == translate('telegram', 'Aufzeichnungsdauer ändern'): + self.mode[update.message.chat_id] = 'resize' + self.resizeHandler(bot, update) + elif strung == translate('telegram', '<-- Zurück'): + self.menuHandler(bot, update) + else: + self.sendSignalPlot(bot, update, strung) + self.signalsHandler(bot, update) + elif self.mode[update.message.chat_id] == 'resize': + if strung == translate('telegram', '<-- Zurück'): + self.mode[update.message.chat_id] = 'signals' + self.signalsHandler(bot, update) + else: + try: + value = int(strung) + except: + value = None + if value: + bot.send_message(chat_id=update.message.chat_id, text=translate( + 'telegram', 'Aufzeichnungsdauer geändert')) + self.logger.resizeSignals(value) + self.mode[update.message.chat_id] = 'signals' + self.signalsHandler(bot, update) + else: + bot.send_message(chat_id=update.message.chat_id, + text=translate('telegram', 'Fehlerhafte Eingabe')) + self.mode[update.message.chat_id] = 'signals' + self.signalsHandler(bot, update) + elif self.mode[update.message.chat_id] == 'plugins': + if strung in self.logger.devicenames.keys(): + self.pluginHandler(bot, update, strung) + self.mode[update.message.chat_id] = 'plugin' + else: + self.menuHandler(bot, update) + elif self.mode[update.message.chat_id] == 'plugin': + commands = [] + if self.logger.pluginStatus[self.current_plugin[update.message.chat_id]] == True: + commands += [translate('telegram', "Gerät beenden")] + elif self.logger.pluginStatus[self.current_plugin[update.message.chat_id]] == False: + commands += [translate('telegram', "Gerät starten")] + else: + commands += [translate('telegram', "Gerätefehler")] + commands += [translate('telegram', "Funktionen"), translate('telegram', "Parameter")] + if strung in commands: + if strung == translate('telegram', "Gerät beenden"): + self.logger.stopPlugin(self.current_plugin[update.message.chat_id]) + self.pluginHandler(bot, update, self.current_plugin[update.message.chat_id]) + elif strung == translate('telegram', "Gerät starten"): + self.logger.startPlugin(self.current_plugin[update.message.chat_id]) + self.pluginHandler(bot, update, self.current_plugin[update.message.chat_id]) + elif strung == translate('telegram', "Funktionen"): + self.mode[update.message.chat_id] = 'pluginfunctions' + self.pluginfunctionsHandler(bot, update) + elif strung == translate('telegram', "Parameter"): + self.mode[update.message.chat_id] = 'pluginparameters' + self.pluginparametersHandler(bot, update) + elif strung == translate('telegram', "<-- Zurück"): + self.mode[update.message.chat_id] = "plugins" + self.current_plugin[update.message.chat_id] = None + self.pluginsHandler(bot, update) + else: + self.pluginHandler(bot, update, self.current_plugin[update.message.chat_id]) + elif self.mode[update.message.chat_id] == 'pluginfunctions': + + commands = [] + name = self.current_plugin[update.message.chat_id] + for fun in self.logger.pluginFunctions.keys(): + if fun.startswith(name+".") and fun not in [name+".close", name+".loadGUI", name+".createTCPClient", name+".sendTCP", name+".plot", name+".setDeviceName", name+".event", name+".stream"]: + commands += [fun.replace(name+".", '')+'()'] + + if strung in commands: + print(strung) + self.current_call[update.message.chat_id] = strung + self.mode[update.message.chat_id] = "call" + self.plugincallHandler(bot, update) + elif strung == translate('telegram', "<-- Zurück"): + self.mode[update.message.chat_id] = "plugin" + self.current_call[update.message.chat_id] = None + self.pluginHandler(bot, update, self.current_plugin[update.message.chat_id]) + else: + self.pluginfunctionsHandler(bot, update) + elif self.mode[update.message.chat_id] == 'pluginparameters': + commands = [] + name = self.current_plugin[update.message.chat_id] + for fun in self.logger.pluginParameters.keys(): + if fun.startswith(name+".") and fun not in [name+".deviceName", name+".close", name+".run", name+".smallGUI", name+".sock", name+".widget"]: + commands += [fun.replace(name+".", '')] + + if strung in commands: + print(strung) + self.current_call[update.message.chat_id] = strung + self.mode[update.message.chat_id] = "call" + self.plugincallHandler(bot, update) + elif strung == translate('telegram', "<-- Zurück"): + self.mode[update.message.chat_id] = "plugin" + self.current_call[update.message.chat_id] = None + self.pluginHandler(bot, update, self.current_plugin[update.message.chat_id]) + else: + self.pluginparametersHandler(bot, update) + elif self.mode[update.message.chat_id] == 'call': + if strung == translate('telegram', "<-- Zurück"): + self.current_call[update.message.chat_id] = None + if "()" in self.current_call[update.message.chat_id]: + self.mode[update.message.chat_id] = "pluginfunctions" + self.pluginfunctionsHandler(bot, update) + else: + self.mode[update.message.chat_id] = "pluginparameters" + self.pluginparametersHandler(bot, update) + else: + if "()" in self.current_call[update.message.chat_id]: + self.temp = [] + try: + exec('self.temp = ['+strung+"]") + except: + print("this was not a python-type: "+strung) + self.logger.callPluginFunction( + self.current_plugin[update.message.chat_id], self.current_call[update.message.chat_id], *self.temp) + else: + value = strung + self.logger.getPluginParameter(self.current_plugin[update.message.chat_id], self.current_call[update.message.chat_id], value) + self.current_call[update.message.chat_id] = None + self.mode[update.message.chat_id] = "plugin" + self.pluginHandler(bot, update, self.current_plugin[update.message.chat_id]) + else: + if strung and strung != "": + bot.send_message(chat_id=update.message.chat_id, text=translate( + 'telegram', 'Wenn dein Text aussieht, wie eine JSON, dann kannst du damit später alles machen')) + else: + bot.send_message(chat_id=update.message.chat_id, text=translate( + 'telegram', 'Was soll ich dazu sagen ...')) + self.menuHandler(bot, update) + +# MENU FUNCTIONS + def sendSignalPlot(self, bot, update, signalname): + bio = BytesIO() + bio.name = 'image.png' + a = signalname.split('.') + data = self.logger.getSignal(self.logger.getSignalId(a[0], a[1])) + + # Make a square figure and axes + plt.gcf().clear() + plt.plot(data[0], data[1]) + plt.xlabel(translate('telegram', 'Zeit [s]')) + plt.savefig('telegram_export.png') + plt.title(signalname) + t = self.createToolTip(self.logger.getSignalId(a[0], a[1])) + print(t) + bot.send_photo(chat_id=update.message.chat_id, photo=open('telegram_export.png', 'rb')) + bot.send_message(chat_id=update.message.chat_id, text=t) + + def createToolTip(self, id): + maxduration = self.calcDuration(list(self.logger.getSignal(id)[0])) + duration = self.logger.getSignal(id)[0][-1]-self.logger.getSignal(id)[0][0] + try: + line1 = time.strftime("%H:%M:%S", time.gmtime(int(duration))) + \ + "/~"+time.strftime("%H:%M:%S", time.gmtime(int(maxduration))) + line2 = str( + len(list(self.logger.getSignal(id)[0])))+"/"+str(self.logger.maxLength) + count = 20 + if len(self.logger.getSignal(id)[0]) <= count: + count = len(self.logger.getSignal(id)[0]) + if count > 1: + meaner = list(self.logger.getSignal(id)[0])[-count:] + diff = 0 + for idx, m in enumerate(meaner[:-1]): + diff += meaner[idx+1]-m + if diff != 0: + line3 = str(round((len(meaner)-1)/diff, 2))+" Hz" + else: + line3 = "? Hz" + else: + line3 = "? Hz" + return line1+"\n"+line2 + "\n" + line3 + except: + return "Tooltip failed" + + def build_menu(self, buttons, n_cols, header_buttons=None, footer_buttons=None): + menu = [buttons[i:i + n_cols] for i in range(0, len(buttons), n_cols)] + if header_buttons: + menu.insert(0, header_buttons) + if footer_buttons: + menu.append(footer_buttons) + return menu + + def resizeHandler(self, bot, update): + commands = [] + commands.append(translate('telegram', '<-- Zurück')) + button_list = [KeyboardButton(s) for s in commands] + reply_markup = ReplyKeyboardMarkup(self.build_menu(button_list, n_cols=1)) + bot.send_message(update.message.chat_id, text=translate( + 'telegram', "Derzeitige Aufzeichnungsdauer: ")+str(self.logger.maxLength), reply_markup=reply_markup) + + def pluginsHandler(self, bot, update): + commands = list(self.logger.devicenames) + commands.append(translate('telegram', '<-- Zurück')) + button_list = [KeyboardButton(s) for s in commands] + reply_markup = ReplyKeyboardMarkup(self.build_menu(button_list, n_cols=1)) + bot.send_message(update.message.chat_id, text=translate( + 'telegram', "Geräte"), reply_markup=reply_markup) + + def signalsHandler(self, bot, update): + commands = ['.'.join(a) for a in self.logger.signalNames] + commands += [translate('telegram', 'Signale löschen'), translate('telegram', + 'Aufzeichnungsdauer ändern'), translate('telegram', '<-- Zurück')] + button_list = [KeyboardButton(s) for s in commands] + reply_markup = ReplyKeyboardMarkup(self.build_menu(button_list, n_cols=1)) + bot.send_message(update.message.chat_id, text=translate( + 'telegram', "Geräte"), reply_markup=reply_markup) + + def pluginHandler(self, bot, update, device): + commands = [] + if self.logger.pluginStatus[device] == True: + commands += [translate('telegram', "Gerät beenden")] + elif self.logger.pluginStatus[device] == False: + commands += [translate('telegram', "Gerät starten")] + else: + commands += [translate('telegram', "Gerätefehler")] + commands += [translate('telegram', "Funktionen"), translate('telegram', + "Parameter"), translate('telegram', "<-- Zurück")] + button_list = [KeyboardButton(s) for s in commands] + self.current_plugin[update.message.chat_id] = device + reply_markup = ReplyKeyboardMarkup(self.build_menu(button_list, n_cols=1)) + bot.send_message(update.message.chat_id, text=device, reply_markup=reply_markup) + + def pluginparametersHandler(self, bot, update): + commands = [] + name = self.current_plugin[update.message.chat_id] + for fun in self.logger.pluginParameters.keys(): + if fun.startswith(name+".") and fun not in [name+".deviceName", name+".close", name+".run", name+".smallGUI", name+".sock", name+".widget"]: + commands += [fun.replace(name+".", '')] + commands += [translate('telegram', "<-- Zurück")] + button_list = [KeyboardButton(s) for s in commands] + reply_markup = ReplyKeyboardMarkup(self.build_menu(button_list, n_cols=1)) + bot.send_message(update.message.chat_id, text=name+' Parameter', reply_markup=reply_markup) + + def pluginfunctionsHandler(self, bot, update): + commands = [] + name = self.current_plugin[update.message.chat_id] + for fun in self.logger.pluginFunctions.keys(): + if fun.startswith(name+".") and fun not in [name+".close", name+".loadGUI", name+".createTCPClient", name+".sendTCP", name+".plot", name+".setDeviceName", name+".event", name+".stream"]: + commands += [fun.replace(name+".", '')+'()'] + commands += [translate('telegram', "<-- Zurück")] + button_list = [KeyboardButton(s) for s in commands] + reply_markup = ReplyKeyboardMarkup(self.build_menu(button_list, n_cols=1)) + bot.send_message(update.message.chat_id, text=name + + translate('telegram', " Funktionen"), reply_markup=reply_markup) + + def plugincallHandler(self, bot, update): + commands = [] + commands += [translate('telegram', "<-- Zurück")] + if "()" in self.current_call[update.message.chat_id]: + infotext = translate('telegram', "Bitte gib Parameter an, falls benötigt") + else: + value = self.logger.getPluginParameter(self.current_plugin[update.message.chat_id], "get", [self.current_call[update.message.chat_id]]) + if value != False: + infotext = translate( + 'telegram', "Bitte gib einen neuen Wert an.\nDerzeitiger Wert: ") + str(value) + else: + infotext = translate('telegram', "Fehler") + button_list = [KeyboardButton(s) for s in commands] + reply_markup = ReplyKeyboardMarkup(self.build_menu(button_list, n_cols=1)) + bot.send_message(update.message.chat_id, text=infotext, reply_markup=reply_markup) + + def menuHandler(self, bot, update): + self.mode[update.message.chat_id] = 'menu' + button_list = [KeyboardButton(s) for s in self.menuCommands] + reply_markup = ReplyKeyboardMarkup(self.build_menu(button_list, n_cols=1)) + bot.send_message(update.message.chat_id, text=translate( + 'telegram', "Hauptmenü"), reply_markup=reply_markup) + + def adjustEventNotificationHandler(self, bot, update): + self.mode[update.message.chat_id] = "adjustEventNotification" + self.adjustEventNotificationCommands = [translate('telegram', "Alle Benachrichtigungen"), translate('telegram', "Warnungen"), translate( + 'telegram', "Nur Fehlermeldungen"), translate('telegram', "Keine Benachrichtigung"), translate('telegram', "<-- Zurück")] + button_list = [KeyboardButton(s) for s in self.adjustEventNotificationCommands] + reply_markup = ReplyKeyboardMarkup(self.build_menu(button_list, n_cols=1)) + bot.send_message(update.message.chat_id, translate('telegram', 'Wähle eine Benachrichtigungsstufe aus. Derzeitige Stufe:\n') + + self.adjustEventNotificationCommands[self.config['telegram_eventlevel']], reply_markup=reply_markup) + + def sendLatest(self, bot, chat_id): + strung = "" + namelist = self.logger.signalNames + for idx, signal in enumerate(namelist): + if signal != ['RTOC', '']: + value = self.logger.getSignal(self.logger.getSignalId(signal[0], signal[1]))[1][-1] + unit = self.logger.getSignalUnits(self.logger.getSignalId(signal[0], signal[1]))[-1] + strung = strung+signal[0]+'.'+signal[1]+': '+str(value)+" "+str(unit)+"\n" + if strung == "": + strung = translate('telegram', "Keine Messwerte vorhanden") + self.sender.send_long_message(strung, chat_id) + + def calcDuration(self, x): + if len(x) > 2: + dt = x[-1]-x[0] + l = len(x) + maxlen = self.logger.maxLength + return dt/l*maxlen + else: + return -1 diff --git a/config.json b/config.json deleted file mode 100644 index e39b1b7..0000000 --- a/config.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "language": "en", - "lastSessions": [], - "darkmode": true, - "scriptWidget": true, - "deviceWidget": true, - "signalsWidget": true, - "pluginsWidget": false, - "eventWidget": true, - "newSignalSymbols": true, - "plotLabelsEnabled": true, - "plotGridEnabled": true, - "grid": [ - true, - true, - 1.0 - ], - "plotLegendEnabled": true, - "blinkingIdentifier": true, - "signalStyles": [], - "defaultRecordLength": 5000, - "plotRate": 8, - "plotInverted": false, - "xTimeBase": true, - "timeAxis": true, - "systemTray": true, - "tcpserver": true, - "defaultScriptSampleTime": 10, - "lastScript": "", - "signalInactivityTimeout": 2, - "tcpPort": 5050 -} \ No newline at end of file diff --git a/example_TCPClients/EspLibrary/rtocTCP.ino b/example_TCPClients/EspLibrary/rtocTCP.ino deleted file mode 100644 index 1573391..0000000 --- a/example_TCPClients/EspLibrary/rtocTCP.ino +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Rui Santos - * Complete Project Details http://randomnerdtutorials.com - * Based on the Arduino Ethernet Web Client Example - * and on the sketch "Sample Arduino Json Web Client" of the Arduino JSON library by Benoit Blanchon (bblanchon.github.io/ArduinoJson) - * https://randomnerdtutorials.com/decoding-and-encoding-json-with-arduino-or-esp8266/ - */ - -#include -#include -#include - -EthernetClient client; - -// Name address for Open Weather Map API -const char* server = "local RTOC-Adress"; - -const unsigned long HTTP_TIMEOUT = 10000; // max respone time from server -const size_t MAX_CONTENT_SIZE = 512; // max size of the HTTP response - -byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; - -// The type of data that we want to extract from the page -struct clientData { - char temp[8]; - char humidity[8]; -}; - -// ARDUINO entry point #1: runs once when you press reset or power the board -void setup() { - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to initialize - } - Serial.println("Serial ready"); - if(!Ethernet.begin(mac)) { - Serial.println("Failed to configure Ethernet"); - return; - } - Serial.println("Ethernet ready"); - delay(1000); -} - -// ARDUINO entry point #2: runs over and over again forever -void loop() { - if(connect(server)) { - if(sendRequest(server, resource) && skipResponseHeaders()) { - clientData clientData; - if(readReponseContent(&clientData)) { - printclientData(&clientData); - } - } - } - disconnect(); - wait(); -} - -// Open connection to the HTTP server -bool connect(const char* hostName) { - Serial.print("Connect to "); - Serial.println(hostName); - - bool ok = client.connect(hostName, 80); - - Serial.println(ok ? "Connected" : "Connection Failed!"); - return ok; -} - -// Send the HTTP GET request to the server -bool sendRequest(const char* host, const char* resource) { - Serial.print("GET "); - Serial.println(resource); - - client.print("GET "); - client.print(resource); - client.println(" HTTP/1.1"); - client.print("Host: "); - client.println(host); - client.println("Connection: close"); - client.println(); - - return true; -} - -// Skip HTTP headers so that we are at the beginning of the response's body -bool skipResponseHeaders() { - // HTTP headers end with an empty line - char endOfHeaders[] = "\r\n\r\n"; - - client.setTimeout(HTTP_TIMEOUT); - bool ok = client.find(endOfHeaders); - - if (!ok) { - Serial.println("No response or invalid response!"); - } - return ok; -} - -// Parse the JSON from the input string and extract the interesting values -// Here is the JSON we need to parse -/*{ - "coord": { - "lon": -8.61, - "lat": 41.15 - }, - "weather": [ - { - "id": 800, - "main": "Clear", - "description": "clear sky", - "icon": "01d" - } - ], - "base": "stations", - "main": { - "temp": 296.15, - "pressure": 1020, - "humidity": 69, - "temp_min": 296.15, - "temp_max": 296.15 - }, - "visibility": 10000, - "wind": { - "speed": 4.6, - "deg": 320 - }, - "clouds": { - "all": 0 - }, - "dt": 1499869800, - "sys": { - "type": 1, - "id": 5959, - "message": 0.0022, - "country": "PT", - "sunrise": 1499836380, - "sunset": 1499890019 - }, - "id": 2735943, - "name": "Porto", - "cod": 200 -}*/ - -bool readReponseContent(struct clientData* clientData) { - // Compute optimal size of the JSON buffer according to what we need to parse. - // See https://bblanchon.github.io/ArduinoJson/assistant/ - const size_t bufferSize = JSON_ARRAY_SIZE(1) + JSON_OBJECT_SIZE(1) + - 2*JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(4) + JSON_OBJECT_SIZE(5) + - JSON_OBJECT_SIZE(6) + JSON_OBJECT_SIZE(12) + 390; - DynamicJsonBuffer jsonBuffer(bufferSize); - - JsonObject& root = jsonBuffer.parseObject(client); - - if (!root.success()) { - Serial.println("JSON parsing failed!"); - return false; - } - - // Here were copy the strings we're interested in using to your struct data - strcpy(clientData->temp, root["main"]["temp"]); - strcpy(clientData->humidity, root["main"]["humidity"]); - // It's not mandatory to make a copy, you could just use the pointers - // Since, they are pointing inside the "content" buffer, so you need to make - // sure it's still in memory when you read the string - - return true; -} - -// Print the data extracted from the JSON -void printclientData(const struct clientData* clientData) { - Serial.print("Temp = "); - Serial.println(clientData->temp); - Serial.print("Humidity = "); - Serial.println(clientData->humidity); -} - -// Close the connection with the HTTP server -void disconnect() { - Serial.println("Disconnect"); - client.stop(); -} - -// Pause for a 1 minute -void wait() { - Serial.println("Wait 60 seconds"); - delay(60000); -} diff --git a/example_TCPClients/PythonistaForIOS/LoggerPlugin.py b/example_TCPClients/PythonistaForIOS/LoggerPlugin.py deleted file mode 100644 index bd68a15..0000000 --- a/example_TCPClients/PythonistaForIOS/LoggerPlugin.py +++ /dev/null @@ -1,200 +0,0 @@ -# from multiprocessing.connection import Client -import traceback -#import socket -#import json -import jsonsocket -import pickle -import time - -class LoggerPlugin: - def __init__(self, stream=None, plot = None, event=None): - # Plugin setup - self.setDeviceName() - self.dataY = [0] # Array containing different data-streams - self.dataX = [None] - self.dataunits = [''] # Represents the unit of the current data - self.datanames = [''] # Names for every data-stream - self.__cb = stream - self.__ev = event - self.__plt = plot - self.sock = None - # ------------- - self.run = False # False -> stops thread - self.smallGUI = False - self.xy = False - - def stream(self, *args, **kwargs): - if self.__cb: - y = kwargs.get('y', [0]) - for idx, arg in enumerate(args): - if idx == 0: - y = arg - kwargs['x'] = [time.time()]*len(y) - self.__cb(*args, **kwargs) - - def plot(self, x=[], y=[], *args, **kwargs): - dataname = kwargs.get('sname', "noName") - devicename = kwargs.get('dname', "noDevice") - dataunit = kwargs.get('unit', "") - for idx, arg in enumerate(args): - if idx == 0: - dataname = arg - if idx == 1: - devicename = arg - if idx == 2: - dataunit = arg - - if y == []: - y = x - x = list(range(len(x))) - - if self.__plt: - self.__plt(x, y, dataname, devicename, dataunit) - else: - print("No event connected") - - def event(self, *args, **kwargs): # text="", dataname=None, devicename=None, xpos=None): - text = kwargs.get('text', "") - dataname = kwargs.get('sname', None) - devicename = kwargs.get('dname', None) - xpos = kwargs.get('x', None) - for idx, arg in enumerate(args): - if idx == 0: - text = arg - if idx == 1: - dataname = arg - if idx == 2: - devicename = arg - if idx == 3: - xpos = arg - - if dataname == None: - if len(self.datanames) != 0: - dataname = self.datanames[0] - else: - dataname = "unknownEvent" - if devicename == None: - devicename = self.deviceName - if self.__ev: - self.__ev(text, dataname, devicename, xpos) - else: - print("No event connected") - - # def createClient(self, address = 'localhost'): - # self.client = Client((address, 5056)) - - def createTCPClient(self, address="localhost"): - # self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - # # Connect the socket to the port where the server is listening - # server_address = (socket.gethostname(), 5055) - # self.sock.connect(server_address) - self.tcpaddress = address - self.sock = jsonsocket.Client() - #self.sock.connect(address, 5055) - #client.close() - - def sendTCP(self, *args, **kwargs): - dataY = kwargs.get('y', None) - datanames = kwargs.get('sname', None) - devicename = kwargs.get('dname', self.devicename) - dataunits = kwargs.get('unit', None) - dataX = kwargs.get('x', None) - signals = kwargs.get('getSignal', None) - events = kwargs.get('getEvent', None) - signallist = kwargs.get('getSignalList', False) - plot = kwargs.get('plot', False) - event = kwargs.get('event', None) - remove = kwargs.get('remove', None) - - for idx, arg in enumerate(args): - if idx == 0: - dataY = arg - if idx == 1: - datanames = arg - if idx == 2: - devicename = arg - if idx == 3: - dataunits = arg - if idx == 4: - dataX = arg - - if dataX == None and dataY != None and not plot: - dataX = [time.time()]*len(dataY) - dicti = {} - if dataY != None: - dicti['plot']=plot - dicti['y']=dataY - dicti['x']=dataX - dicti['sname']=datanames - dicti['dname']=devicename - dicti['unit']=dataunits - if signallist: - dicti['getSignallist']=True - if event != None: - dicti['event']=event - if events != None: - dicti['getEvent']=events - if signals != None: - dicti['getSignal']=signals - if remove != None: - dicti['remove'] = remove - - #data=pickle.dumps(list(dicti)) - - if self.sock and self.run: - try: - self.sock.connect(self.tcpaddress, 5050) - self.sock.send(dicti) - response = self.sock.recv() - self.sock.close() - return response - except: - tb = traceback.format_exc() - print(tb) - print("Error sending over TCP") - self.sock = jsonsocket.Client() - return False - else: - print("Please createTCPClient first") - return False - - # def sendData(self, *args, **kwargs): - # dataY = kwargs.get('y', [1]) - # datanames = kwargs.get('sname', ["noName"]) - # devicename = kwargs.get('dname', "noDevice") - # dataunits = kwargs.get('unit', [""]) - # dataX = kwargs.get('x', [time.time()]*len(dataY)) - # for idx, arg in enumerate(args): - # if idx == 0: - # dataY = arg - # if idx == 1: - # datanames = arg - # if idx == 2: - # devicename = arg - # if idx == 3: - # dataunits = arg - # if idx == 4: - # dataX = arg - # - # if self.client and self.run: - # try: - # self.client.send([dataY, datanames, devicename, dataunits, dataX]) - # # print(self.client.recv()) - # except: - # tb = traceback.format_exc() - # print(tb) - # elif self.run: - # print("Please call 'self.createClient()' before running self.sendData()!") - - def setDeviceName(self, devicename="noDevice"): - self.devicename = devicename # Is shown in GUI - - def close(self): - self.run = False - if self.widget: - self.widget.hide() - self.widget.close() - #if self.client: - # self.client.close() - #if self.sock: - #self.sock.close() diff --git a/example_TCPClients/PythonistaForIOS/README.md b/example_TCPClients/PythonistaForIOS/README.md deleted file mode 100644 index 1d321ec..0000000 --- a/example_TCPClients/PythonistaForIOS/README.md +++ /dev/null @@ -1,4 +0,0 @@ -This small Example sends all Sensor-Data from Apple-iDevices to RTOC running on a pc in the same local network. Pythonista is an iOS-App for Python-Programming, which offers a lot of opportunities. - -It uses the TCP-Functions from jsonsocket.py and LoggerPlugin.py just like Plugins can do. - diff --git a/example_TCPClients/PythonistaForIOS/RTOC.py b/example_TCPClients/PythonistaForIOS/RTOC.py deleted file mode 100644 index 4cc0e80..0000000 --- a/example_TCPClients/PythonistaForIOS/RTOC.py +++ /dev/null @@ -1,129 +0,0 @@ -from LoggerPlugin import LoggerPlugin - -import time -from threading import Thread -import motion -from objc_util import * -import location - -devicename = "Iphone" - - -class Plugin(LoggerPlugin): - def __init__(self, stream=None, plot= None, event=None): - # Plugin setup - super(Plugin, self).__init__(stream, plot, event) - self.samplerate = 10 - self.createTCPClient('192.168.178.103') - # Data-logger thread - self.run = True # False -> stops thread - self.UIDevice = ObjCClass('UIDevice') - self.UIScreen = ObjCClass('UIScreen') - print(str(self.UIDevice.currentDevice().name())) - self.createTCPClient() - self.setDeviceName(str(self.UIDevice.currentDevice().name())) - self.device = self.UIDevice.currentDevice() - - def start(self, dev='192.168.178.103'): - self.run = True - print(dev) - self.tcpaddress =dev - motion.start_updates() - location.start_updates() - print('Device '+dev) - self.device.setBatteryMonitoringEnabled_(True) - self.__updater = Thread(target=self.__updateT) - self.__updater.start() - print("Stream started") - - # THIS IS YOUR THREAD - def __updateT(self): - diff = 0 - self.gen_start = time.time() - while self.run: - print('updatethread') - if diff < 1/self.samplerate: - time.sleep(1/self.samplerate-diff) - start_time = time.time() - y, snames, units = self.sendData() - #print(y) - self.sendTCP(y=y, sname=snames, unit=units) - diff = (time.time() - start_time) - - def sendData(self): - g = motion.get_gravity() - acc = motion.get_user_acceleration() - att = motion.get_attitude() - mag = motion.get_magnetic_field() - bat = self.device.batteryLevel()*100 - batState = self.device.batteryState() - loc = location.get_location() - bri = self.UIScreen.mainScreen().brightness() - - y = [] - snames = [] - units = [] - - y += [g[0],g[1],g[2]] - snames += ['GravityX','GravityY','GravityZ'] - units += ['m/s^2', 'm/s^2', 'm/s^2'] - - y += [acc[0],acc[1],acc[2]] - snames += ['AccelerationX','AccelerationY','AccelerationZ'] - units += ['m/s^2', 'm/s^2', 'm/s^2'] - - y += [att[0],att[1],att[2]] - snames += ['Roll','Pitch','Yawn'] - units += ['°','°','°'] - - y += [mag[0],mag[1],mag[2], mag[3]] - snames += ['MagneticX','MagneticY','MagneticZ', 'MagneticAccuracy'] - units += ['A/m','A/m','A/m','%'] - - y += [bat] - snames += ['Battery'] - units += ['%'] - - y += [batState] - snames += ['BatteryState'] - units += ['%'] - - y += [bri*100] - snames += ['Brightness'] - units += ['%'] - - y += [loc['latitude']] - snames += ['Latitude'] - units += ['"'] - y += [loc['longitude']] - snames += ['Longitude'] - units += ['"'] - y += [loc['altitude']] - snames += ['Altitude'] - units += ['"'] - y += [loc['horizontal_accuracy']] - snames += ['HorizontalAccuracy'] - units += ['%'] - y += [loc['vertical_accuracy']] - snames += ['VerticalAccuracy'] - units += ['%'] - y += [loc['speed']] - snames += ['Speed'] - units += ['m/s'] - y += [loc['course']] - snames += ['Course'] - units += [''] - - return y, snames, units - - def stop(self): - self.run = False - motion.stop_updates() - location.stop_updates() - self.device.setBatteryMonitoringEnabled_(False) - print('Stream stopped') - -if __name__ == "__main__": - standalone = Plugin() - standalone.sendData() - standalone.stop() diff --git a/example_TCPClients/PythonistaForIOS/gui.py b/example_TCPClients/PythonistaForIOS/gui.py deleted file mode 100644 index 5c22acd..0000000 --- a/example_TCPClients/PythonistaForIOS/gui.py +++ /dev/null @@ -1,22 +0,0 @@ -import ui -from RTOC import Plugin as rtoc - -def switchOn(sender): - if sender.value: - dev=sender.superview['textfield1'] - r.start(dev.text) - else: - r.stop() - -def changeSamplerate(sender): - try: - r.samplerate=float(sender.text) - print('Samplerate changed') - except: - print('Not a float') - - -v = ui.load_view() -r = rtoc() -v.present('sheet') - diff --git a/example_TCPClients/PythonistaForIOS/gui.pyui b/example_TCPClients/PythonistaForIOS/gui.pyui deleted file mode 100644 index c736979..0000000 --- a/example_TCPClients/PythonistaForIOS/gui.pyui +++ /dev/null @@ -1,141 +0,0 @@ -[ - { - "nodes" : [ - { - "nodes" : [ - - ], - "frame" : "{{0, 0}, {320, 543}}", - "class" : "View", - "attributes" : { - "frame" : "{{110, 190}, {100, 100}}", - "background_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", - "class" : "View", - "name" : "view1", - "uuid" : "8CBFC217-ACB7-496A-B2A0-8091900867C6" - }, - "selected" : true - }, - { - "nodes" : [ - - ], - "frame" : "{{0, 6}, {320, 32}}", - "class" : "Label", - "attributes" : { - "flex" : "W", - "name" : "label1", - "text_color" : "RGBA(1.000000,1.000000,1.000000,1.000000)", - "frame" : "{{45, 104}, {150, 32}}", - "uuid" : "CBD56E84-A1B8-402F-9135-39B9CA97C198", - "class" : "Label", - "alignment" : "center", - "text" : "RTOC", - "background_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", - "font_size" : 18, - "font_name" : "" - }, - "selected" : false - }, - { - "nodes" : [ - - ], - "frame" : "{{0, 46}, {320, 32}}", - "class" : "TextField", - "attributes" : { - "flex" : "W", - "font_size" : 17, - "frame" : "{{20, 104}, {200, 32}}", - "spellchecking_type" : "default", - "class" : "TextField", - "uuid" : "9EEFF029-8CE0-4162-9846-236564C00733", - "alignment" : "center", - "text" : "192.168.178.103", - "autocorrection_type" : "default", - "name" : "textfield1", - "font_name" : "" - }, - "selected" : false - }, - { - "nodes" : [ - - ], - "frame" : "{{142, 170}, {51, 31}}", - "class" : "Switch", - "attributes" : { - "action" : "switchOn", - "flex" : "WLR", - "frame" : "{{95, 105}, {51, 31}}", - "class" : "Switch", - "uuid" : "4F65BADB-32CB-4E93-BE27-33AB5F7BFE94", - "value" : false, - "background_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", - "name" : "switch1" - }, - "selected" : false - }, - { - "nodes" : [ - - ], - "frame" : "{{149, 113}, {178, 32}}", - "class" : "TextField", - "attributes" : { - "uuid" : "DB69FA66-8C6F-4DC2-A197-5BEC99C59987", - "font_size" : 17, - "background_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", - "frame" : "{{60, 224}, {200, 32}}", - "tint_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", - "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", - "action" : "changeSamplerate", - "alignment" : "left", - "autocorrection_type" : "default", - "text" : "1", - "text_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", - "font_name" : "", - "spellchecking_type" : "default", - "class" : "TextField", - "name" : "textfield2", - "flex" : "WL" - }, - "selected" : false - }, - { - "nodes" : [ - - ], - "frame" : "{{0, 113}, {141, 32}}", - "class" : "Label", - "attributes" : { - "uuid" : "7875D4B4-6351-4700-A301-EDCAF9662C0E", - "font_size" : 18, - "background_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", - "frame" : "{{85, 224}, {150, 32}}", - "tint_color" : "RGBA(1.000000,1.000000,1.000000,1.000000)", - "alignment" : "left", - "alpha" : 1, - "text" : "Samplerate", - "text_color" : "RGBA(1.000000,1.000000,1.000000,1.000000)", - "font_name" : "", - "class" : "Label", - "name" : "label2", - "flex" : "WR" - }, - "selected" : false - } - ], - "frame" : "{{0, 0}, {320, 480}}", - "class" : "View", - "attributes" : { - "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", - "enabled" : true, - "background_color" : "RGBA(1.000000,1.000000,1.000000,1.000000)", - "name" : "RTOC", - "tint_color" : "RGBA(0.000000,0.478000,1.000000,1.000000)", - "flex" : "" - }, - "selected" : false - } -] \ No newline at end of file diff --git a/example_TCPClients/PythonistaForIOS/jsonsocket.py b/example_TCPClients/PythonistaForIOS/jsonsocket.py deleted file mode 100644 index 1bd0cc8..0000000 --- a/example_TCPClients/PythonistaForIOS/jsonsocket.py +++ /dev/null @@ -1,145 +0,0 @@ -#file:jsonsocket.py -#https://github.com/mdebbar/jsonsocket -import json -import socket -import pickle - - -class Server(object): - """ - A JSON socket server used to communicate with a JSON socket client. All the - data is serialized in JSON. How to use it: - - server = Server(host, port) - while True: - server.accept() - data = server.recv() - # shortcut: data = server.accept().recv() - server.send({'status': 'ok'}) - """ - - backlog = 5 - client = None - - def __init__(self, host, port): - self.socket = socket.socket() - self.socket.setblocking(0) - self.socket.settimeout(5.0) - self.socket.bind((host, port)) - self.socket.listen(self.backlog) - - def __del__(self): - self.close() - - def accept(self): - # if a client is already connected, disconnect it - if self.client: - self.client.close() - self.client, self.client_addr = self.socket.accept() - return self - - def send(self, data): - if not self.client: - raise Exception('Cannot send data, no client is connected') - _send(self.client, data) - return self - - def recv(self): - if not self.client: - raise Exception('Cannot receive data, no client is connected') - return _recv(self.client) - - def close(self): - if self.client: - self.client.close() - self.client = None - if self.socket: - self.socket.close() - self.socket = None - - -class Client(object): - """ - A JSON socket client used to communicate with a JSON socket server. All the - data is serialized in JSON. How to use it: - - data = { - 'name': 'Patrick Jane', - 'age': 45, - 'children': ['Susie', 'Mike', 'Philip'] - } - client = Client() - client.connect(host, port) - client.send(data) - response = client.recv() - # or in one line: - response = Client().connect(host, port).send(data).recv() - """ - - socket = None - - def __del__(self): - self.close() - - def connect(self, host, port): - self.socket = socket.socket() - self.socket.setblocking(0) - self.socket.settimeout(5.0) - self.socket.connect((host, port)) - return self - - def send(self, data): - if not self.socket: - raise Exception('You have to connect first before sending data') - _send(self.socket, data) - return self - - def recv(self): - if not self.socket: - raise Exception('You have to connect first before receiving data') - return _recv(self.socket) - - def recv_and_close(self): - data = self.recv() - self.close() - return data - - def close(self): - if self.socket: - self.socket.close() - self.socket = None - -## helper functions ## - -def _send(socket, data): - try: - serialized = json.dumps(data) - #serialized=pickle.dumps(list(data)) - except (TypeError, ValueError): - raise Exception('You can only send JSON-serializable data') - # send the length of the serialized data first - b = '%d\n' % len(serialized) - socket.send(b.encode()) - # send the serialized data - socket.sendall(serialized.encode()) - -def _recv(socket): - # read the length of the data, letter by letter until we reach EOL - length_str = '' - char = socket.recv(1).decode() - while char != '\n': - length_str += char - char = socket.recv(1).decode() - total = int(length_str) - # use a memoryview to receive the data chunk by chunk efficiently - view = memoryview(bytearray(total)) - next_offset = 0 - while total - next_offset > 0: - recv_size = socket.recv_into(view[next_offset:], total - next_offset) - next_offset += recv_size - try: - deserialized = json.loads(view.tobytes()) - except (TypeError, ValueError): - #raise Exception('Data received was not in JSON format') - print("JSON SOCKET ERROR, Data received was not in JSON format") - return deserialized diff --git a/example_TCPClients/PythonistaForIOS/screenshot.PNG b/example_TCPClients/PythonistaForIOS/screenshot.PNG deleted file mode 100644 index ab7fd794fcbb0f71bd177a49719c196b34aaebb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31619 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 diff --git a/example_TCPClients/PythonistaForIOS/test.py b/example_TCPClients/PythonistaForIOS/test.py deleted file mode 100644 index e69de29..0000000 diff --git a/example_scripts/XY-Plot_example.py b/example_scripts/XY-Plot_example.py deleted file mode 100644 index 38793e2..0000000 --- a/example_scripts/XY-Plot_example.py +++ /dev/null @@ -1,4 +0,0 @@ -stream(np.sin(clock), "sinus1","no") -stream(np.sin(clock+1.7), "sinus3","no") -print(no.sinus1.latest*Generator.Square.latest) -stream([no.sinus1.latest*Generator.Square.latest, no.sinus3.latest*Generator.Square.latest], sname="sinus2",dname="no") diff --git a/example_scripts/XY-Plot_example2.py b/example_scripts/XY-Plot_example2.py deleted file mode 100644 index 96878ae..0000000 --- a/example_scripts/XY-Plot_example2.py +++ /dev/null @@ -1,3 +0,0 @@ -stream(np.sin(clock), "sinus1","no") -stream(np.sin(clock+math.pi/2), "sinus3","no") -stream([no.sinus1.latest,no.sinus3.latest], "sinus2","no") diff --git a/example_scripts/allTest.py b/example_scripts/allTest.py deleted file mode 100644 index 63c0f19..0000000 --- a/example_scripts/allTest.py +++ /dev/null @@ -1,37 +0,0 @@ -event("Loop", "Note", "one") -evnet("ten", "Note", "two", x=clock+10) -print("Loop") - -global x=1 -global.x += 1 -stream(global.x, "Global","Test") - -plot(Generator.Square, "klaus", "baum") -plot(Generator.Square, sname="klaus2", dname="baum",unit="rad") - -#clearData() -#exportData() - -x,y = rtoc.resample(Generator.Square,100) -plot(x,y, dname="Generator", sname="Resample",unit="rad") - -x,y = rtoc.resampleFourier(Generator.Square,100) -plot(x,y, dname="Generator", sname="ResampleFourier",unit="rad") - -y = rtoc.mean(Generator.Square,10) -print("Mean ",y) - -x,y = rtoc.runningMean(Generator.Square,10) -plot(x,y, dname="Generator", sname="runningMean",unit="rad") - -x,y, params = rtoc.lsfit(Generator.Square)#,"linear",0,100) -plot(x,y, dname="Generator", sname="Lsfit",unit="rad") - -dydx = rtoc.d(Generator.Square) -print("\nDiff",dydx) - -x,y = rtoc.diff(Generator.Square) -plot(x,y, dname="Generator", sname="diff",unit="rad") - -y = rtoc.PID(Generator.Square,10) -print(y) \ No newline at end of file diff --git a/example_scripts/combineTest.py b/example_scripts/combineTest.py deleted file mode 100644 index fc38126..0000000 --- a/example_scripts/combineTest.py +++ /dev/null @@ -1,5 +0,0 @@ -x, y = rtoc.combine([Generator.Square, Generator2.Sinus], n=2000) - -ycom = y[0]-y[1] - -plot(x,ycom) \ No newline at end of file diff --git a/example_scripts/regelung_example.py b/example_scripts/regelung_example.py deleted file mode 100644 index db3106e..0000000 --- a/example_scripts/regelung_example.py +++ /dev/null @@ -1,11 +0,0 @@ -ki=0 -kp=0 -kd=1 -desired = 0 -global oldI=0 -value = math.sin(clock) -global.oldI=value -stream(value, "signal") -regelung, newI =rtoc.PID(noDevice.signal, desired, kp, ki, kd, global.oldI) -global.oldI = newI -stream(regelung, "regler") diff --git a/example_scripts/sinus_example.py b/example_scripts/sinus_example.py deleted file mode 100644 index 654a011..0000000 --- a/example_scripts/sinus_example.py +++ /dev/null @@ -1,3 +0,0 @@ -print(Generator.gen_level) -if rtoc.d(Generator.Sinus) > 0.8: - Generator.Sinus.latest = 1 \ No newline at end of file diff --git a/example_scripts/sinus_example2.py b/example_scripts/sinus_example2.py deleted file mode 100644 index 7f9ff97..0000000 --- a/example_scripts/sinus_example2.py +++ /dev/null @@ -1,2 +0,0 @@ -if Generator.Sinus.latest >0.5: - Generator.Sinus.latest = 0.5 \ No newline at end of file diff --git a/example_scripts/sinus_multi_example.py b/example_scripts/sinus_multi_example.py deleted file mode 100644 index d14fee6..0000000 --- a/example_scripts/sinus_multi_example.py +++ /dev/null @@ -1,6 +0,0 @@ -stream(np.sin(clock), "sinus1","no") -no.sinus1.latest = 2*no.sinus1.latest -stream(np.sin(clock+2), "sinus2","no") -stream(no.sinus1.latest+no.sinus2.latest,"sinus3","no") - -# Start this script three times to plot all three lines \ No newline at end of file diff --git a/example_scripts/square_example.py b/example_scripts/square_example.py deleted file mode 100644 index eb1fd81..0000000 --- a/example_scripts/square_example.py +++ /dev/null @@ -1,4 +0,0 @@ -print(round(Generator.Square.latest-Generator.offset)) -if(round(Generator.Square.latest-Generator.offset)>= 1): - Generator.offset += 0.1 -Generator.setLabels() \ No newline at end of file diff --git a/example_scripts/square_plot_example.py b/example_scripts/square_plot_example.py deleted file mode 100644 index bc61a27..0000000 --- a/example_scripts/square_plot_example.py +++ /dev/null @@ -1 +0,0 @@ -stream(Generator.Square.latest*2) \ No newline at end of file diff --git a/example_scripts/square_plugin_function_example.py b/example_scripts/square_plugin_function_example.py deleted file mode 100644 index 8d1de32..0000000 --- a/example_scripts/square_plugin_function_example.py +++ /dev/null @@ -1,3 +0,0 @@ -Generator.setLabels() -if Generator.Square.latest==1: - Generator.gen_freq=Generator.gen_freq+1 \ No newline at end of file diff --git a/example_scripts/square_sinus_example.py b/example_scripts/square_sinus_example.py deleted file mode 100644 index 1caccf6..0000000 --- a/example_scripts/square_sinus_example.py +++ /dev/null @@ -1 +0,0 @@ -stream(np.sin(clock)*Generator.Square.latest) \ No newline at end of file diff --git a/example_scripts/square_trigger_example.py b/example_scripts/square_trigger_example.py deleted file mode 100644 index 1969a43..0000000 --- a/example_scripts/square_trigger_example.py +++ /dev/null @@ -1,4 +0,0 @@ -trig Generator.Square.latest == 1: - print("Trigger") -if Generator.Square.latest == 1: - print("no Trigger") \ No newline at end of file diff --git a/example_scripts/square_trigger_example2.py b/example_scripts/square_trigger_example2.py deleted file mode 100644 index 01610fb..0000000 --- a/example_scripts/square_trigger_example2.py +++ /dev/null @@ -1,2 +0,0 @@ -trig Generator.Square.latest > 0: - Generator.gen_level += 1 \ No newline at end of file diff --git a/jsonsocket.py b/jsonsocket.py deleted file mode 100644 index 1bd0cc8..0000000 --- a/jsonsocket.py +++ /dev/null @@ -1,145 +0,0 @@ -#file:jsonsocket.py -#https://github.com/mdebbar/jsonsocket -import json -import socket -import pickle - - -class Server(object): - """ - A JSON socket server used to communicate with a JSON socket client. All the - data is serialized in JSON. How to use it: - - server = Server(host, port) - while True: - server.accept() - data = server.recv() - # shortcut: data = server.accept().recv() - server.send({'status': 'ok'}) - """ - - backlog = 5 - client = None - - def __init__(self, host, port): - self.socket = socket.socket() - self.socket.setblocking(0) - self.socket.settimeout(5.0) - self.socket.bind((host, port)) - self.socket.listen(self.backlog) - - def __del__(self): - self.close() - - def accept(self): - # if a client is already connected, disconnect it - if self.client: - self.client.close() - self.client, self.client_addr = self.socket.accept() - return self - - def send(self, data): - if not self.client: - raise Exception('Cannot send data, no client is connected') - _send(self.client, data) - return self - - def recv(self): - if not self.client: - raise Exception('Cannot receive data, no client is connected') - return _recv(self.client) - - def close(self): - if self.client: - self.client.close() - self.client = None - if self.socket: - self.socket.close() - self.socket = None - - -class Client(object): - """ - A JSON socket client used to communicate with a JSON socket server. All the - data is serialized in JSON. How to use it: - - data = { - 'name': 'Patrick Jane', - 'age': 45, - 'children': ['Susie', 'Mike', 'Philip'] - } - client = Client() - client.connect(host, port) - client.send(data) - response = client.recv() - # or in one line: - response = Client().connect(host, port).send(data).recv() - """ - - socket = None - - def __del__(self): - self.close() - - def connect(self, host, port): - self.socket = socket.socket() - self.socket.setblocking(0) - self.socket.settimeout(5.0) - self.socket.connect((host, port)) - return self - - def send(self, data): - if not self.socket: - raise Exception('You have to connect first before sending data') - _send(self.socket, data) - return self - - def recv(self): - if not self.socket: - raise Exception('You have to connect first before receiving data') - return _recv(self.socket) - - def recv_and_close(self): - data = self.recv() - self.close() - return data - - def close(self): - if self.socket: - self.socket.close() - self.socket = None - -## helper functions ## - -def _send(socket, data): - try: - serialized = json.dumps(data) - #serialized=pickle.dumps(list(data)) - except (TypeError, ValueError): - raise Exception('You can only send JSON-serializable data') - # send the length of the serialized data first - b = '%d\n' % len(serialized) - socket.send(b.encode()) - # send the serialized data - socket.sendall(serialized.encode()) - -def _recv(socket): - # read the length of the data, letter by letter until we reach EOL - length_str = '' - char = socket.recv(1).decode() - while char != '\n': - length_str += char - char = socket.recv(1).decode() - total = int(length_str) - # use a memoryview to receive the data chunk by chunk efficiently - view = memoryview(bytearray(total)) - next_offset = 0 - while total - next_offset > 0: - recv_size = socket.recv_into(view[next_offset:], total - next_offset) - next_offset += recv_size - try: - deserialized = json.loads(view.tobytes()) - except (TypeError, ValueError): - #raise Exception('Data received was not in JSON format') - print("JSON SOCKET ERROR, Data received was not in JSON format") - return deserialized diff --git a/plotStyles.json b/plotStyles.json deleted file mode 100644 index 055509c..0000000 --- a/plotStyles.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "Generator.Square": { - "symbol": { - "style": "1", - "width": 1, - "color": "#afe31e", - "alpha": 1.0, - "shadowColor": "#c8c8c8", - "shadowWidth": 1, - "shadowStyle": "0" - }, - "brush": { - "fillLevel": null, - "fillBrush": "#000000", - "style": "o", - "size": 10, - "color": "#aa55ff", - "pen": "#c8c8c8" - } - } -} \ No newline at end of file diff --git a/plugins/NetWoRTOC.py b/plugins/NetWoRTOC.py deleted file mode 100644 index e2b44f4..0000000 --- a/plugins/NetWoRTOC.py +++ /dev/null @@ -1,114 +0,0 @@ -from LoggerPlugin import LoggerPlugin - -import time -from threading import Thread -import traceback -import requests -from PyQt5 import uic -from PyQt5 import QtWidgets -from PyQt5 import QtCore -import socket -import threading - -devicename = "NetWoRTOC" -HOST = "127.0.0.1" - - -class Plugin(LoggerPlugin): - def __init__(self, stream=None, plot= None, event=None): - # Plugin setup - super(Plugin, self).__init__(stream, plot, event) - self.setDeviceName(devicename) - self.smallGUI = False - - # Data-logger thread - self.run = False # False -> stops thread - self.__updater = Thread(target=self.updateT) # Actualize data - # self.updater.start() - - self.samplerate = 1 - self.__siglist = [] - - # THIS IS YOUR THREAD - def updateT(self): - diff = 0 - while self.run: - if diff < 1/self.samplerate: - time.sleep(1/self.samplerate-diff) - start_time = time.time() - ans = self.sendTCP(getSignalList= True) - if ans: - if 'signallist' in ans.keys(): - if ans['signallist'] != self.__siglist: - self.__siglist = ans['signallist'] - self.updateList() - if self.widget.streamButton.isChecked(): - self.plotSignals() - diff = time.time() - start_time - - def plotSignals(self): - if self.widget.checkBox.isChecked(): - selection = self.__siglist - selection = [".".join(i) for i in selection] - else: - selection = [] - for o in self.widget.listWidget.selectedItems(): - selection.append(o.text()) - if selection != []: - ans = self.sendTCP(getSignal= selection) - if ans != False: - if 'signals' in ans.keys(): - for sig in ans['signals'].keys(): - signame = sig.split('.') - s = ans['signals'][sig] - self.plot(s[0],s[1],sname = signame[1], dname=signame[0]+"_Remote") - - def updateList(self): - t = [] - for o in self.widget.listWidget.selectedItems(): - t.append(o.text()) - self.widget.listWidget.clear() - for sig in self.__siglist: - self.widget.listWidget.addItem('.'.join(sig)) - for idx in range(self.widget.listWidget.count()): - sig = self.widget.listWidget.item(idx) - if sig.text() in t: - self.widget.listWidget.item(idx).setSelected(True) - def loadGUI(self): - self.widget = QtWidgets.QWidget() - uic.loadUi("plugins/netWoRTOC/networtoc.ui", self.widget) - # self.setCallbacks() - self.widget.connectButton.clicked.connect(self.__openConnectionCallback) - self.widget.doubleSpinBox.valueChanged.connect(self.__changeSamplerate) - self.widget.comboBox.setCurrentText(HOST) - self.widget.singleButton.clicked.connect(self.plotSignals) - #self.__openConnectionCallback() - return self.widget - - def __openConnectionCallback(self): - if self.run: - self.run = False - self.widget.connectButton.setText("Verbinden") - else: - address = self.widget.comboBox.currentText() - self.createTCPClient(address) - self.run = True - try: - ok = self.sendTCP() - if ok != False: - ok = True - except: - ok = False - if ok: - self.run = True - self.__updater = Thread(target=self.updateT) - self.__updater.start() - self.widget.connectButton.setText("Beenden") - else: - self.__base_address = "" - self.run = False - self.widget.connectButton.setText("Fehler") - - - def __changeSamplerate(self): - self.samplerate = self.widget.doubleSpinBox.value() diff --git a/plugins/netWoRTOC/networtoc.ui b/plugins/netWoRTOC/networtoc.ui deleted file mode 100644 index b0ab9f9..0000000 --- a/plugins/netWoRTOC/networtoc.ui +++ /dev/null @@ -1,96 +0,0 @@ - - - NetWoRTOC - - - - 0 - 0 - 360 - 389 - - - - NetWoRTOC - - - - - - - - true - - - - 127.0.0.1 - - - - - - - - Verbinden - - - - - - - - - - - Stream - - - true - - - - - - - Samplerate: - - - - - - - 1.000000000000000 - - - - - - - 1Time - - - - - - - - - Alle - - - - - - - true - - - QAbstractItemView::MultiSelection - - - - - - - - diff --git a/run.sh b/run.sh index f852689..2dd2949 100644 --- a/run.sh +++ b/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -cd ~/Software/RealTimeOpenControl +cd RTOC python3 RTOC.py diff --git a/screenshots/RTOC-schematik.png b/screenshots/RTOC-schematik.png new file mode 100644 index 0000000000000000000000000000000000000000..6fffb2623cdd0cb700036edf946df3dd397e24f0 GIT binary patch literal 46835 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 Irj_6S0R*-RP5=M^ literal 0 HcmV?d00001 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..15ffb22 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,13 @@ +[metadata] +# This includes the license file(s) in the wheel. +# https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file +license_files = LICENSE + +[bdist_wheel] +# This flag says to generate wheels that support both Python 2 and Python +# 3. If your code will not run unchanged on both Python 2 and 3, you will +# need to generate separate wheels for each Python version that you +# support. Removing this line (or setting universal to 0) will prevent +# bdist_wheel from trying to make a universal wheel. For more see: +# https://packaging.python.org/guides/distributing-packages-using-setuptools/#wheels +universal=0 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f48c337 --- /dev/null +++ b/setup.py @@ -0,0 +1,161 @@ +# Tutorial at https://blog.jetbrains.com/pycharm/2017/05/how-to-publish-your-package-on-pypi/ +# https://packaging.python.org/tutorials/packaging-projects/ + +# python setup.py sdist bdist_wheel +# python -m twine upload --repository-url https://test.pypi.org/legacy/ dist/* +# python.exe -m pip install --index-url https://test.pypi.org/simple/ RTOC + +# virtual env for testing: +#> virtualenv +# move to directory and use python from There + +# public: +# python setup.py bdist_wheel + +DESCRIPTION = """\ +RealTime OpenControl is a universal measurement, plot and control-software. +It's purpose is to put measurements from different devices (for example 3d-printers, multimeters, power supplies, microcontroller,...) into one tool. +Its fully expandable for every device with Python-Plugins and a running TCP-server. +You can also control the devices (if your plugin has this functionality) with python-scripts, which you can write and run at runtime! This makes it also possible to plot everything else. +There are some example-plugins and example-scripts included. +It also offers an extended plotting-GUI with multiple plots, measure-tools, style-adjustments. +""" + +setupOpts = dict( + name='RTOC', + description='RealTime OpenControl', + long_description=DESCRIPTION, + license='GNU', + url='https://github.com/Haschtl/RealTimeOpenControl', + author='Sebastian Keller', + author_email='sebastiankeller@online.de', + classifiers = [ + "Programming Language :: Python", + "Programming Language :: Python :: 3", + #"Development Status :: 2.3", + "Environment :: Other Environment", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", + "Operating System :: OS Independent", + "Topic :: Scientific/Engineering :: Visualization", + "Topic :: Software Development :: User Interfaces", + ], +) + + +import distutils.dir_util +from distutils.command import build +import os, sys, re +try: + import setuptools + from setuptools import setup + from setuptools.command import install +except ImportError: + sys.stderr.write("Warning: could not import setuptools; falling back to distutils.\n") + from distutils.core import setup + from distutils.command import install + + +# Work around mbcs bug in distutils. +# http://bugs.python.org/issue10945 +import codecs +try: + codecs.lookup('mbcs') +except LookupError: + ascii = codecs.lookup('ascii') + func = lambda name, enc=ascii: {True: enc}.get(name=='mbcs') + codecs.register(func) + + +path = os.path.split(__file__)[0] +#sys.path.insert(0, os.path.join(path, 'tools')) + +version = 1.7 +forcedVersion = 1.7 +gitVersion =1.7 +initVersion=1.0 + + + +class Build(build.build): + """ + * Clear build path before building + """ + def run(self): + global path + + ## Make sure build directory is clean + buildPath = os.path.join(path, self.build_lib) + if os.path.isdir(buildPath): + distutils.dir_util.remove_tree(buildPath) + + ret = build.build.run(self) + + +class Install(install.install): + """ + * Check for previously-installed version before installing + * Set version string in __init__ after building. This helps to ensure that we + know when an installation came from a non-release code base. + """ + def run(self): + global path, version, initVersion, forcedVersion, installVersion + + name = self.config_vars['dist_name'] + path = os.path.join(self.install_libbase, 'RTOC') + if os.path.exists(path): + raise Exception("It appears another version of %s is already " + "installed at %s; remove this before installing." + % (name, path)) + print("Installing to %s" % path) + rval = install.install.run(self) + + + # If the version in __init__ is different from the automatically-generated + # version string, then we will update __init__ in the install directory + if initVersion == version: + return rval + + try: + initfile = os.path.join(path, '__init__.py') + data = open(initfile, 'r').read() + open(initfile, 'w').write(re.sub(r"__version__ = .*", "__version__ = '%s'" % version, data)) + installVersion = version + except: + sys.stderr.write("Warning: Error occurred while setting version string in build path. " + "Installation will use the original version string " + "%s instead.\n" % (initVersion) + ) + if forcedVersion: + raise + installVersion = initVersion + sys.excepthook(*sys.exc_info()) + + return rval + + +setup( + version=version, + packages=setuptools.find_packages(), + #package_dir={'RTOC': 'RTOC', 'RTOC.plugins':'plugins', 'RTOC.example_scripts':'example_scripts'}, ## install examples along with the rest of the source + package_data={ + 'RTOC': ['*'], + 'RTOC': ['*'] + }, + python_requires='>=3', + include_package_data=True, + install_requires = [ + 'numpy', + 'pyqt5', + 'pyqtgraph', + 'markdown2', + 'xlsxwriter', + 'scipy', + 'qtmodern', + 'python-telegram-bot', + 'matplotlib', + 'requests', + 'python-nmap' + ], + **setupOpts +) diff --git a/setupStandalone.py b/setupStandalone.py new file mode 100644 index 0000000..407d68f --- /dev/null +++ b/setupStandalone.py @@ -0,0 +1,25 @@ +# windows: python3 setupStandalone.py bdist_msi + +from cx_Freeze import setup, Executable +import os +import sys + +os.environ['TCL_LIBRARY'] = r'C:\Users\hasch\AppData\Local\Programs\Python\Python36\tcl\tcl8.6' +os.environ['TK_LIBRARY'] = r'C:\Users\hasch\AppData\Local\Programs\Python\Python36\tcl\tk8.6' +pluginimports = ["requests","socket","minimalmodbus", "serial"] +buildOptions = dict(packages = ["telegram", "telegram.ext", "matplotlib", "getopt", "numpy","scipy","sys","os","time", "traceback", "multiprocessing", "json", "csv", "xlsxwriter", "importlib", "threading", "collections", "functools", "math", "random", "PyQt5", "pyqtgraph", 'lxml._elementpath','lxml.etree',"markdown2", "idna.idnadata", "idna"]+pluginimports, excludes = ["scipy.spatial.cKDTree"], includes = ["RTOC/","RTOC/LoggerPlugin", "RTOC/data.scriptLibrary"], include_files = ["RTOC/", "RTOC/data/","RTOC/data/icon.png","example_scripts/","RTOC/plugins/","avbin64.dll","README.md","LICENSE", r"C:\Users\hasch\AppData\Local\Programs\Python\Python36\DLLs\tcl86t.dll", + r"C:\Users\hasch\AppData\Local\Programs\Python\Python36\DLLs\tk86t.dll"]) + +base = 'Win32GUI' if sys.platform=='win32' else None + +executables = [ + Executable('RTOC/RTOC.py', base=base)#, icon="data/icon.png",shortcutName="RealTimeOpenControl",shortcutDir="MyProgramMenu") +] + +setup( + name='RealTimeOpenControl', + version = '1.7', + description = 'RTOC', + options = dict(build_exe = buildOptions), + executables = executables +)