From 641d3f5e010bdcc1b5cf9ecc00df6262aa016cff Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Fri, 7 Aug 2015 19:20:27 +0200 Subject: [PATCH 1/4] add mqtt component --- homeassistant/components/mqtt.py | 192 +++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 homeassistant/components/mqtt.py diff --git a/homeassistant/components/mqtt.py b/homeassistant/components/mqtt.py new file mode 100644 index 00000000000..7baedf5978e --- /dev/null +++ b/homeassistant/components/mqtt.py @@ -0,0 +1,192 @@ +""" +homeassistant.components.mqtt +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +MQTT component, using paho-mqtt. This component needs a MQTT broker like +Mosquitto or Mosca. The Eclipse Foundation is running a public MQTT server +at iot.eclipse.org. If you prefer to use that one, keep in mind to adjust +the topic/client ID and that your messages are public. + +Configuration: + +To use MQTT you will need to add something like the following to your +config/configuration.yaml. + +mqtt: + broker: 127.0.0.1 + port: 1883 + topic: home-assistant + keepalive: 60 + client_id: home-assistant + qos: 0 + retain: 0 + +Variables: + +broker +*Required +This is the IP address of your MQTT broker, e.g. 192.168.1.32. + +port +*Required +The network port to connect to, e.g. 1883. + +topic +*Required +The MQTT topic to subscribe to, e.g. home-assistant. + +keepalive +*Required +The keep alive in seconds for this client, e.g. 60. + +client_id +*Required +A name for this client, e.g. home-assistant. + +qos: 0 +*Required +Quality of service level to use for the subscription. 0, 1, or 2. + +retain: 0 +*Required +If message should be retained. 0 or 1. +""" +import logging + +from homeassistant.helpers import validate_config +from homeassistant.const import ( + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) + +try: + import paho.mqtt.client as mqtt +except ImportError: + mqtt = None + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = "mqtt" +DEPENDENCIES = [] +MQTT_CLIENT = None +MQTT_SEND = 'mqtt_send' +EVENT_MQTT_MESSAGE_RECEIVED = 'MQTT_MESSAGE_RECEIVED' +REQUIREMENTS = ['paho-mqtt>=1.1'] + + +def setup(hass, config): + """ Get the MQTT protocol service. """ + + if not validate_config(config, + {DOMAIN: ['broker', + 'port', + 'topic', + 'keepalive', + 'client_id']}, + _LOGGER): + return False + + if mqtt is None: + _LOGGER.error("Error while importing dependency 'paho-mqtt'.") + return False + + global MQTT_CLIENT + + MQTT_CLIENT = MQTT(hass, + config[DOMAIN]['broker'], + config[DOMAIN]['port'], + config[DOMAIN]['topic'], + config[DOMAIN]['keepalive'], + config[DOMAIN]['client_id']) + + def stop_mqtt(event): + """ Stop MQTT component. """ + MQTT_CLIENT.stop() + + def start_mqtt(event): + """ Launch MQTT component when Home Assistant starts up. """ + try: + MQTT_CLIENT.run() + + except ConnectionRefusedError: + _LOGGER.exception("Can't connect to the broker. " + "Please check your settings and the broker" + "itself.") + return False + + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_mqtt) + + def send_message(call): + """ Sending an MQTT message. """ + subtopic = 'master' + complete_topic = 'home-assistant/{}'.format(str(subtopic)) + MQTT_CLIENT.publish(complete_topic, str(call)) + + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_mqtt) + + hass.services.register(DOMAIN, MQTT_SEND, send_message) + + return True + + +# This is based on one of the paho-mqtt examples: +# http://git.eclipse.org/c/paho/org.eclipse.paho.mqtt.python.git/tree/examples/sub-class.py +# pylint: disable=too-many-arguments, invalid-name +class MQTT(object): + """ Implements messaging service for MQTT. """ + def __init__(self, hass, broker, port, topic, keepalive, clientid=None): + + self.hass = hass + self._broker = broker + self._port = port + self._topic = topic + self._keepalive = keepalive + self.msg = None + + self._mqttc = mqtt.Client(clientid) + self._mqttc.on_message = self.mqtt_on_message + self._mqttc.on_connect = self.mqtt_on_connect + self._mqttc.on_publish = self.mqtt_on_publish + self._mqttc.on_subscribe = self.mqtt_on_subscribe + + def mqtt_on_connect(self, mqttc, obj, flags, rc): + """ Connect callback """ + _LOGGER.info('Connected to broker %s', self._broker) + + def mqtt_on_publish(self, mqttc, obj, mid): + """ Publish callback """ + pass + + def mqtt_on_subscribe(self, mqttc, obj, mid, granted_qos): + """ Subscribe callback """ + complete_topic = self._topic + '/#' + _LOGGER.info('Subscribed to %s', complete_topic) + + def mqtt_on_message(self, mqttc, obj, msg): + """ Message callback """ + self.hass.bus.fire(EVENT_MQTT_MESSAGE_RECEIVED, { + 'topic': msg.topic, + 'qos': str(msg.qos), + 'payload': msg.payload.decode('utf-8'), + }) + + def subscribe(self, topic): + """ Subscribe to a topic. """ + self._mqttc.subscribe(self._topic, 0) + + def unsubscribe(self, topic): + """ Unsubscribe from topic. """ + self._mqttc.unsubscribe(topic) + + def stop(self): + """ Stop the MWTT client. """ + self._mqttc.loop_stop() + + def run(self): + """ Run the MQTT client. """ + self._mqttc.connect(self._broker, + int(self._port), + int(self._keepalive)) + self._mqttc.subscribe(self._topic + '/#', 0) + self._mqttc.loop_start() + + def publish(self, topic, payload): + """ Publish a MQTT message. """ + self._mqttc.publish(topic, payload) From a9d2adea4545cc56c2be55a8f24839b0115ee74e Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Fri, 7 Aug 2015 19:22:17 +0200 Subject: [PATCH 2/4] add mqtt --- .coveragerc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.coveragerc b/.coveragerc index 05ebe32e3a5..84cf66112d1 100644 --- a/.coveragerc +++ b/.coveragerc @@ -18,6 +18,8 @@ omit = homeassistant/components/modbus.py homeassistant/components/*/modbus.py + homeassistant/components/mqtt.py + homeassistant/components/wink.py homeassistant/components/*/wink.py From eac5b193093ed0e5e86acafff3d42260629419f6 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Fri, 7 Aug 2015 19:22:27 +0200 Subject: [PATCH 3/4] add paho-mqtt --- requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements.txt b/requirements.txt index f2f532ca19c..196802fe591 100644 --- a/requirements.txt +++ b/requirements.txt @@ -103,3 +103,6 @@ https://github.com/rkabadi/pyedimax/archive/master.zip # RPI-GPIO platform RPi.GPIO >=0.5.11 + +# PAHO MQTT Binding (protocol.mqtt) +paho-mqtt>=1.1 From 26dbb5ca3fbdb2a7480ed718e894c51678e2fa27 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 8 Aug 2015 18:52:59 +0200 Subject: [PATCH 4/4] make some entries optional, update payload handling, and use qos --- homeassistant/components/mqtt.py | 80 +++++++++++++++++--------------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/mqtt.py b/homeassistant/components/mqtt.py index 7baedf5978e..592706f701f 100644 --- a/homeassistant/components/mqtt.py +++ b/homeassistant/components/mqtt.py @@ -16,9 +16,7 @@ mqtt: port: 1883 topic: home-assistant keepalive: 60 - client_id: home-assistant qos: 0 - retain: 0 Variables: @@ -27,28 +25,21 @@ broker This is the IP address of your MQTT broker, e.g. 192.168.1.32. port -*Required -The network port to connect to, e.g. 1883. +*Optional +The network port to connect to. Default is 1883. topic *Required The MQTT topic to subscribe to, e.g. home-assistant. keepalive -*Required +*Optional The keep alive in seconds for this client, e.g. 60. -client_id -*Required -A name for this client, e.g. home-assistant. - -qos: 0 -*Required -Quality of service level to use for the subscription. 0, 1, or 2. - -retain: 0 -*Required -If message should be retained. 0 or 1. +qos +*Optional +Quality of service level to use for the subscription. +0, 1, or 2, defaults to 0. """ import logging @@ -66,20 +57,30 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = "mqtt" DEPENDENCIES = [] MQTT_CLIENT = None +MQTT_CLIENT_ID = 'home-assistant' +MQTT_DEFAULT_PORT = 1883 +MQTT_DEFAULT_KEEPALIVE = 60 +MQTT_DEFAULT_QOS = 0 MQTT_SEND = 'mqtt_send' EVENT_MQTT_MESSAGE_RECEIVED = 'MQTT_MESSAGE_RECEIVED' REQUIREMENTS = ['paho-mqtt>=1.1'] +ATTR_SUBTOPIC = 'subtopic' +ATTR_PAYLOAD = 'payload' + + +def send_message(hass, subtopic, payload): + """ Send an MQTT message. """ + hass.services.call(DOMAIN, MQTT_SEND, {ATTR_SUBTOPIC: subtopic, + ATTR_PAYLOAD: payload}) + def setup(hass, config): """ Get the MQTT protocol service. """ if not validate_config(config, {DOMAIN: ['broker', - 'port', - 'topic', - 'keepalive', - 'client_id']}, + 'topic']}, _LOGGER): return False @@ -89,12 +90,14 @@ def setup(hass, config): global MQTT_CLIENT - MQTT_CLIENT = MQTT(hass, - config[DOMAIN]['broker'], - config[DOMAIN]['port'], - config[DOMAIN]['topic'], - config[DOMAIN]['keepalive'], - config[DOMAIN]['client_id']) + broker = config[DOMAIN]['broker'] + port = config[DOMAIN].get('port', MQTT_DEFAULT_PORT) + topic = config[DOMAIN]['topic'] + keepalive = config[DOMAIN].get('keepalive', MQTT_DEFAULT_KEEPALIVE) + qos = config[DOMAIN].get('qos', MQTT_DEFAULT_QOS) + + MQTT_CLIENT = MQTT(hass, broker, port, topic, keepalive, qos, + MQTT_CLIENT_ID) def stop_mqtt(event): """ Stop MQTT component. """ @@ -113,15 +116,18 @@ def setup(hass, config): hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_mqtt) - def send_message(call): - """ Sending an MQTT message. """ - subtopic = 'master' - complete_topic = 'home-assistant/{}'.format(str(subtopic)) - MQTT_CLIENT.publish(complete_topic, str(call)) + def mqtt_message(call): + """ Handle sending MQTT message service calls. """ + subtopic = call.data.get(ATTR_SUBTOPIC) + complete_topic = '{}/{}'.format(str(topic), str(subtopic)) + + payload = call.data.get(ATTR_PAYLOAD) + + MQTT_CLIENT.publish(complete_topic, payload=payload) hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_mqtt) - hass.services.register(DOMAIN, MQTT_SEND, send_message) + hass.services.register(DOMAIN, MQTT_SEND, mqtt_message) return True @@ -131,14 +137,14 @@ def setup(hass, config): # pylint: disable=too-many-arguments, invalid-name class MQTT(object): """ Implements messaging service for MQTT. """ - def __init__(self, hass, broker, port, topic, keepalive, clientid=None): + def __init__(self, hass, broker, port, topic, keepalive, qos, clientid): self.hass = hass self._broker = broker self._port = port self._topic = topic self._keepalive = keepalive - self.msg = None + self._qos = qos self._mqttc = mqtt.Client(clientid) self._mqttc.on_message = self.mqtt_on_message @@ -156,7 +162,7 @@ class MQTT(object): def mqtt_on_subscribe(self, mqttc, obj, mid, granted_qos): """ Subscribe callback """ - complete_topic = self._topic + '/#' + complete_topic = '{}/#'.format(self._topic) _LOGGER.info('Subscribed to %s', complete_topic) def mqtt_on_message(self, mqttc, obj, msg): @@ -169,7 +175,7 @@ class MQTT(object): def subscribe(self, topic): """ Subscribe to a topic. """ - self._mqttc.subscribe(self._topic, 0) + self._mqttc.subscribe(self._topic, qos=self._qos) def unsubscribe(self, topic): """ Unsubscribe from topic. """ @@ -184,7 +190,7 @@ class MQTT(object): self._mqttc.connect(self._broker, int(self._port), int(self._keepalive)) - self._mqttc.subscribe(self._topic + '/#', 0) + self._mqttc.subscribe('{}/#'.format(self._topic), qos=self._qos) self._mqttc.loop_start() def publish(self, topic, payload):