Subversion Repositories alarming

Rev

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