diff --git a/.gitignore b/.gitignore index 74b844b..f628654 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ __pycache__/ RTOC/__pycache__/ RTOC/RTLogger/__pycache__/ +RTOC/RTLogger/plugins/test.py RTOC/RTLogger/plugins/__pycache__/ RTOC/RTOC_GUI/__pycache__/ RTOC/lib/__pycache__/ diff --git a/RTOC.egg-info/PKG-INFO b/RTOC.egg-info/PKG-INFO index 2c2305e..17f46b8 100644 --- a/RTOC.egg-info/PKG-INFO +++ b/RTOC.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: RTOC -Version: 2.1.0 +Version: 2.1.1 Summary: RealTime OpenControl Home-page: https://github.com/Haschtl/RealTimeOpenControl Author: Sebastian Keller @@ -8,10 +8,10 @@ Author-email: sebastiankeller@online.de License: GNU Description: # RealTime OpenControl (RTOC) - | [![Documentation Status](https://readthedocs.org/projects/realtimeopencontrol/badge/?version=latest)](https://realtimeopencontrol.readthedocs.io/en/latest/) | [![Builds 2.1.0v](https://img.shields.io/badge/Builds%20version-1.6-brightgreen.svg?style=flat)](https://github.com/Haschtl/RealTimeOpenControl/releases) | [![PyPI pyversions](https://img.shields.io/pypi/pyversions/ansicolortags.svg)](https://pypi.python.org/pypi/RTOC/) | [![PyPI version fury.io](https://badge.fury.io/py/ansicolortags.svg)](https://pypi.python.org/pypi/RTOC/) | [![PyPI license](https://img.shields.io/pypi/l/ansicolortags.svg)](https://pypi.python.org/pypi/RTOC/) | [![GitHub release](https://img.shields.io/github/release/Naereen/StrapDown.js.svg)](https://github.com/Haschtl/RealTimeOpenControl/releases/) | [![Github all releases](https://img.shields.io/github/downloads/Naereen/StrapDown.js/total.svg)](https://github.com/Haschtl/RealTimeOpenControl/releases/) | + | [![Documentation Status](https://readthedocs.org/projects/realtimeopencontrol/badge/?version=latest)](https://realtimeopencontrol.readthedocs.io/en/latest/) | [![Builds 2.1.1v](https://img.shields.io/badge/Builds%20version-1.6-brightgreen.svg?style=flat)](https://github.com/Haschtl/RealTimeOpenControl/releases) | [![PyPI pyversions](https://img.shields.io/pypi/pyversions/ansicolortags.svg)](https://pypi.python.org/pypi/RTOC/) | [![PyPI version fury.io](https://badge.fury.io/py/ansicolortags.svg)](https://pypi.python.org/pypi/RTOC/) | [![PyPI license](https://img.shields.io/pypi/l/ansicolortags.svg)](https://pypi.python.org/pypi/RTOC/) | [![GitHub release](https://img.shields.io/github/release/Naereen/StrapDown.js.svg)](https://github.com/Haschtl/RealTimeOpenControl/releases/) | [![Github all releases](https://img.shields.io/github/downloads/Naereen/StrapDown.js/total.svg)](https://github.com/Haschtl/RealTimeOpenControl/releases/) | - ### Version 2.1.0 + ### Version 2.1.1 ![Usecase](screenshots/RTOC-schematik.png) [Documentation](https://realtimeopencontrol.readthedocs.io/en/latest/) @@ -160,6 +160,6 @@ Classifier: Topic :: Software Development :: User Interfaces Requires-Python: >=3 Description-Content-Type: text/markdown Provides-Extra: Telegram -Provides-Extra: ALL Provides-Extra: GUI Provides-Extra: Webserver +Provides-Extra: ALL diff --git a/RTOC/LoggerPlugin.py b/RTOC/LoggerPlugin.py index 6ee622c..643423b 100644 --- a/RTOC/LoggerPlugin.py +++ b/RTOC/LoggerPlugin.py @@ -1,4 +1,4 @@ -# LoggerPlugin v2.5 +# LoggerPlugin v2.6 import traceback import time import sys @@ -40,18 +40,20 @@ def __init__(self, stream=None, plot=None, event=None): event (method): The callback-method for the event-method """ - def __init__(self, stream=None, plot=None, event=None): + def __init__(self, stream=None, plot=None, event=None, telegramBot=None, *args, **kwargs): # Plugin setup # self.setDeviceName() self._deviceName = "noDevice" self._cb = stream self._ev = event self._plt = plot + self._bot = telegramBot self._sock = None self._tcppassword = '' self._tcpport = 5050 self._tcpaddress = '' self._tcpthread = False + self.widget = None self._pluginThread = None self._oldPerpetualTimer = False self.lockPerpetialTimer = Lock() @@ -159,12 +161,12 @@ def stream(self, y=[], snames=[], dname=None, unit=None, x=None, slist=None, sdi unit.append(sdict[dev][sig][1]) x.append(now) else: - logging.error('STREAM ERROR, signal has not this format: [y, "unit"]') + logging.error('STREAM ERROR, signal {}.{} has not this format: [y, "unit"]'.format(dname, sig)) else: - logging.error('STREAM_ERROR: One signal was malformed') + logging.error('STREAM_ERROR:signal {}.{} was malformed.'.format(dname, sig)) self._cb(y=y, snames=snames, dname=dname, unit=unit, x=x) else: - logging.error('STREAM_ERROR: One device was malformed') + logging.error('STREAM_ERROR: device {} was malformed.'.format(dname)) return True else: logging.error('STREAM_ERROR: The data you provided with in your plugin was wrong."') @@ -265,7 +267,7 @@ def createTCPClient(self, address="localhost", password=None, tcpport=5050, thre self._tcppassword = password self._sock.setKeyword(password) - def sendTCP(self, y=None, sname=None, dname=None, unit=None, x=None, getSignal=None, getLatest=None, getEvent=None, getSignalList=False, getEventList=False, getPluginList=False, getSession=False, plot=False, event=None, remove=None, plugin=None, logger=None, stream=None): + def sendTCP(self, y=None, sname=None, dname=None, unit=None, x=None, getSignal=None, getLatest=None, getEvent=None, getSignalList=False, getEventList=False, getPluginList=False, getSession=False, plot=False, event=None, remove=None, plugin=None, logger=None, stream=None, timeout=5): """ Use any of the arguments described in :doc:`TCP`. @@ -289,6 +291,7 @@ def sendTCP(self, y=None, sname=None, dname=None, unit=None, x=None, getSignal=N getEventList: :py:meth:`.NetworkFunctions.getEventList` getPluginList: :py:meth:`.NetworkFunctions.getPluginList` getSession: :py:meth:`.RT_data.generateSessionJSON` + timeout: TCP-Client Timeout (Default: 5s) Returns: tcp_response (dict), if createTCPClient(threaded=False) @@ -297,12 +300,12 @@ def sendTCP(self, y=None, sname=None, dname=None, unit=None, x=None, getSignal=N """ if self._tcpthread: - t = Thread(target=self._sendTCP, args=(y, sname, dname, unit, x, getSignal, getLatest, getEvent, getSignalList, getEventList, getPluginList, getSession, plot, event, remove, plugin, logger, stream,)) + t = Thread(target=self._sendTCP, args=(y, sname, dname, unit, x, getSignal, getLatest, getEvent, getSignalList, getEventList, getPluginList, getSession, plot, event, remove, plugin, logger, stream, timeout)) t.start() else: - return self._sendTCP(y, sname, dname, unit, x, getSignal, getLatest, getEvent, getSignalList, getEventList, getPluginList, getSession, plot, event, remove, plugin, logger, stream) + return self._sendTCP(y, sname, dname, unit, x, getSignal, getLatest, getEvent, getSignalList, getEventList, getPluginList, getSession, plot, event, remove, plugin, logger, stream, timeout) - def _sendTCP(self, y=None, sname=None, dname=None, unit=None, x=None, getSignal=None, getLatest=None, getEvent=None, getSignalList=False, getEventList=False, getPluginList=False, getSession=False, plot=False, event=None, remove=None, plugin=None, logger=None, stream=None): + def _sendTCP(self, y=None, sname=None, dname=None, unit=None, x=None, getSignal=None, getLatest=None, getEvent=None, getSignalList=False, getEventList=False, getPluginList=False, getSession=False, plot=False, event=None, remove=None, plugin=None, logger=None, stream=None, timeout=5): with lock: if x is None and y is not None and not plot: @@ -343,7 +346,7 @@ def _sendTCP(self, y=None, sname=None, dname=None, unit=None, x=None, getSignal= # dicti['password'] = hex_dig if self._sock: try: - self._sock.connect(self._tcpaddress, self._tcpport, self._tcppassword) + self._sock.connect(self._tcpaddress, self._tcpport, self._tcppassword, timeout=timeout) self._sock.send(dicti) response = self._sock.recv() # self._sock.close() @@ -540,6 +543,45 @@ def __updateT(self, func): func() diff = (time.time() - start_time) + def telegram_send_message(self, text, onlyAdmin=False): + """ + Sends a message to all clients (or only admins). + + Args: + text (str): Text to be send to the clients. + onlyAdmin (bool): If True, only admins will get this message + """ + if self._bot is not None: + self._bot.send_message_to_all(text, onlyAdmin) + else: + logging.warning('TelegramBot is not enabled or wrong configured! Can not send message "{}"'.format(text)) + + def telegram_send_photo(self, path, onlyAdmin=False): + """ + Sends the picture at a given path to all clients (or only admins). + + Args: + path (str): Path to the picture to send. + onlyAdmin (bool): If True, only admins will get this message + """ + if self._bot is not None: + self._bot.send_photo(path, onlyAdmin) + else: + logging.warning('TelegramBot is not enabled or wrong configured! Can not send photo "{}"'.format(path)) + + def telegram_send_document(self, path, onlyAdmin=False): + """ + Sends any document at a given path to all clients (or only admins). + + Args: + path (str): Path to the file to send. + onlyAdmin (bool): If True, only admins will get this message + """ + if self._bot is not None: + self._bot.send_document(path, onlyAdmin) + else: + logging.warning('TelegramBot is not enabled or wrong configured! Can not send file "{}"'.format(path)) + class _perpetualTimer(): @@ -571,7 +613,8 @@ def _handle_function(self): timedelta = timedelta + self._correction if timedelta < 0: timedelta = 0 - if not self._lock.locked() and not self._cancel: + if not self._cancel and not self._lock.locked(): + # with self._lock: self._thread = Timer(timedelta, self._handle_function) self.thread_counter += 1 self._thread.start() diff --git a/RTOC/RTLogger/DeviceFunctions.py b/RTOC/RTLogger/DeviceFunctions.py index 20e00ab..ac1768d 100644 --- a/RTOC/RTLogger/DeviceFunctions.py +++ b/RTOC/RTLogger/DeviceFunctions.py @@ -86,7 +86,7 @@ def startPlugin(self, name, remote=True): # if callback is None: self.pluginObjects[ name] = importlib.import_module( - fullname).Plugin(self.database.addDataCallback, self.database.plot, self.database.addNewEvent) + fullname).Plugin(stream=self.database.addDataCallback, plot=self.database.plot, event=self.database.addNewEvent, telegramBot=self.telegramBot) # else: # self.pluginObjects[name] = importlib.import_module( # fullname).Plugin(callback, self.addNewEvent) diff --git a/RTOC/RTLogger/NetworkFunctions.py b/RTOC/RTLogger/NetworkFunctions.py index 0016fba..d436745 100644 --- a/RTOC/RTLogger/NetworkFunctions.py +++ b/RTOC/RTLogger/NetworkFunctions.py @@ -343,7 +343,7 @@ def getPluginDict(self): dict[name]['parameters'] = [] dict[name]['status'] = False for fun in self.pluginFunctions.keys(): - hiddenFuncs = ["loadGUI", "updateT", "stream", "plot", "event", "createTCPClient", "sendTCP", "close", "cancel", "start", "setSamplerate","setDeviceName",'setPerpetualTimer','setInterval','getDir'] + hiddenFuncs = ["loadGUI", "updateT", "stream", "plot", "event", "createTCPClient", "sendTCP", "close", "cancel", "start", "setSamplerate","setDeviceName",'setPerpetualTimer','setInterval','getDir', 'telegram_send_message', 'telegram_send_photo', 'telegram_send_document'] if fun.startswith(name+".") and fun not in [name+'.'+i for i in hiddenFuncs]: dict[name]['functions'].append(fun.replace(name+".", '')) diff --git a/RTOC/RTLogger/RTLogger.py b/RTOC/RTLogger/RTLogger.py index b42285b..d661e8a 100644 --- a/RTOC/RTLogger/RTLogger.py +++ b/RTOC/RTLogger/RTLogger.py @@ -347,9 +347,9 @@ def _load_config(self): if key2 not in self.__config[key].keys(): self.__config[key][key2] = defaultconfig[key][key2] - except Exception: + except Exception as error: logging.debug(traceback.format_exc()) - logging.error('Error loading config.json') + logging.error('Error loading config.json\n{}'.format(error)) self.__config = _Config(defaultconfig) else: logging.warning('No config-file found.') diff --git a/RTOC/RTLogger/RTRemote.py b/RTOC/RTLogger/RTRemote.py index 27c7b06..f29e154 100644 --- a/RTOC/RTLogger/RTRemote.py +++ b/RTOC/RTLogger/RTRemote.py @@ -97,7 +97,7 @@ def getAllSignals(self): selection.append(".".join(i)) for s in selection: ssplit = s.split('.') - ans = self._sendTCP(getSignal={'dname':ssplit[0], 'sname':ssplit[1], 'xmin': self.xmin, 'xmax': self.xmax, 'maxN': self.maxN}) + ans = self._sendTCP(getSignal={'dname':ssplit[0], 'sname':ssplit[1], 'xmin': self.xmin, 'xmax': self.xmax, 'maxN': self.maxN}, timeout=20) if ans != False: if 'signals' in ans.keys(): for sig in ans['signals'].keys(): @@ -280,15 +280,17 @@ def connect(self, hostname='127.0.0.1', port=5050, name='RemoteDevice', password self.connections.append(newConnection) self.getRemoteDeviceList() - def disconnect(self, hostname): + def disconnect(self, hostname, port): if len(hostname.split(':')) == 2: hostname = hostname.split(':')[0] + index = -1 for idx, c in enumerate(self.connections): - if c.host == hostname: + if c.host == hostname and c.port ==port: self.connections[idx].stop() - self.connections.pop(idx) self.devices.pop(c.name) + # self.devicenames.pop(c.name) + # self.pluginStatus.pop(c.name) devs = [] for dev in self.devicenames.keys(): namesplit = dev.split(':') @@ -299,12 +301,17 @@ def disconnect(self, hostname): self.pluginStatus.pop(dev) if self.logger.reloadDevicesCallback is not None: self.logger.reloadDevicesCallback() - return True - return False + index = idx + break + if index != -1: + self.connections.pop(index) + return True + else: + return False - def getConnection(self, host): + def getConnection(self, host, port): for idx, c in enumerate(self.connections): - if host == c.host: + if host == c.host and port == c.port: return self.connections[idx] return None diff --git a/RTOC/RTLogger/RT_data.py b/RTOC/RTLogger/RT_data.py index b2c8fa5..2482d4d 100644 --- a/RTOC/RTLogger/RT_data.py +++ b/RTOC/RTLogger/RT_data.py @@ -1128,9 +1128,9 @@ def _getSQLSignalUnit(self, sigID): def _appendSQLSignal(self, sigID, x, y): sql = 'UPDATE '+SIGNAL_TABLE_NAME + \ - ' SET X = array_cat(X, ARRAY'+str(list(x))+') WHERE ID ='+str(sigID)+';' + ' SET X = array_cat(X, ARRAY'+str(list(x))+'::NUMERIC[]) WHERE ID ='+str(sigID)+';' sql += '\nUPDATE '+SIGNAL_TABLE_NAME + \ - ' SET Y = array_cat(Y,ARRAY'+str(list(y))+') WHERE ID ='+str(sigID)+';' + ' SET Y = array_cat(Y,ARRAY'+str(list(y))+'::NUMERIC[]) WHERE ID ='+str(sigID)+';' # sql += '\nUPDATE '+SIGNAL_TABLE_NAME+' SET UNIT = \'' + \ # str(dataunit)+'\' WHERE ID ='+str(sigID)+';' self._execute_n_commit(sql) @@ -1360,9 +1360,9 @@ def _SQLplotNewData(self, x, y, dataunit, devicename, signalname, createCallback logging.warning('autoResize is DEPRECATED for postgresql') if hold == 'on': sql = 'UPDATE '+SIGNAL_TABLE_NAME + \ - ' SET X = array_cat(X, '+str(x)+') WHERE ID ='+str(sigID)+';' + ' SET X = array_cat(X, '+str(x)+'::NUMERIC[]) WHERE ID ='+str(sigID)+';' sql += '\nUPDATE '+SIGNAL_TABLE_NAME + \ - ' SET Y = array_cat(Y,'+str(y)+') WHERE ID ='+str(sigID)+';' + ' SET Y = array_cat(Y,'+str(y)+'::NUMERIC[]) WHERE ID ='+str(sigID)+';' sql += '\nUPDATE '+SIGNAL_TABLE_NAME+' SET UNIT = \'' + \ str(dataunit)+'\' WHERE ID ='+str(sigID)+';' elif hold == 'mergeX': diff --git a/RTOC/RTLogger/__main__.py b/RTOC/RTLogger/__main__.py index bd8be56..10892b9 100644 --- a/RTOC/RTLogger/__main__.py +++ b/RTOC/RTLogger/__main__.py @@ -24,6 +24,37 @@ __package__ = "RTOC.RTLogger" __main__ = __name__ +try: + from PyQt5 import QtCore + + app = QtCore.QCoreApplication(sys.argv) + # from PyQt5 import QtWidgets + userpath = os.path.expanduser('~/.RTOC') + if os.path.exists(userpath+"/config.json"): + try: + with open(userpath+"/config.json", encoding="UTF-8") as jsonfile: + config = json.load(jsonfile, encoding="UTF-8") + except Exception: + logging.debug(traceback.format_exc()) + config = {'global': {'language': 'en'}} + else: + config = {'global': {'language': 'en'}} + if config['global']['language'] == 'de': + translator = QtCore.QTranslator() + if getattr(sys, 'frozen', False): + # frozen + packagedir = os.path.dirname(sys.executable) + else: + # unfrozen + packagedir = os.path.dirname(os.path.realpath(__file__)) + translator.load(packagedir+"/RTOC/locales/de_de.qm") + app.installTranslator(translator) + # QtCore.QCoreApplication.installTranslator(translator) + print('German language selected') +except (ImportError, SystemError): + logging.warning('Cannot set language') + + class RTOCDaemon(Daemon): def __init__(self, pidfile, port=5050): @@ -56,7 +87,7 @@ def main(): 'RTOC.RTLogger [-h, -s, -l, -w]\n -h: Hilfe\n-s (--server) [COMMAND]: TCP-Server ohne GUI\n\t- start: Starts the RTOC-daemon\n\t- stop: Stops the RTOC-daemon\n\t- restart: Restarts the RTOC-daemon\n-w Startet RTLogger mit Website\n-p (--port): Starte TCP-Server auf anderem Port (Standart: 5050)\n-c (--config [OPTION=value]): Configure RTOC, type "-c list" to see all options') sys.exit(0) elif opt == '-v': - logging.info("2.1.0") + logging.info("2.1.1") elif opt in ('-s', '--server'): if os.name == 'nt': logging.info( diff --git a/RTOC/RTLogger/plugins/Generator.py b/RTOC/RTLogger/plugins/Generator.py index 2009cf9..762ddb1 100644 --- a/RTOC/RTLogger/plugins/Generator.py +++ b/RTOC/RTLogger/plugins/Generator.py @@ -12,9 +12,9 @@ class Plugin(LoggerPlugin): - def __init__(self, stream=None, plot=None, event=None): + def __init__(self, *args, **kwargs): # Plugin setup - super(Plugin, self).__init__(stream, plot, event) + super(Plugin, self).__init__(*args, **kwargs) self.setDeviceName(devicename) self.smallGUI = True diff --git a/RTOC/RTLogger/telegramBot.py b/RTOC/RTLogger/telegramBot.py index 8922b7c..9aef06f 100644 --- a/RTOC/RTLogger/telegramBot.py +++ b/RTOC/RTLogger/telegramBot.py @@ -189,13 +189,26 @@ def sendEvent(self, message, devicename, signalname, priority): def send_message_to_all(self, message, onlyAdmin=False): for id in self.telegram_clients.keys(): - if onlyAdmin and self.telegram_clients[id]['permission'] == 'admin': + if (onlyAdmin and self.telegram_clients[id]['permission'] == 'admin') or not onlyAdmin: self.send_message(chat_id=int(id), text=message, delete=False) - # try: - # self.bot.send_message(chat_id=int(id), text=message, - # parse_mode=ParseMode.MARKDOWN) - # except Exception: - # self.bot.send_message(chat_id=int(id), text=message) + + def send_photo(self, path, onlyAdmin=False): + try: + for id in self.telegram_clients.keys(): + if (onlyAdmin and self.telegram_clients[id]['permission'] == 'admin') or not onlyAdmin: + self.bot.send_photo(chat_id=int(id), photo=open(path, 'rb')) + except Exception as error: + text = translate('RTOC', 'Error while sending photo:\n{}').format(error) + self.send_message_to_all(text, onlyAdmin) + + def send_document(self, path, onlyAdmin=False): + try: + for id in self.telegram_clients.keys(): + if (onlyAdmin and self.telegram_clients[id]['permission'] == 'admin') or not onlyAdmin: + self.bot.send_document(chat_id=int(id), document=open(path, 'rb')) + except Exception as error: + text = translate('RTOC', 'Error while sending file:\n{}').format(error) + self.send_message_to_all(text, onlyAdmin) def connect(self): idler = Thread(target=self.connectThread) @@ -637,7 +650,7 @@ def deviceFunctionsHandler(self, bot, chat_id): name = self.current_plugin[chat_id] commands = [] for fun in self.logger.pluginFunctions.keys(): - hiddenFuncs = ["loadGUI", "updateT", "stream", "plot", "event", "createTCPClient", "sendTCP", "close", "cancel", "start", "setSamplerate","setDeviceName",'setPerpetualTimer','setInterval','getDir'] + hiddenFuncs = ["loadGUI", "updateT", "stream", "plot", "event", "createTCPClient", "sendTCP", "close", "cancel", "start", "setSamplerate","setDeviceName",'setPerpetualTimer','setInterval','getDir','telegram_send_message', 'telegram_send_photo', 'telegram_send_document'] hiddenFuncs = [name+'.'+i for i in hiddenFuncs] if fun.startswith(name+".") and fun not in hiddenFuncs: parStr = ', '.join(self.logger.pluginFunctions[fun][1]) diff --git a/RTOC/RTOC.py b/RTOC/RTOC.py index 91db735..35fbb06 100644 --- a/RTOC/RTOC.py +++ b/RTOC/RTOC.py @@ -38,7 +38,7 @@ if os.name == 'nt': import ctypes - myappid = 'RTOC.2.1.0' # arbitrary string + myappid = 'RTOC.2.1.1' # arbitrary string ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid) try: diff --git a/RTOC/RTOC_GUI/Actions.py b/RTOC/RTOC_GUI/Actions.py index 4d8f277..5068c73 100644 --- a/RTOC/RTOC_GUI/Actions.py +++ b/RTOC/RTOC_GUI/Actions.py @@ -62,6 +62,7 @@ def connectButtons(self): self.scriptWidgetToggle.triggered.connect(self.toggleScriptWidget) self.eventWidgetToggle.triggered.connect(self.toggleEventWidget) self.deviceRAWWidgetToggle.triggered.connect(self.toggleRAWWidget) + self.refreshDevicesButton.clicked.connect(self.reloadDevices) self.actionDeutsch.triggered.connect( partial(self.toggleLanguage, "de", self.actionDeutsch, [self.actionEnglish], False)) @@ -578,9 +579,9 @@ def connectNewHost(self): return connected return False - def addRemoteHostWidget(self, host, name): + def addRemoteHostWidget(self, host, name, port): remoteHostWidget = QtWidgets.QDockWidget(name, self) - widget = RemoteWidget(self, host, remoteHostWidget, name) + widget = RemoteWidget(self, host, remoteHostWidget, name, port) remoteHostWidget.setWidget(widget) self.remoteHostWidgets.append(remoteHostWidget) self.tabifyDockWidget(self.graphWidget, self.remoteHostWidgets[-1]) @@ -621,15 +622,15 @@ def connectHost(self, host, name, password=''): self.logger.remote.connect(host, port, name, password) retry = True host = hostsplit[0] - self.logger.remote.getConnection(host).tcppassword = password + self.logger.remote.getConnection(host, port).tcppassword = password while retry: retry = False - status = self.logger.remote.getConnection(host).status + status = self.logger.remote.getConnection(host, port).status if status == "protected": text, ok2 = pyqtlib.text_message(None, translate('RTOC', 'Password'), translate('RTOC', "The RTOC server {} is password-protected. Please enter your password.").format(hostname), translate('RTOC', 'TCP-Password')) if ok2: - self.logger.remote.getConnection(host).tcppassword = text + self.logger.remote.getConnection(host, port).tcppassword = text self.logger.remote.connect(host, port, name, text) retry = True ok3 = pyqtlib.alert_message(translate('RTOC', 'Save password'), translate('RTOC', @@ -640,12 +641,12 @@ def connectHost(self, host, name, password=''): pyqtlib.info_message(translate('RTOC', 'Connection established'), translate('RTOC', 'Connection to {} on port {} established.').format(host, port), '') - self.addRemoteHostWidget(host, name) + self.addRemoteHostWidget(host, name, port) return True elif status == "wrongPassword": text, ok = pyqtlib.text_message(None, translate('RTOC', 'Protected'), translate('RTOC', 'Connection to {} on port {} not established').format(host, port), translate('RTOC', 'Password is wrong.')) if ok: - self.logger.remote.getConnection(host).tcppassword = text + self.logger.remote.getConnection(host, port).tcppassword = text self.logger.remote.connect(host, port, name, text) retry = True ok3 = pyqtlib.alert_message(translate('RTOC', 'Save password'), translate('RTOC', @@ -662,7 +663,7 @@ def connectHost(self, host, name, password=''): pyqtlib.info_message(translate('RTOC', 'Connection established'), translate('RTOC', 'Connection to {} on port {} established.').format(host, port), '') - self.addRemoteHostWidget(host, name) + self.addRemoteHostWidget(host, name, port) return True else: print(status) diff --git a/RTOC/RTOC_GUI/remoteWidget.py b/RTOC/RTOC_GUI/remoteWidget.py index cd38205..64ae514 100644 --- a/RTOC/RTOC_GUI/remoteWidget.py +++ b/RTOC/RTOC_GUI/remoteWidget.py @@ -27,7 +27,7 @@ def _(text): class RemoteWidget(QtWidgets.QWidget): update = QtCore.pyqtSignal() - def __init__(self, selfself, remotehost="", parent=None, name="Remote"): + def __init__(self, selfself, remotehost="", parent=None, name="Remote", port=5050): super(RemoteWidget, self).__init__() if getattr(sys, 'frozen', False): # frozen @@ -44,6 +44,7 @@ def __init__(self, selfself, remotehost="", parent=None, name="Remote"): self.hostname = remotehost self.parent = parent self.name = name + self.port = port self.listWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.listWidget.customContextMenuRequested.connect(self.listItemRightClicked) @@ -54,7 +55,7 @@ def __init__(self, selfself, remotehost="", parent=None, name="Remote"): self.clearButton.clicked.connect(self.clear) self.pauseButton.clicked.connect(self.pause) self.saveButton.clicked.connect(self.saveRemoteSession) - self.remote = self.self.logger.remote.getConnection(self.hostname) + self.remote = self.self.logger.remote.getConnection(self.hostname, self.port) self.remote.updateRemoteCallback = self.update.emit self.update.connect(self.updateRemote) if self.remote is not None: @@ -114,7 +115,7 @@ def disconnect(self): else: ans = pyqtlib.alert_message(translate('RTOC', 'Disconnect'), translate('RTOC', 'Do you want to disconnect {}?').format(self.hostname), translate('RTOC', 'Transferred signals will remain.'), "", translate('RTOC', "Yes"), translate('RTOC', "No")) if ans: - ans = self.self.logger.remote.disconnect(self.hostname) + ans = self.self.logger.remote.disconnect(self.hostname, self.port) if ans is False: ans = pyqtlib.info_message(translate('RTOC', 'Error'), translate('RTOC', 'Could not disconnect from {}.').format(self.hostname), '') self.close() diff --git a/RTOC/RTOC_GUI/signalWidget.py b/RTOC/RTOC_GUI/signalWidget.py index 3221218..c95b619 100644 --- a/RTOC/RTOC_GUI/signalWidget.py +++ b/RTOC/RTOC_GUI/signalWidget.py @@ -459,29 +459,33 @@ def createToolTip(self, id, xdata): maxduration = self.calcDuration(list(xdata)) duration = xdata[-1]-xdata[0] try: - if self.logger.config['postgresql']['active']: - line1 = str(timedelta(seconds=duration)) - line2 = str(len(list(xdata))) - else: - line1 = str(timedelta(seconds=duration)) + '/ ~ ' + \ - str(timedelta(seconds=maxduration)) - line2 = str( - len(list(xdata)))+"/"+str(self.logger.config['global']['recordLength']) + # if self.logger.config['postgresql']['active']: + # line1 = translate('RTOC', 'Duration: ')+str(timedelta(seconds=duration)) + # line2 = translate('RTOC', 'Values: ')+str(len(list(xdata))) + # else: + line1 = translate('RTOC', 'Duration: {}/~{}').format(str(timedelta(seconds=round(duration))), str(timedelta(seconds=round(maxduration)))) + line2 = translate('RTOC', 'Values: {}/{}').format(len(list(xdata)), str(self.logger.config['global']['recordLength'])) count = 20 if len(xdata) <= count: count = len(xdata) if count > 1: meaner = list(xdata)[-count:] diff = 0 - for idx, m in enumerate(meaner[:-1]): - diff += meaner[idx+1]-m + # for idx, m in enumerate(meaner[:-1]): + # diff += meaner[idx+1]-m + diff = meaner[-1]-meaner[0] + diff2 = xdata[-1]-xdata[0] if diff != 0: - line3 = str(round((len(meaner)-1)/diff, 2))+" Hz" + latestSamplerate = str(round((len(meaner)-1)/diff, 2)) + samplerate = str(round((len(xdata)-1)/diff2, 2)) else: - line3 = "? Hz" + latestSamplerate = "?" + samplerate = "?" else: - line3 = "? Hz" - return line1+"\n"+line2 + "\n" + line3 + latestSamplerate = "?" + samplerate = "?" + line3 = translate('RTOC', 'Samplerate (latest): {} ({}) Hz').format(samplerate, latestSamplerate) + return self.devicename+'.'+self.signalname+'\n'+line1+"\n"+line2 + "\n" + line3 except Exception: logging.debug(traceback.format_exc()) print(traceback.format_exc()) diff --git a/RTOC/RTOC_GUI/ui/rtoc.ui b/RTOC/RTOC_GUI/ui/rtoc.ui index ce4f78c..3dfe072 100644 --- a/RTOC/RTOC_GUI/ui/rtoc.ui +++ b/RTOC/RTOC_GUI/ui/rtoc.ui @@ -10,6 +10,12 @@ 664 + + + 0 + 0 + + RealTime OpenControl @@ -88,20 +94,44 @@ 0 - - - - 0 - 0 - - - - Search devices - - - true + + + 0 - + + + + + 0 + 0 + + + + Search devices + + + true + + + + + + + + 0 + 0 + + + + + + + + icons/blinking.pngicons/blinking.png + + + + @@ -126,7 +156,7 @@ 0 0 398 - 97 + 88 @@ -192,7 +222,7 @@ - + false @@ -403,10 +433,10 @@ - + - + Values @@ -606,7 +636,7 @@ - + true @@ -728,7 +758,7 @@ 0 0 - 188 + 196 126 @@ -803,7 +833,7 @@ Edit plot - + diff --git a/RTOC/Template.py b/RTOC/Template.py index 27e5a5d..1a7036f 100644 --- a/RTOC/Template.py +++ b/RTOC/Template.py @@ -1,7 +1,7 @@ """ This template shows, how to implement plugins in RTOC -RTOC version 2.1.0 +RTOC version 2.0 A plugin needs to import RTOC.LoggerPlugin to be recognized by RTOC. """ @@ -26,8 +26,8 @@ class Plugin(LoggerPlugin): - def __init__(self, stream=None, plot=None, event=None): - super(Plugin, self).__init__(stream, plot, event) + def __init__(self, *args, **kwargs): + super(Plugin, self).__init__(*args, **kwargs) """Call this to initialize RTOC.LoggerPlugin""" self.setDeviceName(DEVICENAME) @@ -43,13 +43,10 @@ def __init__(self, stream=None, plot=None, event=None): self.setPerpetualTimer(self._updateT, samplerate=SAMPLERATE) """You will need to collect data periodically in many applications. You need to start that in a seperate thread. - RTOC provides a simple way to start a repeated thread with :py:meth:`.LoggerPlugin.setPerpetualTimer`. - The first parameter is the function, which collects data and sends it to RTOC. + RTOC provides a simple way to start a repeated thread with :py:meth:`.LoggerPlugin.setPerpetualTimer`. The first parameter is the function, which collects data and sends it to RTOC. You can define a ``samplerate`` or an ``interval`` to set the samplerate. - You can define a ``samplerate`` or an ``interval`` to set the samplerate. - You can still use normal threads to do the same thing, but in this way, the plugin can be stopped properly. - If you are using normal threads, make sure, to have a loop limited by ' + You can still use normal threads to do the same thing, but in this way, the plugin can be stopped properly. If you are using normal threads, make sure, to have a loop limited by ' ``self.run`` with ``while self.run:``. """ @@ -105,8 +102,27 @@ def loadGUI(self): """ This example will load a QWidget designed with QDesigner """ + self.widget.teleMessageButton.clicked.connect(self._teleMessageAction) + self.widget.telePhotoButton.clicked.connect(self._telePhotoAction) + self.widget.teleFileButton.clicked.connect(self._teleFileAction) + """ + Connect GUI-buttons with python-functions + """ return self.widget # This function needs to return a QWidget + def _teleMessageAction(self): + text = 'Hello world!' + self.telegram_send_message(text, onlyAdmin=False) + + def _telePhotoAction(self): + path = self.getDir(__file__)+'/examplePhoto.png' + self.telegram_send_photo(path, onlyAdmin=False) + + def _teleFileAction(self): + path = self.getDir(__file__)+'/examplePhoto.png' + self.telegram_send_document(path, onlyAdmin=False) + + hasGUI = True # If your plugin has a widget do this diff --git a/RTOC/__init__.py b/RTOC/__init__.py index 75ad07c..eaaab0f 100644 --- a/RTOC/__init__.py +++ b/RTOC/__init__.py @@ -12,7 +12,7 @@ # __package__ = "RTOC" # __main__ = __name__ name = "RTOC" -__version__ = "2.1.0" +__version__ = "2.1.1" def main(): @@ -149,35 +149,35 @@ def setStyleSheet(app, myapp): return app, myapp -def setLanguage(app): - - import gettext - from PyQt5 import QtCore - # from PyQt5 import QtWidgets - userpath = os.path.expanduser('~/.RTOC') - if os.path.exists(userpath+"/config.json"): - try: - with open(userpath+"/config.json", encoding="UTF-8") as jsonfile: - config = json.load(jsonfile, encoding="UTF-8") - except Exception: - logging.debug(traceback.format_exc()) - config = {'global': {'language': 'en'}} - else: - config = {'global': {'language': 'en'}} - if config['global']['language'] == 'de': - translator = QtCore.QTranslator() - if getattr(sys, 'frozen', False): - # frozen - packagedir = os.path.dirname(sys.executable) - else: - # unfrozen - packagedir = os.path.dirname(os.path.realpath(__file__)) - translator.load(packagedir+"/locales/de_de.qm") - app.installTranslator(translator) - - el = gettext.translation('base', localedir='locales', languages=['de']) - el.install() - _ = el.gettext +# def setLanguage(app): +# +# import gettext +# from PyQt5 import QtCore +# # from PyQt5 import QtWidgets +# userpath = os.path.expanduser('~/.RTOC') +# if os.path.exists(userpath+"/config.json"): +# try: +# with open(userpath+"/config.json", encoding="UTF-8") as jsonfile: +# config = json.load(jsonfile, encoding="UTF-8") +# except Exception: +# logging.debug(traceback.format_exc()) +# config = {'global': {'language': 'en'}} +# else: +# config = {'global': {'language': 'en'}} +# if config['global']['language'] == 'de': +# translator = QtCore.QTranslator() +# if getattr(sys, 'frozen', False): +# # frozen +# packagedir = os.path.dirname(sys.executable) +# else: +# # unfrozen +# packagedir = os.path.dirname(os.path.realpath(__file__)) +# translator.load(packagedir+"/locales/de_de.qm") +# app.installTranslator(translator) + + # el = gettext.translation('base', localedir='locales', languages=['de']) + # el.install() + # _ = el.gettext # 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 @@ -203,7 +203,31 @@ def startRemoteRTOC(remotepath): app = QtWidgets.QApplication(sys.argv) - app = setLanguage(app) + # app = setLanguage(app) + from PyQt5 import QtCore + # from PyQt5 import QtWidgets + userpath = os.path.expanduser('~/.RTOC') + if os.path.exists(userpath+"/config.json"): + try: + with open(userpath+"/config.json", encoding="UTF-8") as jsonfile: + config = json.load(jsonfile, encoding="UTF-8") + except Exception: + logging.debug(traceback.format_exc()) + config = {'global': {'language': 'en'}} + else: + config = {'global': {'language': 'en'}} + if config['global']['language'] == 'de': + translator = QtCore.QTranslator() + if getattr(sys, 'frozen', False): + # frozen + packagedir = os.path.dirname(sys.executable) + else: + # unfrozen + packagedir = os.path.dirname(os.path.realpath(__file__)) + translator.load(packagedir+"/locales/de_de.qm") + app.installTranslator(translator) + + myapp = RTOC(False) myapp.config['tcp']['active'] = True @@ -226,7 +250,31 @@ def startRTOC(tcp=None, port=None, local =False): app = QtWidgets.QApplication(sys.argv) - app = setLanguage(app) + # app = setLanguage(app) + from PyQt5 import QtCore + # from PyQt5 import QtWidgets + userpath = os.path.expanduser('~/.RTOC') + if os.path.exists(userpath+"/config.json"): + try: + with open(userpath+"/config.json", encoding="UTF-8") as jsonfile: + config = json.load(jsonfile, encoding="UTF-8") + except Exception: + logging.debug(traceback.format_exc()) + config = {'global': {'language': 'en'}} + else: + config = {'global': {'language': 'en'}} + if config['global']['language'] == 'de': + translator = QtCore.QTranslator() + if getattr(sys, 'frozen', False): + # frozen + packagedir = os.path.dirname(sys.executable) + else: + # unfrozen + packagedir = os.path.dirname(os.path.realpath(__file__)) + translator.load(packagedir+"/locales/de_de.qm") + app.installTranslator(translator) + + myapp = RTOC(tcp, port, local) app, myapp = setStyleSheet(app, myapp) diff --git a/RTOC/__main__.py b/RTOC/__main__.py index 31aa743..3ee51b9 100644 --- a/RTOC/__main__.py +++ b/RTOC/__main__.py @@ -44,7 +44,7 @@ def main(): 'RTOC.py [-h] [-r ]\n -h: Help\n-r (--remote) : TCP client for RTOC server\nFor options without GUI, run "python3 -m RTOC.RTLogger -h"') sys.exit(0) elif opt == '-v': - logging.info("2.1.0") + logging.info("2.1.1") elif opt in ("-r", "--remote"): remotepath = arg startRemoteRTOC(remotepath) diff --git a/RTOC/jsonsocket.py b/RTOC/jsonsocket.py index 0520e7f..251b5ea 100644 --- a/RTOC/jsonsocket.py +++ b/RTOC/jsonsocket.py @@ -87,7 +87,7 @@ class Server(object): reuse_port (bool): Enable/disable reuse_port (default: True) """ - def __init__(self, host, port, keyword=None, reuse_port=True): + def __init__(self, host, port, keyword=None, reuse_port=True, timeout=5): self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.client = None @@ -100,7 +100,7 @@ def __init__(self, host, port, keyword=None, reuse_port=True): # self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 5 * 60) # self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 10) # self.socket.setblocking(0) - self.socket.settimeout(5.0) + self.socket.settimeout(timeout) self.socket.bind((host, port)) self.socket.listen(BACKLOG) @@ -201,7 +201,7 @@ def setKeyword(self, keyword=None): # def __del__(self): # self.close() - def connect(self, host, port, keyword=None, reuse_port=True): + def connect(self, host, port, keyword=None, reuse_port=True, timeout=5): """ Establish a connection to a host (server) @@ -218,7 +218,7 @@ def connect(self, host, port, keyword=None, reuse_port=True): self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) # self.socket.setblocking(0) - self.socket.settimeout(5.0) + self.socket.settimeout(timeout) self.socket.connect((host, port)) self.keyword = keyword self.host = host diff --git a/docs/PLUGINS.rst b/docs/PLUGINS.rst index 9f08244..7a356bb 100644 --- a/docs/PLUGINS.rst +++ b/docs/PLUGINS.rst @@ -13,8 +13,8 @@ Plugins are written in Python3 and need to follow some rules:: from LoggerPlugin import LoggerPlugin # contains all plugin-functions class Plugin(LoggerPlugin): # This must be the main class of your function - def __init__(self, stream=None, plot= None, event=None): - super(Plugin, self).__init__(stream, plot, event)) + def __init__(self, *args, **kwargs): + super(Plugin, self).__init__(*args, **kwargs)) # start your code here diff --git a/loggerServer.py b/loggerServer.py index c65aa9f..4cb0cfe 100644 --- a/loggerServer.py +++ b/loggerServer.py @@ -1,19 +1,53 @@ #!/usr/bin/python3 -u import sys +import os +import json +import logging +import traceback -#sys.path.insert(0,'/home/pi/kellerlogger/') +# sys.path.insert(0,'/home/pi/kellerlogger/') from RTOC.RTLogger.RTLogger import RTLogger +try: + from PyQt5 import QtCore + + app = QtCore.QCoreApplication(sys.argv) + # from PyQt5 import QtWidgets + userpath = os.path.expanduser('~/.RTOC') + if os.path.exists(userpath+"/config.json"): + try: + with open(userpath+"/config.json", encoding="UTF-8") as jsonfile: + config = json.load(jsonfile, encoding="UTF-8") + except Exception: + logging.debug(traceback.format_exc()) + config = {'global': {'language': 'en'}} + else: + config = {'global': {'language': 'en'}} + if config['global']['language'] == 'de': + translator = QtCore.QTranslator() + if getattr(sys, 'frozen', False): + # frozen + packagedir = os.path.dirname(sys.executable) + else: + # unfrozen + packagedir = os.path.dirname(os.path.realpath(__file__)) + translator.load(packagedir+"/RTOC/locales/de_de.qm") + app.installTranslator(translator) + # QtCore.QCoreApplication.installTranslator(translator) + print('German language selected') +except (ImportError, SystemError): + logging.warning('Cannot set language') + logger = RTLogger(True) try: - #logger.startPlugin('Heliotherm') - #logger.getPlugin('Heliotherm').start('192.168.178.72') - #logger.startPlugin('Futtertrocknung') - a=logger.getThread() - if a is not None: - a.join() + # logger.startPlugin('Heliotherm') + # logger.getPlugin('Heliotherm').start('192.168.178.72') + # logger.startPlugin('Futtertrocknung') + a = logger.getThread() + if a is not None: + a.join() except KeyboardInterrupt: - logger.stop() - print("LoggerServer stopped by user") + logger.stop() + print("LoggerServer stopped by user") diff --git a/loggerWebServer.py b/loggerWebServer.py index 9700762..e3838b0 100644 --- a/loggerWebServer.py +++ b/loggerWebServer.py @@ -2,15 +2,50 @@ import sys -#sys.path.insert(0,'/home/pi/kellerlogger/') +import os +import json +import logging +import traceback + +# sys.path.insert(0,'/home/pi/kellerlogger/') from RTOC.RTLogger import RTOC_Web_standalone try: - RTOC_Web_standalone.start(debug=False) - # a=logger.getThread() - # if a is not None: - # a.join() + from PyQt5 import QtCore + + app = QtCore.QCoreApplication(sys.argv) + # from PyQt5 import QtWidgets + userpath = os.path.expanduser('~/.RTOC') + if os.path.exists(userpath+"/config.json"): + try: + with open(userpath+"/config.json", encoding="UTF-8") as jsonfile: + config = json.load(jsonfile, encoding="UTF-8") + except Exception: + logging.debug(traceback.format_exc()) + config = {'global': {'language': 'en'}} + else: + config = {'global': {'language': 'en'}} + if config['global']['language'] == 'de': + translator = QtCore.QTranslator() + if getattr(sys, 'frozen', False): + # frozen + packagedir = os.path.dirname(sys.executable) + else: + # unfrozen + packagedir = os.path.dirname(os.path.realpath(__file__)) + translator.load(packagedir+"/RTOC/locales/de_de.qm") + app.installTranslator(translator) + QtCore.QCoreApplication.installTranslator(translator) + print('German language selected') +except (ImportError, SystemError): + logging.warning('Cannot set language') + +try: + RTOC_Web_standalone.start(debug=False) + # a=logger.getThread() + # if a is not None: + # a.join() except KeyboardInterrupt: - #logger.close() - print("LoggerWebServer stopped by user") + # logger.close() + print("LoggerWebServer stopped by user") diff --git a/setup.py b/setup.py index f361aea..7dc28d5 100644 --- a/setup.py +++ b/setup.py @@ -78,9 +78,9 @@ path = os.path.split(__file__)[0] # sys.path.insert(0, os.path.join(path, 'tools')) -version = "2.1.0" -forcedVersion = "2.1.0" -gitVersion = "2.1.0" +version = "2.1.1" +forcedVersion = "2.1.1" +gitVersion = "2.1.1" initVersion = 1.0 diff --git a/setupStandalone.py b/setupStandalone.py index 00be436..27cfd7b 100644 --- a/setupStandalone.py +++ b/setupStandalone.py @@ -20,7 +20,7 @@ setup( name='RealTimeOpenControl', - version = '2.1.0', + version = '2.1.1', description = 'RTOC', options = dict(build_exe = buildOptions), executables = executables