Matrix - synapse

hi there,

is there anybody who uses matrix/synapse (element) to send messages from openhab?
i think its very similar to xmpp.

maybe someone has already had experience with it and can share it

thanks

1 Like

Note There’s a much easier way of doing this using sendHttpPostRequest - see the next post! Even easier, just use the HTTP binding, and JINJA transformation - see post 4.


I’m self-hosting Matrix using the Synapse package.

I am pushing messages from openHAB to Matrix via MQTT. The flow is roughly as follows, though is not for the faint-of-heart - it’s ugly!

openhab → MQTT broker → paho-mqtt → matrix-commander → Matrix/Synapse

You will need

In openHAB:

  • Create a specific MQTT Thing to publish messages on a specific topic:
Bridge mqtt:broker:MosquittoMqttBroker "Mosquitto MQTT Broker" [
	host="mqtt.lan",
	secure=false,
	port=1883,
	clientID="OpenHAB3",
	username="",
	password=""
]

// Main Bedroom Light
Thing mqtt:topic:log "Log" (mqtt:broker:MosquittoMqttBroker) {
	Channels:
		Type string : strInfo "Info" [
			commandTopic="openhab/log/info"
		]
}
  • Create an Item to interface between an openHAB rule and the Thing
String strLogInfo "Log Info" {channel="mqtt:topic:log:strInfo"}

In Matrix/Synapse

  • Create a new user on my homeserver.
  • Create a room with this new user, and yourself (and others if you’d like)

With matrix-commander

  • Once installed, run the program for the first time so that it creates the correct credentials.

With paho-mqtt

  • Create a python script which will monitor the MQTT topic openhab/log/info and relay any messages received to matrix-commander
#!/usr/bin/env python3
import paho.mqtt.client as mqtt
import os

# The callback for when the client receives a CONNACK response from the server.
def on_connect(client, userdata, flags, rc):
    print("Connected with result code "+str(rc))

    # Subscribing in on_connect() means that if we lose the connection and
    # reconnect then subscriptions will be renewed.
    client.subscribe("openhab/log/info")

# The callback for when a PUBLISH message is received from the server.
def on_message(client, userdata, msg):
    message = msg.payload
    message = message.decode("utf-8")
    print(msg.topic+" "+message)
    os.system("/path/to/matrix-commander/matrix-commander.py -c \"/path/to/matrix-commander/credentials.json\" -s \"/path/to/matrix-commander/store/\" -m \""+message+"\"")

client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message

client.connect("192.168.1.151", 1883, 60)

# Blocking call that processes network traffic, dispatches callbacks and
# handles reconnecting.
# Other loop*() functions are available that give a threaded interface and a
# manual interface.
client.loop_forever()

Back in openHAB

I use Jython rules, so just send a command to the Item with the text that I want to be published to the Matrix room:

events.sendCommand("strLogInfo","DAY mode activated.")

In DSL rules this would be

strLogInfo.sendCommand("DAY mode activated")

Notes

  • I am running Proxmox on a server at home, which has separate LXC containers for each of the services mentioned above. I haven’t tried the instructions above on a single system. I have separate containers for, amongst other things:
    • openHAB
    • Mosquitto MQTT broker
    • Matrix/Synapse
    • matrix-commander & paho-mqtt (together in a container)
  • Probably the best way of interfacing with a Matrix room is using matrix-nio, but as this requires Python3 it can’t be referenced natively in openHAB. Something like HabApp will have to be used, but if you get this to work then it won’t require MQTT at all.
  • Another option might be to use the Exec binding or the Exec action to run matrix-commander directly from openHAB, but for me:
    • I couldn’t be bothered to mess about with the inevitable permissions issues
    • I wanted to keep my openHAB LXC container clean, only hosting openHAB
1 Like

Here’s a much easier way of sending messages to a Matrix room from a rule:

sendHttpPostRequest("https://YOURMATRIXDOMAIN/_matrix/client/r0/rooms/YOURROOMID/send/m.room.message?access_token=YOURACCESSTOKEN", "application/json", "{\"msgtype\":\"m.text\", \"body\":\"YOURMESSAGE\"}")

or a little more readable

val String URL = 'https://YOURMATRIXDOMAIN/_matrix/client/r0/rooms/YOURROOMID/send/m.room.message?access_token=YOURACCESSTOKEN'
var String DATA = '{
    "msgtype": "m.text",
    "body": "YOURMESSAGE"
}'

sendHttpPostRequest(URL, "application/json", DATA)

