Broadlink S1C Kit sensors in HA using python and MQTT

import broadlink
import time, os, datetime

mosquitto_address = ""
mosquitto_port = ""
mosquitto_user = ""
mosquitto_password = ""
broadlink_s1c_ip = ""
broadlink_s1c_mac = ""

devices = broadlink.S1C(host=(broadlink_s1c_ip, 80), mac=bytearray.fromhex(broadlink_s1c_mac))
devices.auth()

sens = devices.get_sensors_status()
now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
old = sens


def checkSensor(v_type, v_name, v_status):
    if v_type == "Door Sensor" and v_status in ("0", "128"):
        sendToMosquito(v_name, "Closed")
    elif v_type == "Door Sensor" and v_status in ("16", "144"):
        sendToMosquito(v_name, "Open")
    elif v_type == "Door Sensor" and v_status == "48":
        sendToMosquito(v_name, "Tampered")
    elif v_type == "Motion Sensor" and v_status in ("0", "128"):
        sendToMosquito(v_name, "No_motion")
    elif v_type == "Motion Sensor" and v_status == "16":
        sendToMosquito(v_name, "Motion_Detected")
    elif v_type == "Motion Sensor" and v_status == "32":
        sendToMosquito(v_name, "Tampered")
    return


def sendToMosquito(v_deviceName, v_payload):
    os.system("mosquitto_pub -h " + mosquitto_address + " -p " + mosquitto_port + " -t 'sensors/s1c/" + v_deviceName + "' -u " + mosquitto_user + " -P " + mosquitto_password + " -m " + v_payload)
    return

#for k, se in enumerate(sens['sensors']):
#    checkSensor(se['type'], ((se['name']).replace(" ", "_")).lower(), str(se['status']))


while 1:
    try:
        sens = devices.get_sensors_status()
        for i, se in enumerate(sens['sensors']):
            if se['status'] != old['sensors'][i]['status']:
                checkSensor(se['type'], ((se['name']).replace(" ", "_")).lower(), str(se['status']))
                old = sens
    except:
        continue
1 Like

@TomerFi You can use binary sensor and automations for the retain option

Here’s an example:

# binary sensor

- platform: mqtt
  name: "Front door sensor"
  qos: 0
  state_topic: "home/entrance/door"
  payload_on: "111111"
  payload_off: "222222"
  device_class: opening
  
  # automations
  
  - alias: 'Front Door Sensor Opened Retain'
  initial_state: 'on'
  trigger:
    platform: mqtt
    topic: sensors/s1c/frontdoor
    payload: 111111
  action:
    service: mqtt.publish
    data:
      topic: 'home/entrance/door'
      payload: '111111'
      retain: 'true'


- alias: 'Front Door Sensor Closed Retain'
  initial_state: 'on'
  trigger:
    platform: mqtt
    topic: sensors/s1c/frontdoor
    payload: 222222
  action:
    service: mqtt.publish
    data:
      topic: 'home/entrance/door'
      payload: '222222'
      retain: 'true’

thanks so much and now it works!

1 Like

@NightRanger
Very cool, thank you!
Please correct me if I’m wrong here, but as far as I can see this configuration will help me retain the the latest state of the door and not the current state, which means that in the following order of events the sensor will show the wrong state:

1- The door is closed and the sensor shows “Closed”.
2- The ha system goes down.
3- I open the door.
4- I boot up my ha system.
5- The sensor will show the door is “Closed” even though the door is actually open, to correct this I’ll have to close the door and reopen it so that the sensor will change to “Open”.

There are a couple of ways to accomplish that,
You can also have an automation that updates a designated variable or an input_text object each time the mqtt sensor changes its state as long as the state exists, and have a template sensor which shows the value of the variable or the input_text object, and this will be the sensor you show in your frontend. I did something similar to this for my AC.

I’m going to try something else, I’ll create another version of your script that will scan the sensors and publish the correct topic for the door state once, I’ll then create an automation that will call this script each time the ha system starts, which means my mqtt sensors will always show the correct state.
I think this will be a great addition to an excellent script done by you.

Yes you are correct it will retain the last sensor state

Hiya all,

sorry for my naivete, I’m a bloody beginner in HA, Hass.io and NOT a coder at all…
I try to wrap my mind around the installation instructions of gabriel.colceriu and TomerFi with mixed success. I think part of it is because the code was not posted as preformatted text, which causes the apostrophes to be messed up and backslashes disappear etc. So I want to change that and re-post his code in preformatted text:


dockerfile:

ARG BUILD_FROM
FROM $BUILD_FROM

