class Worker(): """ This class runs on the background using GPIO Events Changes. It uses a json file to store the settings and a log file to store the logs. When a sensor changes state after the alarm is activated it can enable the Serene, send Mail, call through VoIP. Also there is the updateUI method wich it has to be overridden in the main application. """ def __init__(self, jsonfile, logfile, sipcallfile, optsUpdateUI=None): """ Init for the Worker class """ print("{0}------------ INIT FOR DOOR SENSOR CLASS! ----------------{1}" .format(bcolors.HEADER, bcolors.ENDC)) # Global Variables self.jsonfile = jsonfile self.logfile = logfile self.sipcallfile = sipcallfile self.settings = self.ReadSettings() self.limit = 10 self.logtypes = 'all' # Stop execution on exit self.kill_now = False # Init Alarm self.mynotify = Notify(self.settings) self.mynotify.setupUpdateUI(optsUpdateUI) self.mynotify.setupSendStateMQTT() mylogs = Logs(self.logfile) self.getSensorsLog = mylogs.getSensorsLog self.writeLog("system", "Alarm Booted") mylogs.startTrimThread() # Event Listeners self.sensors = Sensor() self.sensors.on_alert(self.sensorAlert) self.sensors.on_alert_stop(self.sensorStopAlert) self.sensors.on_error(self.sensorError) self.sensors.on_error_stop(self.sensorStopError) self.sensors.add_sensors(self.settings) # Init MQTT Messages self.mynotify.on_disarm_mqtt(self.deactivateAlarm) self.mynotify.on_arm_mqtt(self.activateAlarm) self.mynotify.on_sensor_set_alert(self.sensorAlert) self.mynotify.on_sensor_set_stopalert(self.sensorStopAlert) self.mynotify.sendStateMQTT() def sensorAlert(self, sensorUUID): """ On Sensor Alert, write logs and check for intruder """ name = self.settings['sensors'][sensorUUID]['name'] print("{0}-> Alert Sensor: {2}{1}".format( bcolors.OKGREEN, bcolors.ENDC, name)) self.settings['sensors'][sensorUUID]['alert'] = True self.settings['sensors'][sensorUUID]['online'] = True self.writeNewSettingsToFile(self.settings) stateTopic = self.settings['mqtt']['state_topic'] + '/sensor/' + name self.mynotify.sendSensorMQTT(stateTopic, 'on') self.mynotify.updateUI('settingsChanged', self.getSensorsArmed()) self.writeLog("sensor,start," + sensorUUID, name) self.checkIntruderAlert() def sensorStopAlert(self, sensorUUID): """ On Sensor Alert Stop, write logs """ name = self.settings['sensors'][sensorUUID]['name'] print("{0}<- Stop Alert Sensor: {2}{1}".format( bcolors.OKGREEN, bcolors.ENDC, name)) self.settings['sensors'][sensorUUID]['alert'] = False self.settings['sensors'][sensorUUID]['online'] = True self.writeNewSettingsToFile(self.settings) stateTopic = self.settings['mqtt']['state_topic'] + '/sensor/' + name self.mynotify.sendSensorMQTT(stateTopic, 'off') self.mynotify.updateUI('settingsChanged', self.getSensorsArmed()) self.writeLog("sensor,stop," + sensorUUID, name) def sensorError(self, sensorUUID): """ On Sensor Error, write logs """ name = self.settings['sensors'][sensorUUID]['name'] print("{0}!- Error Sensor: {2}{1}".format( bcolors.FAIL, bcolors.ENDC, name)) # print("Error Sensor", sensorUUID) name = self.settings['sensors'][sensorUUID]['name'] self.settings['sensors'][sensorUUID]['alert'] = True self.settings['sensors'][sensorUUID]['online'] = False self.writeNewSettingsToFile(self.settings) self.writeLog("error", "Lost connection to: " + name) self.mynotify.updateUI('settingsChanged', self.getSensorsArmed()) def sensorStopError(self, sensorUUID): """ On Sensor Stop Error, write logs """ name = self.settings['sensors'][sensorUUID]['name'] print("{0}-- Error Stop Sensor: {2}{1}".format( bcolors.FAIL, bcolors.ENDC, name)) name = self.settings['sensors'][sensorUUID]['name'] self.settings['sensors'][sensorUUID]['online'] = True self.writeNewSettingsToFile(self.settings) self.writeLog("error", "Restored connection to: " + name) self.mynotify.updateUI('settingsChanged', self.getSensorsArmed()) def checkIntruderAlert(self): """ Checks if the alarm is armed and if it finds an active sensor then it calls the intruderAlert method """ if (self.settings['settings']['alarmArmed'] is True): for sensor, sensorvalue in self.settings['sensors'].items(): if (sensorvalue['alert'] is True and sensorvalue['enabled'] is True and self.settings['settings']['alarmTriggered'] is False): self.settings['settings']['alarmTriggered'] = True threadIntruderAlert = threading.Thread( target=self.intruderAlert) threadIntruderAlert.daemon = True threadIntruderAlert.start() def ReadSettings(self): """ Reads the json settings file and returns it """ with open(self.jsonfile) as data_file: settings = json.load(data_file) return settings def writeNewSettingsToFile(self, settings): """ Write the new settings to the json file """ self.mynotify.updateSettings(settings) with open(self.jsonfile, 'w') as outfile: json.dump(settings, outfile, sort_keys=True, indent=4, separators=(',', ': ')) def writeLog(self, logType, message): """ Write log events into a file and send the last to UI. It also uses the timezone from json file to get the local time. """ try: mytimezone = pytz.timezone(self.settings['settings']['timezone']) except Exception: mytimezone = pytz.utc myTimeLog = datetime.now(tz=mytimezone).strftime("%Y-%m-%d %H:%M:%S") logmsg = '({0}) [{1}] {2}\n'.format(logType, myTimeLog, message) with open(self.logfile, "a") as myfile: myfile.write(logmsg) self.mynotify.updateUI('sensorsLog', self.getSensorsLog( self.limit, selectTypes=self.logtypes)) def intruderAlert(self): """ This method is called when an intruder is detected. It calls all the methods whith the actions that we want to do. Sends MQTT message, enables serene, Send mail, Call Voip. """ self.writeLog("alarm", "Intruder Alert") self.enableSerene() self.mynotify.sendStateMQTT() self.mynotify.updateUI('alarmStatus', self.getTriggeredStatus()) threadSendMail = threading.Thread(target=self.sendMail) threadSendMail.daemon = True threadSendMail.start() threadCallVoip = threading.Thread(target=self.callVoip) threadCallVoip.daemon = True threadCallVoip.start() def callVoip(self): """ This method uses a prebuild application in C to connect to the SIP provider and call all the numbers in the json settings file. """ sip_domain = str(self.settings['voip']['domain']) sip_user = str(self.settings['voip']['username']) sip_password = str(self.settings['voip']['password']) sip_repeat = str(self.settings['voip']['timesOfRepeat']) if self.settings['voip']['enable'] is True: for phone_number in self.settings['voip']['numbersToCall']: phone_number = str(phone_number) if self.settings['settings']['alarmTriggered'] is True: self.writeLog("alarm", "Calling " + phone_number) cmd = (self.sipcallfile, '-sd', sip_domain, '-su', sip_user, '-sp', sip_password, '-pn', phone_number, '-s', '1', '-mr', sip_repeat) print("{0}Voip command: {2}{1}".format( bcolors.FADE, bcolors.ENDC, " ".join(cmd))) proc = subprocess.Popen(cmd, stderr=subprocess.PIPE) for line in proc.stderr: sys.stderr.write(line) proc.wait() self.writeLog("alarm", "Call to " + phone_number + " endend") print("{0}Call Ended{1}".format( bcolors.FADE, bcolors.ENDC)) def sendMail(self): """ This method sends an email to all recipients in the json settings file. """ if self.settings['mail']['enable'] is True: mail_user = self.settings['mail']['username'] mail_pwd = self.settings['mail']['password'] smtp_server = self.settings['mail']['smtpServer'] smtp_port = int(self.settings['mail']['smtpPort']) bodyMsg = self.settings['mail']['messageBody'] LogsTriggered = self.getSensorsLog( fromText='Alarm activated')['log'] LogsTriggered.reverse() for logTriggered in LogsTriggered: bodyMsg += '<br>' + logTriggered msg = MIMEText(bodyMsg, 'html') sender = mail_user recipients = self.settings['mail']['recipients'] msg['Subject'] = self.settings['mail']['messageSubject'] msg['From'] = sender msg['To'] = ", ".join(recipients) smtpserver = smtplib.SMTP(smtp_server, smtp_port) smtpserver.ehlo() smtpserver.starttls() smtpserver.login(mail_user, mail_pwd) smtpserver.sendmail(sender, recipients, msg.as_string()) smtpserver.close() self.writeLog("alarm", "Mail sent to: " + ", ".join(recipients)) def enableSerene(self): """ This method enables the output pin for the serene """ if self.settings['serene']['enable'] is True: self.writeLog("alarm", "Serene started") serenePin = int(self.settings['serene']['pin']) outputGPIO().enableOutputPin(serenePin) def stopSerene(self): """ This method disables the output pin for the serene """ if self.settings['serene']['enable'] is True: serenePin = self.settings['serene']['pin'] outputGPIO().disableOutputPin(serenePin) def activateAlarm(self, zones=None): """ Activates the alarm """ if zones is not None: if type(zones) == str: zones = [zones] self.setSensorsZone(zones) self.writeLog("user_action", "Alarm activated") self.settings['settings']['alarmArmed'] = True self.mynotify.sendStateMQTT() self.mynotify.updateUI('settingsChanged', self.getSensorsArmed()) self.writeNewSettingsToFile(self.settings) def deactivateAlarm(self): """ Deactivates the alarm """ self.writeLog("user_action", "Alarm deactivated") self.settings['settings']['alarmTriggered'] = False self.settings['settings']['alarmArmed'] = False self.stopSerene() self.mynotify.sendStateMQTT() self.mynotify.updateUI('settingsChanged', self.getSensorsArmed()) self.writeNewSettingsToFile(self.settings) def getSensorsArmed(self): """ Returns the sensors and alarm status as a json to use it to the UI """ sensorsArmed = {} sensors = self.settings['sensors'] orderedSensors = OrderedDict( sorted(sensors.items(), key=lambda k_v: k_v[1]['name'])) sensorsArmed['sensors'] = orderedSensors sensorsArmed['triggered'] = self.settings['settings']['alarmTriggered'] sensorsArmed['alarmArmed'] = self.settings['settings']['alarmArmed'] return sensorsArmed def getTriggeredStatus(self): """ Returns the status of the alert for the UI """ return {"alert": self.settings['settings']['alarmTriggered']} def setLogFilters(self, limit, logtypes): """ Sets the global filters for the getSensorsLog method """ self.limit = limit self.logtypes = logtypes def getSereneSettings(self): """ Gets the Serene Settings """ return self.settings['serene'] def getMailSettings(self): return self.settings['mail'] def getVoipSettings(self): """ Gets the Voip Settings """ return self.settings['voip'] def getTimezoneSettings(self): """ Get the Timezone Settings """ return self.settings['settings']['timezone'] def getMQTTSettings(self): """ Gets the MQTT Settings """ return self.settings['mqtt'] def setSereneSettings(self, message): """ set Serene Settings """ if self.settings['serene'] != message: self.settings['serene'] = message self.writeLog("user_action", "Settings for Serene changed") self.writeNewSettingsToFile(self.settings) def setMailSettings(self, message): """ Set Mail Settings """ if self.settings['mail'] != message: self.settings['mail'] = message self.writeLog("user_action", "Settings for Mail changed") self.writeNewSettingsToFile(self.settings) def setVoipSettings(self, message): """ Set Voip Settings """ if self.settings['voip'] != message: self.settings['voip'] = message self.writeLog("user_action", "Settings for VoIP changed") self.writeNewSettingsToFile(self.settings) def setTimezoneSettings(self, message): """ Set the Timezone """ if self.settings['settings']['timezone'] != message: self.settings['settings']['timezone'] = message self.writeLog("user_action", "Settings for UI changed") self.writeNewSettingsToFile(self.settings) def setMQTTSettings(self, message): """ Set MQTT Settings """ if self.settings['mqtt'] != message: self.settings['mqtt'] = message self.writeLog("user_action", "Settings for MQTT changed") self.writeNewSettingsToFile(self.settings) self.mynotify.setupSendStateMQTT() def setSensorState(self, sensorUUID, state): """ Activate or Deactivate a sensor """ self.settings['sensors'][sensorUUID]['enabled'] = state self.writeNewSettingsToFile(self.settings) logState = "Deactivated" if state is True: logState = "Activated" logSensorName = self.settings['sensors'][sensorUUID]['name'] self.writeLog("user_action", "{0} sensor: {1}".format( logState, logSensorName)) self.writeNewSettingsToFile(self.settings) def setSensorsZone(self, zones): for sensor, sensorvalue in self.settings['sensors'].items(): sensorZones = sensorvalue.get('zones', []) sensorZones = [item.lower() for item in sensorZones] if not set(sensorZones).isdisjoint(zones): sensorvalue['enabled'] = True else: sensorvalue['enabled'] = False self.mynotify.updateUI('settingsChanged', self.getSensorsArmed()) self.writeNewSettingsToFile(self.settings) def addSensor(self, sensorValues): """ Add a new sensor """ print("{0}New Sensor: {2}{1}".format( bcolors.WARNING, bcolors.ENDC, sensorValues)) key = next(iter(sensorValues)) sensorValues[key]['enabled'] = True sensorValues[key]['online'] = False sensorValues[key]['alert'] = True if 'undefined' in sensorValues: sensorUUID = str(uuid.uuid4()) sensorValues[sensorUUID] = sensorValues.pop('undefined') else: self.sensors.del_sensor(key) self.settings['sensors'].update(sensorValues) self.writeNewSettingsToFile(self.settings) self.sensors.add_sensors(self.settings) self.mynotify.setupSendStateMQTT() def delSensor(self, sensorUUID): """ Delete a sensor """ self.sensors.del_sensor(sensorUUID) del self.settings['sensors'][sensorUUID] self.writeNewSettingsToFile(self.settings)