Example #1
0
 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 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)
Example #3
0
 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)
Example #4
0
 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)
Example #5
0
 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)
Example #6
0
 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)
Example #7
0
 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 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]
Example #9
0
 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()