Rev 3 | Rev 5 | 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 |
3 | daniel-mar | 13 | |
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 | |||
4 | daniel-mar | 29 | output = '''<!DOCTYPE html> |
30 | <html lang="en"> |
||
3 | daniel-mar | 31 | |
32 | <head> |
||
4 | daniel-mar | 33 | <meta charset="UTF-8"> |
3 | daniel-mar | 34 | <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) { |
||
4 | daniel-mar | 46 | document.getElementById("pleasewait").innerHTML = ' <i>Please wait...</i>'; |
3 | daniel-mar | 47 | 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*/); |
||
4 | daniel-mar | 64 | xhr.open('POST', document.location, true); |
3 | daniel-mar | 65 | xhr.send(data); |
66 | } |
||
67 | |||
68 | function onload() { |
||
4 | daniel-mar | 69 | if (document.getElementById('campic') != null) { |
70 | document.getElementById('campic').src = 'http://' + window.location.hostname + ':'''+str(config.motion_stream_port)+'''/'; |
||
71 | } |
||
3 | daniel-mar | 72 | } |
73 | |||
74 | </script>''' |
||
75 | |||
76 | if ismotionrunning(): |
||
4 | daniel-mar | 77 | 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>'; |
||
3 | daniel-mar | 81 | else: |
4 | daniel-mar | 82 | 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>' |
||
3 | daniel-mar | 84 | |
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 | |||
4 | daniel-mar | 106 | 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 | |||
3 | daniel-mar | 114 | def do_POST(self): |
115 | # https://stackoverflow.com/questions/4233218/python-how-do-i-get-key-value-pairs-from-the-basehttprequesthandler-http-post-h |
||
4 | daniel-mar | 116 | # Question: Do we need the cgi package, or can we use functions available in this class (e.g. self_parse_qs?) |
3 | daniel-mar | 117 | 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 | |||
4 | daniel-mar | 162 | 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] |
||
3 | daniel-mar | 167 | 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: |
||
4 | daniel-mar | 174 | # 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() |
||
3 | daniel-mar | 177 | |
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): |
||
4 | daniel-mar | 198 | print 'Starting server, listening to port {0}...'.format(port) |
3 | daniel-mar | 199 | 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 |