#!/usr/bin/python3 # based on https://github.com/cnvogelg/ardu/blob/master/datalog/tools/plot_log.py # This script plots a log file of the format # "unix_timestamp key=value key2=value key3=value ..." # using rrdtool. Keys are arbitrary values of the format "type:number" # (e.g. t:1, t:2, h:1 for temperature and humidity) used to identify each sample. # Note that the keys currently ignored. # # The whole log is plotted into one graph. import sys import subprocess import os import math from string import Template if len(sys.argv) != 3: print("Usage: plot.py ", file=sys.stderr) sys.exit(1) log_file = sys.argv[1] png_file = sys.argv[2] rrd_file = "tmp.rrd" argmax = 20000 #### start of config template_values = { # 0 = everything from the input # otherwise = only the last n "total_samples": 0, } # heartbeat. If you sample at an interval < that value you will not see a # graph. Simply adjust if necessary. heartbeat = 50 create_prefix = [ # Index of the 0 after --start is used below. Adjust code if you # change the amount of values before it. 'rrdtool','create',rrd_file,'--step','30','--start',0, ] create_suffix = [ 'RRA:AVERAGE:0.5:1:$total_samples', 'RRA:MIN:0.5:1:$total_samples', 'RRA:MAX:0.5:1:$total_samples', ] graph = [ # Indices of the two Nones are used below. Adjust the code if you # change the amount of values before them.. 'rrdtool','graph',png_file, '-s',None,'-e',None, '--height=500','--width=1000', '--color=BACK#FFFFFF', #'--title','Temperature', '--vertical-label', 'Temperature °C', '--right-axis-label', 'Humidity g/m³', '--right-axis', '0.5:0', #'--alt-autoscale', '-l','-5','-u','30', '--rigid', '--left-axis-format', '%4.2lf', '--right-axis-format', '%4.2lf', ] colors = [ '#339966', '#663399', '#993366', '#336699', ] #### end of config def chunker(seq, size): return (seq[pos:pos + size] for pos in range(0, len(seq), size)) calc_total_from_input = False if template_values["total_samples"] == 0: calc_total_from_input = True # load samples samples = [] value_names = [] with open(log_file,"r") as f: first_line = True for line in f: if calc_total_from_input: template_values["total_samples"] += 1 elements = line.split(' ') values = [] # timestamp values.append(int(elements[0])) # values for i in range(1, len(elements)): [key, value] = elements[i].split("=") [elem_type, sensorID] = key.split(":") if first_line: value_names.append(key) if elem_type == "t": values.append(float(value)) elif elem_type == "h": # absolute humidity hum=float(value) temp=values[i - 1] # Source: https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/ abshum=(6.112 * math.e**(17.67 * temp / (temp + 243.5)) * hum * 2.1674) / (273.15+temp) values.append(abshum) else: raise ValueError("Unhandled element type") samples.append(values) first_line = False create_values = [] color_counter = 0 for elem in value_names: [elem_type, sensorID] = elem.split(":") color = colors[color_counter] color_counter = (color_counter + 1) % len(colors) if elem_type == "t": create_values.append("DS:temp{sensorID}:GAUGE:{heartbeat}:U:U".format(sensorID=sensorID, heartbeat=heartbeat)) graph.extend([ # TODO change color for each value. select from a list of defined colors 'DEF:temp{sensorID}={rrd_file}:temp{sensorID}:AVERAGE'.format(sensorID=sensorID, rrd_file=rrd_file), 'LINE1:temp{sensorID}{color}:temp {sensorID}\l'.format(sensorID=sensorID, color=color), ]) elif elem_type == "h": create_values.append("DS:abshum{sensorID}:GAUGE:{heartbeat}:U:U".format(sensorID=sensorID, heartbeat=heartbeat)) graph.extend([ # TODO change color for each value. select from a list of defined colors 'DEF:abshum{sensorID}={rrd_file}:abshum{sensorID}:AVERAGE'.format(sensorID=sensorID, rrd_file=rrd_file), 'CDEF:abshum{sensorID}_1=abshum{sensorID},2,*'.format(sensorID=sensorID), 'LINE1:abshum{sensorID}_1{color}:abshum {sensorID}\l'.format(sensorID=sensorID, color=color), ]) else: raise ValueError("Unhandled element type") create = create_prefix + create_values + create_suffix # create rrd start_ts = samples[0][0] end_ts = samples[-1][0] create[6] = str(start_ts - 1) cmd = [] for elem in create: cmd.append(Template(elem).substitute(template_values)) #print("creating rrdb:",create) ret = subprocess.call(cmd) if ret != 0: print("ERROR calling: {}".format(" ".join(create))) sys.exit(1) # update rrd for group in chunker(samples, argmax): cmd = ['rrdupdate', rrd_file, '--'] for sample in group: entry = ":".join(map(str, sample)) cmd.append(entry); ret = subprocess.call(cmd) if ret != 0: print("ERROR calling: {}".format(" ".join(cmd))) sys.exit(1) # graph graph[4] = str(start_ts) graph[6] = str(end_ts) ret = subprocess.call(graph) if ret != 0: print("ERROR calling: {}".format(" ".join(graph))) sys.exit(1) os.unlink(rrd_file) # vim: set noet: