Slack integration: how to integrate

Yep - I finally got a chance to look at this. Seems the rtmbot has changed a bit since I wrote that integration. I have updated my GIST so try it out now and see if you have any luck.

1 Like

works like a charm now! (as I use the script on the same openHABian as OH2, I had to replace the IP-address of my OH2 in slackbot.py with “localhost”, otherwise I got connection refused errrors with the API).

Thanks a lot!

Do you use some actions or rules to send updates from openHAB to slack? e.g. I’d like to have a notification, if my son’s home from school, or the temperature of my heating storage tank is low, …

For that I use mqttwarn - so I publish the message to an MQTT topic (e.g. /openhab/notification/info) and have mqttwarn configured to watch that topic and publish the message to various endpoints (one of which is a Slack channel).

mqttwarn supports dozens of different services for sending messages to.

1 Like

OK, MQTT is on my to do list to check. Thanks.

Hey Thomas,
it’s always nice to see an idea work out in the end! :wink:
Do you think you can rephrase your first posting as instructions rather than a question so we can move this thread over to the Tutorials & Examples category? That would be amazing!

1 Like

ok. I can do that. But first, I have to ask @ben_jones12 two more Things! :wink:

for now I just used send, which worked with Switches (ON/OFF) and Numbers (e.g. 0-100 for light dimmers) and Strings.

From your readme, I get two more Options: “update” and “items”. Can you please explain the difference between send and update? In my cases, they had the same effect. :wink:

Unfortunately the command “items” threw some Errors:

items [Light_EG_WoZi_LG4]

2016-12-13 21:12:47,573 got process_message
2016-12-13 21:12:47,579 Starting new HTTP connection (1): localhost
2016-12-13 21:12:47,714 "GET /rest/items HTTP/1.1" 200 None
2016-12-13 21:12:47,721 Problem in Plugin Class: openHABPlugin: process_message 
{u'text': u'items [Light_EG_WoZi_LG4]', u'ts': u'1xxx.xxx', u'user': u'Uxxx', u'team': u'Txxx', u'type': u'message', u'channel': u'Dxxx'}
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/rtmbot/core.py", line 205, in do
    func(data)
  File "/home/pi/binderhaus/plugins/slackhab.py", line 82, in process_message
    for item in xml.fromstring(r.content).findall('item'):
  File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 1300, in XML
    parser.feed(text)
  File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 1642, in feed
    self._raiseerror(v)
  File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 1506, in _raiseerror
    raise err
ParseError: syntax error: line 1, column 0

I guess, it should write out some items and their states?
http://localhost:8080/rest/items works for me…

Thanks!

FYI - I have just made some changes to my script to make it easier to config and use.

Hi @ben_jones12,

I changed my config as described:

  • added slackhab.ini
  • changed slackhab.py

unfortunately I get this Errors, did I miss something?

2016-12-17 12:59:27,774 got process_user_typing
2016-12-17 12:59:27,877 got process_message
2016-12-17 12:59:27,892 Starting new HTTP connection (1): localhost
2016-12-17 12:59:28,026 "GET /rest/items HTTP/1.1" 200 None
2016-12-17 12:59:28,039 Problem in Plugin Class: SlackhabPlugin: process_message 
{u'text': u'status Binder_Vacation', u'ts': u'1xxx.000010', u'user': u'U3CMCHKJN', u'team': u'Txxx', u'type': u'message', u'channel': u'Dxxx'}
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/rtmbot/core.py", line 205, in do
    func(data)
  File "/home/pi/binderhaus/plugins/slackhab.py", line 81, in process_message
    item = self.get_single_item(filter, channel)
  File "/home/pi/binderhaus/plugins/slackhab.py", line 112, in get_single_item
    items = self.get_items(filter, channel)
  File "/home/pi/binderhaus/plugins/slackhab.py", line 137, in get_items
    for item in xml.fromstring(r.content).findall('item'):
  File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 1300, in XML
    parser.feed(text)
  File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 1642, in feed
    self._raiseerror(v)
  File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 1506, in _raiseerror
    raise err