where:

  • YOURMATRIXDOMAIN your (subdomain) URL, such as matrix.example.org
  • YOURROOMID is the room ID in which you want the messages to appear. If you’re using Element as a client you can click on the room name, go to Advanced and you’ll find the Internal room ID which will look something like !hGbNiOPDjfsJfF:matrix.example.org
  • YOURACCESSTOKEN is the access token for your user. To find out my user token for my Matrix user openhab I executed the following command (substituting the password and URL) in a terminal on the same system that is running openHAB:
curl -XPOST -d '{"type":"m.login.password", "user":"openhab", "password":"PASSWORD"}' "https://matrix.example.org/_matrix/client/r0/login"

This will spit out something like the following, from which you can grab your access token.

{
  "user_id": "@openhab:matrix.example.org",
  "access_token": "YOURACCESSTOKEN",
  "home_server": "matrix.example.org",
  "device_id": "HFNFBSUHSK",
  "well_known": {
    "m.homeserver": {
      "base_url": "https://matrix.example.org/"
    }
  }
}

Notes

  • This assumes you have already setup a specific user (such as openhab) on your Matrix/Synapse homeserver, and that you have created a room with the user and one other user (maybe yourself).
  • In theory there should be a way of sending the access token as a header, rather than part of the URL, but I couldn’t get this to work properly: openHAB’s Jetty would spit out an error about basic authentication and I couldn’t be bothered to investigate further - there’s a chance Matrix/Synapse isn’t quite following the HTTP specification.
  • Using this method your messages from openHAB will be sent un-encrypted. As long as you’re still using HTTPS the message won’t be able to be read as it flies on its way to your Matrix/Synapse homeserver, but it does mean that if someone was to have admin access into your Matrix/Synapse server then they can read your messages. I’m not bothered, but you might be.
  • I’m using Jython (with the helper libraries), so the above code for me actually looks like:
from core.actions import HTTP

matrix_openhab_token = "MYUSERTOKEN"
matrix_homeserver_url = "MYMATRIXDOMAIN"
matrix_openhab_room = "MYROOMID"
matrix_message = "MYMESSAGE"

url = "https://{}/_matrix/client/r0/rooms/{}/send/m.room.message?access_token={}".format(matrix_homeserver_url, matrix_openhab_room,matrix_openhab_token) 

HTTP.sendHttpPostRequest(str(url), 'application/json', '{"msgtype":"m.text", "body":matrix_message}')

Even easier - use the HTTP Binding and JINJA transformation - no rules required.

All the prerequisites/caveats of the previous post still hold true, but everything can be configured via the UI if you desire. Simply, a String Item is linked to a String Channel in the HTTP Thing, and that String Channel uses a JINJA transformation to put the message into JSON required by the Matrix server.

Things file

//Matrix server
Thing http:url:matrix "Matrix" [
	baseURL = "https://YOURMATRIXDOMAIN/",
	refresh = "300",
	timeout ="3000",
	ignoreSSLErrors = "true",
	commandMethod = "POST"
]
{
	Channels:
		Type string: home "Home Room" [
			mode = "WRITEONLY",
			commandExtension = "_matrix/client/r0/rooms/YOURROOMID/send/m.room.message?access_token=YOURACCESSTOKEN",
			commandTransformation = "JINJA:{\"msgtype\":\"m.text\", \"body\":\"{{value}}\"}"
		]
}
OH3 UI YAML
UID: http:url:matrix
label: Matrix
thingTypeUID: http:url
configuration:
  authMode: BASIC
  ignoreSSLErrors: "true"
  baseURL: https://YOURMATRIXDOMAIN/
  delay: 0
  stateMethod: GET
  refresh: "300"
  commandMethod: POST
  timeout: "3000"
  bufferSize: 2048
channels:
  - id: home
    channelTypeUID: http:string
    label: Home Room
    description: null
    configuration:
      mode: WRITEONLY
      commandExtension: _matrix/client/r0/rooms/YOURROOMID/send/m.room.message?access_token=YOURACCESSTOKEN
      commandTransformation: JINJA:{"msgtype":"m.text", "body":"{{value}}"}

Items file

String strMatrixMessageHomeRoom "Matrix message home room" {channel="http:url:matrix:home"}

When I enter something like

https://YOURMATRIXDOMAIN/_matrix/client/r0/rooms/YOURROOMID/send/m.room.message?access_token=YOURACCESSTOKEN

into my browser, it says
grafik

As YOURMATRIXDOMAIN, I entered “matrix.org”. Could it be that this is not working for matrix.org instance?

Oh, no idea! Did you enter known working values for the room ID and access token too?

Wouldn’t surprise me if matrix.org prevented robots.

All of my posts have assumed a self hosted Synapse server.

1 Like