# Add env
ENV LANG C.UTF-8

RUN apk add --no-cache \
    jq \
    py2-pip \
    clang \
    libgcc \
    gcc-gnat \
    libgc++ \
    g++ \
    make \
    libffi-dev \
    openssl-dev \
    python2-dev \
    mosquitto \
    mosquitto-dev \
    mosquitto-libs \
    mosquitto-clients
		
RUN pip install pyaes
RUN pip install broadlink
RUN pip install pycrypto

RUN apk add --no-cache \
    jq \
    py-pip \
    python \
    python-dev \
    python3 \
    mosquitto \
    mosquitto-clients \
    python3-dev \
&& pip install -U pip \
&& pip3 install -U pip \
&& pip install -U virtualenv
		
# Copy data for add-on
COPY run.sh /
RUN chmod a+x /run.sh

CMD [ "/run.sh" ]

config.json:

{
  "name": "Broadlink S1C Python",
  "version": "1.0.7",
  "slug": "s1c",
  "description": "Hass.io addon for the Broadlink Python S1C Script.",
  "startup": "application",
  "boot": "auto",
  "ports": {"5055/tcp": null},
  "options": {"code": null,
              "requirements": []},
  "schema": {"code": "str",
             "requirements": ["str"],
             "clean": "bool?"},
  "map": ["share"]
}

run.sh:

#!/bin/bash
set -e

CONFIG_PATH=/data/options.json
requirements=$(cat /data/options.json | jq -r 'if .requirements then .requirements | join(" ") else "" end')
code=$(cat /data/options.json | jq -r '.code')
clean=$(cat /data/options.json | jq -r '.clean //empty')
py2=$(cat /data/options.json | jq -r '.python2 // empty')

PYTHON=$(which python3)

if [ "${py2}" == "true" ];
then
    PYTHON=$(which python2)
fi

if [ -n "$requirements" ];
then
    if [ "$clean" == "true" ];
    then
	rm -rf /data/venv/
    fi
    if [ ! -f "/data/venv/bin/activate" ];
    then
       mkdir -p /data/venv/
       cd /data/venv
       virtualenv -p ${PYTHON} .
       . bin/activate
    fi
    pip install -U ${requirements}
fi
python ${code}

s1c.py (copied from TomerFi):

import broadlink
import time, os, datetime

mosquitto_address = "x.x.x.x" #your HA IP address
mosquitto_port = "1883" #your HA port
mosquitto_user = "" #your MQTT username
mosquitto_password = "" your MQTT password
broadlink_s1c_ip = "x.x.x.x" #your Broadlink IP address
broadlink_s1c_mac = "34EA......" #your Broadlink MAC address

devices = broadlink.S1C(host=(broadlink_s1c_ip, 80), mac=bytearray.fromhex(broadlink_s1c_mac))
devices.auth()

sens = devices.get_sensors_status()
now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) #This is a line added by me
old = sens

def checkSensor(v_type, v_name, v_status):
    if v_type == "Door Sensor" and v_status in ("0", "128"):
        sendToMosquito(v_name, "Closed")
    elif v_type == "Door Sensor" and v_status in ("16", "144"):
        sendToMosquito(v_name, "Open")
    elif v_type == "Door Sensor" and v_status == "48":
        sendToMosquito(v_name, "Tampered")
    elif v_type == "Motion Sensor" and v_status in ("0", "128"):
        sendToMosquito(v_name, "No_motion")
    elif v_type == "Motion Sensor" and v_status == "16":
        sendToMosquito(v_name, "Motion_Detected")
    elif v_type == "Motion Sensor" and v_status == "32":
        sendToMosquito(v_name, "Tampered")
    return

def sendToMosquito(v_deviceName, v_payload):
    os.system("mosquitto_pub -h " + mosquitto_address + " -p " + mosquitto_port + " -t 'sensors/s1c/" + v_deviceName + "' -u " + mosquitto_user + " -P " + mosquitto_password + " -m " + v_payload)
    return

while 1:
    try:
        sens = devices.get_sensors_status()
        for i, se in enumerate(sens['sensors']):
            if se['status'] != old['sensors'][i]['status']:
                checkSensor(se['type'], ((se['name']).replace(" ", "_")).lower(), str(se['status']))
                old = sens
    except:
        continue

Options:

{
  "code": "/share/s1c.py",
  "requirements": [
    "broadlink"
  ]
}

Does the code look correct to you professionals? I have not changed much except adjusting all the apostrophes and backslashes and formatted it so it looks more clear

Thanks for your helping out a noobie!

Best, Marc

Check out your supervisor log to see errors on installing the addon.

