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"}
2 Likes

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

I have HTTP binding error
org.eclipse.jetty.client.HttpResponseException: HTTP protocol violation: Authentication challenge without WWW-Authenticate header
What could be the problem?

Solved
In curl -XPOST my key code %21C…
HTTP binding need !C…

1 Like

Expansion of the Openhab+Matrix functionality.

Sending commands from the Matrix room to Openhab

Things file

Thing http:url:matrix "Matrix. Message" [
        baseURL = "https://MY_MATRIX_DOMAIN:8448/",
        refresh = "60",
        timeout ="3000",
        ignoreSSLErrors = "false",
        commandMethod = "POST"
]
{
        Channels:
                Type string: openhab_matrix "Matrix. Send message" [
                        mode = "WRITEONLY",
                        commandExtension = "_matrix/client/r0/rooms/MY_ROOM_ID/send/m.room.message?access_token=ACCESS_TOKEN",
                        commandTransformation = "JINJA:{\"msgtype\":\"m.text\",\"format\": \"org.matrix.custom.html\",\"body\":\"{{value}}\", \"formatted_body\":\"{{value}}\"}"
                ]
}
Thing http:url:matrix_command "Matrix. Command" [
        baseURL = "https://MY_MATRIX_DOMAIN:8448/_matrix/client/v3/rooms/MY_ROOM_ID/messages?access_token=ACCESS_TOKEN&dir=b&limit=1"
        refresh = "60",
        timeout ="3000",
        ignoreSSLErrors = "false",
        commandMethod = "GET"
]
{
        Channels:
                Type string: message_type "Message type" [
                        mode = "READONLY",
                        stateTransformation = "JSONPATH:$.chunk[0].content.msgtype"
                ]
                Type string: message "Message" [
                        mode = "READONLY",
                        stateTransformation = "JSONPATH:$.chunk[0].content.body"
                ]
                Type string: message_id "Message id" [
                        mode = "READONLY",
                        stateTransformation = "JSONPATH:$.chunk[0].event_id"
                ]
                Type string: user_id "User id" [
                        mode = "READONLY",
                        stateTransformation = "JSONPATH:$.chunk[0].user_id"
                ]

}

Items file


String openhab_matrix_p "Matrix. Send message" ["Point"] { channel="http:url:matrix:openhab_matrix"}
String matrix_command_message_id_p "Matrix. Message id" ["Point"] { channel="http:url:matrix_command:message_id"}
String matrix_command_message_type_p "Matrix. Message type" ["Point"] { channel="http:url:matrix_command:message_type"}
String matrix_command_message_p "Matrix. Message" ["Point"] { channel="http:url:matrix_command:message"}
String matrix_command_user_id_p "Matrix. User id" ["Point"] { channel="http:url:matrix_command:user_id"}

Rules file


rule "Matrix. Command"
when
    Item matrix_command_message_id_p changed
then
        if (matrix_command_message_type_p.state == "m.text" && matrix_command_user_id_p.state == "YOUR_USER_ID" ) {
                var mm = matrix_command_message_p.state

                if(mm == "Help"){

                var cl = "\u003cp\u003eCommand list:\u003c/p\u003e"
                var cl1 = "\u003cp\u003e1. Help - list command\u003c/p\u003e"
                var cl2 = "\u003cp\u003e2. \u003cb\u003eSwitch off\u003c/b\u003e - Room Switch off\u003c/p\u003e"
                
                openhab_matrix_p.sendCommand(cl + cl1 + cl2)
                return
                }

                if(mm == "Switch off"){
                openhab_matrix_p.sendCommand("\u003cp\u003e" + mm + "\u003c/p\u003e")
                YOUR_SWITCH.sendCommand(OFF)
                return
                }

        }
end
1 Like

Oh incredible - just to confirm, is this so that you can control openHAB by sending messages to a specific Matrix room?

And presumably this is poll based, so every 60 seconds (or whatever), openHAB will check the Matrix room and process every (or just the last?) message and act accordingly?

Yes.
For simplicity of implementation - only the last message.
Polling time, can be changed by parameter

    refresh = "60"

OK, yeah, so perhaps a little tricky in a room with multiple people unless polling every second.

I’ll see if I can build on this to process all messages since the last one - I might have to break away from Blockly for that one…!!

Thanks for sharing!

It’s possible. But it takes more time to implement.
For my tasks. One room. Two accounts, me and openhab. Enough.

If you are looking for a docker based approach with end to end encryption: I summarized my solution here at github. Not ideal but fits my needs, as especially end-to-end encryption is given in difference to the solutions so far and no changes to the Dockerfile of openhab are required.

Hi,

I am gladly using the Matrix Messaging option.
I would like to “format” my message with a “line break”.
How would i do this? From Messagin with xmpp it would have been “\n” in the quoted text-part, but that does not work for Matrix - I tried.

events.sendCommand("MatrixMessage", "TestButton - MatrixMessage: " + test);

I would like the line break after the “TestButton - MatrixMessage:” befor the value of the variable “test” shows up.
Thanks for your help in advance.

Eckart

The following works for me:

events.sendCommand("MatrixMessage", "TestButton - MatrixMessage:\n" + test);

Hi,
thank you for your help, but unfortunately that does not work for me. In tails it shows only:

[WARN ] [p.internal.http.HttpResponseListener] - Requesting ‘https://myserver/_matrix/client/r0/rooms/myroom/send/m.room.message?access_token=mytoken’ (method=‘POST’, content=‘org.eclipse.jetty.client.util.StringContentProvider@db9b5c’) failed: 400 Bad Request

I did not implement the “Matrix.command” part, but that should not make a change in my understanding for the Matrix.message part.
I think I will resign on formatting the messages.
Thanks.

Eckart