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
|
## RRD database update CGI program. Takes JSON measurements from ruuvitag-upload
## (https://github.com/oswjk/ruuvitag-upload) as input and stores them in the correct RRD database.
##
## The JSON format is as follows:
##
## {
## "alias": {
## "timestamp": <seconds since unix epoch>,
## "temperature": <temperature in degrees celcius>,
## "humidity": <relative humidity (0% - 100%)>,
## "pressure": <pressure in kilopascals>,
## "battery_potential": <ruuvitag battery potential in volts>
## },
## "another_alias": {
## ...
## }
## }
##
## The program should be run with an accompanying CGI script as the main driver. The web server will
## execute the script which looks like this:
##
## #!/path/to/update_database
## databaseDir = /path/to/where/databases/are
##
## This will execute update_database with one argument: the path to this script file.
## update_database will read this script and parse it as a configuration file. The databaseDir
## option should point to where the .rrd databases are stored.
##
## The update_database program will use the ``alias`` from measurements as the database name (with
## ``.rrd`` extension).
import json
import os
import osproc
import parsecfg
import strformat
import strutils
type
CgiError = object of CatchableError
code: int
proc newCgiError(code: int, message: string): ref CgiError =
result = newException(CgiError, message)
result.code = code
proc readData(): string =
var size = parseInt(getEnv("CONTENT_LENGTH").string)
if size == 0:
return ""
result = newString(size)
if readBuffer(stdin, addr result[0], size) != 0:
raise newCgiError(400, "not enough data")
proc main() =
if paramCount() != 2:
raise newCgiError(500, "one argument required")
let config = loadConfig(paramStr(1))
let databaseDir = config.getSectionValue("", "databaseDir")
if databaseDir == "":
raise newCgiError(500, "databaseDir is empty")
if getEnv("REQUEST_METHOD") != "POST":
raise newCgiError(400, "request method must be POST")
let j = parseJson(readData())
for alias, measurement in j:
let filename = normalizedPath(joinPath(databaseDir, alias & ".rrd"))
if not filename.startsWith(databaseDir):
raise newCgiError(400, "invalid database")
let
timestamp = measurement["timestamp"].getBiggestInt()
temperature = measurement["temperature"].getFloat()
humidity = measurement["humidity"].getFloat()
pressure = measurement["pressure"].getFloat()
battery = measurement["battery_potential"].getFloat()
data = &"{timestamp}:{temperature}:{humidity}:{pressure}:{battery}"
(_, ec) = execCmdEx(&"rrdtool update {filename} {data}")
if ec != 0:
raise newCgiError(500, "rrdtool failed")
try:
main()
echo("Content-Type: text/plain")
echo("")
except CgiError as e:
echo(&"Status: {e.code}")
echo("Content-Type: text/plain")
echo("")
echo(e.msg)
quit(0)
except JsonParsingError as e:
echo(&"Status: 400 Bad Request")
echo("Content-Type: text/plain")
echo("")
echo(&"invalid json: {e.msg}")
quit(0)
except Exception as e:
echo(&"Status: 500 Internal Server Error")
echo("Content-Type: text/plain")
echo("")
echo(e.msg)
quit(0)
|