231 lines
10 KiB
Python
231 lines
10 KiB
Python
from datetime import date
|
|
import time
|
|
import sys
|
|
import os
|
|
import logging
|
|
import json
|
|
import numpy as np
|
|
|
|
class PrettyFloat(float):
|
|
"""saves bandwidth when converting to JSON
|
|
|
|
a lot of numbers originally have a fixed (low) number of decimal digits
|
|
as the binary representation is not exact, it might happen, that a
|
|
lot of superfluous digits are transmitted:
|
|
|
|
str(1/10*3) == '0.30000000000000004'
|
|
str(PrettyFloat(1/10*3)) == '0.3'
|
|
"""
|
|
def __repr__(self):
|
|
return '%.15g' % self
|
|
|
|
#encode = "!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~"
|
|
|
|
def get_abs_time(times):
|
|
now = int(time.time() + 0.999)
|
|
oneyear = 365 * 24 * 3600
|
|
return [t + now if t < oneyear else t for t in times]
|
|
|
|
class Scanner(object):
|
|
def __init__(self, directory, test_day=None):
|
|
self.directory = directory
|
|
self.last_time = {}
|
|
self.test_day = test_day
|
|
|
|
def scan(self, variable, timerange, result):
|
|
"""return value is true when there are aditionnal points after the time range"""
|
|
start, to, now = get_abs_time(timerange + [0])
|
|
old = None
|
|
t = 0
|
|
for di in range(date.fromtimestamp(start).toordinal(), date.fromtimestamp(to).toordinal() + 1):
|
|
d = date.fromordinal(di)
|
|
year, mon, day = self.test_day if self.test_day else (d.year, d.month, d.day)
|
|
path = self.directory + "logger/%d/%s/%.2d-%.2d.log" % \
|
|
(year, variable.lower(), mon, day)
|
|
try:
|
|
# logging.info("logger path %s", path)
|
|
with open(path) as f:
|
|
t0 = time.mktime((d.year, d.month, d.day, 0, 0, 0, 0, 0, -1))
|
|
for line in f:
|
|
if line[0] != '#':
|
|
t = t0 + (int(line[0:2]) * 60 + int(line[3:5])) * 60 + int(line[6:8])
|
|
if line[-1:] == '\n':
|
|
value = line[9:-1]
|
|
else:
|
|
value = line[9:]
|
|
if t < start:
|
|
old = value
|
|
else:
|
|
if old is not None:
|
|
self.next(start, old, result)
|
|
old = None
|
|
self.next(t, value, result)
|
|
if t > to:
|
|
break
|
|
except IOError:
|
|
# print(f'error reading {path}')
|
|
pass
|
|
if t < start:
|
|
#if t == 0:
|
|
# t = start
|
|
if old is not None:
|
|
self.next(t, old, result)
|
|
if t != self.last_time.get(variable,0):
|
|
self.last_time[variable] = t
|
|
return True
|
|
return False
|
|
|
|
class NumericScanner(Scanner):
|
|
def __init__(self, *args, **kwargs):
|
|
Scanner.__init__(self, *args, **kwargs)
|
|
|
|
def next(self, t, value, result):
|
|
try:
|
|
value = PrettyFloat(value)
|
|
except:
|
|
value = None
|
|
result.append([PrettyFloat(t), value])
|
|
#self.value = value
|
|
#self.last = t
|
|
|
|
def get_message(self, variables, timerange, show_empty=True):
|
|
self.dirty = False
|
|
result = {}
|
|
for var in variables:
|
|
self.last = 0
|
|
curve = []
|
|
if self.scan(var, timerange, curve):
|
|
self.dirty = True
|
|
if show_empty or len(curve) > 1:
|
|
result[var] = curve
|
|
return result
|
|
|
|
class ColorMap(object):
|
|
'''
|
|
ColorMap is using official CSS color names, with the exception of Green, as this
|
|
is defined differently with X11 colors than in SEA, and used heavily in config files.
|
|
Here Green is an alias to Lime (#00FF00) and MidGreen is #008000, which is called Green in CSS.
|
|
The function to_code is case insensitive and accepts also names with underscores.
|
|
The order is choosen by M. Zolliker for the SEA client, originally only the first 16 were used.
|
|
'''
|
|
hex_name = (("#FFFFFF","White"), ("#FF0000","Red"), ("#00FF00","Lime"), ("#0000FF","Blue"), ("#FF00FF","Magenta"),
|
|
("#FFFF00","Yellow"), ("#00FFFF","Cyan"), ("#000000","Black"), ("#FFA500","Orange"), ("#006400","DarkGreen"),
|
|
("#9400D3","DarkViolet"), ("#A52A2A","Brown"), ("#87CEEB","SkyBlue"), ("#808080","Gray"), ("#FF69B4","HotPink"),
|
|
("#FFFFE0","LightYellow"), ("#00FF7F","SpringGreen"), ("#000080","Navy"), ("#1E90FF","DodgerBlue"),
|
|
("#9ACD32","YellowGreen"), ("#008B8B","DarkCyan"), ("#808000","Olive"), ("#DEB887","BurlyWood"),
|
|
("#7B68EE","MediumSlateBlue"), ("#483D8B","DarkSlateBlue"), ("#98FB98","PaleGreen"), ("#FF1493","DeepPink"),
|
|
("#FF6347","Tomato"), ("#32CD32","LimeGreen"), ("#DDA0DD","Plum"), ("#7FFF00","Chartreuse"), ("#800080","Purple"),
|
|
("#00CED1","DarkTurquoise"), ("#8FBC8F","DarkSeaGreen"), ("#4682B4","SteelBlue"), ("#800000","Maroon"),
|
|
("#3CB371","MediumSeaGreen"), ("#FF4500","OrangeRed"), ("#BA55D3","MediumOrchid"), ("#2F4F4F","DarkSlateGray"),
|
|
("#CD853F","Peru"), ("#228B22","ForestGreen"), ("#48D1CC","MediumTurquoise"), ("#DC143C","Crimson"),
|
|
("#D3D3D3","LightGray"), ("#ADFF2F","GreenYellow"), ("#7FFFD4","Aquamarine"), ("#BC8F8F","RosyBrown"),
|
|
("#20B2AA","LightSeaGreen"), ("#C71585","MediumVioletRed"), ("#F0E68C","Khaki"), ("#6495ED","CornflowerBlue"),
|
|
("#556B2F","DarkOliveGreen"), ("#CD5C5C","IndianRed "), ("#2E8B57","SeaGreen"), ("#F08080","LightCoral"),
|
|
("#8A2BE2","BlueViolet"), ("#AFEEEE","PaleTurquoise"), ("#4169E1","RoyalBlue"), ("#0000CD","MediumBlue"),
|
|
("#B8860B","DarkGoldenRod"), ("#00BFFF","DeepSkyBlue"), ("#FFC0CB","Pink"), ("#4B0082","Indigo "), ("#A0522D","Sienna"),
|
|
("#FFD700","Gold"), ("#F4A460","SandyBrown"), ("#DAA520","GoldenRod"), ("#DA70D6","Orchid"), ("#E6E6FA","Lavender"),
|
|
("#5F9EA0","CadetBlue"), ("#D2691E","Chocolate"), ("#66CDAA","MediumAquaMarine"), ("#6B8E23","OliveDrab"),
|
|
("#A9A9A9","DarkGray"), ("#BDB76B","DarkKhaki"), ("#696969","DimGray"), ("#B0C4DE","LightSteelBlue"),
|
|
("#191970","MidnightBlue"), ("#FFE4C4","Bisque"), ("#6A5ACD","SlateBlue"), ("#EE82EE","Violet"),
|
|
("#8B4513","SaddleBrown"), ("#FF7F50","Coral"), ("#008000","MidGreen"), ("#DB7093","PaleVioletRed"), ("#C0C0C0","Silver"),
|
|
("#E0FFFF","LightCyan"), ("#9370DB","MediumPurple"), ("#FF8C00","DarkOrange"), ("#00FA9A","MediumSpringGreen"),
|
|
("#E9967A","DarkSalmon"), ("#778899","LightSlateGray"), ("#9932CC","DarkOrchid"), ("#EEE8AA","PaleGoldenRod"),
|
|
("#F8F8FF","GhostWhite"), ("#FFA07A","LightSalmon"), ("#ADD8E6","LightBlue"), ("#D8BFD8","Thistle"),
|
|
("#FFE4E1","MistyRose"), ("#FFDEAD","NavajoWhite"), ("#40E0D0","Turquoise"), ("#90EE90","LightGreen"),
|
|
("#B22222","FireBrick"), ("#008080","Teal"), ("#F0FFF0","HoneyDew"), ("#FFFACD","LemonChiffon"), ("#FFF5EE","SeaShell"),
|
|
("#F5F5DC","Beige"), ("#DCDCDC","Gainsboro"), ("#FA8072","Salmon"), ("#8B008B","DarkMagenta"), ("#FFB6C1","LightPink"),
|
|
("#708090","SlateGray"), ("#87CEFA","LightSkyBlue"), ("#FFEFD5","PapayaWhip"), ("#D2B48C","Tan"), ("#FFFFF0","Ivory"),
|
|
("#F0FFFF","Azure"), ("#F5DEB3","Wheat"), ("#00008B","DarkBlue"), ("#FFDAB9","PeachPuff"), ("#8B0000","DarkRed"),
|
|
("#FAF0E6","Linen"), ("#B0E0E6","PowderBlue"), ("#FFE4B5","Moccasin"), ("#F5F5F5","WhiteSmoke"), ("#FFF8DC","Cornsilk"),
|
|
("#FFFAFA","Snow"), ("#FFF0F5","LavenderBlush"), ("#FFEBCD","BlanchedAlmond"), ("#F0F8FF","AliceBlue"),
|
|
("#FAEBD7","AntiqueWhite"), ("#FDF5E6","OldLace"), ("#FAFAD2","LightGoldenRodYellow"), ("#F5FFFA","MintCream"),
|
|
("#FFFAF0","FloralWhite"), ("#7CFC00","LawnGreen"), ("#663399","RebeccaPurple"))
|
|
codes = {}
|
|
for i, pair in enumerate(hex_name):
|
|
codes[pair[0]] = i
|
|
low = pair[1].lower()
|
|
codes[low] = i
|
|
codes[low.replace("gray","grey")] = i
|
|
codes["green"] = 2
|
|
codes["fuchsia"] = 4
|
|
codes["aqua"] = 6
|
|
|
|
@staticmethod
|
|
def to_code(colortext):
|
|
try:
|
|
return int(colortext)
|
|
except ValueError:
|
|
return ColorMap.codes.get(colortext.lower().replace("_",""),-1)
|
|
|
|
@staticmethod
|
|
def check_hex(code):
|
|
if not code.startswith("#"):
|
|
return None
|
|
if len(code) == 4: # convert short code to long code
|
|
code = code[0:2] + code[1:3] + code[2:4] + code[3]
|
|
if len(code) != 7:
|
|
return None
|
|
try:
|
|
int(code[1:]) # we have a valid hex color code
|
|
return code
|
|
except ValueError:
|
|
return None
|
|
|
|
@staticmethod
|
|
def to_hex(code):
|
|
try:
|
|
return ColorMap.hex_name[code][0]
|
|
except IndexError:
|
|
return -1
|
|
|
|
class VarsScanner(Scanner):
|
|
colors = {"red":0}
|
|
def __init__(self, directory, test_day=None):
|
|
Scanner.__init__(self, directory, test_day=test_day)
|
|
logging.info('vars dir %s', directory)
|
|
|
|
def next(self, t, value, result):
|
|
logging.info('vars %s', value)
|
|
for var in value.strip().split(" "):
|
|
vars = var.split("|")
|
|
if len(vars) == 1:
|
|
vars.append("")
|
|
if len(vars) == 2:
|
|
vars.append(vars[0])
|
|
if len(vars) == 3:
|
|
vars.append("")
|
|
name, unit, label, color = vars
|
|
if not unit in result:
|
|
result[unit] = dict(tag = unit, unit = unit.split("_")[0], curves=[])
|
|
result[unit]["curves"].append(dict(name=name, label=label, color=color))
|
|
|
|
def get_message(self, time):
|
|
# get last value only
|
|
result = {}
|
|
self.scan("vars", [time, time], result)
|
|
for unit in result:
|
|
color_set = set()
|
|
auto_curves = []
|
|
for curve in result[unit]["curves"]:
|
|
col = curve["color"].strip()
|
|
c = ColorMap.to_code(col)
|
|
if c < 0:
|
|
valid = ColorMap.check_hex(col)
|
|
if valid:
|
|
curve["original_color"] = col
|
|
curve["color"] = valid
|
|
else:
|
|
auto_curves.append(curve)
|
|
curve["original_color"] = col + "?"
|
|
else:
|
|
color_set.add(c)
|
|
curve["original_color"] = col
|
|
curve["color"] = ColorMap.to_hex(c)
|
|
c = 1 # omit white
|
|
for curve in auto_curves:
|
|
while c in color_set: c += 1 # find unused color
|
|
curve["color"] = ColorMap.to_hex(c)
|
|
c += 1
|
|
return result
|
|
|