def run(self): httpd = http.server.HTTPServer(self.addr, self.handler, False) # Prevent the HTTP server from re-binding every handler. # https://stackoverflow.com/questions/46210672/ httpd.socket = self.sock httpd.threadid = self.threadid httpd.timeout = 2 #- socket timeout httpd.server_bind = self.server_close = lambda self: None if 'startup' in devices.Dev[self.dev]: devices.Dev[self.dev]['startup'](self.setStatus, self.getStatus, self.sendCommand) while not InterruptRequested.is_set(): # print ("Start thread %s" % self.threadid) while macros.eventList.nextEvent() < 1: event = macros.eventList.pop() devices.logfile( " = EVENT (%s) %s/%s" % (datetime.datetime.now().strftime("%I:%M:%S"), event.params['device'], event.name), "EVENT") if event.name.startswith("POLL_"): (POLL, devicename, argname) = event.name.split('_', 2) value = devices.Dev[devicename]["pollCallback"]( devicename, argname, event.command, event.params) if value is not False: if value is not None and value != '': self.setStatus(argname, str(value), event.params) self.sendCommand(event.command, event.params) else: self.sendCommand(event.command, event.params) httpd.handle_request()
def discover(settingsFile, timeout, listen, broadcast): if 'requests' not in devices.Modlist: logfile( "URL/Webhook device support requires 'requests' python module.", "WARN") return False default = "IFTTT" if settingsFile.has_section(default): return print("\tConfiguring default URL device, IFTTT ...") settings.backupSettings() try: ControlIniFile = open( path.join(settings.applicationDir, 'settings.ini'), 'w') settingsFile.add_section(default) settingsFile.add_section(default + ' Status') URL = '''https://maker.ifttt.com/trigger/$command/with/key/$status(API_KEY)''' API_KEY = '''Click 'Documentation' from https://ifttt.com/maker_webhooks to get API Key''' settingsFile.set(default, 'URL', URL) settingsFile.set(default + ' Status', 'API_KEY', API_KEY) settingsFile.set(default, 'Type', 'URL') settingsFile.set(default, 'skipRepeats', 'False') settingsFile.write(ControlIniFile) ControlIniFile.close() except Exception as e: logfile("Error writing settings file: %s" % e, "ERROR") settings.restoreSettings()
def discover(settingsFile, timeout, listen, broadcast): if 'gpio' not in devices.Modlist: logfile( "GPIO device support requires Adafruit python module.\npip3 install Adafruit_BBIO", "WARN") return False print("\tDetecting GPIO devices is currently unsupported...")
def SigInt(signum, frame): if threadpool.InterruptRequested.is_set(): print("\n\nAborting!\n") exit() logfile("\nShuting down server ...", "SPECIAL") threadpool.ShutdownRequested.set() threadpool.InterruptRequested.set()
def readSettings(settingsFile, devname): Dev = devices.Dev[devname] if Dev['Type'] == 'URL': Dev['BaseType'] = "url" if 'requests' not in devices.Modlist: logfile( "URL/Webhook device support requires 'requests' python module.", "WARN") return False device = type('', (), {})() device.url = Dev['URL'] if settingsFile.has_option(devname, "Method"): device.method = settingsFile.get(devname, "Method") else: device.method = "POST" if settingsFile.has_option(devname, "Find"): device.find = settingsFile.get(devname, "Find") if settingsFile.has_option(devname, "Set"): device.setvars = settingsFile.get(devname, "Set") else: return False if 'Delay' in Dev: device.delay = Dev['Delay'] else: device.delay = 0.25 #- Otherwise you get a Bad Gateway error from IFTTT Dev['learnCommand'] = None Dev['sendCommand'] = sendCommand Dev['getStatus'] = None Dev['setStatus'] = None Dev['getSensor'] = None Dev['virtualize'] = virtualize Dev['startup'] = startup return device
def execute_shell(command,query): section = "SHELL " + command #devices.logfile (section,"DEBUG") parameters = None if settingsFile.has_option(section,"parameters"): parameters = expandVariables(settingsFile.get(section,"parameters"),query) if settingsFile.has_option(section,"command"): command = expandVariables(settingsFile.get(section,"command"),query) else: devices.logfile ("You must specify at least a \"command\" for any SHELL command","ERROR") execCommand = command if parameters != None: execCommand = [command,parameters] shell = False try: if settingsFile.has_option(section,"shell") and settingsFile.get(section,"shell")!="False": retval = subprocess.check_output(execCommand) else: retval = subprocess.check_output(execCommand,shell=shell) if settingsFile.has_option(section,"store"): setStatus(expandVariables(settingsFile.get(section,"store"),query),str(retval,'utf8').strip(),query) except subprocess.CalledProcessError as e: retval = "Fail: %d; %s" % (e.returncode,e.output) if len(retval) < 1: retval = command return retval
def getSensor(sensorName, params): devicename = params['device'] device = devices.DeviceByName[devicename] Dev = devices.Dev[devicename] try: sensor = device.sensor[sensorName] if sensor.sensorType.startswith("temp"): reading1 = ADC.read(sensor.gpio) time.sleep(0.5) reading2 = ADC.read(sensor.gpio) reading = (reading1 + reading2) / 2 millivolts = reading * 1800 # 1.8V reference = 1800 mV tempC = (millivolts - 500) / 10 if sensor.sensorType == "tempC": sensor.lastread = tempC elif sensor.sensorType == "tempF": tempF = (tempC * 9 / 5) + 32 sensor.lastread = tempF value = round(sensor.lastread) params['value'] = value return value except Exception as e: logfile( "Error finding sensor %s in %s: %s" % (sensorName, devicename, e), "ERROR") traceback.print_exc() return False
def getStatus(commandName, params): if '/' in commandName: (deviceName, commandName) = commandName.split('/') params = params.copy() params['device'] = deviceName else: deviceName = params["device"] sectionName = deviceName + " Status" device = devices.DeviceByName[deviceName] func = devices.Dev[deviceName]["getStatus"] if func is not None: #print ("getStatus func(%s) is %s" % (type(func),func)) retval = func(device, deviceName, commandName, params) if retval is not False: if 'REDIRECT' in retval: (command, deviceName) = retval.split(' ', 2) sectionName = deviceName + " Status" else: return retval if settingsFile.has_option(sectionName, commandName): status = settingsFile.get(sectionName, commandName) return status if settingsFile.has_option('Status', commandName): status = settingsFile.get('Status', commandName) params["globalVariable"] = commandName if status: return status logfile("Can't find %s %s" % (sectionName, commandName), "WARN") return "0"
def getType(statusName, params): deviceName = params["device"] if deviceName in devices.DeviceByName: device = devices.DeviceByName[deviceName] else: logfile("Failed: No such device, %s" % deviceName, "ERROR") return False sectionName = "RADIO " + statusName if settingsFile.has_section(sectionName): if settingsFile.has_option(sectionName, "commands"): varlist = settingsFile.get(sectionName, "commands") pattern = re.compile("\s*(commands|device|deviceDelay)\s*") varlist = pattern.sub('', varlist) return ("list", varlist) else: varlist = settingsFile.options(sectionName) if "device" in varlist: varlist.remove("device") if "deviceDelay" in varlist: varlist.remove("deviceDelay") if "sequence" in varlist: varlist.remove("sequence") varlist = ' '.join(varlist) return ("list", varlist) return ("string", None)
def getSensor(sensorName, params): deviceName = params['device'] device = devices.DeviceByName[deviceName] Dev = devices.Dev[deviceName] try: # print ("Checking sensors %s %s" % (sensorName,deviceName)) if "RM" in Dev['Type'].upper() and "temp" in sensorName: temperature = device.check_temperature() if temperature: if 'deviceDelay' not in params: time.sleep(device.delay) else: time.sleep(float(params['deviceDelay'])) return temperature elif "A1" in Dev['Type'].upper(): result = device.check_sensors() if result: if 'deviceDelay' not in params: time.sleep(device.delay) else: time.sleep(float(params['deviceDelay'])) return result[sensorName] else: #logfile ("I don't know how to find %s for a %s" % (sensorName,Dev['Type']), "ERROR") return False except Exception as e: logfile( "Error finding sensor %s in %s: %s" % (sensorName, deviceName, e), "ERROR") traceback.print_exc() return False
def access_denied(self): response = "Client %s is not allowed to use GET!" % self.client_ip() self._set_headers(self.path) self.wfile.write(bytes('''{ "error": "%s" }''' % response, 'utf-8')) logfile(response, "WARN") #self.close_connection = True return False
def password_required(self): response = "POST Password required from %s" % self.client_ip() self._set_headers(self.path) self.wfile.write(bytes('''{ "error": "%s" }''' % response, 'utf-8')) logfile(response, "WARN") #self.close_connection = True return False
def restoreSettings(): if path.isfile(settingsINI + ".bak"): shutil.copy2(settingsINI + ".bak", settingsINI) else: devices.logfile( "Can't find backup to restore! Refusing to make this worse!", "WARN") sys.exit()
def relayTo(device,query): if device == query['device']: devices.logfile ("RELAY %s attempted to relay to itself" % query['command'],"ERROR") return True newquery = query.copy() newquery['device'] = device #print ("Relaying %s to %s" % (query['command'],device)) sendCommand(query['command'],newquery) return
def backupSettings(): try: shutil.copy2(settingsINI, settingsINI + ".bak") except FileNotFoundError: devices.logfile( "\tNo settings.ini to backup. Hope we're creating one!", "ERROR") except: devices.logfile("Error in backup operation! Refusing to continue!", "WARN") sys.exit()
def sendCommand(command, device, deviceName, params): try: device.fileh.write(macros.expandVariables(command, params)) device.fileh.write("\n") device.fileh.flush() os.fsync(device.fileh) except Exception as e: traceback.print_exc() logfile("Error Writing to %s: %s" % (device.filename, e), "ERROR") return False return True
def execute_event(command,query): section = "EVENT "+command try: newquery = query.copy() newquery['command'] = settingsFile.get(section,"command") for option in settingsFile.options(section): newquery[option] = settingsFile.get(section,option) execute_event_raw(command,newquery) return "command" except Exception as e: devices.logfile ("EVENT Failed: %s" % e,"ERROR") return False
def execute_wol(command,query): section = "WOL "+command #devices.logfile (section,"DEBUG") try: port = None mac = expandVariables(settingsFile.get(section,"mac"),query) ip = expandVariables(settingsFile.get(section,"ip"),query) if settingsFile.has_option(section,"port"): port = expandVariables(settingsFile.get(section,"port"),query) return wol.wake(mac,ip,port) except Exception as e: devices.logfile ("WOL Failed: %s" % e,"ERROR") return False
def execute_logicnode_raw(query): try: value = expandVariables(query['test'],query) newcommand = None if str(value) in query: newcommand = expandVariables(query[str(value)],query) elif value.isnumeric(): if value == "1" and "on" in query: newcommand = expandVariables(query["on"],query) return sendCommand("." + newcommand,query) elif value == "0" and "off" in query: newcommand = expandVariables(query["off"],query) return sendCommand("." + newcommand,query) value = float(value) if "compare" in query: compareVar = expandVariables(query["compare"],query) try: compare = float(compareVar) except: compare = float(getStatus(compareVar,query)) else: compare = 0 newvalue = value - compare if newvalue < 0: if "less" in query: newcommand = expandVariables(query["less"],query) elif "neg" in query: newcommand = expandVariables(query["neg"],query) elif newvalue > 0: if "more" in query: newcommand = expandVariables(query["more"],query) elif "pos" in query: newcommand = expandVariables(query["pos"],query) else: if "equal" in query: newcommand = expandVariables(query["equal"],query) elif "zero" in query: newcommand = expandVariables(query["zero"],query) if newcommand is None: if "else" in query: newcommand = expandVariables(query["else"],query) if newcommand is not None: return sendCommand("." + newcommand,query) return False except Exception as e: if "error" in query: newcommand = expandVariables(query['error'],query) return sendCommand("." + newcommand,query) devices.logfile ("LOGIC Failed: %s" % e, "ERROR") traceback.print_exc() return False
def readSettings(settingsFile, devname): try: Dev = devices.Dev[devname] if Dev['Type'] == 'GPIO': Dev['BaseType'] = "gpio" device = type('', (), {})() device.sensor = {} ADC.setup() for section in settingsFile.sections(): if section.startswith("GPIO "): sensorname = section[5:] sensor = device.sensor[sensorname] = type('', (), {})() sensor.sensorType = settingsFile.get(section, "type") sensor.gpio = settingsFile.get(section, "gpio") sensor.lastread = False if settingsFile.has_option(section, "poll"): sensor.poll = float(settingsFile.get(section, "poll")) * 60 else: sensor.poll = 300.0 #- 5 minutes if settingsFile.has_option(section, "trigger"): sensor.trigger = trigger = settingsFile.get( section, "trigger") initialparams = {} initialparams['device'] = devname macros.eventList.add( "POLL_" + devname + "_" + sensorname, 4.0, sensor.trigger, initialparams) sensor.lastread = None #- Change this to read the status variable else: return False if 'Delay' in Dev: device.delay = Dev['Delay'] else: device.delay = 0.0 #- set the callbacks Dev['learnCommand'] = None Dev['sendCommand'] = None Dev['getStatus'] = None Dev['setStatus'] = None Dev['getSensor'] = getSensor Dev['pollCallback'] = pollCallback return device except Exception as e: logfile( "GPIO device support requires Adafruit python module.\npip3 install Adafruit_BBIO", "WARN") return None
def execute_ping(command,query): section = "PING "+command #devices.logfile (section,"DEBUG") try: host = expandVariables(settingsFile.get(section,"host"),query) if ping(host): rawcommand = settingsFile.get(section,"on") else: rawcommand = settingsFile.get(section,"off") # print ("Command will be %s" % rawcommand) result = sendCommand(rawcommand,query) return command except Exception as e: devices.logfile ("PING Failed: %s" % e,"ERROR") return False
def execute_logicnode(command,query): section = "LOGIC "+command #devices.logfile (section,"DEBUG") newcommand = None newquery = query.copy() newquery['command'] = command if settingsFile.has_option(section,"test"): newquery["test"] = expandVariables(settingsFile.get(section,"test"),query) else: devices.logfile ("LOGIC Failed: A test value is requiWARN","ERROR") return for var in settingsFile.options(section): newquery[var] = settingsFile.get(section,var) execute_logicnode_raw(newquery) return command
def expandVariables(commandString,query): statusVar = commandString.find("$status(") newquery = query.copy() while statusVar > -1: endVar = commandString.find(")",statusVar) if endVar < 0: devices.logfile ("No END Parenthesis found in $status variable","ERROR") statusVar = -1 varname = commandString[statusVar+8:endVar] varvalue = getStatus(varname,newquery) commandString = commandString.replace(commandString[statusVar:endVar+1],varvalue) statusVar = commandString.find("$status(") firstPass = commandString secondPass = string.Template(firstPass).substitute(newquery) return secondPass
def readSettings(settingsFile, devname): try: Dev = devices.Dev[devname] if Dev['Type'] == 'RM' or Dev['Type'] == 'RM2': device = broadlink.rm((Dev['IPAddress'], 80), Dev['MACAddress'], Dev['Device']) elif Dev['Type'] == 'MP1': device = broadlink.mp1((Dev['IPAddress'], 80), Dev['MACAddress'], Dev['Device']) elif Dev['Type'] == 'SP1': device = broadlink.sp1((Dev['IPAddress'], 80), Dev['MACAddress'], Dev['Device']) elif Dev['Type'] == 'SP2': device = broadlink.sp2((Dev['IPAddress'], 80), Dev['MACAddress'], Dev['Device']) elif Dev['Type'] == 'A1': device = broadlink.a1((Dev['IPAddress'], 80), Dev['MACAddress'], Dev['Device']) elif Dev['Type'] == 'HYSEN': device = broadlink.hysen((Dev['IPAddress'], 80), Dev['MACAddress'], Dev['Device']) elif Dev['Type'] == 'S1C': device = broadlink.S1C((Dev['IPAddress'], 80), Dev['MACAddress'], Dev['Device']) elif Dev['Type'] == 'DOOYA': device = broadlink.dooya((Dev['IPAddress'], 80), Dev['MACAddress'], Dev['Device']) else: return False Dev['BaseType'] = "broadlink" if 'Delay' in Dev: device.delay = Dev['Delay'] else: device.delay = 0.0 #- set the callbacks Dev['learnCommand'] = learnCommand Dev['sendCommand'] = sendCommand Dev['getStatus'] = None Dev['setStatus'] = None Dev['getSensor'] = getSensor return device except Exception as e: logfile( "Broadlink device support requires broadlink python module.\npip3 install broadlink", "WARN") return None
def execute_event_raw(command,query): #print ("RAW: %s" % command) try: newcommand = expandVariables(query['command'],query) delay = 0.0 if 'seconds' in query: delay += float(expandVariables(str(query['seconds']),query)) if 'minutes' in query: delay += float(expandVariables(str(query['minutes']),query)) * 60 if 'hours' in query: delay += float(expandVariables(str(query['hours']),query)) * 3600 eventList.add(command,delay,newcommand,query) return command except Exception as e: devices.logfile ("EVENT Failed: %s" % e,"ERROR") traceback.print_exc() return False
def start(handler_class=Handler, port=8080, listen='0.0.0.0'): addr = (listen, port) if settings.Hostname == "localhost": name = '' else: name = settings.Hostname + " " logfile( '\nStarting RestHome server %son %s:%s ...\n' % (name, listen, port), "SPECIAL") sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(addr) sock.listen(5) devices.startUp(sock, addr, setStatus, getStatus, sendCommand, handler_class) while not threadpool.InterruptRequested.is_set(): threadpool.InterruptRequested.wait(2) logfile("Closing Server ...", "SPECIAL") sock.close()
def do_GET(self): self.Parameters = collections.defaultdict(lambda: ' ') try: if GlobalPassword and ('/ui/' not in self.path and 'getIcon' not in self.path and self.path != '/favicon.ico'): try: if RestrictAccess and self.client_ip( ) not in RestrictAccess: return self.access_denied() return self.messageHandler() except NameError as e: logfile("Error: %s" % e, "ERROR") traceback.print_exc() self.password_required() else: return self.messageHandler() except NameError: #- No security specified self.messageHandler()
def discover(settingsFile, timeout, listen, broadcast): if 'broadlink' not in devices.Modlist: logfile( "Broadlink device support requires broadlink python module.\npip3 install broadlink", "WARN") return False print("\tDetecting Broadlink devices ...") try: broadlinkDevices = broadlink.discover(timeout, listen, broadcast) except: broadlinkDevices = broadlink.discover(timeout, listen) settings.backupSettings() try: ControlIniFile = open( path.join(settings.applicationDir, 'settings.ini'), 'w') for device in broadlinkDevices: try: device.hostname = socket.gethostbyaddr(device.host[0])[0] if "." in device.hostname: device.hostname = device.hostname.split('.')[0] except: device.hostname = "Broadlink" + device.type.upper() if device.hostname in devices.DeviceByName: device.hostname = "%s-%s" % (device.hostname, str( device.host).split('.')[3]) if not settingsFile.has_section(device.hostname): settingsFile.add_section(device.hostname) settingsFile.set(device.hostname, 'IPAddress', str(device.host[0])) hexmac = ':'.join(["%02x" % (x) for x in reversed(device.mac)]) settingsFile.set(device.hostname, 'MACAddress', hexmac) settingsFile.set(device.hostname, 'Device', hex(device.devtype)) settingsFile.set(device.hostname, 'Timeout', str(device.timeout)) settingsFile.set(device.hostname, 'Type', device.type.upper()) print("\t\t%s: Found %s on %s (%s) type: %s" % (device.hostname, device.type, device.host, hexmac, hex(device.devtype))) settingsFile.write(ControlIniFile) ControlIniFile.close() except Exception as e: logfile("Error writing settings file: %s" % e, "ERROR") settings.restoreSettings()
def do_POST(self): self.Parameters = collections.defaultdict(lambda: ' ') password = '' try: content_len = int(self.headers.get('content-length', 0)) JSONbody = self.rfile.read(content_len).decode("utf-8") #print ("POST body: %s" % JSONbody) self.Parameters.update(json.loads(JSONbody)) password = self.Parameters['password'] except: traceback.print_exc() pass try: if not GlobalPassword: return self.messageHandler() if GlobalPassword and GlobalPassword == password: return self.messageHandler() else: logfile('''POST Password Wrong: "%s"''' % password, "WARN") except NameError: return self.password_required() self.password_required()
def learnCommand(devname, device, params): try: device.enter_learning() start = time.time() #- We want the real device delay here, not the modified one #- min of 0.5 second. Result is almost always 1 second sleeptime = max(devices.Dev[devname]["Delay"], 0.5) LearnedCommand = None while LearnedCommand is None and time.time( ) - start < settings.GlobalTimeout: time.sleep(sleeptime) LearnedCommand = device.check_data() if LearnedCommand is None: logfile('Command not received', "ERROR") return False AdditionalData = bytearray([0x00, 0x00, 0x00, 0x00]) finalCommand = AdditionalData + LearnedCommand AESEncryption = AES.new(device.key, AES.MODE_CBC, bytes(device.iv)) return binascii.hexlify(AESEncryption.decrypt(bytes(finalCommand))) except: traceback.print_exc()