ParseError: syntax error: line 1, column 0
2016-12-17 12:59:28,150 got process_desktop_notification
2016-12-17 12:59:31,274 got process_pong
2016-12-17 12:59:32,081 got process_user_typing
2016-12-17 12:59:32,184 got process_message
2016-12-17 12:59:32,191 Starting new HTTP connection (1): localhost
2016-12-17 12:59:32,353 "GET /rest/items HTTP/1.1" 200 None
2016-12-17 12:59:32,361 Problem in Plugin Class: SlackhabPlugin: process_message 
{u'text': u'send Binder_Vacation OFF', u'ts': u'1xxx.000011', u'user': u'Uxxx', u'team': u'Txxx', u'type': u'message', u'channel': u'Dxxx'}
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/rtmbot/core.py", line 205, in do
    func(data)
  File "/home/pi/binderhaus/plugins/slackhab.py", line 54, in process_message
    item = self.get_single_item(filter, channel)
  File "/home/pi/binderhaus/plugins/slackhab.py", line 112, in get_single_item
    items = self.get_items(filter, channel)
  File "/home/pi/binderhaus/plugins/slackhab.py", line 137, in get_items
    for item in xml.fromstring(r.content).findall('item'):
  File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 1300, in XML
    parser.feed(text)
  File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 1642, in feed
    self._raiseerror(v)
  File "/usr/lib/python2.7/xml/etree/ElementTree.py", line 1506, in _raiseerror
    raise err
ParseError: syntax error: line 1, column 0
2016-12-17 12:59:32,468 got process_desktop_notification
2016-12-17 12:59:35,284 got process_pong

items
Switch Binder_Vacation "Urlaub" (gBinder)

rtmbot.conf:

# Add the following to rtmbot.conf
DEBUG: True # make this False in production
SLACK_TOKEN: "xoxb-114746204098-xxx"
ACTIVE_PLUGINS:
#    - plugins.repeat.RepeatPlugin
    - plugins.slackhab.SlackhabPlugin

./plugins/slackhab.ini:

[slackhab]
debug            = True
openhab_url      = http://localhost:8080
slackhab_user_id = Uxxx

./plugins/slackhab.py (copied from your gist):

import ConfigParser
import os
import requests
import time
import xml.etree.ElementTree as xml

from rtmbot.core import Plugin

SCRIPTDIR  = os.path.dirname(__file__)
SCRIPTNAME = os.path.splitext(os.path.basename(__file__))[0]
CONFIGFILE = os.getenv(SCRIPTNAME.upper() + 'INI', os.path.join(SCRIPTDIR, SCRIPTNAME + '.ini'))

# parse our config/ini file
config = ConfigParser.ConfigParser()
config.read(CONFIGFILE)

debug            = config.get('slackhab', 'debug')
openhab_url      = config.get('slackhab', 'openhab_url')
slackhab_user_id = config.get('slackhab', 'slackhab_user_id')

# headers required for openhab REST API requests
headers          = { 'Content-Type': 'text/plain' }

