def on_start(self): # clear up previous manifest if any message = Message(self) message.recipient = "*/*" message.command = "MANIFEST" message.args = self.manifest["package"] message.set_null() message.retain = True self.send(message) # publish the new manifest message = Message(self) message.recipient = "*/*" message.command = "MANIFEST" message.args = self.manifest["package"] message.set_data(self.manifest) message.retain = True self.send(message) # start all the requested modules for entry in self.modules: self.start_module(entry) self.sleep(0.1) self.sleep(60) # loop forever while True: self.sleep(10)
def on_message(client, userdata, msg): try: self.log_debug("received mqtt message on " + str(msg.topic)) # find the sensor matching the topic for sensor_id in self.sensors: configuration = self.sensors[sensor_id] # exclude pull sensors if "topic" not in configuration: continue # if the message is for this sensor if msg.topic == str(configuration["topic"]): image = msg.payload self.log_debug("received an image for " + sensor_id) # analyze the image image = self.analyze_image(sensor_id, configuration, image) if image is None: return image = base64.b64encode(image) # prepare the message message = Message(self) message.recipient = "controller/hub" message.command = "IN" message.args = sensor_id message.set("value", image) # send the message to the controller self.send(message) break except Exception, e: self.log_error("Unable to process mqtt message: " + exception.get(e))
def retention_policies(self): # ask the database module to purge the data message = Message(self) message.recipient = "controller/db" message.command = "PURGE_ALERTS" message.set_data(self.config["retention"]) self.send(message)
def run_rule(self, rule_id, macro=None): # if macro is defined, run the rule for that macro only, otherwise run for all the macros macros = [macro] if macro is not None else self.rules[rule_id].keys() for macro in macros: rule = self.rules[rule_id][macro] # ensure this rule is not run too often to avoid loops if self.config[ "loop_safeguard"] > 0 and rule_id in self.last_run and macro in self.last_run[ rule_id] and time.time() - self.last_run[rule_id][ macro] < self.config["loop_safeguard"]: return # keep track of the last time this run has run if rule_id not in self.last_run: self.last_run[rule_id] = {} self.last_run[rule_id][macro] = time.time() self.log_debug("[" + rule_id + "][" + macro + "] running rule") # for each sensor we need the value which will be asked to the db module. Keep track of both values and requests if rule_id not in self.requests: self.requests[rule_id] = {} self.requests[rule_id][macro] = [] if rule_id not in self.values: self.values[rule_id] = {} self.values[rule_id][macro] = {} # for every constant, store its value as is so will be ready for the evaluation if "constants" in rule: for constant_id, value in rule["constants"].iteritems(): self.values[rule_id][macro][constant_id] = value # for every variable, retrieve its latest value to the database if "variables" in rule: for variable_id, variable in rule["variables"].iteritems(): # process the variable string (0: request, 1: start, 2: end, 3: sensor_id) match = re.match(self.variable_regexp, variable) if match is None: continue # query db for the data command, start, end, sensor_id = match.groups() message = Message(self) message.recipient = "controller/db" message.command = message.command = "GET_" + command if command != "" else "GET" message.args = sensor_id start = -1 if start is None else int(start) end = -1 if end is None else int(end.replace(",", "")) message.set("start", start) message.set("end", end) self.sessions.register( message, { "rule_id": rule_id, "variable_id": variable_id, "macro": macro, }) self.log_debug("[" + rule_id + "][" + macro + "][" + variable_id + "] requesting db for " + message.command + " " + message.args + ": " + str(message.get_data())) self.send(message) # keep track of the requests so that once all the data will be available the rule will be evaluated self.requests[rule_id][macro].append( message.get_request_id()) # add a placeholder at the end to ensure the rule is not evaluated before all the definitions are retrieved self.requests[rule_id][macro].append("LAST") # if the rule requires no data to retrieve, just evaluate it if len(self.requests[rule_id][macro]) == 0: self.evaluate_rule(rule_id, macro)
def on_start(self): # request all sensors' configuration so to filter sensors of interest self.add_configuration_listener("sensors/#", 1) # kill rtl_433 if running sdk.python.utils.command.run("killall rtl_433") # run rtl_433 and handle the output command = self.config['command']+" "+self.config['arguments'] self.log_debug("running command "+command) process = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) prev_output = "" while True: # read a line from the output output = process.stdout.readline() if output == '' and process.poll() is not None: # process ended, break self.log_info("rtl_433 has ended") break if output: # output available try: # avoid handling the same exact output, skipping if prev_output == output: continue # parse the json output json_output = json.loads(output) except Exception,e: # not json format, ignoring continue self.log_debug("Received: "+str(json_output)) # for each registered sensor for sensor_id in self.sensors: sensor = self.sensors[sensor_id] # apply the filter if any if "filter" in sensor: search = {} if "&" in sensor["filter"]: key_values = sensor["filter"].split("&") else: key_values = [sensor["filter"]] for key_value in key_values: if "=" not in key_value: continue key, value = key_value.split("=") search[key] = value # check if the output matches the search string found = True for key, value in search.iteritems(): # check every key/value pair if key not in json_output: found = False if str(value) != str(json_output[key]): found = False if not found: continue # prepare the message message = Message(self) message.recipient = "controller/hub" message.command = "IN" message.args = sensor_id value = json_output[sensor["measure"]] if "measure" in sensor and sensor["measure"] in json_output else 1 message.set("value", value) # send the measure to the controller self.send(message) self.log_debug("Matched sensor "+sensor_id+" with value "+str(value)) # keep track of the last line of output prev_output = output
def on_message(client, userdata, msg): try: # find the sensor matching the topic for sensor_id in self.sensors: sensor = self.sensors[sensor_id] if mqtt.topic_matches_sub(sensor["topic"], msg.topic): self.log_debug("received " + str(msg.payload) + " for " + sensor_id + " on topic " + str(msg.topic)) # if JSON payload is expected if "key" in sensor: try: data = json.loads(msg.payload) if sensor["key"] not in data: return # apply the filter if any if "filter" in sensor: search = {} if "&" in sensor["filter"]: key_values = sensor["filter"].split( "&") else: key_values = [sensor["filter"]] for key_value in key_values: if "=" not in key_value: continue key, value = key_value.split("=") search[key] = value # check if the output matches the search string found = True for key, value in search.iteritems(): # check every key/value pair if key not in data: found = False if key in data and str(value).lower( ) != str(data[key]).lower(): found = False # not matching, skip to the next sensor if not found: continue value = data[sensor["key"]] except Exception, e: self.log_warning( "Unable to parse JSON payload " + str(msg.payload) + ": " + exception.get(e)) return # else consider the entire payload else: value = msg.payload # prepare the message message = Message(self) message.recipient = "controller/hub" message.command = "IN" message.args = sensor_id message.set("value", value) # send the measure to the controller self.send(message) except Exception, e: self.log_warning("runtime error during on_message(): " + exception.get(e)) return
def on_start(self): self.log_debug("listening for UDP datagrams on port " + str(self.config["port_listen"])) # bind to the network sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) sock.bind(("", self.config["port_listen"])) while True: try: # new data arrives data, addr = sock.recvfrom(1024) self.log_debug("received " + data) # the message is expected to be in JSON format data = json.loads(data) if data["type"] != "WirelessMessage": continue # parse content content = data["data"][0] # sensor just started if content == "STARTED": self.log_info(data["id"] + " has just started") self.tx(data["id"], "ACK", True) elif content == "AWAKE": # send a message if there is something in the queue if data["id"] in self.queue and len( self.queue[data["id"]]) > 0: self.tx(data["id"], queue[data["id"]]) self.queue[data["id"]] = [] else: # look for the sensor_id associated to this measure sensor = None for sensor_id in self.sensors: if data["id"] == self.sensors[sensor_id][ "node_id"] and content.startswith( self.sensors[sensor_id]["measure"]): sensor = self.sensors[sensor_id] break # if not registered, skip it if sensor is None: continue # prepare the message message = Message(self) message.recipient = "controller/hub" message.command = "IN" message.args = sensor_id # generate the timestamp # date_in = datetime.datetime.strptime(data["timestamp"],"%d %b %Y %H:%M:%S +0000") # message.set("timestamp", int(time.mktime(date_in.timetuple()))) # strip out the measure from the value message.set( "value", content.replace(self.sensors[sensor_id]["measure"], "")) # send the measure to the controller self.send(message) except Exception, e: self.log_warning("unable to parse " + str(data) + ": " + exception.get(e))
def on_stop(self): # remove the manifest message = Message(self) message.recipient = "*/*" message.command = "MANIFEST" message.args = self.manifest["package"] message.set_null() message.retain = True self.send(message)
def __poll_sensor(self, sensor_id, configuration): # simulate a message from the hub to trigger the sensor message = Message(self) message.sender = "controller/hub" message.recipient = self.fullname message.command = "IN" message.args = sensor_id message.set_data(configuration) self.on_message(message)
def on_message(self, message): # handle response from the chatbot if message.sender == "controller/chatbot" and message.command == "ASK": content = message.get("content") message = Message(self) message.recipient = "notification/" + self.config["speaker"] message.command = "RUN" message.args = "info" message.set_data(content) self.send(message)
def event_callback(self, pin): if pin not in self.pins: return sensor_id = self.pins[pin] value = 1 if self.gpio_object.input(pin) else 0 self.log_debug("GPIO input on pin " + str(pin) + " is now " + str(value)) message = Message(self) message.recipient = "controller/hub" message.command = "IN" message.args = sensor_id message.set("value", value) self.send(message)
def process_message(self, update, context): # clean up request request = update.message.text.replace( "/" + self.config["command_handler"] + " ", "").lower() # ask our chatbot what to respond message = Message(self) message.recipient = "controller/chatbot" message.command = "ASK" message.set("request", request) message.set("accept", ["text", "image"]) self.sessions.register(message, {"update": update}) self.send(message)
def clear_config(self, filename, version): # send a null so to cancel retention if filename != self.index_key: self.log_debug("Unpublishing configuration "+filename+" (v"+str(version)+")") message = Message(self) message.recipient = "*/*" message.command = "CONF" message.args = filename message.config_schema = version # remove the retained message message.set_null() message.retain = True self.send(message)
def publish_config(self, filename, version, content): if filename != self.index_key: self.log_debug("Publishing configuration " + filename + " (v" + str(version) + ")") message = Message(self) message.recipient = "*/*" message.command = "CONF" message.args = filename message.config_schema = version message.set_data(content) # configuration is retained so when a module connects, immediately get the latest config message.retain = True self.send(message)
def __do_log(self, message): # print the message self.logger.info( sdk.python.utils.strings.format_log_line(message.args, message.sender, message.get_data())) # ask db to save the log db_message = Message(self) db_message.recipient = "controller/db" db_message.command = "SAVE_LOG" db_message.args = message.args db_message.set_data("[" + message.sender + "] " + str(message.get_data())) self.send(db_message)
def on_message(client, userdata, msg): # find the sensor matching the topic for sensor_id in self.sensors: sensor = self.sensors[sensor_id] if mqtt.topic_matches_sub(sensor["topic"], msg.topic): self.log_debug("received " + str(msg.payload) + " for " + sensor_id + " on topic " + str(msg.topic)) # prepare the message message = Message(self) message.recipient = "controller/hub" message.command = "IN" message.args = sensor_id message.set("value", msg.payload) # send the measure to the controller self.send(message)
def ping(self): # TODO: move into a dedicated monitoring module for module in self.modules: # raise a warning if a module becomes unreachable if module["ping"] > 10: self.log_warning("module " + module["fullname"] + " is unreachable") # periodically ping all registered modules message = Message(self) message.recipient = module["fullname"] message.command = "PING" self.log_debug("Pinging " + module["fullname"] + "...") module["ping"] = time.time( ) # keep track of the timestamp of the request self.send(message) self.sleep(1)
def on_start(self): while self.stopping == False: # init slack self.slack_init() if not self.slack_initialized: self.sleep(self.sleep_on_error) continue # connect to slack self.slack_connect() if not self.slack_connected: self.sleep(self.sleep_on_error) continue # read a rtm stream try: output_list = self.slack.rtm_read() except Exception, e: self.log_warning("unable to read from slack: " + exception.get(e)) self.slack_initialized = False self.slack_connected = False self.sleep(self.sleep_on_error) continue if output_list and len(output_list) > 0: for output in output_list: if not output or 'text' not in output: continue if output['user'] == self.bot_id: continue # if the message is to the bot if self.bot_id in output['text'] or self.config[ "bot_name"] in output['text'] or output[ 'channel'].startswith("D"): # clean up the request request = output['text'] request = request.replace(self.config["bot_name"], '') request = request.replace(self.bot_id, '') request = request.lower() channel = output['channel'] # ask our chatbot what to respond message = Message(self) message.recipient = "controller/chatbot" message.command = "ASK" message.set("request", request) message.set("accept", ["text", "image"]) self.sessions.register(message, {"channel": channel}) self.send(message) self.slack_typing(channel) self.sleep(1)
def on_message(client, userdata, msg): self.log_debug("received on "+str(msg.topic)+": "+str(msg.payload)) # find the sensor matching the topic for sensor_id in self.sensors: configuration = self.sensors[sensor_id] # if the message is for this sensor if msg.topic.endswith("/"+str(configuration["device_id"])): # parse the payload try: data = json.loads(msg.payload) except Exception,e: self.log_warning("Unable to parse payload "+str(msg.payload)+": "+exception.get(e)) return # skip if key is missing from the payload if configuration["key"] not in data: continue # apply the filter if any if "filter" in configuration: search = {} if "&" in configuration["filter"]: key_values = configuration["filter"].split("&") else: key_values = [configuration["filter"]] for key_value in key_values: if "=" not in key_value: continue key, value = key_value.split("=") search[key] = value # check if the output matches the search string found = True for key, value in search.iteritems(): # check every key/value pair if key not in data: found = False if key in data and str(value).lower() != str(data[key]).lower(): found = False # not matching, skip to the next sensor if not found: continue value = data[configuration["key"]] # ignore empty values (when homeassistant is true, zigbee2mqtt will report an empty value just after a value if value == "": continue self.log_debug("reporting "+sensor_id+" with value "+str(value)) # prepare the message message = Message(self) message.recipient = "controller/hub" message.command = "IN" message.args = sensor_id message.set("value", value) # send the measure to the controller self.send(message)
def on_start(self): # start the pulseaudio daemon self.log_info("Starting audio daemon...") self.log_debug( sdk.python.utils.command.run("setup/start_pulseaudio.sh")) # start the service input_file = "/tmp/audio_input.wav" listening_message = True while True: if listening_message: self.log_info("Listening for voice commands...") # run sox to record a voice sample trimming silence at the beginning and at the end device = "-t alsa " + str( self.config["device"]) if self.config["device"] != "" else "" command = "sox " + device + " " + input_file + " trim 0 " + str( self.recorder_max_duration) + " silence 1 " + str( self.recorder_start_duration) + " " + str( self.recorder_start_threshold) + "% 1 " + str( self.recorder_end_duration) + " " + str( self.recorder_end_threshold) + "%" sdk.python.utils.command.run(command) # ensure the sample contains any sound max_amplitude = sdk.python.utils.command.run( "killall sox 2>&1 2>/dev/null; sox " + input_file + " -n stat 2>&1|grep 'Maximum amplitude'|awk '{print $3}'") if not sdk.python.utils.numbers.is_number(max_amplitude) or float( max_amplitude) == 0: listening_message = False continue self.log_info("Captured voice sample, processing...") listening_message = True # recognize the speech request = "" if self.config["engine"] == "google": # use the speech recognition engine to make google recognizing the file recognizer = speech_recognition.Recognizer() # open the input file with speech_recognition.AudioFile(input_file) as source: audio = recognizer.record(source) try: # perform the speech recognition results = recognizer.recognize_google( audio, show_all=True, language=self.house["language"]) # identify the best result if len(results) != 0: best_result = max( results["alternative"], key=lambda alternative: alternative["confidence"]) request = best_result["transcript"] except speech_recognition.UnknownValueError: self.log_warning( "Google Speech Recognition could not understand the audio" ) except speech_recognition.RequestError as e: self.log_warning( "Could not request results from Google Speech Recognition module; {0}" .format(e)) elif self.config["engine"] == "pocketsphinx": # run pocketsphinx to recognize the speech in the audio file language = self.house["language"].replace("-", "_") command = "pocketsphinx_continuous -infile " + input_file + " -hmm /usr/share/pocketsphinx/model/hmm/" + language + "/hub4wsj_sc_8k/ -dict /usr/share/pocketsphinx/model/lm/" + language + "/cmu07a.dic 2>/dev/null" output = sdk.python.utils.command.run(command) request = output.replace("000000000: ", "") if self.debug: # repeat the question message = Message(self) message.recipient = "notification/" + self.config["speaker"] message.command = "RUN" message.args = "info" message.set_data("I have understood: " + request) self.send(message) # ask the chatbot what to respond message = Message(self) message.recipient = "controller/chatbot" message.command = "ASK" message.set("request", request) message.set("accept", ["text"]) self.send(message)
def on_message(self, message): # somebody ask this chatbot about something if message.command == "ASK": request = message.get("request") accept = message.get("accept") # remove weird characters from the request request = self.cleanup.sub(' ', request) action = None # build up the vocabularies to check based on what the sender asks vocabularies = [] if "text" in accept: vocabularies.extend([ self.vocabulary["custom"], self.vocabulary_rules, self.vocabulary_sensors_text ]) if "image" in accept: vocabularies.extend([self.vocabulary_sensors_image]) # evaluate each dictionary individually until we find a good answer for kb in vocabularies: evaluation = self.evaluate(request, kb) if evaluation is None: continue keywords = evaluation[0] score = evaluation[1] # if we are confident enough if score > self.not_understood_score: actions = kb[keywords] # pick up a random action and break action = actions[sdk.python.utils.numbers.randint( 0, len(actions) - 1)] self.log_info("I've been asked by " + message.sender + " '" + request + "'. I am " + str(score) + "% sure to respond with '" + str(action) + "'") break # if we have no good answer, just tell the sender if action is None: action = self.vocabulary["not_understood"][ sdk.python.utils.numbers.randint( 0, len(self.vocabulary["not_understood"]) - 1)] self.log_info( "I've been asked by " + message.sender + " '" + request + "' but I'm not sure enough so I'd respond with '" + str(action) + "'") # reponse is a static text if keywords in self.vocabulary[ "custom"] or action in self.vocabulary["not_understood"]: # respond back message.reply() message.set("type", "text") message.set("content", action) self.send(message) # reponse is associated to a rule elif keywords in self.vocabulary_rules: # ask alerter to run the rule (requesting module has to intercept NOTIFY broadcast alerter_msg = Message(self) alerter_msg.recipient = "controller/alerter" alerter_msg.command = "RUN" alerter_msg.args = action self.sessions.register(alerter_msg, {"message": message}) self.send(alerter_msg) # reponse is associated to a sensor elif keywords in self.vocabulary_sensors_text or keywords in self.vocabulary_sensors_image: # ask the db for the latest value of the sensor (continues in message.command == "GET") db_msg = Message(self) db_msg.recipient = "controller/db" db_msg.command = "GET" db_msg.args = action self.sessions.register(db_msg, { "message": message, "description": keywords.lower() }) self.send(db_msg) # received latest value from a sensor elif message.sender == "controller/db" and message.command == "GET": session = self.sessions.restore(message) if session is None: return sensor_id = message.args sensor = self.sensors[sensor_id] value = str(message.get("data")[0]) if len( message.get("data")) > 0 else "N.A." if sensor_id in self.sensors and "unit" in self.sensors[sensor_id]: value = value + str(self.sensors[sensor_id]["unit"]) message = session["message"] message.reply() if sensor["format"] == "image": type = "image" message.set( "description", sensor["description"] if "description" in sensor else "") else: value_is = self.vocabulary["value_is"][ sdk.python.utils.numbers.randint( 0, len(self.vocabulary["value_is"]) - 1)] value = session["description"] + " " + value_is + " " + value type = "text" message.set("type", type) message.set("content", value) self.send(message) # received response from alerter after running the requested rule elif message.sender == "controller/alerter" and message.command == "RUN": session = self.sessions.restore(message) if session is None: return text = message.get_data() # retrieve requesting message message = session["message"] message.reply() message.set("type", "text") value_is = self.vocabulary["value_is"][ sdk.python.utils.numbers.randint( 0, len(self.vocabulary["value_is"]) - 1)] message.set("content", text) self.send(message)
def evaluate_rule(self, rule_id, macro): rule = self.rules[rule_id][macro] # 1) evaluate all the conditions of the rule or_evaluations = [] for or_conditions in rule["conditions"]: and_evaluations = [] for and_conditions in or_conditions: # remove spaces and_conditions = re.sub(' +', ' ', and_conditions) # look for sub expressions (grouped within parenthesis) and calculate them individually expressions = re.findall("\(([^\)]+)\)", and_conditions) for i in range(len(expressions)): expression = expressions[i] # subexpression will become internal variables placeholder = "%exp_" + str(i) + "%" # expression format is "exp1 operator exp2" (e.g. a == b) exp1, operator, exp2 = expression.split(' ') # calculate the sub expression exp1_value = self.values[rule_id][macro][exp1] exp2_value = self.values[rule_id][macro][exp2] exp_value = self.evaluate_condition( exp1_value, operator, exp2_value) self.log_debug("[" + rule_id + "][" + macro + "] resolving " + exp1 + " (" + sdk.python.utils.strings.truncate( str(exp1_value), 50) + ") " + operator + " " + exp2 + " (" + sdk.python.utils.strings.truncate( str(exp2_value), 50) + "): " + str(exp_value) + " (alias " + placeholder + ")") # store the sub expressions result in the values self.values[rule_id][macro][placeholder] = exp_value and_conditions = and_conditions.replace( "(" + expression + ")", placeholder) # evaluate the main expression a, operator, b = and_conditions.split(' ') a_value = self.values[rule_id][macro][a] b_value = self.values[rule_id][macro][b] sub_evaluation = self.is_true(a_value, operator, b_value) self.log_debug( "[" + rule_id + "][" + macro + "] evaluating condition " + a + " (" + sdk.python.utils.strings.truncate(str(a_value), 50) + ") " + operator + " " + b + " (" + sdk.python.utils.strings.truncate(str(b_value), 50) + "): " + str(sub_evaluation)) and_evaluations.append(sub_evaluation) # evaluation is true if all the conditions are met and_evaluation = True for evaluation in and_evaluations: if not evaluation: and_evaluation = False self.log_debug("[" + rule_id + "][" + macro + "] AND block evaluates to " + str(and_evaluation)) or_evaluations.append(and_evaluation) # evaluation is true if at least one condition is met or_evaluation = False for evaluation in or_evaluations: if evaluation: or_evaluation = True # if there were no conditions, the rule evaluates to true if len(or_evaluations) == 0: or_evaluation = True self.log_debug("[" + rule_id + "][" + macro + "] rule evaluates to " + str(or_evaluation)) # evaluate to false, just return if not or_evaluation: return # if suppress is in place for this rule, just return if self.filter_notification(rule_id, rule): return # 2) execute the requested actions if "actions" in rule: for action in rule["actions"]: action = re.sub(' +', ' ', action) # replace constants and variables placeholders in the action with their values action = self.format_placeholders(rule_id, macro, action) # execute the action action_split = action.split(" ") command = action_split[0] # set the sensor to a value or poll it if command == "SET" or command == "POLL": sensor_id = action_split[1] message = Message(self) message.recipient = "controller/hub" message.command = command message.args = sensor_id if command == "SET": message.set_data(action_split[2]) self.send(message) # run another rule elif command == "RUN": rule_to_run = action_split[1] message = Message(self) message.recipient = "controller/alerter" message.command = command message.args = rule_to_run self.send(message) # 3) format the alert text # replace constants and variables placeholders in the alert text with their values alert_text = self.format_placeholders(rule_id, macro, rule["text"]) # 4) notify about the alert and save it if rule["severity"] != "none" and rule_id not in self.on_demand: self.log_info("[" + rule_id + "][" + rule["severity"] + "] " + alert_text) if rule["severity"] != "debug": # ask db to save the alert message = Message(self) message.recipient = "controller/db" message.command = "SAVE_ALERT" message.args = rule["severity"] message.set_data(alert_text) self.send(message) # trigger output modules for notifications message = Message(self) message.recipient = "*/*" message.command = "NOTIFY" message.args = rule["severity"] + "/" + rule_id message.set_data(alert_text) self.send(message) # 5) if rule is manually requested to run, respond back if rule_id in self.on_demand: # retrieve original message message = self.on_demand[rule_id] message.reply() message.set_data(alert_text) self.send(message) del self.on_demand[rule_id] # 6) clean up, rule completed execution, remove the rule_id from the request queue and all the collected values del self.requests[rule_id][macro] if len(self.requests[rule_id]) == 0: del self.requests[rule_id] del self.values[rule_id][macro] if len(self.values[rule_id]) == 0: del self.values[rule_id]
def process_inbound(self, node_id, child_id, command, ack, type, payload): # ensure command and type are valid if command >= len(self.commands): self.log_error("[" + str(node_id) + "][" + str(child_id) + "] command not supported: " + str(command)) return if type >= len(self.types[command]): self.log_error("[" + str(node_id) + "][" + str(child_id) + "] type not supported: " + str(type)) return # map the correspoding command and type string command_string = self.commands[command] type_string = self.types[command][type] ack_string = self.acks[ack] self.log_debug("[" + str(node_id) + "][" + str(child_id) + "][" + command_string + "][" + type_string + "][" + ack_string + "] received: " + str(payload)) # handle protocol messages if command_string == "PRESENTATION": # handle presentation messages self.log_debug("[" + str(node_id) + "][" + str(child_id) + "] presented as " + type_string) elif command_string == "SET": # handle set messages (messages from sensors handled below) self.log_debug("[" + str(node_id) + "][" + str(child_id) + "][" + command_string + "][" + type_string + "]: " + payload) elif command_string == "REQ": # handle req messages self.log_debug("[" + str(node_id) + "][" + str(child_id) + "][" + command_string + "][" + type_string + "]: " + payload) elif command_string == "INTERNAL": # handle internal messages if type_string == "I_TIME": # return the time as requested by the sensor self.log_debug("[" + str(node_id) + "] requesting timestamp") self.tx(node_id, child_id, command_string, type_string, int(time.time())) elif type_string == "I_SKETCH_NAME": # log the sketch name self.log_debug("[" + str(node_id) + "] reported sketch name: " + str(payload)) elif type_string == "I_SKETCH_VERSION": # log the sketch version self.log_debug("[" + str(node_id) + "] reported sketch version: " + str(payload)) elif type_string == "I_ID_REQUEST": # return the next available id self.log_debug("[" + str(node_id) + "] requesting node_id") # we assume low node_id are assigned statically and we can manage the upper end for id in range(100, 254): found = False # cycle over registered sensors for sensor_id in self.sensors: sensor = self.sensors[sensor_id] if id == sensor["node_id"]: found = True break # if id is already registered, go to the next if found: continue # assign the id which is not allocated yet to the node else: self.tx(node_id, child_id, command_string, "I_ID_RESPONSE", str(id)) break elif type_string == "I_CONFIG": # return the controller's configuration self.log_debug("[" + str(node_id) + "] requesting configuration") metric = "I" if self.units == "imperial" else "M" self.tx(node_id, child_id, command_string, type_string, metric) elif type_string == "I_BATTERY_LEVEL": # log the battery level self.log_debug("[" + str(node_id) + "] reporting battery level: " + str(payload) + "%") elif type_string == "I_LOG_MESSAGE": # log a custom message self.log_debug("[" + str(node_id) + "] logging: " + str(payload)) elif type_string == "I_GATEWAY_READY": # report gateway report self.log_debug("[" + str(node_id) + "] reporting gateway ready") elif type_string == "I_POST_SLEEP_NOTIFICATION": # report awake self.log_debug("[" + str(node_id) + "] reporting awake") elif type_string == "I_HEARTBEAT_RESPONSE" or type_string == "I_PRE_SLEEP_NOTIFICATION": # handle smart sleep self.log_debug("[" + str(node_id) + "] going to sleep") if node_id in self.queue and not self.queue[node_id].empty(): # process the queue while not self.queue[node_id].empty(): node_id, child_id, command_string, type_string, payload = self.queue[ node_id].get() # send the message self.tx(node_id, child_id, command_string, type_string, payload) else: self.log_debug("[" + str(node_id) + "] received " + type_string) elif command_string == "STREAM": # handle stream messages return else: self.log_error(" Invalid command " + command_string) # handle messages for registered sensors for sensor_id in self.sensors: sensor = self.sensors[sensor_id] if node_id == sensor["node_id"] and child_id == sensor[ "child_id"] and command_string == sensor[ "command"] and type_string == sensor["type"]: # prepare the message message = Message(self) message.recipient = "controller/hub" message.command = "IN" message.args = sensor_id message.set("value", payload) # send the measure to the controller self.send(message)
def on_message(self, message): # requested to update/save a configuration file if message.command == "SAVE": if self.parse_topic(message.args) is None: return version, filename = self.parse_topic(message.args) self.save_config_file(filename, version, message.get_data()) # requested to delete a configuration file elif message.command == "DELETE": if self.parse_topic(message.args) is None: return version, filename = self.parse_topic(message.args) self.delete_config_file(filename, version) # requested to rename a configuration file elif message.command == "RENAME": if self.parse_topic(message.args) is None: return version, from_filename = self.parse_topic(message.args) to_filename = message.get_data() self.rename_config_file(from_filename, to_filename, version) # a module just subscribed to a configuration topic elif message.command == "SUBSCRIBE": # if config is not loaded yet, return, the sender will call back later if self.load_config_running: return # split pattern requested from configuration version match = re.match("^([^\/]+)\/(.+)$", message.get_data()) if match is None: return version, pattern = match.groups() # check if we have this configuration file by cycling through all the topics if self.index is None: return for topic in self.index: # if the pattern subscribed matches the configuration topic if mqtt.topic_matches_sub(pattern, topic): # when a service subscribes to all the sensors, just send over those associated with those service to avoid sending out too many messages if self.gateway_version >= 2 and message.sender.startswith("service/") and pattern == "sensors/#": if "service" not in self.index[topic]["content"] or "service/"+self.index[topic]["content"]["service"]["name"] != message.sender: continue # respond to the module (directly) with the requested configuration file self.publish_config(topic, self.index[topic]["version"], self.index[topic]["content"], message.sender) # ack the subscribe request so the sender module will not re-send the subscribe request message again ack_message = Message(self) ack_message.recipient = message.sender ack_message.command = "SUBSCRIBE_ACK" ack_message.set_data(message.get_data()) self.send(ack_message) # receive manifest file (configuration is already loaded), it may contain default configurations elif message.command == "MANIFEST": if message.is_null: return manifest = message.get_data() # ensure this is a manifest we can handle if manifest["manifest_schema"] != self.supported_manifest_schema: return self.log_debug("Received manifest from "+message.sender) # if not accepting default configurations or if there are no default config, just return if not self.accept_default_config or self.force_reload or not "default_config" in manifest: return # ensure we have not already received the same manifest before if message.sender in self.manifests and self.manifests[message.sender] == self.get_hash(str(manifest)): return self.manifests[message.sender] = self.get_hash(str(manifest)) # if there is a default configuration in the manifest file, save it default_config = manifest["default_config"] for entry in default_config: for filename_with_version in entry: if self.parse_filename(filename_with_version) is None: return filename, version = self.parse_filename(filename_with_version) file_content = entry[filename_with_version] # do not overwrite existing files since the user may have changed default values # for updated configurations, prevent saving the new version, letting the module managing the upgrade if filename in self.index: continue # ensure the file is in a valid YAML format try: content = yaml.safe_dump(file_content, default_flow_style=False) except Exception,e: self.log_warning("unable to save "+filename+", invalid YAML format: "+str(file_content)+" - "+exception.get(e)) return # save the new/updated default configuration file self.log_debug("Received new default configuration file "+filename) self.save_config_file(filename, version, file_content, False) self.reload_config()