yea the Hass.io broke down and I had to restore a snapshot. therefore no access to the log.
Can you kindly tell me… the “ports” in config.json, should it be 5055 or 80?
Thanks

Mine it is on 5055 and the addon installs. That “ports” value on config.json is only to expose network ports from the container. I think that is no need for that option. As i said in my first post the addon is developed from Beslibre`s addon python_exec and i coppied much of the code without changing much.

Hi again,

I finally managed to start the addon, but after a while I receive this error in logs and the addon stops:

Logs
starting version 3.2.2
Running virtualenv with interpreter /usr/bin/python3
Using base prefix '/usr'
New python executable in /data/venv/bin/python3
Also creating executable in /data/venv/bin/python
Installing setuptools, pip, wheel...done.
Collecting broadlink
  Using cached broadlink-0.5.tar.gz
Collecting pycrypto==2.6.1 (from broadlink)
  Using cached pycrypto-2.6.1.tar.gz
Building wheels for collected packages: broadlink, pycrypto
  Running setup.py bdist_wheel for broadlink: started
  Running setup.py bdist_wheel for broadlink: finished with status 'done'
  Stored in directory: /root/.cache/pip/wheels/b2/8c/e9/35817ee911d6e3496fff13a2c405cca66a9b64120d08486e1a
  Running setup.py bdist_wheel for pycrypto: started
  Running setup.py bdist_wheel for pycrypto: still running...
  Running setup.py bdist_wheel for pycrypto: finished with status 'done'
  Stored in directory: /root/.cache/pip/wheels/80/1f/94/f76e9746864f198eb0e304aeec319159fa41b082f61281ffce
Successfully built broadlink pycrypto
Installing collected packages: pycrypto, broadlink
Successfully installed broadlink-0.5 pycrypto-2.6.1
Traceback (most recent call last):
  File "/share/s1c.py", line 11, in <module>
    devices = broadlink.S1C(host=(broadlink_s1c_ip, 80), mac=bytearray.fromhex(broadlink_s1c_mac))
AttributeError: module 'broadlink' has no attribute 'S1C'

Any idea what I’m doing wrong?
Thanks you all!

You have to use jazzina’s code to work:

jazzina code wasn’t added to the official python-broadlink library and I didn’t want to to make any breaking changes to my Hassbian so I just downloaded the library from here:

wget python-broadlink/broadlink/__init__.py at master · jazzina/python-broadlink · GitHub -O /home/pi/s1c/broadlink.py

And put the broadlink.py file in the same folder as s1c.py is.

I’m too dumb for this…
Now I get this error:

starting version 3.2.2
Requirement already up-to-date: broadlink in /usr/lib/python2.7/site-packages
Requirement already up-to-date: pyaes==1.6.0 in /usr/lib/python2.7/site-packages (from broadlink)
Traceback (most recent call last):
  File "/share/s1c.py", line 12, in <module>
    devices.auth()
  File "/share/broadlink.py", line 208, in auth
    response = self.send_packet(0x65, payload)
  File "/share/broadlink.py", line 284, in send_packet
    response = self.cs.recvfrom(2048)
socket.timeout: timed out

Sorry to be a pain…

Set the ip and mac of the S1C Broadlink device on the s1c.py or be sure that the informations are correct.

I checked and double checked, they are correct.
The MAC addy is entered without the semicolons, right?
ie. broadlink_s1c_ip = “192.168.1.200”
broadlink_s1c_mac = “34EA34AABBCC”

Correct. But be sure that the ip and the mac is correct. Verify on your router. Set up the router to serve the same ip to the s1c.

Yes, I’ve done that. The router shows the Broadlink Bridge on the reserved address 192.168.1.200 and the MAC address is correct. But I get the above error message regardless :frowning:

You need to set the IP address and MAC address of the s1c not the rm bridge

Well, after today seems that my script doesn’t work anymore. Reinstalled the addon, restarted the pi but nothing works. The script get’s stuck on:

starting version 3.2.2
Requirement already up-to-date: broadlink in /usr/lib/python2.7/site-packages
Requirement already up-to-date: pyaes==1.6.0 in /usr/lib/python2.7/site-packages (from broadlink)

I think i will do a reinstall of the all system. Anyone got any ideas?

Sorry for being unclear. I meant the S1C. I don’t have any RM bridges… I will move the S1C so it’s on the same router as the Pi, maybe that will help.

Yes, I get the same log. I think I solved my previous error message by moving the S1C near the same router as the Pi. For some reason, the Pi couldn’t see the S1C on a different router. Thanks for all your help and assistance!