-
Notifications
You must be signed in to change notification settings - Fork 0
/
monitor.py
executable file
·159 lines (125 loc) · 5.03 KB
/
monitor.py
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
#!/usr/bin/env python
# based on code by henryk ploetz
# https://hackaday.io/project/5301-reverse-engineering-a-low-cost-usb-co-monitor/log/17909-all-your-base-are-belong-to-us
import os, sys, fcntl, time, librato, yaml, socket
import requests
def callback_function(error, result):
if error:
print(error)
return
print(result)
def decrypt(key, data):
cstate = [0x48, 0x74, 0x65, 0x6D, 0x70, 0x39, 0x39, 0x65]
shuffle = [2, 4, 0, 7, 1, 6, 5, 3]
phase1 = [0] * 8
for i, o in enumerate(shuffle):
phase1[o] = data[i]
phase2 = [0] * 8
for i in range(8):
phase2[i] = phase1[i] ^ key[i]
phase3 = [0] * 8
for i in range(8):
phase3[i] = ( (phase2[i] >> 3) | (phase2[ (i-1+8)%8 ] << 5) ) & 0xff
ctmp = [0] * 8
for i in range(8):
ctmp[i] = ( (cstate[i] >> 4) | (cstate[i]<<4) ) & 0xff
out = [0] * 8
for i in range(8):
out[i] = (0x100 + phase3[i] - ctmp[i]) & 0xff
return out
def hd(d):
return " ".join("%02X" % e for e in d)
def now():
return int(time.time())
def notifySlack(co2, config, upper_threshold):
if ((not config) or (not "webhook" in config)):
return
webhookUrl = config["webhook"]
channel = config["channel"] if "channel" in config else "#general"
botName = config["botname"] if "botname" in config else "CO2bot"
icon = config["icon"] if "icon" in config else ":robot_face:"
if (co2 > upper_threshold):
message = "Dude, you should open a window. We have *%dppm* in here." % co2
else:
message = "Ok, you can close the window now. We're down to *%dppm*." % co2
try:
payload = {
'channel': channel,
'username': botName,
'text': message,
'icon_emoji': icon
}
requests.post(webhookUrl, json=payload)
except:
print "Unexpected error:", sys.exc_info()[0]
def publish(client, prefix, co2, tmp):
try:
client.submit(prefix + ".co2", co2)
client.submit(prefix + ".tmp", tmp)
except:
print "Unexpected error:", sys.exc_info()[0]
def config(config_file=None):
"""Get config from file; if no config_file is passed in as argument
default to "config.yaml" in script dir"""
if config_file is None:
script_base_dir = os.path.dirname(os.path.realpath(sys.argv[0])) + "/"
config_file = script_base_dir + "config.yaml"
with open(config_file, 'r') as stream:
return yaml.load(stream)
def client(config):
return librato.connect(config["user"], config["token"])
if __name__ == "__main__":
"""main"""
# use lock on socket to indicate that script is already running
try:
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
## Create an abstract socket, by prefixing it with null.
s.bind('\0postconnect_gateway_notify_lock')
except socket.error, e:
# if script is already running just exit silently
sys.exit(0)
key = [0xc4, 0xc6, 0xc0, 0x92, 0x40, 0x23, 0xdc, 0x96]
fp = open(sys.argv[1], "a+b", 0)
HIDIOCSFEATURE_9 = 0xC0094806
set_report = "\x00" + "".join(chr(e) for e in key)
fcntl.ioctl(fp, HIDIOCSFEATURE_9, set_report)
values = {}
stamp = now()
notified = False
try:
config = config(config_file=sys.argv[2])
except IndexError:
config = config()
client = client(config)
while True:
data = list(ord(e) for e in fp.read(8))
decrypted = decrypt(key, data)
if decrypted[4] != 0x0d or (sum(decrypted[:3]) & 0xff) != decrypted[3]:
print hd(data), " => ", hd(decrypted), "Checksum error"
else:
op = decrypted[0]
val = decrypted[1] << 8 | decrypted[2]
values[op] = val
if (0x50 in values) and (0x42 in values):
co2 = values[0x50]
tmp = (values[0x42]/16.0-273.15)
# check if it's a sensible value
# (i.e. within the measuring range plus some margin)
if (co2 > 5000 or co2 < 0):
continue
print "CO2: %4i TMP: %3.1f" % (co2, tmp)
if now() - stamp > 5:
print ">>>"
publish(client, config["prefix"], co2, tmp)
# publish to slack, if configured
if ("slack" in config):
upper_threshold = config["slack"]["upper_threshold"] if "upper_threshold" in config["slack"] else 800
lower_threshold = config["slack"]["lower_threshold"] if "lower_threshold" in config["slack"] else 600
if (co2 > upper_threshold) and (not notified):
notified = True
notifySlack(co2, config["slack"], upper_threshold)
elif (co2 < lower_threshold):
if (notified):
notifySlack(co2, config["slack"], upper_threshold)
notified = False
stamp = now()