Subversion Repositories alarming

Rev

Rev 8 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  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.                         if g_bellListenerProc != None:
  202.                                 sp.Popen.terminate(g_bellListenerProc)
  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):
  213.         print 'Starting server, listening to port {0}...'.format(port)
  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.  
  235.