class SlackhabPlugin(Plugin):

    def process_message(self, data):
        print_debug("rx'd message: %s" % (str(data)))

        # check we have sufficient details
        if 'channel' not in data or 'user' not in data or 'text' not in data:
            return

        channel = data['channel']
        user = data['user']
        text = data['text']

        # first check if we are interested in this command
        command_text = get_command_text(channel, user, text)

        if command_text is None:
            return

        tokens = command_text.split()
        if len(tokens) == 0:
            return

        command = tokens[0].lower()
        print_debug("command: %s" % (command))

        if command == "send" and len(tokens) >= 3:
            filter = tokens[1]
            value = " ".join(tokens[2:])
            
            item = self.get_single_item(filter, channel)
            if item is None:
                return
                
            url = openhab_url + '/rest/items/' + get_item_attr(item, 'name')
            r = requests.post(url, headers=headers, data=normalise_value(value))
            
            if self.check_response(r, channel):
                self.outputs.append([ channel, "```Sent %s command to %s```" % (value, get_item_attr(item, 'name')) ])

        elif command == "update" and len(tokens) >= 3:
            filter = tokens[1]
            value = " ".join(tokens[2:])
            
            item = self.get_single_item(filter, channel)
            if item is None:
                return
            
            url = openhab_url + '/rest/items/' + get_item_attr(item, 'name') + '/state'
            r = requests.put(url, headers=headers, data=normalise_value(value))

            if self.check_response(r, channel):
               self.outputs.append([ channel, "```Sent %s update to %s```" % (value, get_item_attr(item, 'name')) ])

        elif command == "status" and len(tokens) >= 2:
            filter = tokens[1]

            item = self.get_single_item(filter, channel)
            if item is None:
                return
            
            url = openhab_url + '/rest/items/' + get_item_attr(item, 'name') + '/state'
            r = requests.get(url, headers=headers)

            if self.check_response(r, channel):
                self.outputs.append([ channel, "```%s is %s```" % (get_item_attr(item, 'name'), r.text) ])

        elif command == "items":
            filter = None
            if len(tokens) > 1:
                filter = tokens[1]

            output = ""
            maxtypelen = 0
            maxnamelen = 0

            items = self.get_items(filter, channel)
            print_debug("%d items match %s" % (len(items), filter))

            if len(items) == 0:
                if filter is None:
                    self.outputs.append([ channel, "```No items found```" ])
                else:
                    self.outputs.append([ channel, "```No items found matching '%s'```" % (filter) ])
            else:
                self.print_items(items, channel)

    def get_single_item(self, filter, channel):    
        items = self.get_items(filter, channel)
        items_count = len(items)
        
        if items_count == 1:
            print_debug("found single matching item: %s" % (get_item_attr(items[0], 'name')))
            return items[0]

        if items_count > 1:
            self.outputs.append([ channel, "```Found %d items matching '%s', please restrict your filter```" % (items_count, filter) ])
            self.print_items(items, channel)
        else:
            self.outputs.append([ channel, "```No item found matching '%s'```" % (filter) ])
        return None
        
    def get_items(self, filter, channel):
        # cache this maybe?
        url = openhab_url + '/rest/items'
        r = requests.get(url, headers=headers)

        if not self.check_response(r, channel):
            return []
            
        filter = filter.lower()
        items = []

        for item in xml.fromstring(r.content).findall('item'):
            name = get_item_attr(item, 'name').lower()
            if filter is None or filter in name:
                items.append(item)

        return items

    def print_items(self, items, channel):
        output = ""
        maxtypelen = 0
        maxnamelen = 0

        # get the max name and type lengths so we can format our output nicely
        for item in items:
            name  = get_item_attr(item, 'name')
            type  = get_item_attr(item, 'type')
            if len(type) > maxtypelen:
                maxtypelen = len(type)
            if len(name) > maxnamelen:
                maxnamelen = len(name)

        for item in items:
            name  = get_item_attr(item, 'name')
            state = get_item_attr(item, 'state')
            type  = get_item_attr(item, 'type')
            output = output + "%s%s%s\n" % ( type.ljust( maxtypelen + 5 ), name.ljust( maxnamelen + 5 ), state )
            if len(output) >= 8000:
                output = output + "... (too much output, please specify a filter)"
                break

        self.outputs.append([ channel, "```%s```" % (output) ])


    def check_response(self, r, channel):
        # check our rest api call was successful
        if r.status_code == 200:
            return True
        if r.status_code == 201:
            return True

        # log the response code/reason back to our slack channel
        self.outputs.append([ channel, "```%d: %s```" % (r.status_code, r.reason) ])
        return False

def normalise_value(state):
    if state.lower() == "on":
        return "ON"
    if state.lower() == "off":
        return "OFF"
    if state.lower() == "open":
        return "OPEN"
    if state.lower() == "closed":
        return "CLOSED"

    return state

