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