Broadlink-mqtt integration with openhab


(Nakh Home) #1

Hi

i am trying to have an openhab integration with broadlink devices.

1 i am aware about this binding but the M1 is not supported and also there’s a lot of sockets connections error in the log.

2.i know that it’s also possible to use some scripts based on this Python broadlink project

it works pretty fine

3.i have found this project Broadlink-mqtt.
it’s really nice, it’s a script which susbcribe to a mqtt feed and send commands to broadlink
it works for all the broadlink devices expect the S1

this script should run as a service.

moreover, i need a such service for each device.

i would prefer the mqtt approach but how to manage such services?

how to create a service from a python script?

which approach would you recommend me for stability and monitoring 2 or 3?

G


(Psyciknz) #2

I have a variation of the original script which does support multiple devices.


(Nakh Home) #3

REALLY IMPRESSIVE!

Thank you.

is there a way to retrieve the current state of the switch ?
in case, that the switch status is updated manually, openhab will be updated according to the last openhab update.

is it possible to know the manual change status?

if yes, it will be an excellent path to install 2 ways switches

till now, i didn’t find a simple solution for two ways switches without neutral wire .

even with z-wave, do you have any ideas ?

G


(Psyciknz) #4

Not sure with mqtt. You normally have outgoing commands (mqtt> for setting state, and mqtt< for getting status), but only if the device reports it.

With the broadlink, I’ve not seen a switchable device, as I only have two of the rm3’s


(Nakh Home) #5

Thanks for your answer

the service do not start

 Main PID: 3030 (code=exited, status=1/FAILURE)

