Skip to content

Commit 2b3cf9b

Browse files
author
Sebastian
committed
release 1.8.9
**MOVED User-data from ~/Documents to ~/.RTOC** Moved network-options in menubar from 'File' to 'Network' You can now load multiple files in one session. - Added plugin-downloader from RTOC-Reposity - Opened Scripts are now beeing saved in session - Added autoresize and hold parameters for plot()-function - fixed loadScript - fixed scripts - fixed netWoRTOC 'division by zero' crash - fixed some GUI problems - fixed linux-darkmode bug - fixed CSV import - Updated wiki
1 parent 334cc8a commit 2b3cf9b

31 files changed

+2003
-757
lines changed

README.md

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# RealTime OpenControl (RTOC)
22

3-
### Version 1.8.7.5
3+
### Version 1.8.9
44

55
[**This README is available in GERMAN here.**](misc/README_german.md)
66

77
[Documentation](https://github.com/Haschtl/RealTimeOpenControl/wiki)
88

9-
RealTime OpenControl enables simple real-time data recording, visualization and editing. The recording can be done with a local Python scripts or via TCP locally/from the network. Visualization and editing is available locally, in the network (TCP and HTML) and via Telegram on the smartphone.
9+
RealTime OpenControl enables simple real-time data recording, visualization and editing. The recording can be done with a local Python scripts or via TCP locally/from the network. Visualization and editing is available locally, in the network (TCP and HTML) and via Telegram on the smartphone. You have full control over your python-plugins via TCP and Telegram.
1010

1111
In addition to data recording, events can also be recorded. These can, for example, trigger a telegram message.
1212

@@ -48,7 +48,7 @@ python3 -m RTOC -r <ADRESS>
4848
After the first start RTOC creates a directory for user plugins, temporary user data and settings.
4949

5050
```
51-
user@rtoc-server:~$ ls Documents/RTOC
51+
user@rtoc-server:~$ ls .RTOC
5252
config.json // Settings for RTOC
5353
devices/ // Directory for user-plugins
5454
plotStyles.json // Custom plotstyles for signals are stored in this file
@@ -72,7 +72,7 @@ Extract the .zip file into a directory. RTOC is started by double-clicking on "R
7272
After the first start RTOC creates a directory for user plugins, temporary user data and settings.
7373

7474
```
75-
user@rtoc-server:~$ ls Documents/RTOC
75+
user@rtoc-server:~$ ls .RTOC
7676
config.json // Settings for RTOC
7777
devices/ // Directory for user-plugins
7878
plotStyles.json // Custom plotstyles for signals are stored in this file
@@ -83,15 +83,19 @@ plotStyles.json // Custom plotstyles for signals are stored in this file
8383
To use RTOC, the following dependencies must be installed
8484

8585
```python
86-
pip3 install numpy pyqt5 pyqtgraph markdown2 xslxwriter scipy qtmodern
86+
pip3 install numpy pyqt5 pyqtgraph markdown2 xslxwriter scipy
8787
```
8888

8989
The following packages should also be installed
9090

9191
```
92-
pip3 install python-telegram-bot matplotlib requests python-nmap bokeh pycryptdomex
92+
pip3 install python-telegram-bot matplotlib requests python-nmap bokeh pycryptdomex pyGithub
9393
```
9494

95+
You can use different stylesheets if you want. Just install one of these with pip:
96+
'QDarkStyle', 'qtmodern', 'qdarkgraystyle'.
97+
98+
9599
The RTOC repository can then be cloned with
96100

97101
```shell
@@ -113,7 +117,7 @@ python3 RTOC -r <ADRESS>
113117
After the first start RTOC creates a directory for user plugins, temporary user data and settings.
114118

115119
```
116-
user@rtoc-server:~$ ls Documents/RTOC
120+
user@rtoc-server:~$ ls .RTOC
117121
config.json // Settings for RTOC
118122
devices/ // Directory for user-plugins
119123
plotStyles.json // Custom plotstyles for signals are stored in this file
@@ -133,7 +137,7 @@ plotStyles.json // Custom plotstyles for signals are stored in this file
133137
- function generator: generates sine, square, sawtooth, random, AC, DC
134138
- NetWoRTOC: Control and data exchange between several RTOC-servers
135139

136-
You can get more plugins from the [RTOC-plugin-repository](https://github.com/haschtl/rtoc-plugins):
140+
You can get more plugins from the [RTOC-plugin-repository](https://github.com/Haschtl/rtoc-plugins). Simply follow the steps described in the [documentation](https://github.com/Haschtl/RealTimeOpenControl/wiki/RTOC-Repo):
137141
- System: For recording many system variables (CPU, Memory, Network,...)
138142
- Octoprint: Recording of 3D printers
139143
- DPS5020: power supply unit recording and control (possibly also DPS5005, ...)
@@ -242,6 +246,7 @@ The connection between RTOC server and client can be encrypted end-to-end (DES)
242246
- [Taurus PyQtGraph](https://github.com/taurus-org/taurus_pyqtgraph.git)
243247
- [ImportCode script from avtivestate.com](http://code.activestate.com/recipes/82234-importing-a-dynamically-generated-module/)
244248
- [VC820Py from adnidor (for HoldPeak_VC820 plugin)](https://github.com/adnidor/vc820py)
249+
- [PyQt5 CSV-Editor](https://python-forum.io/Thread-Read-Write-CSV-Qt5)
245250

246251
All icons used in this software (including plugins) are kindly provided by [Icons8](www.icons8.com)
247252

RTOC.egg-info/PKG-INFO

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Metadata-Version: 1.2
22
Name: RTOC
3-
Version: 1.8.7.5
3+
Version: 1.8.9
44
Summary: RealTime OpenControl
55
Home-page: https://github.com/Haschtl/RealTimeOpenControl
66
Author: Sebastian Keller

RTOC.egg-info/SOURCES.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,23 @@ README.md
33
setup.cfg
44
setup.py
55
RTOC/LoggerPlugin.py
6+
RTOC/PluginDownloader.py
67
RTOC/RTLogger.py
78
RTOC/RTOC.py
89
RTOC/RTOC_Web.py
910
RTOC/__init__.py
1011
RTOC/__main__.py
12+
RTOC/csv_edit.py
1113
RTOC/jsonsocket.py
1214
RTOC/telegramBot.py
15+
RTOC/testdaten2.csv
1316
RTOC.egg-info/PKG-INFO
1417
RTOC.egg-info/SOURCES.txt
1518
RTOC.egg-info/dependency_links.txt
1619
RTOC.egg-info/requires.txt
1720
RTOC.egg-info/top_level.txt
1821
RTOC/__pycache__/LoggerPlugin.cpython-36.pyc
22+
RTOC/__pycache__/PluginDownloader.cpython-36.pyc
1923
RTOC/__pycache__/RTLogger.cpython-36.pyc
2024
RTOC/__pycache__/RTOC.cpython-36.pyc
2125
RTOC/__pycache__/__init__.cpython-36.pyc
@@ -65,13 +69,15 @@ RTOC/data/lib/__pycache__/general_lib.cpython-36.pyc
6569
RTOC/data/lib/__pycache__/pyqt_customlib.cpython-36.pyc
6670
RTOC/data/ui/darkmode.html
6771
RTOC/data/ui/eventWidget.ui
72+
RTOC/data/ui/getPlugins.ui
6873
RTOC/data/ui/gridViewWidget.ui
6974
RTOC/data/ui/lightmode.html
7075
RTOC/data/ui/messtoolDialog.ui
7176
RTOC/data/ui/plotToolsWidget.ui
7277
RTOC/data/ui/plotViewWidget.ui
7378
RTOC/data/ui/plotWidget.ui
7479
RTOC/data/ui/qtmodern.qss
80+
RTOC/data/ui/qtmodern_frame.qss
7581
RTOC/data/ui/rtoc.ui
7682
RTOC/data/ui/scriptHelpWidget.ui
7783
RTOC/data/ui/scriptSubWidget.ui

RTOC.egg-info/requires.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ requests
1010
python-nmap
1111
bokeh
1212
pycryptodomex
13+
pyGithub

RTOC/LoggerPlugin.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ def plot(self, x=[], y=[], *args, **kwargs):
6060
dataname = kwargs.get('sname', "noName")
6161
devicename = kwargs.get('dname', "noDevice")
6262
dataunit = kwargs.get('unit', "")
63+
hold = kwargs.get('hold', "off")
64+
autoResize = kwargs.get('autoResize', False)
6365
for idx, arg in enumerate(args):
6466
if idx == 0:
6567
dataname = arg
@@ -73,7 +75,7 @@ def plot(self, x=[], y=[], *args, **kwargs):
7375
x = list(range(len(x)))
7476

7577
if self.__plt:
76-
self.__plt(x, y, dataname, devicename, dataunit)
78+
self.__plt(x, y, dataname, devicename, dataunit, hold=hold, autoResize=autoResize)
7779
else:
7880
print("No event connected")
7981

RTOC/PluginDownloader.py

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
import sys
2+
import os
3+
from PyQt5 import QtCore
4+
from PyQt5 import uic
5+
from PyQt5 import QtWidgets, QtGui
6+
from PyQt5.QtCore import QCoreApplication
7+
8+
#from urllib.request import Request, urlopen, urlretrieve
9+
#from bs4 import BeautifulSoup
10+
import json
11+
import traceback
12+
import markdown2
13+
#import urllib.request
14+
# import requests
15+
import base64
16+
try:
17+
from .data.lib import pyqt_customlib as pyqtlib
18+
except ImportError:
19+
from data.lib import pyqt_customlib as pyqtlib
20+
21+
from github import Github
22+
23+
translate = QCoreApplication.translate
24+
25+
class PluginDownloader(QtWidgets.QWidget):
26+
def __init__(self, userpath, repo='Haschtl/RTOC-Plugins'):
27+
super(PluginDownloader, self).__init__()
28+
self.repo = None
29+
self.repoName = None
30+
self.onlinePlugins = []
31+
self.localPlugins = {}
32+
self.localPluginInfos = {}
33+
self.pluginInfos ={}
34+
self.userpath = userpath
35+
36+
self.currentname = ''
37+
self.installed = False
38+
self.uptodate = False
39+
40+
if getattr(sys, 'frozen', False):
41+
# frozen
42+
packagedir = os.path.dirname(sys.executable)
43+
else:
44+
# unfrozen
45+
packagedir = os.path.dirname(os.path.realpath(__file__))
46+
uic.loadUi(packagedir+"/data/ui/getPlugins.ui", self)
47+
48+
self.pluginList.currentTextChanged.connect(self.loadPlugin)
49+
self.installButton.clicked.connect(self.installPlugin)
50+
self.removeButton.clicked.connect(self.removePlugin)
51+
self.loadRepo(repo)
52+
self.loadLocalPlugins(userpath)
53+
print(self.localPlugins)
54+
55+
def loadLocalPlugins(self, userpath):
56+
self.localPlugins = {}
57+
self.localPluginInfos = {}
58+
sys.path.insert(0, userpath)
59+
import devices
60+
61+
subfolders = [f.name for f in os.scandir(list(devices.__path__)[0]) if f.is_dir()]
62+
for folder in subfolders:
63+
if folder not in ['', '__pycache__', '.git']:
64+
a =__import__(devices.__name__+"."+ folder)
65+
#for finder, name, ispkg in loggerlib.iter_namespace(devices):
66+
#fullpath = pkgutil.extend_path(list(devices.__path__)[0], folder)
67+
#print(fullpath)
68+
#for finder, name, ispkg in pkgutil.iter_modules(fullpath,devices.__name__+'.'+folder+ "."):
69+
#for root, dirs, files in os.walklevel(list(devices.__path__)[0], level=1):
70+
for files in os.listdir(list(devices.__path__)[0]+"/"+folder):
71+
if files.endswith('.py'):
72+
name = devices.__name__+'.'+folder+"."+files.replace('.py','')
73+
namesplit = name.split('.')
74+
print(name)
75+
if namesplit[-1] not in ["LoggerPlugin"]:
76+
self.localPlugins[namesplit[-1]] = list(devices.__path__)[0]+'/'+folder
77+
for plug in self.localPlugins.keys():
78+
#localPluginInfos
79+
if os.path.exists(self.localPlugins[plug]+"/info.json"):
80+
with open(self.localPlugins[plug]+"/info.json") as f:
81+
data = json.load(f)
82+
self.localPluginInfos[plug] = data
83+
else:
84+
print('Plugin '+plug+' was not installed from any repository')
85+
self.localPluginInfos[plug] = False
86+
87+
def loadRepo(self, repo):
88+
self.repoName = repo
89+
self.github = Github('RTOC-Downloader','thisissupersave123')
90+
self.repo = self.github.get_repo(self.repoName)
91+
dirs = self.repo.get_contents('')
92+
for dir in dirs:
93+
try:
94+
realdir = dir.path
95+
if realdir.find('.')==-1 and realdir != '__pycache__':
96+
info = self.repo.get_contents(realdir+"/info.json")
97+
data = json.loads(info.decoded_content.decode('utf-8'))
98+
#print(data)
99+
self.pluginInfos[realdir]=data
100+
self.onlinePlugins.append(realdir)
101+
except:
102+
print(info.decoded_content.decode('utf-8'))
103+
tb = traceback.format_exc()
104+
#print(tb)
105+
print('Error in '+dir.path)
106+
107+
for p in self.onlinePlugins:
108+
self.pluginList.addItem(p)
109+
110+
def loadPlugin(self, strung):
111+
self.currentname = strung
112+
self.installed = False
113+
self.uptodate = False
114+
try:
115+
info = self.pluginInfos[strung]
116+
print(strung)
117+
strung = "#"+strung+"\n\n"
118+
strung += "### Version: "+info['version']
119+
if self.currentname in self.localPluginInfos.keys():
120+
self.installed = True
121+
if self.localPluginInfos[self.currentname] != False:
122+
strung += ' (Installed: '+ self.localPluginInfos[self.currentname]['version']+')'
123+
else:
124+
strung += ' (was not installed from repo)'
125+
if info['version'] == self.localPluginInfos[self.currentname]['version']:
126+
self.uptodate = True
127+
strung += "\n\n"
128+
strung += "*OS:* "+info['OS']+"\n\n"
129+
strung += "*GUI:* "+str(info['GUI'])+"\n\n"
130+
strung += "###Info:\n"+info['Info'].replace('\n','\n\n')
131+
except:
132+
strung = "# Error loading description from repo\n\n"
133+
tb = traceback.format_exc()
134+
strung += tb
135+
136+
self.pluginInfo.setText(markdown2.markdown(strung))
137+
138+
if self.installed and self.uptodate:
139+
self.installButton.hide()
140+
self.removeButton.show()
141+
if self.installed and not self.uptodate:
142+
self.installButton.show()
143+
self.removeButton.show()
144+
if not self.installed:
145+
self.installButton.show()
146+
self.removeButton.hide()
147+
148+
def installPlugin(self):
149+
strung = translate('Downloader',"Aktuelle Version: ")+self.pluginInfos[self.currentname]['version']
150+
if self.currentname in self.localPluginInfos.keys():
151+
self.installed = True
152+
if self.localPluginInfos[self.currentname] != False:
153+
strung += translate('Downloader',' (Installiert: ')+ self.localPluginInfos[self.currentname]['version']+')'
154+
else:
155+
strung += translate('Downloader',' (Wurde nicht mit der RTOC-Repository installiert)')
156+
ok = pyqtlib.alert_message("Install plugin", "Möchtest du wirklich "+self.currentname+ " installieren", strung)
157+
if ok:
158+
print('install')
159+
url = 'https://minhaskamal.github.io/DownGit/#/home?url=https://github.com/'+self.repoName+'/tree/master/'+self.currentname
160+
print(url)
161+
#urllib.request.urlretrieve(url, self.userpath+'/temp.zip')
162+
163+
# f = open(self.userpath+'/temp.zip','wb')
164+
# f.write(urllib.request.urlopen(url).read())
165+
# f.close()
166+
167+
# r = requests.get(url)
168+
#
169+
# with open(self.userpath+'/temp.zip', "wb") as code:
170+
# code.write(r.content)
171+
self.download_directory(self.currentname)
172+
print('Download finished')
173+
pyqtlib.info_message(translate('Downloader',"Fertig"), translate('Downloader',"Installation abgeschlossen"), translate('Downloader',"Bitte starte RTOC neu, um neue Plugins zu benutzen."))
174+
175+
if self.localPluginInfos[self.currentname] != False:
176+
if self.localPluginInfos[self.currentname]['dependencies'] != []:
177+
info = '\n'.join(self.localPluginInfos[self.currentname]['dependencies'])
178+
pyqtlib.info_message(translate('Abhängigkeiten'), "Dieses plugin benötigt einige Abhängigkeiten, die mittels 'pip3 install PACKAGE' installiert werden müssen.", info)
179+
180+
def removePlugin(self):
181+
ok = pyqtlib.alert_message(translate('Downloader',"Plugin entfernen"), translate('Downloader',"Möchtest du wirklich ")+self.currentname+ translate('Downloader'," entfernen"), "")
182+
if ok:
183+
print('remove')
184+
if os.path.exists(self.userpath+"/devices/"+self.currentname):
185+
import shutil
186+
shutil.rmtree(self.userpath+"/devices/"+self.currentname)
187+
#os.removedirs(self.userpath+"/devices/"+self.currentname)
188+
self.loadLocalPlugins(self.userpath)
189+
self.loadPlugin(self.currentname)
190+
191+
def download_directory(self, server_path):
192+
"Download all contents at server_path with commit tag sha in the repository."
193+
contents = self.repo.get_dir_contents(server_path)
194+
if not os.path.exists(self.userpath+"/devices/"+self.currentname):
195+
os.mkdir(self.userpath+"/devices/"+self.currentname)
196+
for content in contents:
197+
print("Processing "+ content.path)
198+
if content.type == 'dir' and '__pycache__' not in content.path:
199+
if not os.path.exists(self.userpath+"/devices/"+content.path):
200+
os.mkdir(self.userpath+"/devices/"+content.path)
201+
self.download_directory(content.path)
202+
elif '__pycache__' not in content.path:
203+
try:
204+
path = content.path
205+
file_content = self.repo.get_contents(path)
206+
file_data = base64.b64decode(file_content.content)
207+
file_out = open(self.userpath+"/devices/"+content.path, "w")
208+
file_out.write(file_data.decode('utf-8'))
209+
file_out.close()
210+
except IOError as exc:
211+
print('Error processing %s: %s', content.path, exc)
212+
213+
self.loadLocalPlugins(self.userpath)
214+
self.loadPlugin(self.currentname)
215+
# def read_url(self, url):
216+
# url = url.replace(" ","%20")
217+
# req = Request(url)
218+
# a = urlopen(req).read()
219+
# soup = BeautifulSoup(a, 'html.parser')
220+
# x = (soup.find_all('a'))
221+
# for i in x:
222+
# file_name = i.extract().get_text()
223+
# url_new = url + file_name
224+
# url_new = url_new.replace(" ","%20")
225+
# if len(file_name)>1:
226+
# if(file_name[-1]=='/' and file_name[0]!='.'):
227+
# self.read_url(url_new+"/")
228+
# print(url_new)
229+
230+
if __name__ == "__main__":
231+
import sys
232+
userpath = os.path.expanduser('~/.RTOC')
233+
if not os.path.exists(userpath):
234+
os.mkdir(userpath)
235+
if not os.path.exists(userpath+'/devices'):
236+
os.mkdir(userpath+'/devices')
237+
app = QtWidgets.QApplication(sys.argv)
238+
app.setApplicationName('MyWindow')
239+
main = PluginDownloader(userpath)
240+
main.setWindowTitle("Plugin Downloader")
241+
main.show()
242+
243+
sys.exit(app.exec_())

0 commit comments

Comments
 (0)