Login | ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/alarming/trunk/Server/daemon.py
Revision: 8
Committed: Sun Jul 7 22:02:12 2019 UTC (13 months ago) by daniel-marschall
Content type: text/x-python
File size: 7773 byte(s)

File Contents

# Content
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

Properties

Name Value
svn:executable *