package main
import (
- "time"
- //~ "./brain"
- pubsub "github.com/tuxychandru/pubsub"
- r3events "svn.spreadspace.org/realraum/go.svn/r3events"
- )
+ "time"
+ //~ "./brain"
+ pubsub "github.com/tuxychandru/pubsub"
+ r3events "svn.spreadspace.org/realraum/go.svn/r3events"
+)
func MetaEventRoutine_Presence(ps *pubsub.PubSub, movement_timeout, button_timeout int64) {
- var last_door_cmd *r3events.DoorCommandEvent
- var last_presence bool
- var last_event_indicating_presence, last_frontlock_use, last_manual_lockhandling int64
- var front_locked, front_shut, back_shut bool = true, true, true
+ var last_door_cmd *r3events.DoorCommandEvent
+ var last_presence bool
+ var last_event_indicating_presence, last_frontlock_use, last_manual_lockhandling int64
+ var front_locked, front_shut, back_shut bool = true, true, true
- events_chan := ps.Sub("door", "doorcmd", "buttons", "movement")
- defer ps.Unsub(events_chan, "door", "doorcmd", "buttons", "movement")
+ events_chan := ps.Sub("door", "doorcmd", "buttons", "movement")
+ defer ps.Unsub(events_chan, "door", "doorcmd", "buttons", "movement")
- for event := range(events_chan) {
- Debug_.Printf("Presence prior: %t : %T %+v", last_presence, event, event)
- new_presence := last_presence
- ts := time.Now().Unix()
- switch evnt := event.(type) {
- case r3events.SomethingReallyIsMoving:
- if evnt.Movement {
- //ignore movements that happened just after locking door
- if (evnt.Ts - last_event_indicating_presence) > movement_timeout {
- new_presence = true
- }
- last_event_indicating_presence = evnt.Ts
- } else {
- if last_presence { Syslog_.Printf("Presence: Mhh, SomethingReallyIsMoving{%+v} received but presence still true. Quite still a bunch we have here.", evnt) }
- if front_locked && front_shut && back_shut && evnt.Confidence >= 90 && last_event_indicating_presence > 1800 && (last_door_cmd == nil || (last_door_cmd.Using != "Button" && last_door_cmd.Ts >= last_manual_lockhandling )){
- new_presence = false
- }
- }
- case r3events.BoreDoomButtonPressEvent:
- new_presence = true
- last_event_indicating_presence = evnt.Ts
- case r3events.DoorCommandEvent:
- last_door_cmd = &evnt
- case r3events.DoorManualMovementEvent:
- last_manual_lockhandling = evnt.Ts
- last_event_indicating_presence = evnt.Ts
- case r3events.DoorLockUpdate:
- front_locked = evnt.Locked
- last_frontlock_use = evnt.Ts
- last_event_indicating_presence = evnt.Ts
- case r3events.DoorAjarUpdate:
- if front_shut == false && evnt.Shut && front_locked && evnt.Ts - last_frontlock_use > 2 {
- Syslog_.Print("Presence: ignoring frontdoor ajar event, since obviously someone is fooling around with the microswitch while the door is still open")
- } else {
- front_shut = evnt.Shut
- }
- last_event_indicating_presence = evnt.Ts
- case r3events.BackdoorAjarUpdate:
- back_shut = evnt.Shut
- last_event_indicating_presence = evnt.Ts
- }
+ for event := range events_chan {
+ Debug_.Printf("Presence prior: %t : %T %+v", last_presence, event, event)
+ new_presence := last_presence
+ ts := time.Now().Unix()
+ evnt_type := r3events.NameOfStruct(event)
+ switch evnt := event.(type) {
+ case r3events.SomethingReallyIsMoving:
+ if evnt.Movement {
+ //ignore movements that happened just after locking door
+ if (evnt.Ts - last_event_indicating_presence) > movement_timeout {
+ new_presence = true
+ }
+ last_event_indicating_presence = evnt.Ts
+ } else {
+ if last_presence {
+ Syslog_.Printf("Presence: Mhh, SomethingReallyIsMoving{%+v} received but presence still true. Quite still a bunch we have here.", evnt)
+ }
+ if front_locked && front_shut && back_shut && evnt.Confidence >= 90 && last_event_indicating_presence > 1800 && (last_door_cmd == nil || (last_door_cmd.Using != "Button" && last_door_cmd.Ts >= last_manual_lockhandling)) {
+ new_presence = false
+ }
+ }
+ case r3events.BoreDoomButtonPressEvent:
+ new_presence = true
+ last_event_indicating_presence = evnt.Ts
+ case r3events.DoorCommandEvent:
+ last_door_cmd = &evnt
+ case r3events.DoorManualMovementEvent:
+ last_manual_lockhandling = evnt.Ts
+ last_event_indicating_presence = evnt.Ts
+ case r3events.DoorLockUpdate:
+ front_locked = evnt.Locked
+ last_frontlock_use = evnt.Ts
+ last_event_indicating_presence = evnt.Ts
+ case r3events.DoorAjarUpdate:
+ if front_shut == false && evnt.Shut && front_locked && evnt.Ts-last_frontlock_use > 2 {
+ Syslog_.Print("Presence: ignoring frontdoor ajar event, since obviously someone is fooling around with the microswitch while the door is still open")
+ } else {
+ front_shut = evnt.Shut
+ }
+ last_event_indicating_presence = evnt.Ts
+ case r3events.BackdoorAjarUpdate:
+ back_shut = evnt.Shut
+ last_event_indicating_presence = evnt.Ts
+ }
- any_door_unlocked := (front_locked == false)
- any_door_ajar := ! (front_shut && back_shut)
+ any_door_unlocked := (front_locked == false)
+ any_door_ajar := !(front_shut && back_shut)
- if new_presence != last_presence {
- //... skip state check .. we had a definite presence event
- } else if any_door_unlocked || any_door_ajar {
- new_presence = true
- } else if last_door_cmd != nil && (last_door_cmd.Using == "Button" || last_door_cmd.Ts < last_manual_lockhandling) {
- // if last_door_cmd is set then: if either door was closed using Button or if time of manual lock movement is greater (newer) than timestamp of last_door_cmd
- new_presence = true
- } else {
- new_presence = false
- }
- //~ Debug_.Printf("Presence: new: %s , last:%s", new_presence, last_presence)
- if new_presence != last_presence {
- last_presence = new_presence
- ps.Pub(r3events.PresenceUpdate{new_presence, ts} , "presence")
- Syslog_.Printf("Presence: %t", new_presence)
- }
- }
-}
\ No newline at end of file
+ if new_presence != last_presence {
+ //... skip state check .. we had a definite presence event
+ } else if any_door_unlocked || any_door_ajar {
+ new_presence = true
+ } else if last_door_cmd != nil && (last_door_cmd.Using == "Button" || last_door_cmd.Ts < last_manual_lockhandling) {
+ // if last_door_cmd is set then: if either door was closed using Button or if time of manual lock movement is greater (newer) than timestamp of last_door_cmd
+ new_presence = true
+ } else if evnt_type == "DoorCommandEvent" {
+ //don't set presence to false for just a door command, wait until we receive a LockUpdate
+ //(fixes "door manually locked -> door opened with card from outside -> nobody present status sent since door is locked and we havent received door unloked event yet"-problem)
+ continue
+ } else {
+ new_presence = false
+ }
+ //~ Debug_.Printf("Presence: new: %s , last:%s", new_presence, last_presence)
+ if new_presence != last_presence {
+ last_presence = new_presence
+ ps.Pub(r3events.PresenceUpdate{new_presence, ts}, "presence")
+ Syslog_.Printf("Presence: %t", new_presence)
+ }
+ }
+}
--- /dev/null
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+import os
+import os.path
+import sys
+#import threading
+import logging
+import logging.handlers
+import time
+import signal
+import subprocess
+import types
+import ConfigParser
+import traceback
+import random
+import zmq
+import zmq.utils.jsonapi as json
+import urllib
+import ephem
+
+logger = logging.getLogger()
+logger.setLevel(logging.INFO)
+lh_syslog = logging.handlers.SysLogHandler(address="/dev/log",facility=logging.handlers.SysLogHandler.LOG_LOCAL2)
+lh_syslog.setFormatter(logging.Formatter('play-sound-status.py: %(levelname)s %(message)s'))
+logger.addHandler(lh_syslog)
+lh_stderr = logging.StreamHandler()
+logger.addHandler(lh_stderr)
+
+def isTheSunDown():
+ ephemobs=ephem.Observer()
+ ephemobs.lat='47.06'
+ ephemobs.lon='15.45'
+ ephemsun=ephem.Sun()
+ ephemsun.compute()
+ return ephemobs.date > ephemobs.previous_setting(ephemsun) and ephemobs.date < ephemobs.next_rising(ephemsun)
+
+
+class UWSConfig:
+ def __init__(self,configfile=None):
+ self.configfile=configfile
+ self.config_parser=ConfigParser.ConfigParser()
+ #make option variable names case sensitive
+ self.config_parser.optionxform = str
+ self.config_parser.add_section('debug')
+ self.config_parser.set('debug','enabled',"False")
+ self.config_parser.add_section('broker')
+ self.config_parser.set('broker','uri',"tcp://torwaechter.realraum.at:4244")
+ self.config_parser.add_section('tracker')
+ self.config_parser.set('tracker','secs_movement_before_presence_to_launch_event','1')
+ self.config_parser.set('tracker','secs_presence_before_movement_to_launch_event','120')
+ self.config_mtime=0
+ if not self.configfile is None:
+ try:
+ cf_handle = open(self.configfile,"r")
+ cf_handle.close()
+ except IOError:
+ self.writeConfigFile()
+ else:
+ self.checkConfigUpdates()
+
+ def checkConfigUpdates(self):
+ global logger
+ if self.configfile is None:
+ return
+ logging.debug("Checking Configfile mtime: "+self.configfile)
+ try:
+ mtime = os.path.getmtime(self.configfile)
+ except (IOError,OSError):
+ return
+ if self.config_mtime < mtime:
+ logging.debug("Reading Configfile")
+ try:
+ self.config_parser.read(self.configfile)
+ self.config_mtime=os.path.getmtime(self.configfile)
+ except (ConfigParser.ParsingError, IOError), pe_ex:
+ logging.error("Error parsing Configfile: "+str(pe_ex))
+ if self.config_parser.get('debug','enabled') == "True":
+ logger.setLevel(logging.DEBUG)
+ else:
+ logger.setLevel(logging.INFO)
+
+ def writeConfigFile(self):
+ if self.configfile is None:
+ return
+ logging.debug("Writing Configfile "+self.configfile)
+ try:
+ cf_handle = open(self.configfile,"w")
+ self.config_parser.write(cf_handle)
+ cf_handle.close()
+ self.config_mtime=os.path.getmtime(self.configfile)
+ except IOError, io_ex:
+ logging.error("Error writing Configfile: "+str(io_ex))
+ self.configfile=None
+
+ def getValue(self, name):
+ underscore_pos=name.find('_')
+ if underscore_pos < 0:
+ raise AttributeError
+ return self.getSectionValue(name[0:underscore_pos], name[underscore_pos+1:])
+
+ def getSectionValue(self, section, name):
+ try:
+ return self.config_parser.get(section,name)
+ except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
+ return None
+
+ def __getattr__(self, name):
+ underscore_pos=name.find('_')
+ if underscore_pos < 0:
+ raise AttributeError
+ try:
+ return self.config_parser.get(name[0:underscore_pos], name[underscore_pos+1:])
+ except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
+ raise AttributeError
+
+
+def runRandomAction(action_list,user,args=[]):
+ if not type(action_list) == types.ListType:
+ raise ValueError("runRandomAction: action_list must be a list")
+ return executeAction(random.choice(action_list),user,args)
+
+def runRemoteCommand(remote_host,remote_shell,user,args=[]):
+ global sshp,uwscfg
+ sshp = None
+ try:
+ cmd = "ssh -i /flash/tuer/id_rsa -o PasswordAuthentication=no -o StrictHostKeyChecking=no %RHOST% %RSHELL%"
+ cmd = cmd.replace("%RHOST%",remote_host).replace("%RSHELL%",remote_shell).replace("%ARG%", " ".join(args)).replace("%USER%", user)
+ logging.debug("runRemoteCommand: Executing: "+cmd)
+ sshp = subprocess.Popen(cmd.split(" "), bufsize=1024, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False)
+ logging.debug("runRemoteCommand: pid %d: running=%d" % (sshp.pid,sshp.poll() is None))
+ if not sshp.poll() is None:
+ logging.error("runRemoteCommand: subprocess %d not started ?, returncode: %d" % (sshp.pid,sshp.returncode))
+ return False
+ return True
+ except Exception, ex:
+ logging.error("runRemoteCommand: "+str(ex))
+ traceback.print_exc(file=sys.stdout)
+ if not sshp is None and sshp.poll() is None:
+ if sys.hexversion >= 0x020600F0:
+ sshp.terminate()
+ else:
+ subprocess.call(["kill",str(sshp.pid)])
+ time.sleep(1.5)
+ if sshp.poll() is None:
+ logging.error("runRemoteCommand: subprocess still alive, sending SIGKILL to pid %d" % (sshp.pid))
+ if sys.hexversion >= 0x020600F0:
+ sshp.kill()
+ else:
+ subprocess.call(["kill","-9",str(sshp.pid)])
+ time.sleep(5)
+ return False
+
+def runShellCommand(cmd,ptimeout,stdinput,user,args=[]):
+ global uwscfg
+ cmd = cmd.replace("%ARG%"," ".join(args)).replace("%USER%", user)
+ if ptimeout is None or float(ptimeout) > 45:
+ ptimeout = 45
+ else:
+ ptimeout = int(float(ptimeout))
+ popenTimeout2(cmd,stdinput,ptimeout=ptimeout)
+
+def executeAction(action_name, user, args=[]):
+ if action_name is None:
+ logging.error("executeAction: action_name is None")
+ return False
+ action_type = uwscfg.getValue(action_name+"_type")
+ if action_type is None:
+ logging.error("executeAction: action %s not found or has no type" % action_name)
+ return False
+ action_delay=uwscfg.getValue(action_name+"_delay")
+ logging.info("executeAction %s of type %s for user %s with delay %s" % (action_name,action_type,user,action_delay))
+ if not action_delay is None:
+ time.sleep(float(action_delay))
+
+ action_arg = uwscfg.getValue(action_name+"_arg")
+ if not action_arg is None:
+ args += [action_arg]
+
+ #"registered" actions
+ if action_type == "remotecmd":
+ return runRemoteCommand(uwscfg.getSectionValue(action_name,"remote_host"), uwscfg.getSectionValue(action_name,"remote_shell"), user=user, args=args)
+ elif action_type == "shellcmd":
+ return runShellCommand(cmd=uwscfg.getSectionValue(action_name,"cmd"), ptimeout=uwscfg.getSectionValue(action_name,"timeout"), stdinput=uwscfg.getSectionValue(action_name,"stdinput"), user=user, args=args)
+ elif action_type == "nothing":
+ return True
+ elif action_type == "random":
+ return runRandomAction(action_list=uwscfg.getSectionValue(action_name,"one_of").split(" "),user=user,args=args)
+ else:
+ return executeAction(action_type,user=user,args=args)
+
+def playThemeOf(user,fallback_default):
+ global uwscfg
+ uwscfg.checkConfigUpdates()
+ if user is None:
+ user = ""
+ config=uwscfg.getValue("mapping_"+str(user))
+ if config is None:
+ config=uwscfg.getValue("mapping_"+str(fallback_default))
+ logging.debug("playThemeOf: action for user %s: %s" % (user,config))
+ executeAction(config,user,[])
+
+def popenTimeout1(cmd, pinput, returncode_ok=[0], ptimeout = 20.0, pcheckint = 0.25):
+ logging.debug("popenTimeout1: starting: " + cmd)
+ try:
+ sppoo = subprocess.Popen(cmd, stdin=subprocess.PIPE, shell=True)
+ sppoo.communicate(input=pinput)
+ timeout_counter=ptimeout
+ while timeout_counter > 0:
+ time.sleep(pcheckint)
+ timeout_counter -= pcheckint
+ if not sppoo.poll() is None:
+ logging.debug("popenTimeout1: subprocess %d finished, returncode: %d" % (sppoo.pid,sppoo.returncode))
+ return (sppoo.returncode in returncode_ok)
+ #timeout reached
+ logging.error("popenTimeout1: subprocess took too long (>%fs), sending SIGTERM to pid %d" % (ptimeout,sppoo.pid))
+ if sys.hexversion >= 0x020600F0:
+ sppoo.terminate()
+ else:
+ subprocess.call(["kill",str(sppoo.pid)])
+ time.sleep(1.0)
+ if sppoo.poll() is None:
+ logging.error("popenTimeout1: subprocess still alive, sending SIGKILL to pid %d" % (sppoo.pid))
+ if sys.hexversion >= 0x020600F0:
+ sppoo.kill()
+ else:
+ subprocess.call(["kill","-9",str(sppoo.pid)])
+ return False
+ except Exception, e:
+ logging.error("popenTimeout1: "+str(e))
+ return False
+
+def popenTimeout2(cmd, pinput, returncode_ok=[0], ptimeout=21):
+ logging.debug("popenTimeout2: starting: " + cmd)
+ try:
+ sppoo = subprocess.Popen(cmd, stdin=subprocess.PIPE, shell=True)
+ if sys.hexversion >= 0x020600F0:
+ old_shandler = signal.signal(signal.SIGALRM,lambda sn,sf: sppoo.kill())
+ else:
+ old_shandler = signal.signal(signal.SIGALRM,lambda sn,sf: os.system("kill -9 %d" % sppoo.pid))
+ signal.alarm(ptimeout) #schedule alarm
+ if not pinput is None:
+ sppoo.communicate(input=pinput)
+ sppoo.wait()
+ signal.alarm(0) #disable pending alarms
+ signal.signal(signal.SIGALRM, old_shandler)
+ logging.debug("popenTimeout2: subprocess %d finished, returncode: %d" % (sppoo.pid,sppoo.returncode))
+ if sppoo.returncode < 0:
+ logging.error("popenTimeout2: subprocess took too long (>%ds) and pid %d was killed" % (ptimeout,sppoo.pid))
+ return (sppoo.returncode in returncode_ok)
+ except Exception, e:
+ logging.error("popenTimeout2: "+str(e))
+ try:
+ signal.signal(signal.SIGALRM, old_shandler)
+ except:
+ pass
+ return False
+
+def decodeR3Message(multipart_msg):
+ try:
+ return (multipart_msg[0], json.loads(multipart_msg[1]))
+ except Exception, e:
+ logging.debug("decodeR3Message:"+str(e))
+ return ("",{})
+
+
+def touchURL(url):
+ try:
+ f = urllib.urlopen(url)
+ rq_response = f.read()
+ logging.debug("touchURL: url: "+url)
+ #logging.debug("touchURL: Response "+rq_response)
+ f.close()
+ return rq_response
+ except Exception, e:
+ logging.error("touchURL: "+str(e))
+
+
+def exitHandler(signum, frame):
+ logging.info("stopping")
+ try:
+ zmqsub.close()
+ zmqctx.destroy()
+ except:
+ pass
+ sys.exit(0)
+
+#signals proapbly don't work because of readline
+#signal.signal(signal.SIGTERM, exitHandler)
+signal.signal(signal.SIGINT, exitHandler)
+signal.signal(signal.SIGQUIT, exitHandler)
+
+logging.info("Door Status Listener 'PlaySound' started")
+
+if len(sys.argv) > 1:
+ uwscfg = UWSConfig(sys.argv[1])
+else:
+ uwscfg = UWSConfig()
+
+while True:
+ try:
+ #Start zmq connection to publish / forward sensor data
+ zmqctx = zmq.Context()
+ zmqctx.linger = 0
+ zmqsub = zmqctx.socket(zmq.SUB)
+ zmqsub.setsockopt(zmq.SUBSCRIBE, "DoorCommandEvent")
+ zmqsub.setsockopt(zmq.SUBSCRIBE, "PresenceUpdate")
+ zmqsub.setsockopt(zmq.SUBSCRIBE, "BoreDoomButtonPressEvent")
+# zmqsub.setsockopt(zmq.SUBSCRIBE, "MovementSensorUpdate")
+ zmqsub.setsockopt(zmq.SUBSCRIBE, "DoorAjarUpdate")
+ zmqsub.setsockopt(zmq.SUBSCRIBE, "DoorProblemEvent")
+ zmqsub.connect(uwscfg.broker_uri)
+
+ last_status=None
+ last_user=None
+ unixts_panic_button=None
+ unixts_last_movement=0
+ unixts_last_presence=0
+ while True:
+ data = zmqsub.recv_multipart()
+ (structname, dictdata) = decodeR3Message(data)
+ logging.debug("Got data: " + structname + ":"+ str(dictdata))
+
+ #uwscfg.checkConfigUpdates()
+
+ if structname == "PresenceUpdate" and "Present" in dictdata:
+ if dictdata["Present"] and last_status != dictdata["Present"]:
+ #someone just arrived
+ if isTheSunDown():
+ touchURL("http://slug.realraum.at/cgi-bin/switch.cgi?id=mashadecke&power=on")
+ touchURL("http://localhost/cgi-bin/mswitch.cgi?ceiling3=1&ceiling4=1")
+ touchURL("http://slug.realraum.at/cgi-bin/switch.cgi?id=couchred&power=on")
+ touchURL("http://slug.realraum.at/cgi-bin/switch.cgi?id=bluebar&power=on")
+ last_status=dictdata["Present"]
+ if not last_status:
+ #everybody left
+ touchURL("http://localhost/cgi-bin/mswitch.cgi?ceiling1=0&ceiling2=0&ceiling3=0&ceiling4=0&ceiling5=0&ceiling6=0")
+ touchURL("http://slug.realraum.at/cgi-bin/switch.cgi?id=all&power=off")
+ continue
+
+ except Exception, ex:
+ logging.error("main: "+str(ex))
+ traceback.print_exc(file=sys.stdout)
+ try:
+ zmqsub.close()
+ zmqctx.destroy()
+ except:
+ pass
+ time.sleep(5)