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