mirror of
https://github.com/FAUSheppy/ths-datenlogger
synced 2025-12-06 04:11:34 +01:00
Initial
This commit is contained in:
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
*.png
|
||||
*.dbf
|
||||
__py*
|
||||
*.swp
|
||||
*.dbf
|
||||
*.DBF
|
||||
*.xls
|
||||
build/
|
||||
dist/
|
||||
83
config_parse.py
Normal file
83
config_parse.py
Normal file
@@ -0,0 +1,83 @@
|
||||
import configparser
|
||||
import sys
|
||||
|
||||
conf = None
|
||||
default_conf = None
|
||||
|
||||
def parse_config():
|
||||
global conf
|
||||
global default_conf
|
||||
|
||||
conf = configparser.ConfigParser()
|
||||
conf.read("ths_config.txt")
|
||||
default_conf = configparser.ConfigParser()
|
||||
default_conf.read("ths_readonly_default.conf")
|
||||
|
||||
if conf == None or (len(conf.sections()) == 0 and len(default_conf.sections()) == 0):
|
||||
print("Error: Missing configuration file, cannot continue")
|
||||
raise Exception("Missing configuration file")
|
||||
|
||||
def get_keys(like=None):
|
||||
ret = conf["plot"].keys()
|
||||
if like != None:
|
||||
ret = list(filter(lambda x:like in x,ret))
|
||||
if len(ret) == 0:
|
||||
print("No options that contain the string '%s'"%like)
|
||||
return ""
|
||||
return ret
|
||||
|
||||
def change_cfg(key,value):
|
||||
global conf
|
||||
confs = conf["plot"]
|
||||
v = str(value)
|
||||
key = str(key)
|
||||
if key not in confs:
|
||||
return False
|
||||
else:
|
||||
confs[key] = value
|
||||
return True
|
||||
|
||||
|
||||
|
||||
|
||||
def CFG(tag):
|
||||
global conf
|
||||
global default_conf
|
||||
|
||||
if conf == None:
|
||||
parse_config()
|
||||
if len(default_conf.sections()) > 0:
|
||||
default_confs = default_conf["plot"]
|
||||
else:
|
||||
default_confs = None
|
||||
confs = conf["plot"]
|
||||
|
||||
if tag in confs:
|
||||
return parse_cfg(confs[tag])
|
||||
elif default_confs != None and tag in default_confs:
|
||||
print("Warning: %s no found in configuration, defaulting to %s" % (str(tag),str(default_conf[tag])),sys.stderr)
|
||||
return parse_cfg(default_confs[tag])
|
||||
else:
|
||||
raise Exception("Error: configuration option %s not found in configuration and no default value for it, cannot continue, exit." % str(tag))
|
||||
|
||||
def parse_cfg(c):
|
||||
if c == None:
|
||||
raise Exception("Config key (%s) found but has no value. Cannot continue, exit." % str(c))
|
||||
c = c.strip("'")
|
||||
c = c.strip('"')
|
||||
if c in ["yes","ja","True","Yes","Ja","true"]:
|
||||
return True
|
||||
if c in ["no","nein","False","No","Nein","false"]:
|
||||
return False
|
||||
try:
|
||||
return int(c)
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return float(c)
|
||||
except ValueError:
|
||||
pass
|
||||
return c
|
||||
|
||||
|
||||
CFG("show_avg")
|
||||
8
constants.py
Normal file
8
constants.py
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/python3
|
||||
from config_parse import CFG
|
||||
GLOBAL_FONT = {'family':CFG("font"),'weight':'normal','size': CFG("global_text_size")}
|
||||
BASE_PATH=CFG("default_source_path")
|
||||
SOURCE_PATH=CFG("default_target_dir")
|
||||
FIGURE=0
|
||||
AXIS=1
|
||||
CALLBACK=2
|
||||
61
frontend_new.py
Normal file
61
frontend_new.py
Normal file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/python3
|
||||
import sys
|
||||
import tkinter
|
||||
from tkinter import filedialog
|
||||
|
||||
import plot_main
|
||||
import input_backend
|
||||
import frontend_utils as futils
|
||||
from config_parse import CFG
|
||||
from language import LAN
|
||||
|
||||
l = LAN[CFG("language")]
|
||||
tk = tkinter.Tk()
|
||||
tk.withdraw()
|
||||
|
||||
def main_repl(datapoints,path,date1=None,date2=None,done1=False,done2=False):
|
||||
### READ IN DATES ###
|
||||
futils.info_list(datapoints)
|
||||
while not done1:
|
||||
date1,done1 = futils.input_date_repl(datapoints,startdate=True)
|
||||
while not done2:
|
||||
date2,done2 = futils.input_date_repl(datapoints,startdate=False)
|
||||
|
||||
### CHECK DATES ###
|
||||
done1,done2 = futils.check_dates(path,date1,date2)
|
||||
if not done1 or not done2:
|
||||
main_repl(datapoints,path,date1,date2,done1,done2)
|
||||
else:
|
||||
plot_main.plot(datapoints,path,date1,date2)
|
||||
|
||||
def selection_repl(path):
|
||||
if path != None:
|
||||
datapoints = input_backend.read_in_file(path)
|
||||
if CFG("debug_no_interactive"):
|
||||
plot_main.plot(datapoints,path)
|
||||
return None
|
||||
main_repl(datapoints,path)
|
||||
else:
|
||||
tmp=input( "\n -> Type 'n' or 'new' <ENTER> to restart with another file\n -> Type 'r' or 'restart'<ENTER> to use the current file again\n (restart or selecting the same file WILL OVERRIDE the picture you just generated!)\n -> 'c'<ENTER> oder 'c <CONFIG_OPTION_NAME> <NEW_VALUE>' um Konfigurationsoptionen zu ändern\n -> Or press just <ENTER> to exit: ")
|
||||
if tmp == None or tmp == "":
|
||||
return None
|
||||
elif tmp in ["r","restart"]:
|
||||
return path
|
||||
elif tmp in ["n","new"]:
|
||||
return futils.open_file()
|
||||
elif tmp.startswith('c'):
|
||||
raise NotImplementedError("On the fly configuration not yet implemented.")
|
||||
else:
|
||||
return path
|
||||
|
||||
def main():
|
||||
### PREVENT MULTICORE SUPPORT ###
|
||||
if CFG("enable_multicore_support"):
|
||||
raise NotImplementedError("multiprocessing not fully implemented")
|
||||
|
||||
### PROMT TO OPEN FILE ###
|
||||
FILE_READY = False
|
||||
while True:
|
||||
path = selection_repl(futils.open_file())
|
||||
if path == None:
|
||||
break
|
||||
217
frontend_utils.py
Normal file
217
frontend_utils.py
Normal file
@@ -0,0 +1,217 @@
|
||||
#!/usr/bin/python3
|
||||
import sys
|
||||
import tkinter
|
||||
import plot_main
|
||||
import config_parse
|
||||
from config_parse import CFG
|
||||
from language import LAN
|
||||
from datetime import datetime
|
||||
|
||||
l = LAN[CFG("language")]
|
||||
timeformat = "%d.%m.%y %H:%M:%S (%A)"
|
||||
def parse_date_from_user_input(s,end_of_day=False,datapoints=None):
|
||||
today = datetime.now()
|
||||
day = 0
|
||||
month = 0
|
||||
year = 0
|
||||
hour = 0
|
||||
minute = 0
|
||||
second = 0
|
||||
|
||||
## EMPTY ##
|
||||
if s == None or s == "":
|
||||
return None
|
||||
|
||||
## TIME ##
|
||||
if len(s.split(" ")) > 1:
|
||||
time = s.split(" ")[1]
|
||||
time_a = time.split(":")
|
||||
if len(time_a) > 0:
|
||||
hour = int(time_a[0])
|
||||
elif end_of_day > 0:
|
||||
hour = 23
|
||||
if len(time_a) > 1:
|
||||
minute = int(time_a[1])
|
||||
elif end_of_day:
|
||||
minute = 59
|
||||
if len(time_a) > 2:
|
||||
second = int(time_a[2])
|
||||
elif end_of_day:
|
||||
second = 59
|
||||
elif end_of_day:
|
||||
hour = 23
|
||||
minute = 59
|
||||
second = 59
|
||||
|
||||
## DATE ##
|
||||
tmp = s.split(" ")[0]
|
||||
|
||||
## allow more speperators ##
|
||||
sep = None
|
||||
for c in ["-",".",","]:
|
||||
if c in tmp:
|
||||
sep = c
|
||||
break
|
||||
if sep == None:
|
||||
sep = "-"
|
||||
tmp = tmp.strip(sep)
|
||||
|
||||
if len(tmp.split(sep)) == 0:
|
||||
raise ValueError("Invalid Date '%s'"%str(s))
|
||||
else:
|
||||
date_a = tmp.split(sep)
|
||||
if len(date_a) > 0:
|
||||
day = int(date_a[0])
|
||||
if len(date_a) > 1:
|
||||
month = int(date_a[1])
|
||||
if len(date_a) > 2:
|
||||
year = int(date_a[2])
|
||||
|
||||
if year == 0:
|
||||
if today.month > month:
|
||||
year = today.year
|
||||
else:
|
||||
year = today.year-1
|
||||
if month == 0:
|
||||
if today.day > day and today.year == year:
|
||||
month = today.month
|
||||
else:
|
||||
month = today.month-1
|
||||
if month < 1:
|
||||
month = 12-month
|
||||
ret = datetime(year,month,day,hour,minute,second)
|
||||
try:
|
||||
times = datapoints[CFG("plot_temperatur_key")].times
|
||||
if ( ret > max(times) or ret < min(times) ) and min(times).day < ret.day < max(times).day and min(times).month == max(times).month:
|
||||
month = min(times.month)
|
||||
except Exception as e:
|
||||
print("Warning, magic date selection failed for an unknown reason")
|
||||
|
||||
ret = datetime(year,month,day,hour,minute,second)
|
||||
return ret
|
||||
|
||||
def info_list(datapoints):
|
||||
if len(datapoints.keys()) > 0:
|
||||
print("Erster Datensatz: "+min(datapoints[list(datapoints.keys())[0]].times).strftime(timeformat))
|
||||
print("Letzer Datensatz: "+max(datapoints[list(datapoints.keys())[0]].times).strftime(timeformat))
|
||||
print("Anzahl Datensätze: "+str(len(datapoints[list(datapoints.keys())[0]].times)))
|
||||
else:
|
||||
print("Keine Datesätze gefunden!")
|
||||
print_sep_line(True)
|
||||
|
||||
def input_date_repl(datapoints,startdate=True):
|
||||
date = None
|
||||
while True:
|
||||
try:
|
||||
if startdate:
|
||||
ret = input(l["input_first_date"])
|
||||
else:
|
||||
ret = input(l["input_second_date"])
|
||||
except EOFError:
|
||||
return (date,True)
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(2)
|
||||
if ret in ["h","help","hilfe"]:
|
||||
if startdate:
|
||||
print(l["input_first_date_help"])
|
||||
else:
|
||||
print(l["input_second_date_help"])
|
||||
continue
|
||||
elif ret == "list":
|
||||
info_list(datapoints)
|
||||
continue
|
||||
else:
|
||||
try:
|
||||
if startdate:
|
||||
date=parse_date_from_user_input(ret,datapoints=datapoints)
|
||||
else:
|
||||
date=parse_date_from_user_input(ret,True,datapoints)
|
||||
return (date,True)
|
||||
except ValueError as e:
|
||||
print(l["cannot_parse_date"] + "( was: {} )\n".format(ret))
|
||||
return (None,False)
|
||||
|
||||
def print_sep_line(ln=False):
|
||||
if not ln:
|
||||
print("-----------------------------------------------")
|
||||
else:
|
||||
print("-----------------------------------------------",end='')
|
||||
|
||||
|
||||
def check_dates(path,date1,date2,options=""):
|
||||
print_sep_line()
|
||||
if options!="":
|
||||
print("Config options: %s"%options)
|
||||
print("Datei: %s"%path)
|
||||
if date1 == None and date2 == None:
|
||||
print("Info: Keine Zeitbeschränkung gewählt. Alle vorhandenen Daten werden verwendet.")
|
||||
return (True,True)
|
||||
elif date1 == None:
|
||||
print("Alle Werte vor %s"%date2.strftime(timeformat))
|
||||
elif date2 == None:
|
||||
print("Alle Werte nach %s"%date1.strftime(timeformat))
|
||||
else:
|
||||
print("Start: %s\nEnde: %s"%(date1.strftime(timeformat),date2.strftime(timeformat)))
|
||||
|
||||
FIRST=True
|
||||
while(True):
|
||||
try:
|
||||
if FIRST:
|
||||
ret = input("Stimmt das so?\n -> <ENTER> für ja/weiter\n -> 's'<ENTER> für Startzeit ändern\n -> 'e'<ENTER> für Endzeit ändern\n -> 'b'<ENTER> für beides ändern\n -> 'exit'<ENTER> to exit\n---> ")
|
||||
else:
|
||||
ret = input("Versuchen sie es nochmal: ")
|
||||
except EOFError:
|
||||
ret = ""
|
||||
|
||||
if ret == 's':
|
||||
return (False,True)
|
||||
elif ret == 'e':
|
||||
return (True,False)
|
||||
elif ret == 'b':
|
||||
return (False,False)
|
||||
elif ret.startswith('c '):
|
||||
tmp = config_options(ret)
|
||||
if tmp == "":
|
||||
pass
|
||||
else:
|
||||
options += "\n "+tmp
|
||||
check_dates(path,date1,date2,options)
|
||||
elif ret == "":
|
||||
return (True,True)
|
||||
elif ret == "exit":
|
||||
sys.exit(0)
|
||||
FIRST=False
|
||||
|
||||
def open_file():
|
||||
front_end_source_path = CFG("default_source_path")
|
||||
if CFG("use_input_filename"):
|
||||
f = front_end_source_path + CFG("input_filename")
|
||||
return f
|
||||
|
||||
path=None
|
||||
path=tkinter.filedialog.askopenfilename(filetypes=(("DBF/XLS Files",("*.DBF","*.dbf","*.xls","*.XLS")),("All Files","*.*")))
|
||||
if path == None or path=="":
|
||||
print("Error: No file selected!")
|
||||
return None
|
||||
try:
|
||||
open(path,'r').close()
|
||||
except IOError:
|
||||
print("Error: Unable to open selected file, perhaps it does no longer exist or you have insufficient permissions to open it?")
|
||||
return None
|
||||
return path
|
||||
|
||||
def config_options(string):
|
||||
opt = ""
|
||||
arg = string.split(" ")
|
||||
if len(arg) == 2:
|
||||
for l in config_parse.get_keys(arg[1]):
|
||||
print(l)
|
||||
elif len(arg) == 3:
|
||||
if not config_parse.change_cfg(arg[1],arg[2]):
|
||||
print("Option %s does not exist."%str(arg[1]))
|
||||
else:
|
||||
opt += "set %s %s"%(arg[1],arg[2])
|
||||
print("set %s %s"%(arg[1],arg[2]))
|
||||
else:
|
||||
print("Ussage: c <configname> <value> / c <part_of_config_name> / c (= list all ), e to exit")
|
||||
return opt
|
||||
13
init.py
Executable file
13
init.py
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/python3
|
||||
import frontend_new
|
||||
import sys
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
frontend_new.main()
|
||||
sys.exit(0)
|
||||
except KeyboardInterrupt as e:
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
input("Ein Fehler ist aufgetreten, <ENTER> um zu beenden, wenn dieser Fehler unerwartet war -> Mail!")
|
||||
sys.exit(1)
|
||||
29
init.spec
Normal file
29
init.spec
Normal file
@@ -0,0 +1,29 @@
|
||||
# -*- mode: python -*-
|
||||
|
||||
block_cipher = None
|
||||
|
||||
|
||||
a = Analysis(['init.py'],
|
||||
pathex=['Z:\\home\\ik15ydit\\reps\\random-code\\ths'],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher)
|
||||
pyz = PYZ(a.pure, a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
name='init',
|
||||
debug=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
runtime_tmpdir=None,
|
||||
console=True )
|
||||
133
input_backend.py
Normal file
133
input_backend.py
Normal file
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/python3
|
||||
from config_parse import CFG
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from dbfread import DBF
|
||||
import plot_timeutils
|
||||
|
||||
line_colors = ['b', 'r', 'g', 'c', 'm', 'y']
|
||||
tname = CFG("temperatur_plot_name")
|
||||
hname = CFG("humidity_plot_name")
|
||||
dname = CFG("dewcels_plot_name")
|
||||
color_id = 0
|
||||
|
||||
class Data:
|
||||
def __init__(self,name,plot=False):
|
||||
global color_id,line_colors
|
||||
self.name = name
|
||||
self.color=line_colors[color_id%len(line_colors)]
|
||||
color_id += 1
|
||||
self.data = []
|
||||
self.times = []
|
||||
self.plot = plot
|
||||
|
||||
## no idea on what kind of drugs I was when i wrote this function (it is somewhat ingenious though) ##
|
||||
def get_timeframe(self, callback,date1=None,date2=None):
|
||||
r=dict()
|
||||
for t,c in zip(self.times,self.data):
|
||||
t = callback(t,date1,date2)
|
||||
if t == None:
|
||||
continue
|
||||
if t in r:
|
||||
r[t]+=[c]
|
||||
else:
|
||||
r.update({t:[c]})
|
||||
arr_t = []
|
||||
arr_v = []
|
||||
for k,v in r.items():
|
||||
arr_t += [k]
|
||||
arr_v += [sum(v)/len(v)]
|
||||
arr_t = [x for x,_ in sorted(zip(arr_t,arr_v))]
|
||||
arr_v = [x for _,x in sorted(zip(arr_t,arr_v))]
|
||||
return (arr_t,arr_v)
|
||||
|
||||
def parse_line(datapoints,line,timekey,keys,time_parser,timeformat=None):
|
||||
# This function expects:
|
||||
# - datapoints { String:DataObject }
|
||||
# - line { String:Any }
|
||||
# - timekey String (key for timevalue in 'line')
|
||||
# - keys [ (String,String) ] (source_key in 'line' to target_key in 'datapoints')
|
||||
time = time_parser(line[ timekey ],timeformat)
|
||||
for key in keys:
|
||||
datapoints[ key[1] ].data += [ line[ key[0] ] ]
|
||||
datapoints[ key[1] ].times += [ time ]
|
||||
|
||||
def read_in_file(path,backend=None):
|
||||
global tname
|
||||
global hname
|
||||
global dname
|
||||
global opath
|
||||
|
||||
datapoints = dict()
|
||||
|
||||
pt=CFG("plot_temperatur_key")
|
||||
ph=CFG("plot_humidity_key")
|
||||
pd=CFG("plot_dewcels_key")
|
||||
|
||||
## NAME PADDING ##
|
||||
max_name_len = max(len(tname),len(hname),len(dname))
|
||||
while len(tname) < max_name_len:
|
||||
tname += " "
|
||||
while len(hname) < max_name_len:
|
||||
hname += " "
|
||||
while len(dname) < max_name_len:
|
||||
dname += " "
|
||||
|
||||
datapoints.update({ pt:Data( tname,CFG("plot_temperatur") ) })
|
||||
datapoints[pt].color = CFG("temperatur_color")
|
||||
|
||||
datapoints.update({ ph:Data( hname,CFG("plot_humidity") ) })
|
||||
datapoints[ph].color = CFG("humidity_color")
|
||||
|
||||
datapoints.update({ pd:Data( dname,CFG("plot_dewcels") ) })
|
||||
datapoints[pd].color = CFG("dewcels_color")
|
||||
|
||||
if path == None:
|
||||
raise Exception("Path in plot.read_in was None")
|
||||
elif backend != None:
|
||||
backend(path)
|
||||
elif path.endswith(".DBF") or path.endswith(".dbf"):
|
||||
dbfread(path,datapoints,pt,ph,pd)
|
||||
elif path.endswith(".xls") or path.endswith(".XLS"):
|
||||
csvread(path,datapoints,pt,ph,pd)
|
||||
else:
|
||||
raise NotImplementedError("Cannot determine filetype, cannot continue. Exit.")
|
||||
|
||||
check_read_in(datapoints)
|
||||
return datapoints
|
||||
|
||||
def dbfread(path,datapoints,pt,ph,pd):
|
||||
for record in DBF(path):
|
||||
parse_line(datapoints,record,'DATETIME',[ ('TEMPCELS',pt) , ('HUMIDITY',ph) , ('DEWCELS',pd) ] ,plot_timeutils.time_from_dbf)
|
||||
|
||||
def csvread(path,datapoints,pt,ph,pd):
|
||||
count = 0;
|
||||
with open(path) as f:
|
||||
for l in f:
|
||||
if l.startswith(">>") or l.startswith("--") or l.startswith("NO."):
|
||||
count += 1
|
||||
continue
|
||||
else:
|
||||
row_arg = list(map(lambda s:s.replace(" ","").replace(",","."),l.split("\t")))
|
||||
row = {"temp":None,"hum":None,"taupunkt":None,"datetime":None}
|
||||
row["datetime"] = row_arg[1]+row_arg[2]
|
||||
row["temp"] = float(row_arg[3])
|
||||
row["hum"] = float(row_arg[4])
|
||||
row["taupunkt"] = float(row_arg[5])
|
||||
parse_line(datapoints,row,'datetime',[ ('temp',pt) , ('hum',ph) , ('taupunkt',pd) ],\
|
||||
plot_timeutils.time_from_csv,timeformat="%d-%m-%Y%H:%M:%S")
|
||||
print("Info: Ignored %d lines at beginning of file"%count)
|
||||
|
||||
def check_read_in(datapoints):
|
||||
good = False
|
||||
for v in datapoints.values():
|
||||
if len(v.times) != len(v.data):
|
||||
print("more timestamps than data (or visa versa), this indicates that the file is corrupted, cannot continue")
|
||||
good = False
|
||||
break
|
||||
if len(v.times) > 1:
|
||||
good = True
|
||||
if not good:
|
||||
input("reading input file failed for an unknown reason, <ENTER> to exit")
|
||||
import sys
|
||||
sys.exit(1)
|
||||
23
language.py
Normal file
23
language.py
Normal file
@@ -0,0 +1,23 @@
|
||||
end="\nDrücken sie 'STRG + C' ('STEUERUNG CANCEL') <ENTER> um das Program zu beenden"
|
||||
input_date = "Geben sie den Zeitpunkt an, an dem der Plot {} soll! \n\
|
||||
\nDatum/Uhrzeit im Format 'DD-MM-YYYY HH:MM:SS'. Wird die Uhrzeit weggelassen, \n\
|
||||
so wird 00:00:00 (Startzeit) bzw. 23:59:59 (Endzeit) angenommen.\n\
|
||||
Werden Jahr oder Monat weggelassen wird (versucht) ein passendes Datum zu wählen.\n\
|
||||
Lassen sie die Zeile leer um mit dem ersten existierenden Wert anzufangen \n\n\
|
||||
list<ENTER> um eine Übersicht über die gefunden Datenwerte zu erhalten\n\n\
|
||||
Beispiele für Formate (angenommen es ist der 12.01.2017): \n\n\
|
||||
11 12 -> 11.01.2017 12:00:00 \n\
|
||||
11-01 -> 11.01.2017 00:00:00 \n\
|
||||
13 -> 13.12.2016 00:00:00 \n\
|
||||
13-01-2017 -> 13.01.2017 00:00:00 \n\
|
||||
13-1-2017 17:1:4 -> 13.01.2017 17:01:04 (nuller können also weggelassen werden)\n"
|
||||
hilfe=" oder h/help/hilfe <ENTER> für Hilfe\n"
|
||||
|
||||
LAN = {"DE":{},"EN":{}}
|
||||
|
||||
LAN["DE"]["input_first_date_help"] = input_date.format("beginnen") + end + "\n"
|
||||
LAN["DE"]["input_second_date_help"] = input_date.format("enden") + end + "\n"
|
||||
LAN["DE"]["input_first_date"] = "\nStartzeit"+hilfe+"(Format: DD-MM-YY HH:MM:SS): "
|
||||
LAN["DE"]["input_second_date"] = "\nEndzeit "+hilfe+"(Format: DD-MM-YY HH:MM:SS): "
|
||||
LAN["DE"]["cannot_parse_date"] = "Konnte Datum/Uhrzeit nicht verarbeiten! \n"
|
||||
LAN["DE"]["dstart_bigger_dend"] = "Startzeit > Endzeit. MÖÖÖÖP \n"
|
||||
231
plot_graphutils.py
Normal file
231
plot_graphutils.py
Normal file
@@ -0,0 +1,231 @@
|
||||
#!/usr/bin/python3
|
||||
from config_parse import CFG
|
||||
from datetime import datetime, timedelta
|
||||
import matplotlib
|
||||
matplotlib.use(CFG("use_gui_backend"))
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.dates
|
||||
import matplotlib.ticker as ticker
|
||||
from constants import *
|
||||
import math
|
||||
import plot_timeutils
|
||||
matplotlib.rc('font', **GLOBAL_FONT)
|
||||
|
||||
def getlimits_y(y):
|
||||
ymax = max(y)+CFG("empty_space_above_plot")
|
||||
y_min_height = CFG("yaxis_minnimum_hight")
|
||||
if y_min_height != 0 and y_min_height > ymax:
|
||||
ymax = y_min_height
|
||||
y_start_val = CFG("yaxis_start_value")
|
||||
if y_start_val < min(y) or CFG("yaxis_force_start_value"):
|
||||
ymin=y_start_val
|
||||
else:
|
||||
ymin=min(y)
|
||||
return (ymin,ymax)
|
||||
|
||||
def avg(array):
|
||||
return sum(array)/float(len(array))
|
||||
|
||||
def legend_box_contents(name,y):
|
||||
if CFG("show_min"):
|
||||
name += " min: %.1f,"%min(y)
|
||||
if CFG("show_max"):
|
||||
name += " max: %.1f,"%max(y)
|
||||
if CFG("show_avg"):
|
||||
name += " Mittelwert: %.1f,"% avg(y)
|
||||
return name.rstrip(",")
|
||||
|
||||
def general_background_setup(tup,ymin,ymax,x):
|
||||
|
||||
unix_x = list(map(plot_timeutils.unix,x))
|
||||
|
||||
### SET AXIS LIMITS ###
|
||||
tup[AXIS].set_ylim([ymin,ymax])
|
||||
tup[AXIS].set_xlim([plot_timeutils.unix(min(x)),plot_timeutils.unix(max(x))])
|
||||
|
||||
if CFG("draw_thresholds"):
|
||||
hcrit=CFG("humidity_critical")
|
||||
hwarn=CFG("humidity_warning")
|
||||
tlow=CFG("acceptable_temp_low")
|
||||
thigh=CFG("acceptable_temp_high")
|
||||
tup[AXIS].axhline(y=CFG("target_temperatur"),ls=CFG("hline_line_style"),lw=CFG("hline_line_width"),color=CFG("acceptable_temp_color"))
|
||||
tup[AXIS].axhline(y=hcrit,ls=CFG("hline_line_style"),lw=CFG("hline_line_width"),color=CFG("humidity_crit_color"))
|
||||
tup[AXIS].axhspan(hwarn,hcrit,color=CFG("humidity_warning_color"),alpha=CFG("humidity_warning_alpha"))
|
||||
tup[AXIS].axhspan(hcrit,ymax,color=CFG("humidity_crit_color"),alpha=CFG("humidity_crit_alpha"))
|
||||
tup[AXIS].axhspan(tlow,thigh,color=CFG("acceptable_temp_color"),alpha=CFG("acceptable_temp_alpha"))
|
||||
|
||||
#### GRID ####
|
||||
major_xticks = gen_xticks_from_timeseries(x)
|
||||
minor_xticks = get_minor_xticks_from_major(major_xticks)
|
||||
if CFG("raster"):
|
||||
grid(tup,major_xticks,ymin,ymax)
|
||||
|
||||
#### XTICKS ####
|
||||
tup[AXIS].set_xticks(major_xticks)
|
||||
tup[AXIS].xaxis.set_major_formatter(ticker.FuncFormatter(xlabel_formater_callback))
|
||||
tup[AXIS].xaxis.set_major_locator(ticker.FixedLocator(major_xticks, nbins=None))
|
||||
tup[AXIS].xaxis.set_minor_locator(ticker.FixedLocator(minor_xticks, nbins=None))
|
||||
tup[AXIS].xaxis.set_tick_params(which='minor',width=0.2,direction="out")
|
||||
|
||||
tup[AXIS].yaxis.set_major_locator(ticker.MultipleLocator(CFG("y_tick_interval")))
|
||||
tup[AXIS].yaxis.set_minor_locator(ticker.MultipleLocator(1))
|
||||
tup[AXIS].yaxis.set_tick_params(which='minor',width=0.2,direction="out")
|
||||
|
||||
tup[AXIS].tick_params(axis='x',which="major",labelsize=CFG("xticks_font_size"));
|
||||
tup[AXIS].tick_params(axis='y',which="major",labelsize=CFG("yticks_font_size"));
|
||||
|
||||
## ROTATION XLABELS ##
|
||||
rotation=CFG("xticks_label_degree")
|
||||
if rotation > 0:
|
||||
plt.xticks(rotation=rotation,ha='right')
|
||||
|
||||
## AXIS LABELS
|
||||
ylabel_box = dict(boxstyle="square",facecolor='grey', alpha=0.4, edgecolor='black',lw=0.5)
|
||||
xlabel_box = ylabel_box
|
||||
label_size = 6
|
||||
spacing=0.1
|
||||
tup[AXIS].set_ylabel(CFG("y_label"),rotation='horizontal',size=label_size,bbox=ylabel_box)
|
||||
tup[AXIS].yaxis.set_label_coords(0.055,0.95)
|
||||
tup[AXIS].set_xlabel(CFG("x_label"),size=label_size,bbox=xlabel_box)
|
||||
tup[AXIS].xaxis.set_label_coords(0.925,0.05)
|
||||
|
||||
## GENERAL LEGEND ##
|
||||
legend_handle = tup[AXIS].legend(
|
||||
loc=CFG("legend_location"),
|
||||
edgecolor="inherit",
|
||||
fancybox=False,
|
||||
borderaxespad=spacing,
|
||||
prop={'family': 'monospace','size':CFG("legend_font_size")}
|
||||
)
|
||||
legend_handle.get_frame().set_linewidth(0.2)
|
||||
tup[AXIS].set_aspect(get_aspect_ratio(unix_x,ymin,ymax,major_xticks))
|
||||
|
||||
|
||||
def get_aspect_ratio(ux,ymin,ymax,xticks):
|
||||
ratio = 100
|
||||
tmp = CFG("aspect_ratio")
|
||||
if str(tmp) == "A4":
|
||||
ratio = a4_aspect()
|
||||
else:
|
||||
ratio=tmp
|
||||
magic_value = 3.25
|
||||
return ratio * ( max(ux) - min(ux) ) / float(ymax - ymin + magic_value)
|
||||
|
||||
def a4_aspect():
|
||||
return 1/math.sqrt(2)
|
||||
|
||||
def grid(tup,xticks,ymin,ymax):
|
||||
lw = CFG("grid_line_width")
|
||||
ls = CFG("grid_line_style")
|
||||
color = CFG("grid_line_color")
|
||||
hour_mul = 24
|
||||
expected_vlines = len(list(filter(lambda xt: xt%3600 < 60,xticks)))
|
||||
safety_first = 60*60 +10
|
||||
step = xticks[1]-xticks[0]
|
||||
if step < (24*3600)-safety_first:
|
||||
if expected_vlines <= 6:
|
||||
hour_mul = 1
|
||||
elif expected_vlines <=12:
|
||||
hour_mul = 2
|
||||
elif expected_vlines <=24:
|
||||
hour_mul = 4
|
||||
|
||||
for xt in xticks:
|
||||
leck_mich = datetime.fromtimestamp(xt)
|
||||
if leck_mich.hour == leck_mich.minute == leck_mich.second == 0:
|
||||
tup[AXIS].axvline(xt,ls="-",lw=CFG("major_line_width"),color=color)
|
||||
else:
|
||||
tup[AXIS].axvline(xt,ls=ls,lw=lw,color=color)
|
||||
## HLINES ##
|
||||
y_interval = CFG("raster_hline_prefered_interval")
|
||||
cur = ymin
|
||||
while cur < ymax:
|
||||
cur += y_interval
|
||||
tup[AXIS].axhline(cur,ls=ls,lw=lw,color=color)
|
||||
|
||||
def find_step(step,x,total_xticks):
|
||||
intervals = parse_possible_intervals()
|
||||
start = min(x)
|
||||
if CFG("always_allow_days_as_xticks") and step > timedelta(days=1)/2:
|
||||
step = timedelta(days=round(step.days+1))
|
||||
start = min(x).replace(hour=0,second=0,minute=0)
|
||||
return (start,step)
|
||||
|
||||
min_delta_step = timedelta(days=1) # the actual step that has the lowest delta
|
||||
min_delta = timedelta(days=1000) # the delta o thus step
|
||||
for s in intervals:
|
||||
delta = max(s,step)-min(s,step)
|
||||
if delta < min_delta:
|
||||
min_delta_step = s
|
||||
min_delta = delta
|
||||
|
||||
step = min_delta_step
|
||||
start = plot_timeutils.round_time_to_step(start,step)
|
||||
|
||||
warn_on_too_much_xticks(x,total_xticks,step)
|
||||
return (start,step)
|
||||
|
||||
def parse_possible_intervals():
|
||||
intervals = CFG("acceptable_x_intervals")
|
||||
parsed_intervals = []
|
||||
for s in intervals.split(','):
|
||||
try:
|
||||
st = int(s[:-1])
|
||||
except ValueError:
|
||||
raise ValueError("'acceptable_x_intervals' muss die Form 'Zahl[s(econds),m(minutes),h(ours),d(days)]' haben!")
|
||||
except Exception:
|
||||
raise ValueError("invalid intervals for x_labels %s [index out of bounds], did you write something like this ',,,,' ?]"%str(intervals))
|
||||
if s.endswith("s"):
|
||||
if 60 % st != 0:
|
||||
raise ValueError("interval must fit to next bigger interval so basicly for hours 24%interval==0")
|
||||
parsed_intervals += [timedelta(seconds=st)]
|
||||
elif s.endswith("m"):
|
||||
if 60 % st != 0:
|
||||
raise ValueError("interval must fit to next bigger interval so basicly for hours 24%interval==0")
|
||||
parsed_intervals += [timedelta(minutes=st)]
|
||||
elif s.endswith("h"):
|
||||
if 24 % st != 0:
|
||||
raise ValueError("interval must fit to next bigger interval so basicly for hours 24%interval==0")
|
||||
parsed_intervals += [timedelta(hours=st)]
|
||||
elif s.endswith("d"):
|
||||
parsed_intervals += [timedelta(days=st)]
|
||||
else:
|
||||
raise ValueError("invalide Zeitspezifizierer in %s (muss, s,m,h oder d sein)"%str(intervals))
|
||||
return parsed_intervals
|
||||
|
||||
def warn_on_too_much_xticks(x,total_xticks,step):
|
||||
if (max(x)-min(x))/step > 2*total_xticks:
|
||||
print("Warnung: maximales xinterval zu niedrig eine sinnvolle Anzahl an xticks zu generieren (total x_ticks: %d"%total_xticks)
|
||||
|
||||
def get_minor_xticks_from_major(major):
|
||||
mult = CFG("minor_xticks_per_major")
|
||||
step = (major[1]-major[0])/mult
|
||||
ret = []
|
||||
for x in major:
|
||||
if x == max(major):
|
||||
break
|
||||
ret += [x+ 0*step]
|
||||
ret += [x+ 1*step]
|
||||
ret += [x+ 2*step]
|
||||
ret += [x+ 3*step]
|
||||
ret += [x+ 4*step]
|
||||
return ret
|
||||
|
||||
def gen_xticks_from_timeseries(x):
|
||||
ticks=CFG("prefered_total_xticks")
|
||||
xmin = min(x)
|
||||
xmax = max(x)
|
||||
delta = xmax-xmin
|
||||
step = delta/ticks
|
||||
cur,step = find_step(step,x,ticks)
|
||||
xticks = []
|
||||
xmax += step*CFG("add_x_labels_at_end")
|
||||
while cur < xmax:
|
||||
xticks += [plot_timeutils.unix(cur)]
|
||||
cur+=step
|
||||
return xticks
|
||||
|
||||
def xlabel_formater_callback(tick_val, tick_pos):
|
||||
dt = datetime.fromtimestamp(tick_val)
|
||||
tformat = CFG("timeformat_x_axis").replace('$','%')
|
||||
return dt.strftime(tformat)
|
||||
12
plot_imageutils.py
Normal file
12
plot_imageutils.py
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/python3
|
||||
from config_parse import CFG
|
||||
from PIL import Image
|
||||
import math
|
||||
def check_and_rotate(path):
|
||||
img = Image.open(path)
|
||||
div=abs(float(img.size[1])/float(img.size[0])-a4_aspect())/a4_aspect()*100
|
||||
print("Seitenverhältnisabweichung zu A4: %.2f"%div+r'%')
|
||||
img.rotate(CFG("image_rotation"),expand=True).save(path.strip(".png")+"_rotated.png")
|
||||
|
||||
def a4_aspect():
|
||||
return 1/math.sqrt(2)
|
||||
68
plot_main.py
Normal file
68
plot_main.py
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/python3
|
||||
import sys
|
||||
from config_parse import CFG
|
||||
from constants import *
|
||||
from datetime import datetime, timedelta
|
||||
from frontend_utils import open_file
|
||||
from constants import *
|
||||
|
||||
import math
|
||||
import matplotlib
|
||||
matplotlib.use(CFG("use_gui_backend"))
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.dates
|
||||
import matplotlib.ticker as ticker
|
||||
|
||||
import plot_graphutils
|
||||
import plot_imageutils
|
||||
import plot_timeutils
|
||||
|
||||
|
||||
def plot(datapoints,path=None,date1=None,date2=None):
|
||||
plotname = "" if CFG("name_of_plot") == "None" else CFG("name_of_plot")
|
||||
tup = [None,None,plot_timeutils.between_dates,plotname]
|
||||
if CFG("enable_multicore_support"):
|
||||
thread = Process(target=__plot,args=(tup,datapoints,date1,date2))
|
||||
thread.start()
|
||||
else:
|
||||
__plot(tup,datapoints,path,date1,date2)
|
||||
|
||||
def __plot(tup,datapoints,path,date1=None,date2=None):
|
||||
NO_SERIES = True
|
||||
x,y,ymin,ymax,unix_x,major_xticks = ( [] , [], -1 , -1 , [], [] )
|
||||
lw = CFG("plot_line_width")
|
||||
ls = CFG("plot_line_style")
|
||||
tup[FIGURE],tup[AXIS] = plt.subplots(1, 1)
|
||||
|
||||
for g in datapoints.values():
|
||||
#### GET AND CHECK TIMEFRAMES ####
|
||||
x,y, = g.get_timeframe(tup[CALLBACK],date1,date2)
|
||||
if len(x) <= 0 or len(y) <= 0:
|
||||
print("Warning: Empty series of data '%s' (wrong start/end time?)"%g.name)
|
||||
continue
|
||||
else:
|
||||
NO_SERIES = False
|
||||
unix_x = list(map(plot_timeutils.unix,x))
|
||||
ymin,ymax = plot_graphutils.getlimits_y(y)
|
||||
|
||||
#### GET LINE STYLES ####
|
||||
legend_label = plot_graphutils.legend_box_contents(g.name,y)
|
||||
tup[AXIS].plot(unix_x, y,ls=ls,lw=lw,marker="None", label=legend_label, color=g.color)
|
||||
|
||||
if NO_SERIES:
|
||||
print("Error: no data, nothing to plot. cannot continue. exit.")
|
||||
sys.exit(1)
|
||||
|
||||
## GRID ##
|
||||
plot_graphutils.general_background_setup(tup,ymin,ymax,x)
|
||||
|
||||
## using unix_x relys on unix_x to be the same for all plots ##
|
||||
if path == None:
|
||||
path = open_file()
|
||||
## TODO function for picpathn for picpath
|
||||
pic_path = path + ".png"
|
||||
tup[FIGURE].savefig(pic_path,dpi=CFG("outfile_resolution_in_dpi"), bbox_inches='tight',transparent=CFG("transparent_background"))
|
||||
|
||||
### do operations on the finished png ###
|
||||
plot_imageutils.check_and_rotate(pic_path)
|
||||
39
plot_timeutils.py
Normal file
39
plot_timeutils.py
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/python3
|
||||
from config_parse import CFG
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
def between_dates(t,date1,date2):
|
||||
return t if (date1 == None or date1 < t) and (date2 == None or date2 > t) else None
|
||||
|
||||
def time_from_dbf(l,timeformat):
|
||||
timeformat=None #dont need that here
|
||||
offset_d = datetime(1970,1,1)-datetime(1900,1,1)
|
||||
shit_epoch = l*24*60*60 #days to seconds
|
||||
unix_epoch = datetime.fromtimestamp(shit_epoch)-offset_d
|
||||
return (unix_epoch-timedelta(days=2)+timedelta(hours=CFG("add_hours_to_input"))).replace(microsecond=0)
|
||||
|
||||
def time_from_csv(l,timeformat):
|
||||
return datetime.strptime(l,timeformat)
|
||||
|
||||
def unix(dt):
|
||||
return dt.timestamp()
|
||||
|
||||
def round_time_to_step(start,step):
|
||||
start += step / 2
|
||||
discard = timedelta(days=0)
|
||||
hround = int(step.seconds/3600)
|
||||
mround = int(step.seconds/60)
|
||||
if step >= timedelta(days=1):
|
||||
discard = timedelta(days=start.day % step.days,hours=start.hour,minutes=start.minute,seconds=start.second)
|
||||
elif step >= timedelta(hours=1):
|
||||
if hround != 0:
|
||||
discard = timedelta(hours=start.hour % hround,minutes=start.minute,seconds=start.second)
|
||||
elif step >= timedelta(minutes=1):
|
||||
if mround != 0:
|
||||
discard = timedelta(minutes=start.minute % mround,seconds=start.second)
|
||||
elif step >= timedelta(seconds=1):
|
||||
discard = timedelta(seconds=start.second % step.seconds)
|
||||
else:
|
||||
raise ValueError("Rounding time failed, this actually should be impossible. wtf. ("+str(start)+","+str(step)+","+str(discard)+")")
|
||||
start -= discard
|
||||
return start
|
||||
2
req.sh
Normal file
2
req.sh
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
python3 -m pip install matplotlib sys configparser datetime os dbfread multiprocessing tkinter PIL
|
||||
0
test_cases/__init__.py
Normal file
0
test_cases/__init__.py
Normal file
22
test_cases/graphutils_test.py
Normal file
22
test_cases/graphutils_test.py
Normal file
@@ -0,0 +1,22 @@
|
||||
import unittest
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
import random
|
||||
import itertools
|
||||
|
||||
class Graphutils_Test(unittest.TestCase):
|
||||
def test_get_y_limits(self):
|
||||
import plot_graphutils as gu
|
||||
y_axis_values = [[32.3, 60.3, 35.1, 34.9, 33.0, 32.0, 32.7, 32.4, 34.0, 32.7, 33.2, 32.7, 33.2, 32.4, 34.0, \
|
||||
32.9, 33.4, 32.2, 30.8, 41.6, 34.7, 32.6, 35.1, 33.5, 32.5, 37.6, 32.6, 32.3, 31.3, 33.0, 34.0,\
|
||||
32.7, 32.7, 32.4, 32.8, 34.0, 34.1, 32.5, 33.5, 33.8, 31.0, 32.8, 34.9],[6.4, 15.3, 7.9, 7.1,\
|
||||
6.9, 6.4, 7.0, 6.4, 6.8, 5.8, 6.1, 6.6, 6.1, 6.8, 6.8, 5.9, 6.9, 6.3, 6.9, 12.2, 7.9, 6.5, 7.9,\
|
||||
6.9, 6.5, 8.9, 6.8, 6.4, 6.4, 6.0, 6.7, 6.5, 7.0, 6.4, 6.7, 7.6, 6.9, 6.5, 7.2, 6.9, 4.9, 6.7,\
|
||||
7.9],[24.0, 23.5, 24.4, 23.5, 24.3, 24.2, 24.5, 24.0, 23.6, 23.2, 23.2, 24.1, 23.2, 24.5, 23.6,\
|
||||
23.2, 24.1, 24.0, 25.4, 26.3, 24.5, 24.0, 24.4, 24.0, 24.0, 24.4, 24.4, 24.0, 24.6, 23.2, 23.5,\
|
||||
24.0, 24.5, 24.0, 24.1, 24.5, 23.7, 24.0, 24.4, 23.8, 23.0, 24.1, 24.5]]
|
||||
yl_values = [(0,95),(0,95),(0,95)]
|
||||
for y,yl in zip(y_axis_values,yl_values):
|
||||
self.assertEqual(yl,gu.getlimits_y(y))
|
||||
|
||||
58
test_cases/timeutils_test.py
Normal file
58
test_cases/timeutils_test.py
Normal file
@@ -0,0 +1,58 @@
|
||||
import unittest
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
import random
|
||||
import itertools
|
||||
|
||||
class Timeutil_Test(unittest.TestCase):
|
||||
DATES = []
|
||||
STEPS = [ (timedelta(hours=1)),timedelta(hours=4),timedelta(days=1),timedelta(minutes=1),timedelta(minutes=3)]
|
||||
def setUpClass():
|
||||
random.seed("0")
|
||||
for x in range(0,10000):
|
||||
tmp = datetime(2018,1,1) + ( (random.random()-0.5) * timedelta(days=2*365) )
|
||||
tmp = tmp.replace(microsecond=0)
|
||||
Timeutil_Test.DATES += [ tmp ]
|
||||
|
||||
def test_between_dates(self):
|
||||
import plot_timeutils as tu
|
||||
d = Timeutil_Test.DATES
|
||||
count = 0
|
||||
while(count < len(d)-2):
|
||||
t = d[count+0]
|
||||
d1 = d[count+1]
|
||||
d2 = d[count+2]
|
||||
self.assertEqual(btw_wrapper(tu.between_dates(t,d1,d2), t), d1 < t < d2, "t: "+str(t)+", d1: "+str(d1)+", d2: "+str(d2) )
|
||||
self.assertEqual(btw_wrapper(tu.between_dates(t,d1,None), t), d1 < t , "t: "+str(t)+", d1: "+str(d1)+", d2: "+str(d2) )
|
||||
self.assertEqual(btw_wrapper(tu.between_dates(t,None,d1), t), d1 > t , "t: "+str(t)+", d1: "+str(d1)+", d2: "+str(d2) )
|
||||
self.assertEqual(btw_wrapper(tu.between_dates(t,None,None),t), True , "t: "+str(t)+", d1: "+str(d1)+", d2: "+str(d2) )
|
||||
count+=1
|
||||
|
||||
|
||||
def test_parse_time_dbf(self):
|
||||
import plot_timeutils as tu
|
||||
ind = [43121.6821296,43121.6856018,43121.689074,43121.6925462,43121.6960185,43121.6994907,43121.7029629,43121.7064351,43121.7099074,43121.7133796,43121.7168518,43121.720324,43121.7237962,43121.7272685,43121.7307407,43121.7342129,43121.7376851,43121.7411574,43121.7446296,43121.7481018,43121.751574,43121.7550462,43121.7585185,43121.7619907,43121.7654629,43121.7689351,43121.7724074,43121.7758796,43121.7793518,43121.782824,43121.7862962,43121.7897685,43121.7932407,43121.7967129,43121.8001851,43121.8036574,43121.8071296,43121.8106018,43121.814074,43121.8175462,43121.8210185,43121.8244907,43121.8279629]
|
||||
outd = ["2018-01-21 18:22:15","2018-01-21 18:27:15","2018-01-21 18:32:15","2018-01-21 18:37:15","2018-01-21 18:42:15","2018-01-21 18:47:15","2018-01-21 18:52:15","2018-01-21 18:57:15","2018-01-21 19:02:15","2018-01-21 19:07:15","2018-01-21 19:12:15","2018-01-21 19:17:15","2018-01-21 19:22:15","2018-01-21 19:27:15","2018-01-21 19:32:15","2018-01-21 19:37:15","2018-01-21 19:42:15","2018-01-21 19:47:15","2018-01-21 19:52:15","2018-01-21 19:57:15","2018-01-21 20:02:15","2018-01-21 20:07:15","2018-01-21 20:12:15","2018-01-21 20:17:15","2018-01-21 20:22:15","2018-01-21 20:27:15","2018-01-21 20:32:15","2018-01-21 20:37:15","2018-01-21 20:42:15","2018-01-21 20:47:15","2018-01-21 20:52:15","2018-01-21 20:57:15","2018-01-21 21:02:15","2018-01-21 21:07:15","2018-01-21 21:12:15","2018-01-21 21:17:15","2018-01-21 21:22:15","2018-01-21 21:27:15","2018-01-21 21:32:15","2018-01-21 21:37:15","2018-01-21 21:42:15","2018-01-21 21:47:15","2018-01-21 21:52:15"]
|
||||
for i,o in zip(ind,outd):
|
||||
self.assertEqual(str(tu.parse_time_dbf(i)),o)
|
||||
|
||||
def test_round_time_to_step(self):
|
||||
import plot_timeutils as tu
|
||||
for s in Timeutil_Test.STEPS:
|
||||
for d in Timeutil_Test.DATES:
|
||||
rounded = tu.round_time_to_step(d,s)
|
||||
if s < timedelta(minutes=60):
|
||||
self.assertEquals(rounded.minute * 60 % s.seconds ,0,'date: '+str(d)+' rounded: '+str(rounded)+' step: '+str(s))
|
||||
elif s < timedelta(hours=24):
|
||||
self.assertEquals(rounded.hour*60*60 % s.seconds ,0,'date: '+str(d)+' rounded: '+str(rounded)+' step: '+str(s))
|
||||
elif s >= timedelta(days=1):
|
||||
self.assertEquals(rounded.day % s.days ,0,'date: '+str(d)+' rounded: '+str(rounded)+' step: '+str(s))
|
||||
else:
|
||||
raise AssertionError(int(s.days),0,'date: '+str(d)+' rounded: '+str(rounded)+' step: '+str(s))
|
||||
|
||||
|
||||
def btw_wrapper(inp,a):
|
||||
if inp == a:
|
||||
return True
|
||||
return False
|
||||
107
ths_config.txt
Normal file
107
ths_config.txt
Normal file
@@ -0,0 +1,107 @@
|
||||
# Ich bin ein Kommentar, das Programm ignoriert Zeilen die mit '#' beginnen.
|
||||
# Die Zeile '[plot] darf NICHT gelöscht oder verändert werden!
|
||||
|
||||
[plot]
|
||||
plot_humidity = True
|
||||
plot_temperatur = True
|
||||
plot_dewcels = False
|
||||
show_avg = True
|
||||
show_min = True
|
||||
show_max = True
|
||||
raster = True
|
||||
draw_thresholds = True
|
||||
interactive = True
|
||||
language = DE
|
||||
|
||||
default_source_path = /home/ik15ydit/reps/random-code/ths/
|
||||
default_target_dir = /home/ik15ydit/reps/random-code/ths/
|
||||
output_filename = test.png
|
||||
|
||||
humidity_critical = 55
|
||||
humidity_warning = 50
|
||||
acceptable_temp_low = 18
|
||||
acceptable_temp_high = 22
|
||||
target_temperatur = 20
|
||||
|
||||
temperatur_plot_name = Innenlufttemperatur
|
||||
humidity_plot_name = rel. Luftfeuchtigkeit
|
||||
dewcels_plot_name = Taupunkt
|
||||
y_label = Temp./r.L.
|
||||
x_label = Datum/Uhrzeit
|
||||
font = calibri
|
||||
global_text_size = 7
|
||||
xticks_font_size = 5
|
||||
yticks_font_size = 5
|
||||
timeformat_x_axis = '$d.$m, $H:$M'
|
||||
acceptable_x_intervals = 1m,5m,10m,30m,1h,2h,4h,6h
|
||||
image_rotation = 90
|
||||
transparent_background = no
|
||||
name_of_plot = None
|
||||
aspect_ratio = A4
|
||||
|
||||
############# eye_candy #############
|
||||
empty_space_above_plot = 10
|
||||
yaxis_minnimum_hight = 95
|
||||
yaxis_start_value = 0
|
||||
|
||||
# True: die Y-Achse beginnt auch bei xaxis_start_value wenn dadurch Werte nicht angezeit werden
|
||||
# False: wenn ein Wert im plot kleiner xaxis_start_value ist beginnt die Y-Achse beim kleinsten Wert im Plot
|
||||
yaxis_force_start_value = False
|
||||
|
||||
# ein höheres alpha für zu einer stärkeren Sättigung der Hintergrundfarbe (0 und es ist ganz weg)
|
||||
humidity_crit_alpha = 0.35
|
||||
humidity_warning_alpha = 0.35
|
||||
acceptable_temp_alpha = 0.20
|
||||
|
||||
# Farben: cyan, yellow, green, red, blue, black, white, grey oder RGBA-Wert
|
||||
humidity_crit_color = red
|
||||
humidity_warning_color = yellow
|
||||
acceptable_temp_color = blue
|
||||
|
||||
# Farbe der linie des graphen
|
||||
humidity_color = red
|
||||
temperatur_color = blue
|
||||
dewcels_color = green
|
||||
|
||||
plot_line_width = 0.5
|
||||
plot_line_style = solid
|
||||
|
||||
hline_draw_lines = True
|
||||
# linestyles: https://matplotlib.org/devdocs/gallery/lines_bars_and_markers/line_styles_reference.html
|
||||
hline_line_style = --
|
||||
hline_line_width = 0.5
|
||||
grid_line_style = :
|
||||
grid_line_width = 0.15
|
||||
grid_line_color = black
|
||||
|
||||
############# technical #############
|
||||
enable_multicore_support = False
|
||||
raster_alligment_auto = True
|
||||
raster_hline_prefered_interval = 5
|
||||
raster_minimum_hlines = 10
|
||||
y_tick_interval = 5
|
||||
outfile_resolution_in_dpi = 200
|
||||
legend_location = upper right
|
||||
terminate_on_missing_input_file = True
|
||||
terminate_on_fail = True
|
||||
add_hours_to_input = 1
|
||||
prefered_total_xticks = 24
|
||||
use_gui_backend = Agg
|
||||
add_x_labels_at_end = 1
|
||||
xticks_label_degree = 45
|
||||
major_line_width = 0.5
|
||||
legend_font_size = 5
|
||||
minor_xticks_per_major = 5
|
||||
terminate_on_warning = no
|
||||
|
||||
###### DEBUGGING ######
|
||||
no_ask_date_input = no
|
||||
input_filename = test.dbf
|
||||
use_input_filename = no
|
||||
debug_no_interactive = no
|
||||
|
||||
###### THINGS THERE IS REALLY NO REASON TO CHANGE ######
|
||||
plot_temperatur_key = TEMP
|
||||
plot_humidity_key = HUMIDITY
|
||||
plot_dewcels_key = DEWCELS
|
||||
always_allow_days_as_xticks = yes
|
||||
Reference in New Issue
Block a user