Rev 6 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
3 | daniel-mar | 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 |
||
4 | daniel-mar | 12 | import threading |
6 | daniel-mar | 13 | import subprocess as sp |
3 | daniel-mar | 14 | |
15 | g_subscribed = [] |
||
6 | daniel-mar | 16 | g_bellListenerProc = None |
3 | daniel-mar | 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 | |||
4 | daniel-mar | 31 | output = '''<!DOCTYPE html> |
32 | <html lang="en"> |
||
3 | daniel-mar | 33 | |
34 | <head> |
||
4 | daniel-mar | 35 | <meta charset="UTF-8"> |
3 | daniel-mar | 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) { |
||
4 | daniel-mar | 48 | document.getElementById("pleasewait").innerHTML = ' <i>Please wait...</i>'; |
3 | daniel-mar | 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*/); |
||
4 | daniel-mar | 66 | xhr.open('POST', document.location, true); |
3 | daniel-mar | 67 | xhr.send(data); |
68 | } |
||
69 | |||
70 | function onload() { |
||
4 | daniel-mar | 71 | if (document.getElementById('campic') != null) { |
72 | document.getElementById('campic').src = 'http://' + window.location.hostname + ':'''+str(config.motion_stream_port)+'''/'; |
||
73 | } |
||
3 | daniel-mar | 74 | } |
75 | |||
76 | </script>''' |
||
77 | |||
78 | if ismotionrunning(): |
||
4 | daniel-mar | 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>'; |
||
3 | daniel-mar | 83 | else: |
4 | daniel-mar | 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>' |
||
3 | daniel-mar | 86 | |
87 | output = output + '<h2>Subscribers</h2>' |
||
88 | |||
89 | found_subs = 0 |
||
5 | daniel-mar | 90 | for subscriber in g_subscribed[:]: |
91 | if int(time.time()) > subscriber[2]: |
||
92 | g_subscribed.remove(subscriber) |
||
3 | daniel-mar | 93 | else: |
94 | found_subs = found_subs + 1 |
||
5 | daniel-mar | 95 | output = output + "<p>{0}:{1}</p>".format(subscriber[0], subscriber[1]) |
3 | daniel-mar | 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 | |||
4 | daniel-mar | 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 | |||
3 | daniel-mar | 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 |
||
4 | daniel-mar | 118 | # Question: Do we need the cgi package, or can we use functions available in this class (e.g. self_parse_qs?) |
3 | daniel-mar | 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 |
||
6 | daniel-mar | 131 | global g_bellListenerProc |
3 | daniel-mar | 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 |
||
5 | daniel-mar | 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) |
||
3 | daniel-mar | 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 | |||
5 | daniel-mar | 165 | for subscriber in g_subscribed[:]: |
4 | daniel-mar | 166 | client_ip = subscriber[0] |
167 | client_port = subscriber[1] |
||
168 | client_expires = subscriber[2] |
||
169 | client_targets = subscriber[3] |
||
3 | daniel-mar | 170 | |
5 | daniel-mar | 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 | |||
3 | daniel-mar | 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 |
||
8 | daniel-mar | 189 | # TODO: Actually, we should call this API "alarm_on" instead of "motion_on", since we also enable a doorbell checker |
3 | daniel-mar | 190 | print "Motion start" |
6 | daniel-mar | 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: |
||
8 | daniel-mar | 194 | g_bellListenerProc = sp.Popen([os.path.dirname(os.path.abspath(__file__)) + "/doorbell/bell_listener.py"]) |
3 | daniel-mar | 195 | if pvget(postvars, "action")[0] == "motion_off": # 1.3.6.1.4.1.37476.2.4.1.101 |
8 | daniel-mar | 196 | # TODO: Actually, we should call this API "alarm_off" instead of "motion_off", since we also disable a doorbell checker |
3 | daniel-mar | 197 | print "Motion stop" |
6 | daniel-mar | 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) |
||
3 | daniel-mar | 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): |
||
4 | daniel-mar | 212 | print 'Starting server, listening to port {0}...'.format(port) |
3 | daniel-mar | 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 |