Feb 11 13:39:15 SMHPI systemd[1]: Started Starts and stops the Broadink Python script.
Feb 11 13:39:15 SMHPI python[3030]: /usr/bin/python: can't find '__main__' module in 'etc/openhab2/scripts/MQTT/broadlink/mqtt_SP2_1'
Feb 11 13:39:15 SMHPI systemd[1]: mqtt_SP2_1.service: Main process exited, code=exited, status=1/FAILURE
Feb 11 13:39:15 SMHPI systemd[1]: mqtt_SP2_1.service: Unit entered failed state.
Feb 11 13:39:15 SMHPI systemd[1]: mqtt_SP2_1.service: Failed with result 'exit-code'.
Feb 11 13:39:32 SMHPI systemd[1]: [/usr/lib/systemd/system/mqtt_SP2_1.service:8] Path 'etc/openhab2/scripts/MQTT/broadlink/mqtt_SP2_1/mqtt.conf' is not absolute, ignoring.
Feb 11 13:39:32 SMHPI systemd[1]: [/usr/lib/systemd/system/mqtt_SP2_1.service:11] Working directory path 'etc/openhab2/scripts/MQTT/broadlink/mqtt_SP2_1/mqtt_SP2_1.py' is not absolute, ignoring```

(Psyciknz) #6

Does it run manually? Where does the service expect the python module to exist?


(Nakh Home) #7

Thanks a lot it works!


(Nakh Home) #8

@psyciknz do the script support A1 ?

what can be retrieved ? temperature ? noise? ??

thanks!


(Psyciknz) #9

I only that the rm3, so the support of anything else might be a little immature, and I’ve not seen an A1 to know what it does.


(Nakh Home) #10

Thanks for your answer

i have found this nice project based on your project to get A1 sensors

i can create a local service which can give back to the http request information

http://localhost:8081/a1/temperature =>
{temperature:19.5 }

http://localhost:8081/a1/light =>
{ “light”: “dim” }

http://localhost:8081/a1/temperature =>
{ "temperature:19.5}

http://localhost:8081/a1/noise =>
{ "noise:normal}

my question is how to create a such item and more than this.

How to run periodically this script to check if a sensor has changed?

i will be glad to have an integration between my mosquitto server and those http requests

i have added an item as below

String Sensor_Data "Data" { http="<[http://localhost:8081/a1/temperature:60000:REGEX((.*))]" }

i have also tried this

String Sensor_Data "Data" { http="<[http://192.168.1.110:8081/a1/temperature:60000:JSONPATH(*.temperature))]" }

but no answer
2018-02-22%2023_54_05-debug

your help will be more than appreciated


(Nakh Home) #11

Hi

i have updated your script to support A1 with broadlink:

#!/usr/bin/env python

import paho.mqtt.client as paho  # pip install paho-mqtt
import broadlink  # pip install broadlink
import os
import sys
import time
import logging
import logging.config
import socket
import sched
HAVE_TLS = True
try:
    import ssl
except ImportError:
    HAVE_TLS = False
from threading import Thread
from test import TestDevice

# read initial config files
dirname = os.path.dirname(os.path.abspath(__file__)) + '/'
logging.config.fileConfig(dirname + 'logging.conf')
CONFIG = os.getenv('BROADLINKMQTTCONFIG', dirname + 'mqtt.conf')


class Config(object):
    def __init__(self, filename=CONFIG):
        self.config = {}
        self.config['ca_certs']     = None
        self.config['tls_version']  = None
        self.config['certfile']     = None
        self.config['keyfile']      = None
        self.config['tls_insecure'] = False
        self.config['tls']          = False
        execfile(filename, self.config)

        if HAVE_TLS == False:
            logging.error("TLS parameters set but no TLS available (SSL)")
            sys.exit(2)

        if self.config.get('ca_certs') is not None:
            self.config['tls'] = True

        if self.config.get('tls_version') is not None:
            if self.config.get('tls_version') == 'tlsv1':
                self.config['tls_version'] = ssl.PROTOCOL_TLSv1
            if self.config.get('tls_version') == 'tlsv1.2':
                # TLS v1.2 is available starting from python 2.7.9 and requires openssl version 1.0.1+.
                if sys.version_info >= (2,7,9):
                    self.config['tls_version'] = ssl.PROTOCOL_TLSv1_2
                else:
                    logging.error("TLS version 1.2 not available but 'tlsv1.2' is set.")
                    sys.exit(2)
            if self.config.get('tls_version') == 'sslv3':
                self.config['tls_version'] = ssl.PROTOCOL_SSLv3

    def get(self, key, default='special empty value'):
        v = self.config.get(key, default)
        if v == 'special empty value':
            logging.error("Configuration parameter '%s' should be specified" % key)
            sys.exit(2)
        return v


try:
    cf = Config()
except Exception, e:
    print "Cannot load configuration from file %s: %s" % (CONFIG, str(e))
    sys.exit(2)

qos = cf.get('mqtt_qos', 0)
retain = cf.get('mqtt_retain', False)

topic_prefix = cf.get('mqtt_topic_prefix', 'broadlink/')


# noinspection PyUnusedLocal
def on_message(client, device, msg):
    command = msg.topic[len(topic_prefix):]

    if command == 'temperature' or command == 'energy':  # internal notification
        return

    try:
        action = str(msg.payload)
        logging.debug("Received MQTT message " + msg.topic + " " + action)
        
        if device.type == 'A1' or device.type == 'a1':
            return
            
        if command == 'power':
            if device.type == 'SP1' or device.type == 'SP2':
                state = action == 'on'
                logging.debug("Setting power state to {0}".format(state))
                device.set_power(1 if state else 0)
                return

            if device.type == 'MP1':
                parts = action.split("/", 2)
                if len(parts) == 2:
                    sid = int(parts[0])
                    state = parts[1] == 'on'
                    logging.debug("Setting power state of socket {0} to {1}".format(sid, state))
                    device.set_power(sid, state)
                    return

        if device.type == 'RM2':
            file = dirname + "commands/" + command

            if action == '' or action == 'auto':
                record_or_replay(device, file)
                return
            elif action == 'record':
                record(device, file)
                return
            elif action == 'replay':
                replay(device, file)
                return
            elif action == 'macro':
                file = dirname + "macros/" + command
                macro(device, file)
                return

        logging.debug("Unrecognized MQTT message " + action)
    except Exception:
        logging.exception("Error")


# noinspection PyUnusedLocal
def on_connect(client, device, flags, result_code):
    topic = topic_prefix + '#'
    logging.debug("Connected to MQTT broker, subscribing to topic " + topic)
    mqttc.subscribe(topic, qos)


# noinspection PyUnusedLocal
def on_disconnect(client, device, rc):
    logging.debug("OOOOPS! Broadlink disconnects")
    time.sleep(10)


def record_or_replay(device, file):
    if os.path.isfile(file):
        replay(device, file)
    else:
        record(device, file)


def record(device, file):
    logging.debug("Recording command to file " + file)
    # receive packet
    device.enter_learning()
    ir_packet = None
    attempt = 0
    while ir_packet is None and attempt < 6:
        time.sleep(5)
        ir_packet = device.check_data()
        attempt = attempt + 1
    if ir_packet is not None:
        # write to file
        directory = os.path.dirname(file)
        if not os.path.exists(directory):
            os.makedirs(directory)
        with open(file, 'wb') as f:
            f.write(str(ir_packet).encode('hex'))
        logging.debug("Done")
    else:
        logging.warn("No command received")


def replay(device, file):
    logging.debug("Replaying command from file " + file)
    with open(file, 'rb') as f:
        ir_packet = f.read()
    device.send_data(ir_packet.decode('hex'))


def macro(device, file):
    logging.debug("Replaying macro from file " + file)
    with open(file, 'rb') as f:
        for line in f:
            line = line.strip(' \n\r\t')
            if len(line) == 0 or line.startswith("#"):
                continue
            if line.startswith("pause "):
                pause = int(line[6:].strip())
                logging.debug("Pause for " + str(pause) + " milliseconds")
                time.sleep(pause / 1000.0)
            else:
                command_file = dirname + "commands/" + line
                replay(device, command_file)


def get_device(cf):
    device_type = cf.get('device_type', 'lookup')
    if device_type == 'lookup':
        local_address = cf.get('local_address', None)
        lookup_timeout = cf.get('lookup_timeout', 20)
        devices = broadlink.discover(timeout=lookup_timeout) if local_address is None else \
            broadlink.discover(timeout=lookup_timeout, local_ip_address=local_address)
        if len(devices) == 0:
            logging.error('No Broadlink device found')
            sys.exit(2)
        if len(devices) > 1:
            logging.error('More than one Broadlink device found (' + ', '.join([d.host for d in devices]) + ')')
            sys.exit(2)
        return devices[0]
    elif device_type == 'test':
        return TestDevice(cf)
    else:
        host = (cf.get('device_host'), 80)
        mac = bytearray.fromhex(cf.get('device_mac').replace(':', ' '))
        if device_type == 'rm':
            return broadlink.rm(host=host, mac=mac)
        elif device_type == 'sp1':
            return broadlink.sp1(host=host, mac=mac)
        elif device_type == 'sp2':
            return broadlink.sp2(host=host, mac=mac)
        elif device_type == 'a1':
            return broadlink.a1(host=host, mac=mac)
        elif device_type == 'mp1':
            return broadlink.mp1(host=host, mac=mac)
        else:
            logging.error('Incorrect device configured: ' + device_type)
            sys.exit(2)


def broadlink_rm_temperature_timer(scheduler, delay, device):
    scheduler.enter(delay, 1, broadlink_rm_temperature_timer, [scheduler, delay, device])

    try:
        temperature = str(device.check_temperature())
        topic = topic_prefix + "temperature"
        logging.debug("Sending RM temperature " + temperature + " to topic " + topic)
        mqttc.publish(topic, temperature, qos=qos, retain=retain)
    except:
        logging.exception("Error")


def broadlink_sp_energy_timer(scheduler, delay, device):
    scheduler.enter(delay, 1, broadlink_sp_energy_timer, [scheduler, delay, device])

    try:
        energy = str(device.get_energy())
        topic = topic_prefix + "energy"
        logging.debug("Sending SP energy " + energy + " to topic " + topic)
        mqttc.publish(topic, energy, qos=qos, retain=retain)
    except:
        logging.exception("Error")

def broadlink_a1_sensors_timer(scheduler, delay, device):
    scheduler.enter(delay, 1, broadlink_a1_sensors_timer, [scheduler, delay, device])

    try:
        sensors = device.check_sensors_raw()
        for name in sensors:
            topic = topic_prefix + "sensor/" + name
                
            value = str(sensors[name])
            logging.debug("Sending A1 " + name + " " + value + " to topic " + topic)
            mqttc.publish(topic, value, qos=qos, retain=retain)
    except:
        logging.exception("Error")
		
		
class SchedulerThread(Thread):
    def __init__(self, scheduler):
        Thread.__init__(self)
        self.scheduler = scheduler

    def run(self):
        try:
            self.scheduler.run()
        except:
            logging.exception("Error")


		
if __name__ == '__main__':
    device = get_device(cf)
    device.auth()
    logging.debug('Connected to %s Broadlink device at %s' % (device.type, device.host))

    clientid = cf.get('mqtt_clientid', 'broadlink-%s' % os.getpid())
    # initialise MQTT broker connection
    mqttc = paho.Client(clientid, clean_session=cf.get('mqtt_clean_session', False), userdata=device)

    mqttc.on_message = on_message    


    mqttc.on_connect = on_connect
    mqttc.on_disconnect = on_disconnect

    mqttc.will_set('clients/broadlink', payload="Adios!", qos=0, retain=False)

    # Delays will be: 3, 6, 12, 24, 30, 30, ...
    # mqttc.reconnect_delay_set(delay=3, delay_max=30, exponential_backoff=True)

    if cf.get('tls') == True:
        mqttc.tls_set(cf.get('ca_certs'), cf.get('certfile'), cf.get('keyfile'), tls_version=cf.get('tls_version'), ciphers=None)

    if cf.get('tls_insecure'):
        mqttc.tls_insecure_set(True)

    mqttc.username_pw_set(cf.get('mqtt_username'), cf.get('mqtt_password'))
    mqttc.connect(cf.get('mqtt_broker', 'localhost'), int(cf.get('mqtt_port', '1883')), 60)

    broadlink_rm_temperature_interval = cf.get('broadlink_rm_temperature_interval', 0)
    if device.type == 'RM2' and broadlink_rm_temperature_interval > 0:
        scheduler = sched.scheduler(time.time, time.sleep)
        scheduler.enter(broadlink_rm_temperature_interval, 1, broadlink_rm_temperature_timer,
                        [scheduler, broadlink_rm_temperature_interval, device])
        # scheduler.run()
        tt = SchedulerThread(scheduler)
        tt.daemon = True
        tt.start()

    broadlink_sp_energy_interval = cf.get('broadlink_sp_energy_interval', 0)
    if device.type == 'SP2' and broadlink_sp_energy_interval > 0:
        scheduler = sched.scheduler(time.time, time.sleep)
        scheduler.enter(broadlink_sp_energy_interval, 1, broadlink_sp_energy_timer,[scheduler, broadlink_sp_energy_interval, device])
						
						
						
    broadlink_a1_sensors_interval = cf.get('broadlink_a1_sensors_interval', 0)
	#print "gggg " + broadlink_a1_sensors_interval 
    if device.type == 'A1' and broadlink_a1_sensors_interval > 0:
        scheduler = sched.scheduler(time.time, time.sleep)
        scheduler.enter(broadlink_a1_sensors_interval, 1, broadlink_a1_sensors_timer
                        ,[scheduler, broadlink_a1_sensors_interval, device])					
						
						
        # scheduler.run()
        tt = SchedulerThread(scheduler)
        tt.daemon = True
        tt.start()

    while True:
        try:
            mqttc.loop_forever()
        except socket.error:
            time.sleep(5)
        except KeyboardInterrupt:
            sys.exit(0)
        except:
            logging.exception("Error")


(Psyciknz) #12

Have a look at https://github.com/eschava/broadlink-mqtt version (the original), he may have A1 support also…or you can contribute your changes.

He’s also now implemented multiple device support.


(Aaron) #13

Do you have a tutorial on how to set this up or can you point me in the right direction will it work running on windows


(Nakh Home) #14