Login | ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/alarming/trunk/Server/daemon.py
Revision: 6
Committed: Mon Jun 10 17:40:51 2019 UTC (15 months, 2 weeks ago) by daniel-marschall
Content type: text/x-python
File size: 7542 byte(s)
Log Message:
NEW: Doorbell listener

File Contents

# User Rev Content
1 daniel-marschall 3 #!/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 daniel-marschall 4 import threading
13 daniel-marschall 6 import subprocess as sp
14 daniel-marschall 3
15     g_subscribed = []
16 daniel-marschall 6 g_bellListenerProc = None
17 daniel-marschall 3
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 daniel-marschall 4 output = '''<!DOCTYPE html>
32     <html lang="en">
33 daniel-marschall 3
34     <head>
35 daniel-marschall 4 <meta charset="UTF-8">
36 daniel-marschall 3 <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 daniel-marschall 4 document.getElementById("pleasewait").innerHTML = ' <i>Please wait...</i>';
49 daniel-marschall 3 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 daniel-marschall 4 xhr.open('POST', document.location, true);
67 daniel-marschall 3 xhr.send(data);
68     }
69    
70     function onload() {
71 daniel-marschall 4 if (document.getElementById('campic') != null) {
72     document.getElementById('campic').src = 'http://' + window.location.hostname + ':'''+str(config.motion_stream_port)+'''/';
73     }
74 daniel-marschall 3 }
75    
76     </script>'''
77    
78     if ismotionrunning():
79 daniel-marschall 4 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 daniel-marschall 3 else:
84 daniel-marschall 4 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 daniel-marschall 3
87     output = output + '<h2>Subscribers</h2>'
88    
89     found_subs = 0
90 daniel-marschall 5 for subscriber in g_subscribed[:]:
91     if int(time.time()) > subscriber[2]:
92     g_subscribed.remove(subscriber)
93 daniel-marschall 3 else:
94     found_subs = found_subs + 1
95 daniel-marschall 5 output = output + "<p>{0}:{1}</p>".format(subscriber[0], subscriber[1])
96 daniel-marschall 3
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 daniel-marschall 4 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 daniel-marschall 3 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 daniel-marschall 4 # Question: Do we need the cgi package, or can we use functions available in this class (e.g. self_parse_qs?)
119 daniel-marschall 3 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 daniel-marschall 6 global g_bellListenerProc
132 daniel-marschall 3
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 daniel-marschall 5 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 daniel-marschall 3
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 daniel-marschall 5 for subscriber in g_subscribed[:]:
166 daniel-marschall 4 client_ip = subscriber[0]
167     client_port = subscriber[1]
168     client_expires = subscriber[2]
169     client_targets = subscriber[3]
170 daniel-marschall 3
171 daniel-marschall 5 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 daniel-marschall 3 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     print "Motion start"
190 daniel-marschall 6 if config.enable_motion_detect:
191     os.system(os.path.dirname(os.path.abspath(__file__)) + "/motion/motion_start_safe")
192     if config.enable_doorbell_listener:
193     g_bellListenerProc = sp.Popen(['python3',os.path.dirname(os.path.abspath(__file__)) + "/doorbell/bell_listener.py"])
194 daniel-marschall 3 if pvget(postvars, "action")[0] == "motion_off": # 1.3.6.1.4.1.37476.2.4.1.101
195     print "Motion stop"
196 daniel-marschall 6 if config.enable_motion_detect:
197     os.system(os.path.dirname(os.path.abspath(__file__)) + "/motion/motion_stop_safe")
198     if config.enable_doorbell_listener:
199     sp.Popen.terminate(g_bellListenerProc)
200 daniel-marschall 3
201     self._output(200, '')
202    
203     def pvget(ary, key):
204     if ary.get(key) == None:
205     return [""]
206     else:
207     return ary.get(key)
208    
209     def run(server_class=HTTPServer, handler_class=S, port=8085):
210 daniel-marschall 4 print 'Starting server, listening to port {0}...'.format(port)
211 daniel-marschall 3 server_address = ('', port)
212     httpd = server_class(server_address, handler_class)
213     httpd.serve_forever()
214    
215     def ismotionrunning():
216     p = subprocess.Popen(["ps", "aux"], stdout=subprocess.PIPE)
217     out, err = p.communicate()
218     try:
219     return ('/usr/bin/motion' in str(out))
220     except:
221     res = os.system("pidof -x /usr/bin/motion > /dev/null")
222     return (res >> 8) == 0
223    
224     if __name__ == "__main__":
225     from sys import argv
226    
227     if len(argv) == 2:
228     run(port=int(argv[1]))
229     else:
230     run()
231    

Properties

Name Value
svn:executable *