Skip to content

Commit

Permalink
release 2.1.1
Browse files Browse the repository at this point in the history
- added plugin-functions to send telegram messages, photos and files
- PLUGIN __INIT__ changed!!! Please review the examples
- Added more info in signal-tooltip (GUI)
- GUI signal-list looks nicer now
- Possibility to select timerange for Remote-Signals.
- Increased remote-TCP-timeout to 20s due to slow database on some 
devices (raspberrypi)
  • Loading branch information
Sebastian committed Jun 18, 2019
1 parent b65a86f commit fa8a7e0
Show file tree
Hide file tree
Showing 25 changed files with 418 additions and 154 deletions.
1 change: 1 addition & 0 deletions .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__/
Expand Down
8 changes: 4 additions & 4 deletions RTOC.egg-info/PKG-INFO
@@ -1,17 +1,17 @@
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
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/)

Expand Down Expand Up @@ -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
65 changes: 54 additions & 11 deletions RTOC/LoggerPlugin.py
@@ -1,4 +1,4 @@
# LoggerPlugin v2.5
# LoggerPlugin v2.6
import traceback
import time
import sys
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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."')
Expand Down Expand Up @@ -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`.
Expand All @@ -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)
Expand All @@ -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:
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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():
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion RTOC/RTLogger/DeviceFunctions.py
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion RTOC/RTLogger/NetworkFunctions.py
Expand Up @@ -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+".", ''))
Expand Down
4 changes: 2 additions & 2 deletions RTOC/RTLogger/RTLogger.py
Expand Up @@ -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.')
Expand Down
23 changes: 15 additions & 8 deletions RTOC/RTLogger/RTRemote.py
Expand Up @@ -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():
Expand Down Expand Up @@ -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(':')
Expand All @@ -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

Expand Down
8 changes: 4 additions & 4 deletions RTOC/RTLogger/RT_data.py
Expand Up @@ -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)
Expand Down Expand Up @@ -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':
Expand Down
33 changes: 32 additions & 1 deletion RTOC/RTLogger/__main__.py
Expand Up @@ -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):
Expand Down Expand Up @@ -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(
Expand Down
4 changes: 2 additions & 2 deletions RTOC/RTLogger/plugins/Generator.py
Expand Up @@ -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

Expand Down

0 comments on commit fa8a7e0

Please sign in to comment.