summaryrefslogtreecommitdiffstats
path: root/plot.py
blob: b48ab95dc84b8dec89cd09f7c96a3d2da24c36de (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#!/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 <log> <png>", 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: