Login | ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/alarming/trunk/Server/daemon.py
Revision: 4
Committed: Tue May 7 09:36:39 2019 UTC (16 months, 3 weeks ago) by daniel-marschall
Content type: text/x-python
File size: 6922 byte(s)
Log Message:
Notifications now sent via threads

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

Properties

Name Value
svn:executable *