def get_command_text(channel, user, text):
    if channel == "" or channel is None:
        return None
    if user == "" or user is None:
        return None
    if text == "" or text is None:
        return None

    # check for a message directed at our bot
    user_tag = "<@%s>:" % (slackhab_user_id)
    if text.startswith(user_tag):
        return text[len(user_tag):]

    user_tag = "<@%s>" % (slackhab_user_id)
    if text.startswith(user_tag):
        return text[len(user_tag):]

    # check for a DM to our bot
    if channel.startswith("D"):
        return text

    return None

def get_item_names(items):
    return ", ".join(get_item_attr(item, 'name') for item in items)
        
def get_item_attr(item, attr):
    return item.find(attr).text

def print_debug(message):
    if debug:
        print message

curl http://localhost:8080/rest/items in the console brings up the item:

[(...){"link":"http://localhost:8080/rest/items/Binder_Vacation","state":"OFF","type":"Switch","name":"Binder_Vacation","label":"Urlaub","tags":[],"groupNames":["gBinder"]} (...)]

What do you see if you put http://<openhab-ip>:8080/rest/items in your browser? Looks like the XML parser is having trouble with the XML returned.

Hi Ben. I posted it above. Of course there are more items than this. But does “send” need this, too?

That is JSON, not XML. My script is expecting XML. I didn’t realise OH2 had changed the REST API format to JSON - sorry!!

You will need to update my script to parse the JSON (pretty easy in Python with some Googling).

1 Like

I added a diff to your gist here that makes your excellent Slack integration work under openHAB 2 (parse JSON and not XML). The wiki still needs a bit of an update, though. :wink:

1 Like

Awesome - thanks John - I will be sure to pick this up when I upgrade to OH2!!

1 Like

That’s so great! Thanks @watou!!!
I’ll finish my docu over the next days - and will post it then.

Ben can you share you mqttwarn.ini ? My config won’t post to slack.

item file

Switch	Wohnzimmer		{ channel="hue:0100:xxxxxxxxxxx:1:brightness" }

String      VT_Notify_Trace                     { mqtt=">[broker:/openhab/notification/trace:state:*:default]" }
String      VT_Notify_Info                      { mqtt=">[broker:/openhab/notification/info:state:*:default]" }
String      VT_Notify_Alert                     { mqtt=">[broker:/openhab/notification/alert:state:*:default]" }
String      VT_Notify_Warn                      { mqtt=">[broker:/openhab/notification/warn:state:*:default]" }
String      VT_Notify_Alarm                     { mqtt=">[broker:/openhab/notification/alarm:state:*:default]" }

rule file

rule "Wohnzimmer Licht"
when
    Item Wohnzimmer changed
then
    VT_Notify_Alarm.postUpdate("The burglar alarm has been tripped!!")
end

mqttwarn.ini

[config:slack]
token = 'xoxb-xxxxxxxxxxxx-xxxxxxxxxxxx'
targets = {
                # #channel/@user        username,       icon
   'general'    : [ '#allgemein',               "openhab",      ':syringe:' ],
  }

[/openhab/notification/]
targets = slack:mqtt-client
format  = -->{name}<--

Got it working with this config on mqttwarn.ini
Dont forget to add “slack” on launch inside mqttwarn.ini !

[config:slack]
token = 'xoxb-xxxxxxxxxxxx-xxxxxxxxxxxx'
targets = {
              #  #channel/@user   username, icon
   'mqtt-client'  : [ '#allgemein',       "openhab",   ':hankey:' ]
  }

[/openhab/notification/]
targets = file:mylog, log:info

[#]
targets = slack:mqtt-client
1 Like

FYI - I have updated this to use the new Slack Python SDK and dockerised it - see https://hub.docker.com/repository/docker/sumnerboy12/slackhab.

Thank you, Ben. I’ve been running your dockerised version since you shared it and it’s as great as it ever was. It stops occasionally for some odd reason but restarts no problem. I also switched to the dockerised mqttwarn and aside from needing to add the slack library to the image, also working great. Cheers! John

Good to hear! I have also switched to dockerised mqttwarn. Thanks for the feedback.