def stop_module(self, entry): if not entry["started"]: return try: self.threads[entry["fullname"]].join() except Exception, e: print "Error while stopping module " + entry[ "fullname"] + ": " + exception.get(e)
def load_default_config(self): config_dir = "default_config" default_config = [] if not os.path.isdir(config_dir): return default_config # walk through the filesystem containing the default configuration for (current_path, dirnames, filenames) in os.walk(config_dir): for filename in filenames: if filename[0] == ".": continue file = current_path + os.sep + filename # parse the file paths name, extension = os.path.splitext(file) if extension != ".yml": continue # remove base configuration dir to build the topic topic = name.replace(config_dir + os.sep, "") # if aliases are used, alter the filename if it is matching config_file, config_version = topic.split(".") if config_file in self.aliases: topic = self.aliases[config_file] + "." + config_version # read the file's content with open(file) as f: content = f.read() # ensure the yaml file is valid try: content = yaml.load(content, Loader=yaml.SafeLoader) except Exception, e: self.log_warning( "configuration file in an invalid YAML format: " + filename + " - " + exception.get(e)) continue # keep track of the valid configuration file default_config.append({topic: content})
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 on_start(self): # ensure config path exists, otherwise create it if not os.path.exists(self.config_dir): try: os.makedirs(self.config_dir) except Exception,e: self.log_error("unable to create directory "+self.config_dir+": "+exception.get(e))
def on_message(self, message): if message.command == "IN": sensor_id = message.args # ensure configuration is valid if not self.is_valid_configuration(["url"], message.get_data()): return url = message.get("url") # if the raw data is cached, take it from there, otherwise request the data and cache it cache_key = "/".join([url]) if self.cache.find(cache_key): data = self.cache.get(cache_key) else: try: data = sdk.python.utils.web.get(url) except Exception,e: self.log_error("unable to connect to "+csv_file+": "+exception.get(e)) return self.cache.add(cache_key, data) message.reply() # parse the feed feed = feedparser.parse(data) result = "" for i in range(len(feed["entries"])): entry = feed["entries"][i] # return a single string containing all the entries result = result + entry["title"].encode('ascii','ignore')+"\n" message.set("value", result) # send the response back self.send(message)
def connect(self): hostname = self.module.hostname if self.module.hostname is not None else self.module.config[ "hostname"] port = self.module.port if self.module.port is not None else self.module.config[ "port"] database = self.module.database if self.module.database is not None else self.module.config[ "database"] password = None if "password" in self.module.config: password = self.module.config["password"] if self.module.password is not None: password = self.module.password while not self.connected: try: self.module.log_debug("Connecting to database " + str(database) + " at " + hostname + ":" + str(port)) self.db = redis.StrictRedis(host=hostname, port=port, db=int(database), password=password) if self.db.ping(): self.db_version = self.db.info().get('redis_version') self.module.log_info("Connected to database #" + str(database) + " at " + hostname + ":" + str(port) + ", redis version " + self.db_version) self.connected = True except Exception, e: self.module.log_error("Unable to connect to " + hostname + ":" + str(port) + " - " + exception.get(e)) self.module.sleep(5) if self.module.stopping: break
def on_message(self, message): if message.command == "IN": if not self.is_valid_configuration( ["request", "latitude", "longitude"], message.get_data()): return sensor_id = message.args request = message.get("request") location = "lat=" + str(message.get("latitude")) + "&lon=" + str( message.get("longitude")) # if the raw data is cached, take it from there, otherwise request the data and cache it cache_key = location if self.cache.find(cache_key): data = self.cache.get(cache_key) else: url = self.url + "?" + location try: data = sdk.python.utils.web.get(url) except Exception, e: self.log_error("unable to connect to " + url + ": " + exception.get(e)) return self.cache.add(cache_key, data) # parse the raw data try: parsed_json = json.loads(data) except Exception, e: self.log_error("invalid JSON returned") return
def slack_init(self): if self.slack_initialized: return self.log_debug("Initializing slack...") # initialize the library try: # initialize the library self.slack = SlackClient(self.config["bot_token"]) # test the authentication auth = self.slack.api_call("auth.test") if not auth["ok"]: self.log_error("authentication failed: " + auth["error"]) return # retrieve the bot id self.bot_id = self.get_user_id(self.config["bot_name"]) if self.bot_id is None: self.log_error("unable to find your bot " + self.config["bot_name"] + ". Ensure it is configured correctly") return # retrieve the channel id self.channel_id = self.get_channel_id(self.config["channel"]) if self.channel_id is None: self.log_error("unable to find the channel " + self.config["channel"]) return self.slack_initialized = True except Exception, e: self.log_warning("unable to initialize slack: " + exception.get(e))
def tx(self, node_id, child_id, command_string, type_string, payload, ack=0, system_message=False): # map the correspoding command and type command = self.commands.index(command_string) type = self.types[command].index(type_string) ack_string = self.acks[ack] if not system_message: self.log_info("[" + str(node_id) + "][" + str(child_id) + "][" + command_string + "][" + type_string + "] sending message: " + str(payload)) # prepare the message msg = str(node_id) + ";" + str(child_id) + ";" + str( command) + ";" + str(ack) + ";" + str(type) + ";" + str( payload) + "\n" # send the message through the serial port try: self.gateway.write(msg) except Exception, e: self.log_error("unable to send " + str(msg) + " to the serial gateway: " + exception.get(e))
def connect(self): hostname = self.module.hostname if self.module.hostname is not None else self.module.config[ "hostname"] port = self.module.port if self.module.port is not None else self.module.config[ "port"] database = self.module.database if self.module.database is not None else self.module.config[ "database"] username = self.module.username if self.module.username is not None else self.module.config[ "username"] password = self.module.password if self.module.password is not None else self.module.config[ "password"] while not self.connected: try: self.module.log_debug("Connecting to database " + str(database) + " at " + hostname + ":" + str(port)) self.client = pymongo.MongoClient("mongodb://" + username + ":" + password + "@" + hostname + ":" + str(port) + "/" + str(database)) self.db = self.client[database] self.db_version = str(self.client.server_info()["version"]) self.module.log_info("Connected to database " + str(database) + " at " + hostname + ":" + str(port) + ", mongodb version " + self.db_version) self.connected = True except Exception, e: self.module.log_error("Unable to connect to " + hostname + ":" + str(port) + " - " + exception.get(e)) self.module.sleep(5) if self.module.stopping: break
def __on_disconnect(client, userdata, rc): self.module.connected = False # call user's callback try: self.module.on_disconnect() except Exception,e: self.module.log_error("runtime error during on_disconnect(): "+exception.get(e))
def send_sms(self, to, text): protocol = "https://" if self.config["ssl"] else "http://" url = protocol+self.config["hostname"]+"/myaccount/sendsms.php?username="******"username"]+"&password="******"password"]+"&from="+str(self.config["from"])+"&to="+str(to)+"&text="+text try: response = sdk.python.utils.web.get(url) except Exception,e: self.log_warning("unable to connect to the sms module: "+exception.get(e)) return False
def on_notify(self, severity, text): # connect to the modem try: self.log_debug("Connecting to GSM modem on port "+self.config["port"]+" with baud rate "+str(self.config["baud"])) modem = serial.Serial(self.config["port"], self.config["baud"], timeout=0.5) except Exception,e: self.log_error("Unable to connect to the GSM modem: "+exception.get(e)) return
def on_message(client, userdata, msg): try: self.log_debug("received "+str(msg.topic)+": "+str(msg.payload)) # split the topic topic, node_id, child_id, command, ack, type = msg.topic.rsplit("/", 5) except Exception,e: self.log_error("Invalid format ("+msg.topic+"): "+exception.get(e)) return
def parse(self, message): self.log_debug("received " + str(message)) # parse the message try: node_id, child_id, command, ack, type, payload = message.split(";") except Exception, e: self.log_debug("Invalid format (" + message + "): " + exception.get(e)) return None
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 read(self): line = "" # read a line try: line = self.gateway.readline().rstrip() except Exception, e: self.log_error("Unable to receive data from the serial gateway: " + exception.get(e)) return None
def on_start(self): self.log_info("Starting mysensors MQTT gateway") # connect to the mqtt broker self.connect() # start loop (in the background) # TODO: reconnect try: self.gateway.loop_start() except Exception,e: self.log_error("Unexpected runtime error: "+exception.get(e))
def slack_message(self, message): # TODO: image try: self.slack.api_call("chat.postMessage", channel=self.channel_id, text=message, as_user=True) except Exception, e: self.log_warning("unable to post message to slack: " + exception.get(e))
def connect(self): try: # connect to the ethernet gateway self.log_info("Connecting to ethernet gateway on "+self.config["hostname"]+":"+str(self.config["port"])) self.gateway = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.gateway.connect((self.config["hostname"], self.config["port"])) return True except Exception,e: self.log_error("Unable to connect to the ethernet gateway: "+exception.get(e)) return False
def get_manifest(self, manifest_file): if not os.path.isfile(manifest_file): raise Exception(manifest_file + " not found") with open(manifest_file) as f: content = f.read() # ensure the manifest is valid try: manifest = yaml.load(content, Loader=yaml.SafeLoader) except Exception, e: raise Exception(manifest_file + " is an invalid manifest:- " + exception.get(e))
def slack_message(self, channel, message): if not self.slack_connected: return # TODO: image try: self.slack.api_call("chat.postMessage", channel=channel, text=message, as_user=True) except Exception, e: self.log_warning("unable to post message to slack: " + exception.get(e))
def __remove_schedule(self, sensor_id): # if already scheduled, stop it if sensor_id in self.__jobs: try: for job in self.__scheduler.get_jobs(): if job.id == self.__jobs[sensor_id]: self.__scheduler.remove_job(self.__jobs[sensor_id]) except Exception, e: self.log_error("Unable to remove scheduled job for sensor " + sensor_id + ": " + exception.get(e)) del self.__jobs[sensor_id]
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) # receive manifest file, it may contain default configurations elif message.command == "MANIFEST": if message.is_null: return manifest = message.get_data() if manifest["manifest_schema"] != self.supported_manifest_schema: return self.log_debug("Received manifest from " + message.sender) if not self.accept_default_config or self.force_reload or not "default_config" in manifest: return # 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.old_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()
def connect(self): try: # connect to the serial gateway self.log_info("Connecting to serial gateway on " + self.config["port"] + " with baud rate " + str(self.config["baud"])) self.gateway = serial.Serial(self.config["port"], self.config["baud"]) return True except Exception, e: self.log_error("Unable to connect to the serial gateway: " + exception.get(e)) return False
def tx(self, node_id, child_id, command_string, type_string, payload, ack=0, system_message=False): # map the correspoding command and type command = self.commands.index(command_string) type = self.types[command].index(type_string) ack_string = self.acks[ack] if not system_message: self.log_info("["+str(node_id)+"]["+str(child_id)+"]["+command_string+"]["+type_string+"] sending message: "+str(payload)) # publish the payload to the mqtt broker topic = self.config["publish_topic_prefix"]+"/"+str(node_id)+"/"+str(child_id)+"/"+str(command)+"/"+str(ack)+"/"+str(type) self.log_debug("publishing on topic "+topic+": "+str(payload)) try: self.gateway.publish(topic, str(payload)) except Exception,e: self.log_error("unable to publish "+str(payload)+" on topic "+topic+": "+exception.get(e))
def read(self): # read a line try: line = "" while True: c = self.gateway.recv(1) # identify broken connections if not c: return None if c == '\n' or c == '': break else: line += c except Exception,e: self.log_error("Unable to receive data from the ethernet gateway: "+exception.get(e)) return None
def save_config_file(self, file, version, data, reload_after_save=True): # ensure filename is valid if ".." in file: self.log_warning("invalid file " + file) return content = None # ensure the file is in the correct format try: content = yaml.safe_dump(data, default_flow_style=False) except Exception, e: self.log_warning("unable to save " + file + ", invalid YAML format: " + str(data) + " - " + exception.get(e)) return
def stop(self): # stop all message consumer threads for consumer in self.consumers: consumer.join() # do nothing if not connected to the gateway if self.gateway == None: return # stop the mqtt network thread self.gateway.loop_stop() # disconnect from the gateway self.gateway.disconnect() try: self.module.on_disconnect() except Exception,e: self.module.log_error("runtime error during on_disconnect(): "+exception.get(e))
def slack_image(self, channel, message, title): # save the file first since when using content= the filetype is not inferred correctly f = open(self.tmp_file, "w") f.write(base64.b64decode(message)) f.close() # upload the file try: self.slack.api_call("files.upload", channels=channel, file=open(self.tmp_file, 'rb'), filename=self.tmp_file, title=title) except Exception, e: self.log_warning("unable to upload file to slack: " + exception.get(e))