1 |
daniel-marschall |
3 |
#!/usr/bin/env python |
2 |
|
|
|
3 |
|
|
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer |
4 |
|
|
import SocketServer |
5 |
|
|
from urlparse import parse_qs |
6 |
|
|
import os |
7 |
|
|
import cgi |
8 |
|
|
import time |
9 |
|
|
import requests |
10 |
|
|
import subprocess |
11 |
|
|
import config |
12 |
daniel-marschall |
4 |
import threading |
13 |
daniel-marschall |
3 |
|
14 |
|
|
g_subscribed = [] |
15 |
|
|
|
16 |
|
|
class S(BaseHTTPRequestHandler): |
17 |
|
|
def _output(self, code, content): |
18 |
|
|
self.send_response(code) |
19 |
|
|
self.send_header('Content-type', 'text/html') |
20 |
|
|
self.end_headers() |
21 |
|
|
self.wfile.write(content) |
22 |
|
|
|
23 |
|
|
def do_GET(self): |
24 |
|
|
try: |
25 |
|
|
action = parse_qs(self.path[2:]).get("action")[0] |
26 |
|
|
except: |
27 |
|
|
action = None |
28 |
|
|
|
29 |
daniel-marschall |
4 |
output = '''<!DOCTYPE html> |
30 |
|
|
<html lang="en"> |
31 |
daniel-marschall |
3 |
|
32 |
|
|
<head> |
33 |
daniel-marschall |
4 |
<meta charset="UTF-8"> |
34 |
daniel-marschall |
3 |
<title>Motion camera</title> |
35 |
|
|
</head> |
36 |
|
|
|
37 |
|
|
<body onload="onload()"> |
38 |
|
|
|
39 |
|
|
<script> |
40 |
|
|
|
41 |
|
|
function sleep (time) { |
42 |
|
|
return new Promise((resolve) => setTimeout(resolve, time)); |
43 |
|
|
} |
44 |
|
|
|
45 |
|
|
function _toggle_alarm(st) { |
46 |
daniel-marschall |
4 |
document.getElementById("pleasewait").innerHTML = ' <i>Please wait...</i>'; |
47 |
daniel-marschall |
3 |
var xhr = new XMLHttpRequest(); |
48 |
|
|
xhr.onreadystatechange = function () { |
49 |
|
|
var DONE = 4; // readyState 4 means the request is done. |
50 |
|
|
var OK = 200; // status 200 is a successful return. |
51 |
|
|
if (xhr.readyState === DONE) { |
52 |
|
|
if (xhr.status === OK) { |
53 |
|
|
sleep(5000).then(() => { |
54 |
|
|
document.location.reload(); |
55 |
|
|
}); |
56 |
|
|
} else { |
57 |
|
|
alert('Error: ' + xhr.status); // An error occurred during the request. |
58 |
|
|
} |
59 |
|
|
} |
60 |
|
|
}; |
61 |
|
|
|
62 |
|
|
var data = new FormData(); |
63 |
|
|
data.append('action', st ? 'motion_on'/*1.3.6.1.4.1.37476.2.4.1.100*/ : 'motion_off'/*1.3.6.1.4.1.37476.2.4.1.101*/); |
64 |
daniel-marschall |
4 |
xhr.open('POST', document.location, true); |
65 |
daniel-marschall |
3 |
xhr.send(data); |
66 |
|
|
} |
67 |
|
|
|
68 |
|
|
function onload() { |
69 |
daniel-marschall |
4 |
if (document.getElementById('campic') != null) { |
70 |
|
|
document.getElementById('campic').src = 'http://' + window.location.hostname + ':'''+str(config.motion_stream_port)+'''/'; |
71 |
|
|
} |
72 |
daniel-marschall |
3 |
} |
73 |
|
|
|
74 |
|
|
</script>''' |
75 |
|
|
|
76 |
|
|
if ismotionrunning(): |
77 |
daniel-marschall |
4 |
output = output + '<h2>Motion detection ON</h2>' |
78 |
|
|
output = output + '<p><a href="javascript:_toggle_alarm(0)">Disable motion detection</a><span id="pleasewait"></span></p>' |
79 |
|
|
output = output + '<p>Showing camera stream from port {0}. If you don\'t see a picture, please check your configuration or firewall.</p>'.format(config.motion_stream_port) |
80 |
|
|
output = output + '<p><img id="campic" src="" alt=""></p>'; |
81 |
daniel-marschall |
3 |
else: |
82 |
daniel-marschall |
4 |
output = output + '<h2>Motion detection OFF</h2>' |
83 |
|
|
output = output + '<p><a href="javascript:_toggle_alarm(1)">Enable motion detection</a><span id="pleasewait"></span></p>' |
84 |
daniel-marschall |
3 |
|
85 |
|
|
output = output + '<h2>Subscribers</h2>' |
86 |
|
|
|
87 |
|
|
found_subs = 0 |
88 |
|
|
for x in g_subscribed[:]: |
89 |
|
|
if int(time.time()) > x[2]: |
90 |
|
|
g_subscribed.remove(x) |
91 |
|
|
else: |
92 |
|
|
found_subs = found_subs + 1 |
93 |
|
|
output = output + "<p>{0}:{1}</p>".format(x[0], x[1]) |
94 |
|
|
|
95 |
|
|
if found_subs == 0: |
96 |
|
|
output = output + '<p>None</p>' |
97 |
|
|
|
98 |
|
|
output = output + '</body>' |
99 |
|
|
output = output + '</html>' |
100 |
|
|
|
101 |
|
|
self._output(200, output) |
102 |
|
|
|
103 |
|
|
def do_HEAD(self): |
104 |
|
|
self._output(200, '') |
105 |
|
|
|
106 |
daniel-marschall |
4 |
def thr_client_notify(self, client_ip, client_port, server_targets): |
107 |
|
|
print "ALERT: Will alert client http://{0}:{1} and tell that targets {2} sent an alert".format(client_ip, client_port, server_targets) |
108 |
|
|
d = {"action": "client_alert", # 1.3.6.1.4.1.37476.2.4.1.3 |
109 |
|
|
"targets": server_targets, |
110 |
|
|
"motion_port": config.motion_stream_port, |
111 |
|
|
"simulation": "0"} |
112 |
|
|
requests.post("http://{0}:{1}".format(client_ip, client_port), data=d) |
113 |
|
|
|
114 |
daniel-marschall |
3 |
def do_POST(self): |
115 |
|
|
# https://stackoverflow.com/questions/4233218/python-how-do-i-get-key-value-pairs-from-the-basehttprequesthandler-http-post-h |
116 |
daniel-marschall |
4 |
# Question: Do we need the cgi package, or can we use functions available in this class (e.g. self_parse_qs?) |
117 |
daniel-marschall |
3 |
ctype, pdict = cgi.parse_header(self.headers.getheader('content-type')) |
118 |
|
|
if ctype == 'multipart/form-data': |
119 |
|
|
postvars = cgi.parse_multipart(self.rfile, pdict) |
120 |
|
|
elif ctype == 'application/x-www-form-urlencoded': |
121 |
|
|
length = int(self.headers.getheader('content-length')) |
122 |
|
|
postvars = cgi.parse_qs(self.rfile.read(length), keep_blank_values=1) |
123 |
|
|
else: |
124 |
|
|
postvars = {} |
125 |
|
|
|
126 |
|
|
# --- |
127 |
|
|
|
128 |
|
|
global g_subscribed |
129 |
|
|
|
130 |
|
|
if pvget(postvars, "action")[0] == "client_subscribe": # 1.3.6.1.4.1.37476.2.4.1.1 |
131 |
|
|
client_ip = self.client_address[0] |
132 |
|
|
client_port = pvget(postvars, "port")[0] |
133 |
|
|
client_ttl = pvget(postvars, "ttl")[0] |
134 |
|
|
client_targets = pvget(postvars, "targets") |
135 |
|
|
|
136 |
|
|
client_expires = int(time.time()) + int(client_ttl) |
137 |
|
|
|
138 |
|
|
print "Client subscribed: {0}:{1}, searching for targets {2}".format(client_ip, client_port, client_targets) |
139 |
|
|
|
140 |
|
|
# Remove all expired entries, and previous entries of that client |
141 |
|
|
for x in g_subscribed[:]: |
142 |
|
|
if int(time.time()) > x[2]: |
143 |
|
|
g_subscribed.remove(x) |
144 |
|
|
elif (x[0] == client_ip) and (x[1] == client_port) and (x[3] == client_targets): |
145 |
|
|
g_subscribed.remove(x) |
146 |
|
|
|
147 |
|
|
# Now add our new client |
148 |
|
|
g_subscribed.append([client_ip, client_port, client_expires, client_targets]) |
149 |
|
|
|
150 |
|
|
# Send parameters of the device(s) |
151 |
|
|
d = {"action": "client_alert", # 1.3.6.1.4.1.37476.2.4.1.3 |
152 |
|
|
"targets": ['1.3.6.1.4.1.37476.2.4.2.0', '1.3.6.1.4.1.37476.2.4.2.1002'], |
153 |
|
|
"motion_port": config.motion_stream_port, |
154 |
|
|
"simulation": "1"} |
155 |
|
|
requests.post("http://{0}:{1}".format(client_ip, client_port), data=d) |
156 |
|
|
|
157 |
|
|
if pvget(postvars, "action")[0] == "server_alert": # 1.3.6.1.4.1.37476.2.4.1.2 |
158 |
|
|
server_targets = pvget(postvars, "targets") |
159 |
|
|
|
160 |
|
|
found_g = 0 |
161 |
|
|
|
162 |
daniel-marschall |
4 |
for subscriber in g_subscribed: |
163 |
|
|
client_ip = subscriber[0] |
164 |
|
|
client_port = subscriber[1] |
165 |
|
|
client_expires = subscriber[2] |
166 |
|
|
client_targets = subscriber[3] |
167 |
daniel-marschall |
3 |
found_c = 0 |
168 |
|
|
for st in server_targets: |
169 |
|
|
for ct in client_targets: |
170 |
|
|
if ct == st: |
171 |
|
|
found_c = found_c + 1 |
172 |
|
|
found_g = found_g + 1 |
173 |
|
|
if found_c > 0: |
174 |
daniel-marschall |
4 |
# Notify clients via threads, so that all clients are equally fast notified |
175 |
|
|
thread = threading.Thread(target=self.thr_client_notify, args=(client_ip, client_port, server_targets, )) |
176 |
|
|
thread.start() |
177 |
daniel-marschall |
3 |
|
178 |
|
|
if found_g == 0: |
179 |
|
|
print "ALERT {0}, but nobody is listening!".format(server_targets) |
180 |
|
|
|
181 |
|
|
if pvget(postvars, "action")[0] == "motion_on": # 1.3.6.1.4.1.37476.2.4.1.100 |
182 |
|
|
print "Motion start" |
183 |
|
|
os.system(os.path.dirname(os.path.abspath(__file__)) + "/motion/motion_start_safe") |
184 |
|
|
|
185 |
|
|
if pvget(postvars, "action")[0] == "motion_off": # 1.3.6.1.4.1.37476.2.4.1.101 |
186 |
|
|
print "Motion stop" |
187 |
|
|
os.system(os.path.dirname(os.path.abspath(__file__)) + "/motion/motion_stop_safe") |
188 |
|
|
|
189 |
|
|
self._output(200, '') |
190 |
|
|
|
191 |
|
|
def pvget(ary, key): |
192 |
|
|
if ary.get(key) == None: |
193 |
|
|
return [""] |
194 |
|
|
else: |
195 |
|
|
return ary.get(key) |
196 |
|
|
|
197 |
|
|
def run(server_class=HTTPServer, handler_class=S, port=8085): |
198 |
daniel-marschall |
4 |
print 'Starting server, listening to port {0}...'.format(port) |
199 |
daniel-marschall |
3 |
server_address = ('', port) |
200 |
|
|
httpd = server_class(server_address, handler_class) |
201 |
|
|
httpd.serve_forever() |
202 |
|
|
|
203 |
|
|
def ismotionrunning(): |
204 |
|
|
p = subprocess.Popen(["ps", "aux"], stdout=subprocess.PIPE) |
205 |
|
|
out, err = p.communicate() |
206 |
|
|
try: |
207 |
|
|
return ('/usr/bin/motion' in str(out)) |
208 |
|
|
except: |
209 |
|
|
res = os.system("pidof -x /usr/bin/motion > /dev/null") |
210 |
|
|
return (res >> 8) == 0 |
211 |
|
|
|
212 |
|
|
if __name__ == "__main__": |
213 |
|
|
from sys import argv |
214 |
|
|
|
215 |
|
|
if len(argv) == 2: |
216 |
|
|
run(port=int(argv[1])) |
217 |
|
|
else: |
218 |
|
|
run() |
219 |
|
|
|