diff --git a/src/main.py b/src/main.py index 24d48ca..93bb852 100644 --- a/src/main.py +++ b/src/main.py @@ -13,94 +13,94 @@ logging.basicConfig(level=logging.INFO) _LOGGER = logging.getLogger(__name__) def process_gateway_messages(gateway, client): - while True: - try: - packet = gateway._queue.get() - _LOGGER.debug("data from queuee: " + format(packet)) + while True: + try: + packet = gateway._queue.get() + _LOGGER.debug("data from queuee: " + format(packet)) - sid = packet.get("sid", None) - model = packet.get("model", "") - data = packet.get("data", "") + sid = packet.get("sid", None) + model = packet.get("model", "") + data = packet.get("data", "") - if (sid != None and data != ""): - data_decoded = json.loads(data) - client.publish(model, sid, data_decoded) - gateway._queue.task_done() - except Exception as e: - _LOGGER.error('Error while sending from gateway to mqtt: ', str(e)) + if (sid != None and data != ""): + data_decoded = json.loads(data) + client.publish(model, sid, data_decoded) + gateway._queue.task_done() + except Exception as e: + _LOGGER.error('Error while sending from gateway to mqtt: ', str(e)) def read_motion_data(gateway, client, polling_interval, polling_models): - first = True - while True: - try: - for device_type in gateway.XIAOMI_DEVICES: - devices = gateway.XIAOMI_DEVICES[device_type] - for device in devices: - model = device.get("model", "") - if (model not in polling_models): - continue - sid = device['sid'] + first = True + while True: + try: + for device_type in gateway.XIAOMI_DEVICES: + devices = gateway.XIAOMI_DEVICES[device_type] + for device in devices: + model = device.get("model", "") + if (model not in polling_models): + continue + sid = device['sid'] - sensor_resp = gateway.get_from_hub(sid) - if (sensor_resp == None): - continue; - if (sensor_resp['sid'] != sid): - _LOGGER.error("Error: Response sid(" + sensor_resp['sid'] + ") differs from requested(" + sid + "). Skipping.") - continue; + sensor_resp = gateway.get_from_hub(sid) + if (sensor_resp == None): + continue; + if (sensor_resp['sid'] != sid): + _LOGGER.error("Error: Response sid(" + sensor_resp['sid'] + ") differs from requested(" + sid + "). Skipping.") + continue; - data = json.loads(sensor_resp['data']) - state = data.get("status", None) - short_id = sensor_resp['short_id'] - if ( device['data'] != data or first): - device['data'] = data - _LOGGER.debug("Polling result differs for " + str(model) + " with sid(First: " + str(first) + "): " + str(sid) + "; " + str(data)) - client.publish(model, sid, data) - first = False - except Exception as e: - _LOGGER.error('Error while sending from mqtt to gateway: ', str(e)) - time.sleep(polling_interval) + data = json.loads(sensor_resp['data']) + state = data.get("status", None) + short_id = sensor_resp['short_id'] + if ( device['data'] != data or first): + device['data'] = data + _LOGGER.debug("Polling result differs for " + str(model) + " with sid(First: " + str(first) + "): " + str(sid) + "; " + str(data)) + client.publish(model, sid, data) + first = False + except Exception as e: + _LOGGER.error('Error while sending from mqtt to gateway: ', str(e)) + time.sleep(polling_interval) def process_mqtt_messages(gateway, client): - while True: - try: - data = client._queue.get() - _LOGGER.debug("data from mqtt: " + format(data)) + while True: + try: + data = client._queue.get() + _LOGGER.debug("data from mqtt: " + format(data)) - sid = data.get("sid", None) - param = data.get("param", None) - value = data.get("value", None) + sid = data.get("sid", None) + values = data.get("values", dict()) - resp = gateway.write_to_hub(sid, param, value) - client._queue.task_done() - except Exception as e: - _LOGGER.error('Error while sending from mqtt to gateway: ', str(e)) + resp = gateway.write_to_hub(sid, **values) + client._queue.task_done() + except Exception as e: + _LOGGER.error('Error while sending from mqtt to gateway: ', str(e)) if __name__ == "__main__": - _LOGGER.info("Loading config file...") - config=yamlparser.load_yaml('config/config.yaml') - gateway_pass = yamlparser.get_gateway_password(config) - polling_interval = config['gateway'].get("polling_interval", 2) - polling_models = config['gateway'].get("polling_models", ['motion']) + _LOGGER.info("Loading config file...") + config=yamlparser.load_yaml('config/config.yaml') + gateway_pass = yamlparser.get_gateway_password(config) + polling_interval = config['gateway'].get("polling_interval", 2) + polling_models = config['gateway'].get("polling_models", ['motion']) - _LOGGER.info("Init mqtt client.") - client = mqtt.Mqtt(config) - client.connect() - #only this devices can be controlled from MQTT - client.subscribe("gateway", "+", "+", "set") - client.subscribe("plug", "+", "status", "set") + _LOGGER.info("Init mqtt client.") + client = mqtt.Mqtt(config) + client.connect() + #only this devices can be controlled from MQTT + client.subscribe("gateway", "+", "+", "set") + client.subscribe("gateway", "+", "write", None) + client.subscribe("plug", "+", "status", "set") - gateway = XiaomiHub(gateway_pass) - t1 = threading.Thread(target=process_gateway_messages, args=[gateway, client]) - t1.daemon = True - t1.start() + gateway = XiaomiHub(gateway_pass) + t1 = threading.Thread(target=process_gateway_messages, args=[gateway, client]) + t1.daemon = True + t1.start() - t2 = threading.Thread(target=process_mqtt_messages, args=[gateway, client]) - t2.daemon = True - t2.start() + t2 = threading.Thread(target=process_mqtt_messages, args=[gateway, client]) + t2.daemon = True + t2.start() - t3 = threading.Thread(target=read_motion_data, args=[gateway, client, polling_interval, polling_models]) - t3.daemon = True - t3.start() + t3 = threading.Thread(target=read_motion_data, args=[gateway, client, polling_interval, polling_models]) + t3.daemon = True + t3.start() - while True: - time.sleep(10) + while True: + time.sleep(10) diff --git a/src/mqtt.py b/src/mqtt.py index ec28762..64a5b12 100644 --- a/src/mqtt.py +++ b/src/mqtt.py @@ -3,165 +3,190 @@ import os import logging from queue import Queue from threading import Thread +import json _LOGGER = logging.getLogger(__name__) class Mqtt: - username = "" - password = "" - server = "localhost" - port = 1883 - prefix = "home" + username = "" + password = "" + server = "localhost" + port = 1883 + prefix = "home" - _client = None - _sids = None - _queue = None - _threads = None + _client = None + _sids = None + _queue = None + _threads = None - def __init__(self, config): - if (config == None): - raise "Config is null" + def __init__(self, config): + if (config == None): + raise "Config is null" - #load sids dictionary - self._sids = config.get("sids", None) - if (self._sids == None): - self._sids = dict({}) + #load sids dictionary + self._sids = config.get("sids", None) + if (self._sids == None): + self._sids = dict({}) - #load mqtt settings - mqttConfig = config.get("mqtt", None) - if (mqttConfig == None): - raise "Config mqtt section is null" + #load mqtt settings + mqttConfig = config.get("mqtt", None) + if (mqttConfig == None): + raise "Config mqtt section is null" - self.username = mqttConfig.get("username", "") - self.password = mqttConfig.get("password", "") - self.server = mqttConfig.get("server", "localhost") - self.port = mqttConfig.get("port", 1883) - self.prefix = mqttConfig.get("prefix", "home") - self._queue = Queue() - self._threads = [] + self.username = mqttConfig.get("username", "") + self.password = mqttConfig.get("password", "") + self.server = mqttConfig.get("server", "localhost") + self.port = mqttConfig.get("port", 1883) + self.prefix = mqttConfig.get("prefix", "home") + self._queue = Queue() + self._threads = [] - def connect(self): - _LOGGER.info("Connecting to MQTT server " + self.server + ":" + str(self.port) + " with username (" + self.username + ":" + self.password + ")") - self._client = mqtt.Client() - if (self.username != "" and self.password != ""): - self._client.username_pw_set(self.username, self.password) - self._client.on_message = self._mqtt_process_message - self._client.on_connect = self._mqtt_on_connect - self._client.connect(self.server, self.port, 60) + def connect(self): + _LOGGER.info("Connecting to MQTT server " + self.server + ":" + str(self.port) + " with username (" + self.username + ":" + self.password + ")") + self._client = mqtt.Client() + if (self.username != "" and self.password != ""): + self._client.username_pw_set(self.username, self.password) + self._client.on_message = self._mqtt_process_message + self._client.on_connect = self._mqtt_on_connect + self._client.connect(self.server, self.port, 60) - #run message processing loop - t1 = Thread(target=self._mqtt_loop) - t1.start() - self._threads.append(t1) + #run message processing loop + t1 = Thread(target=self._mqtt_loop) + t1.start() + self._threads.append(t1) - def subscribe(self, model="+", name="+", prop="+", command="set"): - topic = self.prefix + "/" + model + "/" + name + "/" + prop + "/" + command - _LOGGER.info("Subscibing to " + topic + ".") - self._client.subscribe(topic) + def subscribe(self, model="+", name="+", prop="+", command=None): + topic = self.prefix + "/" + model + "/" + name + "/" + prop + if command is not None: + topic += "/" + command + _LOGGER.info("Subscibing to " + topic + ".") + self._client.subscribe(topic) - def publish(self, model, sid, data, retain=True): - sidprops = self._sids.get(sid, None) - if (sidprops != None): - model = sidprops.get("model",model) - sid = sidprops.get("name",sid) + def publish(self, model, sid, data, retain=True): + sidprops = self._sids.get(sid, None) + if (sidprops != None): + model = sidprops.get("model",model) + sid = sidprops.get("name",sid) - # _LOGGER.info("data is " + format(data)) - PATH_FMT = self.prefix + "/{model}/{sid}/{prop}" - for key, value in data.items(): - # fix for latest motion value - if (model == "motion" and key == "no_motion"): - key="status" - value="no_motion" - if (model == "magnet" and key == "no_close"): - key="status" - value="open" + # _LOGGER.info("data is " + format(data)) + PATH_FMT = self.prefix + "/{model}/{sid}/{prop}" + for key, value in data.items(): + # fix for latest motion value + if (model == "motion" and key == "no_motion"): + key="status" + value="no_motion" + if (model == "magnet" and key == "no_close"): + key="status" + value="open" - # do not retain event-based sensors (like switches and cubes). - if (model in ["switch", "cube"]): - retain = False + # do not retain event-based sensors (like switches and cubes). + if (model in ["switch", "cube"]): + retain = False - # fix for rgb format - if (key == "rgb" and self._is_int(value)): - value = self._color_xiaomi_to_rgb(value) + # fix for rgb format + if (key == "rgb" and self._is_int(value)): + value = self._color_xiaomi_to_rgb(value) - topic = PATH_FMT.format(model=model, sid=sid, prop=key) - _LOGGER.info("Publishing message to topic " + topic + ": " + str(value) + ".") - self._client.publish(topic, payload=value, qos=0, retain=retain) + topic = PATH_FMT.format(model=model, sid=sid, prop=key) + _LOGGER.info("Publishing message to topic " + topic + ": " + str(value) + ".") + self._client.publish(topic, payload=value, qos=0, retain=retain) - def _mqtt_on_connect(self, client, userdata, rc, unk): - _LOGGER.info("Connected to mqtt server.") + def _mqtt_on_connect(self, client, userdata, rc, unk): + _LOGGER.info("Connected to mqtt server.") - def _mqtt_process_message(self, client, userdata, msg): - _LOGGER.info("Processing message in " + str(msg.topic) + ": " + str(msg.payload) + ".") - parts = msg.topic.split("/") - if (len(parts) != 5): - return - model = parts[1] - query_sid = parts[2] #sid or name part - param = parts[3] #param part - value = (msg.payload).decode('utf-8') - if self._is_int(value): - value = int(value) - name = "" # we will find it next - sid = query_sid + def _mqtt_process_message(self, client, userdata, msg): + _LOGGER.info("Processing message in " + str(msg.topic) + ": " + str(msg.payload) + ".") + parts = msg.topic.split("/") + if len(parts) < 4: + # should we return an error message ? + return - isFound = False - for current_sid in self._sids: - if (current_sid == None): - continue - sidprops = self._sids.get(current_sid, None) - if sidprops == None: - continue - sidname = sidprops.get("name", current_sid) - sidmodel = sidprops.get("model", "") - if (sidname == query_sid and sidmodel == model): - sid = current_sid - name = sidname - isFound = True - break - else: - _LOGGER.debug(sidmodel + "-" + sidname + " is not " + model + "-" + query_sid + ".") - continue + model = parts[1] + query_sid = parts[2] #sid or name part + param = parts[3] #param part + method = None + if len(parts) > 4: + method = parts[4] + else: + method = parts[3] - if isFound == False: - return + name = "" # we will find it next + sid = query_sid + isFound = False + for current_sid in self._sids: + if (current_sid == None): + continue + sidprops = self._sids.get(current_sid, None) + if sidprops == None: + continue + sidname = sidprops.get("name", current_sid) + sidmodel = sidprops.get("model", "") + if (sidname == query_sid and sidmodel == model): + sid = current_sid + name = sidname + isFound = True + break + else: + _LOGGER.debug(sidmodel + "-" + sidname + " is not " + model + "-" + query_sid + ".") + continue - # fix for rgb format - if (param == "rgb" and "," in str(value)): - value = self._color_rgb_to_xiaomi(value) + if isFound == False: + # should we return an error message ? + return - data = {'sid': sid, 'model': model, 'name': name, 'param':param, 'value':value} - # put in process queuee - self._queue.put(data) + if method == "set": + # use single value set method - def _mqtt_loop(self): - _LOGGER.info("Starting mqtt loop.") - self._client.loop_forever() + value = (msg.payload).decode('utf-8') + if self._is_int(value): + value = int(value) - def _color_xiaomi_to_rgb(self, xiaomi_color): - intval = int(xiaomi_color) - blue = (intval) & 255 - green = (intval >> 8) & 255 - red = (intval >> 16) & 255 - bright = (intval >> 24) & 255 - value = str(red)+","+str(green)+","+str(blue)+","+str(bright) - return value + # fix for rgb format + if (param == "rgb" and "," in str(value)): + value = self._color_rgb_to_xiaomi(value) - def _color_rgb_to_xiaomi(self, rgb_string): - arr = rgb_string.split(",") - r = int(arr[0]) - g = int(arr[1]) - b = int(arr[2]) - if len(arr)>3: - bright = int(arr[3]) - else: - bright = 255 - value = int('%02x%02x%02x%02x' % (bright, r, g, b), 16) - return value + # prepare values dict + data = {'sid': sid, 'model': model, 'name': name, + 'values': {param: value}} + # put in process queuee + self._queue.put(data) + + elif method == "write": + # use raw write method to the sensor, we expect a jsonified dict here. + values = json.loads((msg.payload).decode('utf-8')) + data = {'sid': sid, 'model': model, 'name': name, + 'values': values} + # put in process queuee + self._queue.put(data) - def _is_int(self, x): - try: - tmp = int(x) - return True - except Exception as e: - return False + def _mqtt_loop(self): + _LOGGER.info("Starting mqtt loop.") + self._client.loop_forever() + + def _color_xiaomi_to_rgb(self, xiaomi_color): + intval = int(xiaomi_color) + blue = (intval) & 255 + green = (intval >> 8) & 255 + red = (intval >> 16) & 255 + bright = (intval >> 24) & 255 + value = str(red)+","+str(green)+","+str(blue)+","+str(bright) + return value + + def _color_rgb_to_xiaomi(self, rgb_string): + arr = rgb_string.split(",") + r = int(arr[0]) + g = int(arr[1]) + b = int(arr[2]) + if len(arr)>3: + bright = int(arr[3]) + else: + bright = 255 + value = int('%02x%02x%02x%02x' % (bright, r, g, b), 16) + return value + + def _is_int(self, x): + try: + tmp = int(x) + return True + except Exception as e: + return False diff --git a/src/xiaomihub.py b/src/xiaomihub.py index 7b70f76..8a72e9c 100644 --- a/src/xiaomihub.py +++ b/src/xiaomihub.py @@ -55,7 +55,7 @@ class XiaomiHub: _LOGGER.error("Cannot discover hub using whois: {0}".format(e)) self._socket.close() - + if self.GATEWAY_IP is None: _LOGGER.error('No Gateway found. Cannot continue') return None @@ -149,14 +149,14 @@ class XiaomiHub: _LOGGER.error("Cannot connect to Gateway") socket.close() - def write_to_hub(self, sid, data_key, datavalue): + def write_to_hub(self, sid, **values): key = self._get_key() - if type(datavalue) == int: - datavalue_formatted = str(datavalue) - else: - datavalue_formatted = '"' + datavalue + '"' - cmd = '{ "cmd":"write","sid":"' + sid + '","data":"{"' + data_key + '":' + datavalue_formatted + ',"key":"' + key + '"}}' - return self._send_cmd(cmd, "write_ack") + cmd = { + "cmd": "write", + "sid": sid, + "data": dict(key=key, **values) + } + return self._send_cmd(json.dumps(cmd), "write_ack") def get_from_hub(self, sid): cmd = '{ "cmd":"read","sid":"' + sid + '"}'