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