Creating ROS openhab_bridge with HABApp, ROS openhab_msgs, openhab_subscribers and openhab_publishers

I have taken on the topic of ROS and openHAB to dovetail better with each other. ROS applications can easily be started via the Exec binding. Access to items is done via the REST API. How I can write a publisher and subscriber to all items, I have possibly already solved.

openhab_msgs

There should be no binding, because ROS only supports C++ or Python.

ROS allows you to define your own message types. And here I could use a little help so that I don’t miss anything important. For the commands, I would like to cover all possibilities. I will explain it in more detail for each type. In ROS you can use enumerations for many types.

Generally the item name (string), DateTime of last change (time) and state or command should be sent. The DateTime of the last change should be inside the std_msgs/Header:
http://docs.ros.org/en/noetic/api/std_msgs/html/msg/Header.html

I orient myself to the openHAB items:

The openhab_msgs can be found here:

ColorState

Color information (RGB)

# Color information (RGB)

Header header
string item # the name of the item
float32 r
float32 g
float32 b

You have to split the RGB values into three fields (r, g, b).

ColorCommand

OnOff, IncreaseDecrease, Percent, HSB

# Color information (RGB)

# Possible commands for Color
string ON=ON
string OFF=OFF
string INCREASE=INCREASE
string DECREASE=DECREASE

string command # Color command enumerated above
bool iscommand # False if you do not want to send a command enumerated above
bool ispercentage # False if you do not want to send a percentage to your item

int8 PERCENTAGE0=0
[...]
int8 PERCENTAGE100=100

int8 percentage
bool ishsb # False if you do not want to send HSB values for changing the item
uint32 hue        # Color of the light
uint32 saturation # Saturation of the light
uint32 brightness # Brightness of the light
Header header
string item # the name of the item

ContactState

Status of contacts, e.g. door/window contacts. Does not accept commands, only status updates.

# Status of contacts e.g. door/window contacts.
# Does not accept commands, only status updates.

# Possbile states for Contact
string OPEN=OPEN
string CLOSED=CLOSED

string state
Header header
string item # the name of the item

ContactCommand

OpenClosed

# Status of contacts e.g. door/window contacts.
# Does not accept commands, only status updates.

# Possbile commands for Contact
string OPEN=OPEN
string CLOSED=CLOSED

string command # Contact commands enumerated above
Header header
string item # the name of the item

DateTimeState

Stores date and time

# Stores date and time

Header header
string item # the name of the item
time state # the datetime value of the item

I am actually not sure if DateTime in ROS could really be sent with time.

DateTimeCommand

n/a

# Stores date and time

Header header
string item # the name of the item
time command # the datetime value of the item

DimmerState

Percentage value for dimmers

# Percentage value for dimmers

# Possible states for Dimmer
int8 PERCENTAGE0=0
[...]
int8 PERCENTAGE100=100

int8 state # Dimmer state enumerated above
Header header
string item # the name of the item

In ROS it is apparently not possible to restrict a value for a field between two values. Instead I work again with an enumeration.

DimmerCommand

OnOff, IncreaseDecrease, Percent

# Percentage value for dimmers

# Possible commands for Dimmer
string ON=ON
string OFF=OFF
string INCREASE=INCREASE
string DECREASE=DECREASE

string command # Dimmer command enumerated above
bool iscommand # True if you want to send a command enumerated above
bool ispercentage # True if you want to send a percentage enumerated above

# Possible percentages for Dimmer
int8 PERCENTAGE0=0
[...]
int8 PERCENTAGE100=100

int8 percentage
Header header
string item # the name of the item

Also it is not possible in ROS that I use int8 and string for the same field. One possibility would be that I pass percentage as a string and again use an enumeration for this. The other variant would be again over two variables and accordingly also two enumerations.

GroupState

Item to nest other items / collect them in groups

# Item to nest other items / collect them in groups.

Header header
string item # the name of the group

With groups, it may make little sense for them to be published. One knows at most the name of the group then. Here it makes also no sense that one sends a command. It is also not possible. Possibly, that I have not tested you could change the name of a group about postUpdate. But that would mess up the system.

ImageState

Binary data of an image

# Binary data of an image (openHAB) converted to sensor_msgs/Image of ROS

Header header
string item # the name of the item
sensor_msgs/Image state # the real image data

ROS would actually cause problems if you were to send something as a binary. What would definitely be advantageous is if you convert the binary back to an image and then send it in ROS accordingly. So in the publisher you have to convert the image.

ImageCommand

n/a

# Binary data of an image (openHAB) converted to sensor_msgs/Image of ROS

Header header
string item # the name of the item
sensor_msgs/Image command # the real image data

In openHAB there is unfortunately no sendCommand for Image. Now, of course, there are robots that record a camera or otherwise generate images. My idea would be that the subscriber then also performs a postUpdate and that one thus changes an image item. This could be without a channel initially simply defined as zero. This definitely needs to be tested in more detail later.

LocationState

GPS coordinates

# GPS coordinates

Header header
string item # the name of the item
# Latitude [degrees]. Positive is north of equator; negative is south.
float64 latitude

# Longitude [degrees]. Positive is east of prime meridian; negative is west.
float64 longitude

# Altitude [m]. Positive is above the WGS 84 ellipsoid
# (quiet NaN if no altitude is available).
float64 altitude

With the location items, I am unsure how to use them properly. Does the status change at all? I’ll come back with the robot example… Theoretically yes also conceivable that he drives and the GPS coordinates change.

LocationCommand

Point

# GPS coordinates

Header header
string item # the name of the item
# Latitude [degrees]. Positive is north of equator; negative is south.
float64 latitude

# Longitude [degrees]. Positive is east of prime meridian; negative is west.
float64 longitude

# Altitude [m]. Positive is above the WGS 84 ellipsoid
# (quiet NaN if no altitude is available).
float64 altitude

NumberState

Values in number format

# Values in number format

Header header
string item # the name of the item
float64 state # the number value of the item

I don’t think I have to say much about Number. It can be different numerical values. With ROS float64 would be best suited here.

NumberCommand

Decimal

# Values in number format

Header header
string item # the name of the item
float64 command # the number command for the item

PlayerState

Allows control of players (e.g. audio players)

# Allows control of players (e.g. audio players)

# Possible states for Player
string PLAY=PLAY
string PAUSE=PAUSE
string NEXT=NEXT
string PREVIOUS=PREVIOUS
string REWIND=REWIND
string FASTFORWARD=FASTFORWARD

string state # Player state enumerated above
Header header
string item # the name of the item

The status for player is sent as enumeration. Can this item accept other statuses?

PlayerCommand

PlayPause, NextPrevious, RewindFastforward

# Allows control of players (e.g. audio players)

# Possible commands for Player
string PLAY=PLAY
string PAUSE=PAUSE
string NEXT=NEXT
string PREVIOUS=PREVIOUS
string REWIND=REWIND
string FASTFORWARD=FASTFORWARD

string command # Player command enumerated above
Header header
string item # the name of the item

RollershutterState

Roller shutter Item, typically used for blinds

# Roller shutter Item, typically used for blinds

# Possible states for Rollershutter
string UP=UP
string DOWN=DOWN
string STOP=STOP
string MOVE=MOVE

string state # Rollershutter state enumerated above
bool isstate
bool ispercentage

int8 PERCENTAGE0=0
[...]
int8 PERCENTAGE100=100

int8 percentage
Header header
string item # the name of the item

Again, as described above, one could work with boolean variables and a second enumeration.

RollershutterCommand

UpDown, StopMove, Percent

# Roller shutter Item, typically used for blinds

# Possible commands for Rollershutter
string UP=UP
string DOWN=DOWN
string STOP=STOP
string MOVE=MOVE

string command # Rollershutter command enumerated above
bool iscommand
bool ispercentage

# Possible percentages for Rollershutter
int8 PERCENTAGE0=0
[...]
int8 PERCENTAGE100=100

int8 percentage
Header header
string item # the name of the item

Again, as described above, one could work with boolean variables and a second enumeration.

StringState

Stores texts

# Stores texts

Header header
string item # the name of the item
string state # string value of the item

Easy. I have just to send a string.

StringCommand

String

# Stores texts

Header header
string item # the name of the item
string command # string command for the item

Easy. I have just to send a string and then replace the old string with sendCommand.

SwitchState

Switch Item, used for anything that needs to be switched ON and OFF

# Switch Item, used fo anything that needs to be switched ON and OFF

# Possible states for Switch
string ON=ON
string OFF=OFF

string state # Switch state enumerated above
Header header
string item # the name of the item

The enumeration includes ON or OFF.

SwitchCommand

OnOff

# Switch Item, used fo anything that needs to be switched ON and OFF

# Possible commands for Switch
string ON=ON
string OFF=OFF

string command # Switch command enumerated above
Header header
string item # the name of the item

The enumeration includes ON or OFF.

openhab_bridge

The openhab_bridge.py is a bridge between openHAB and ROS. It is written in Python and can be used with HABApp. For this, HABApp must be adapted accordingly so that it can use the PYTHONPATH of the system. In this PYTHONPATH all necessary directories for libraries of ROS are entered, as well as all further modules, which are needed and which were installed by pip.

For that I “hacked” HABApp for using the Robot Operating System (ROS). The problem is that rospy (library for ROS Python) only can initialize a node in a main thread and the HABApp Rules are not called by a main thread.

I tested it with Ubuntu 20.04, ROS Noetic Ninjemys and openHAB 3.

You can read about it here:

I edited /opt/habapp/bin/habapp to:

#!/opt/habapp/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
import rospy
from HABApp.__main__ import main
if __name__ == '__main__':
    rospy.init_node("openhab", anonymous=False)
    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
    sys.exit(main())

So after starting HABApp I can see the node:

rosnode list
/openhab
/rosout

You have to run HABApp with /usr/bin/python3 and not with python inside the venv. This is because I installed ROS not inside that virtual environment. Then I added HABApp to my $PYTHOPATH:

echo $PYTHONPATH
$PYTHONPATH:/usr/lib/python3.8/site-packages:/usr/lib/python3/dist-packages:/opt/ros/noetic/lib/python3/dist-packages:/core/roslib/src:/opt/habapp/bin:/opt/habapp/lib/python3.8/site-packages:/home/ubuntu/catkin_ws/devel/lib/python3/dist-packages:/home/ubuntu/.local/lib/python3.8/site-packages:/usr/local/lib/python3.8/dist-packages

A small hint might be to be careful with pip what really ends up being installed where. For example, in my case the user is called ubuntu and I accidentally installed something local for the user via pip. Means that I had to specify one more path.

Now I can run habapp outside of the virtual environment. And off course I run roscore before starting HABApp.

I also changed my service file for systemd to:

[Unit]
Description=HABApp
Documentation=https://habapp.readthedocs.io
Requires=openhab.service
After=openhab.service
BindsTo=openhab.service
PartOf=openhab.service

[Service]
Type=simple
User=openhab
Group=openhab
UMask=002
ExecStart=/bin/bash -c 'source /etc/environment; /usr/bin/python3 /opt/habapp/bin/habapp -c /etc/openhab/habapp'
Restart=on-failure
RestartSec=30s

[Install]
WantedBy=openhab.service

And inside $OH_CONF/habapp/rules I created at least following python script and saved it as openhab_bridge.py

import logging
import HABApp
from HABApp import Parameter
from HABApp.core.events import ValueChangeEvent, ValueUpdateEvent
from HABApp.openhab.events import ItemStateEvent
from HABApp.core.items import Item
from HABApp.openhab.items import OpenhabItem
from HABApp.openhab.items import ColorItem, ContactItem, DatetimeItem, DimmerItem, GroupItem, ImageItem, LocationItem, NumberItem, PlayerItem, RollershutterItem, StringItem, SwitchItem
import rospy
from openhab_msgs.msg import ColorState, ContactState, DateTimeState, DimmerState, GroupState, ImageState, LocationState, NumberState, PlayerState, RollershutterState, StringState, SwitchState
from openhab_msgs.msg import ColorCommand, ContactCommand, DateTimeCommand, DimmerCommand, ImageCommand, LocationCommand, NumberCommand, PlayerCommand, RollershutterCommand, StringCommand, SwitchCommand
from dateutil import parser
from datetime import datetime, timezone
import base64
import io
import cv2
from imageio import imread
from cv_bridge import CvBridge
from sensor_msgs.msg import Image

log = logging.getLogger('openhab_bridge')
log_state = Parameter('openhab_bridge', 'log_state', default_value=True).value

class OpenHABBridge(HABApp.Rule):
    def __init__(self):
        super().__init__()

        for item in self.get_items(type=OpenhabItem):
            if type(item) is ColorItem:
                item.listen_event(self.ColorState, ItemStateEvent)
                rospy.Subscriber(
                    f'/openhab/items/{item.name}/command', ColorCommand, self.ColorCallback)
            elif type(item) is ContactItem:
                item.listen_event(self.ContactState, ItemStateEvent)
                rospy.Subscriber(
                    f'/openhab/items/{item.name}/command', ContactCommand, self.ContactCallback)
            elif type(item) is DatetimeItem:
                item.listen_event(self.DateTimeState, ItemStateEvent)
                rospy.Subscriber(
                    f'/openhab/items/{item.name}/command', DateTimeCommand, self.DateTimeCallback)
            elif type(item) is GroupItem:
                item.listen_event(self.GroupState, ItemStateEvent)
            elif type(item) is DimmerItem:
                item.listen_event(self.DimmerState, ItemStateEvent)
                rospy.Subscriber(
                    f'/openhab/items/{item.name}/command', DimmerCommand, self.DimmerCallback)
            elif type(item) is ImageItem:
                item.listen_event(self.ImageState, ItemStateEvent)
                rospy.Subscriber(
                    f'/openhab/items/{item.name}/command', ImageCommand, self.ImageCallback)
            elif type(item) is LocationItem:
                item.listen_event(self.LocationState, ItemStateEvent)
                rospy.Subscriber(
                    f'/openhab/items/{item.name}/command', LocationCommand, self.LocationCallback)
            elif type(item) is NumberItem:
                item.listen_event(self.NumberState, ItemStateEvent)
                rospy.Subscriber(
                    f'/openhab/items/{item.name}/command', NumberCommand, self.NumberCallback)
            elif type(item) is PlayerItem:
                item.listen_event(self.PlayerState, ItemStateEvent)
                rospy.Subscriber(
                    f'/openhab/items/{item.name}/command', PlayerCommand, self.PlayerCallback)
            elif type(item) is RollershutterItem:
                item.listen_event(self.RollershutterState, ItemStateEvent)
                rospy.Subscriber(
                    f'/openhab/items/{item.name}/command', RollershutterCommand, self.RollershutterCallback)
            elif type(item) is StringItem:
                item.listen_event(self.StringState, ItemStateEvent)
                rospy.Subscriber(
                    f'/openhab/items/{item.name}/command', StringCommand, self.StringCallback)
            elif type(item) is SwitchItem:
                item.listen_event(self.SwitchState, ItemStateEvent)
                rospy.Subscriber(
                    f'/openhab/items/{item.name}/command', SwitchCommand, self.SwitchCallback)

        rospy.spin()

    def ColorState(self, event: ItemStateEvent):
        item = event.name
        value = event.value

        log.info("is ColorItem")
        msg = ColorState()
        msg.r = float(value[0])
        msg.g = float(value[1])
        msg.b = float(value[2])

        pub = rospy.Publisher(
            f'/openhab/items/{item}/state', ColorState, queue_size=1)

        for my_item in self.get_items(name=item):
            item_type = my_item

        if hasattr(item_type, 'last_update'):
            msg.header.stamp = rospy.Time.from_sec(parser.parse(
                str(item_type.last_update)).replace(tzinfo=timezone.utc).timestamp())
        else:
            msg.header.stamp = rospy.Time.now()
        msg.header.frame_id = "/base_link"
        msg.item = str(item)

        log.info(
            f'Published ROS topic /openhab/items/{item}/state with {value}')

        rate = rospy.Rate(1)
        counter = 0

        while counter < 1:
            # wait for a connection to publisher
            # you can do whatever you like here or simply do nothing

            connections = pub.get_num_connections()
            if connections > 0:
                rospy.loginfo(
                    f'Published ROS topic /openhab/state/{item} with {value}')
                # pub.publish(msg)
                counter = counter + 1
            else:
                rate.sleep()

    def ContactState(self, event: ItemStateEvent):
        item = event.name
        value = event.value

        log.info("is ContactItem")
        msg = ContactState()
        if value == "OPEN":
            msg.state = ContactState.OPEN
        elif value == "CLOSED":
            msg.state = ContactState.CLOSED

        pub = rospy.Publisher(
            f'/openhab/items/{item}/state', ContactState, queue_size=1)

        for my_item in self.get_items(name=item):
            item_type = my_item

        if hasattr(item_type, 'last_update'):
            msg.header.stamp = rospy.Time.from_sec(parser.parse(
                str(item_type.last_update)).replace(tzinfo=timezone.utc).timestamp())
        else:
            msg.header.stamp = rospy.Time.now()
        msg.header.frame_id = "/base_link"
        msg.item = str(item)

        log.info(
            f'Published ROS topic /openhab/items/{item}/state with {value}')

        rate = rospy.Rate(1)
        counter = 0

        while counter < 1:
            # wait for a connection to publisher
            # you can do whatever you like here or simply do nothing

            connections = pub.get_num_connections()
            if connections > 0:
                rospy.loginfo(
                    f'Published ROS topic /openhab/state/{item} with {value}')
                # pub.publish(msg)
                counter = counter + 1
            else:
                rate.sleep()

    def DateTimeState(self, event: ItemStateEvent):
        item = event.name
        value = event.value

        log.info("is DatetimeItem")
        msg = DateTimeState()
        msg.state = rospy.Time.from_sec(parser.parse(
            str(value)).replace(tzinfo=timezone.utc).timestamp())

        pub = rospy.Publisher(
            f'/openhab/items/{item}/state', DateTimeState, queue_size=1)

        for my_item in self.get_items(name=item):
            item_type = my_item

        if hasattr(item_type, 'last_update'):
            msg.header.stamp = rospy.Time.from_sec(parser.parse(
                str(item_type.last_update)).replace(tzinfo=timezone.utc).timestamp())
        else:
            msg.header.stamp = rospy.Time.now()
        msg.header.frame_id = "/base_link"
        msg.item = str(item)

        log.info(
            f'Published ROS topic /openhab/items/{item}/state with {value}')

        rate = rospy.Rate(1)
        counter = 0

        while counter < 1:
            # wait for a connection to publisher
            # you can do whatever you like here or simply do nothing

            connections = pub.get_num_connections()
            if connections > 0:
                rospy.loginfo(
                    f'Published ROS topic /openhab/state/{item} with {value}')
                # pub.publish(msg)
                counter = counter + 1
            else:
                rate.sleep()

    def DimmerState(self, event: ItemStateEvent):
        item = event.name
        value = event.value

        log.info("is DimmerItem")
        msg = DimmerState()
        if 0 <= value <= 100:
            msg.state = int(value)

        pub = rospy.Publisher(
            f'/openhab/items/{item}/state', DimmerState, queue_size=1)

        for my_item in self.get_items(name=item):
            item_type = my_item

        if hasattr(item_type, 'last_update'):
            msg.header.stamp = rospy.Time.from_sec(parser.parse(
                str(item_type.last_update)).replace(tzinfo=timezone.utc).timestamp())
        else:
            msg.header.stamp = rospy.Time.now()
        msg.header.frame_id = "/base_link"
        msg.item = str(item)

        log.info(
            f'Published ROS topic /openhab/items/{item}/state with {value}')

        rate = rospy.Rate(1)
        counter = 0

        while counter < 1:
            # wait for a connection to publisher
            # you can do whatever you like here or simply do nothing

            connections = pub.get_num_connections()
            if connections > 0:
                rospy.loginfo(
                    f'Published ROS topic /openhab/state/{item} with {value}')
                # pub.publish(msg)
                counter = counter + 1
            else:
                rate.sleep()

    def GroupState(self, event: ItemStateEvent):
        item = event.name
        value = event.value

        log.info("is GroupItem")
        msg = GroupState()

        pub = rospy.Publisher(
            f'/openhab/items/{item}/state', GroupState, queue_size=1)

        for my_item in self.get_items(name=item):
            item_type = my_item

        if hasattr(item_type, 'last_update'):
            msg.header.stamp = rospy.Time.from_sec(parser.parse(
                str(item_type.last_update)).replace(tzinfo=timezone.utc).timestamp())
        else:
            msg.header.stamp = rospy.Time.now()
        msg.header.frame_id = "/base_link"
        msg.item = str(item)

        log.info(
            f'Published ROS topic /openhab/items/{item}/state with {value}')

        rate = rospy.Rate(1)
        counter = 0

        while counter < 1:
            # wait for a connection to publisher
            # you can do whatever you like here or simply do nothing

            connections = pub.get_num_connections()
            if connections > 0:
                rospy.loginfo(
                    f'Published ROS topic /openhab/state/{item} with {value}')
                # pub.publish(msg)
                counter = counter + 1
            else:
                rate.sleep()

    def ImageState(self, event: ItemStateEvent):
        item = event.name
        value = event.value

        log.info("is ImageItem")
        msg = ImageState()
        b64_bytes = base64.b64encode(value)
        b64_string = b64_bytes.decode()
        img = imread(io.BytesIO(base64.b64decode(b64_string)))
        cv2_img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)

        bridge = CvBridge()
        a = Image()
        a = bridge.cv2_to_imgmsg(
            cv2_img, encoding="passthrough")

        pub = rospy.Publisher(
            f'/openhab/items/{item}/state', ImageState, queue_size=1)

        for my_item in self.get_items(name=item):
            item_type = my_item

        if hasattr(item_type, 'last_update'):
            msg.header.stamp = rospy.Time.from_sec(parser.parse(
                str(item_type.last_update)).replace(tzinfo=timezone.utc).timestamp())
        else:
            msg.header.stamp = rospy.Time.now()
        msg.header.frame_id = "/base_link"
        msg.item = str(item)

        log.info(
            f'Published ROS topic /openhab/items/{item}/state with {value}')

        rate = rospy.Rate(1)
        counter = 0

        while counter < 1:
            # wait for a connection to publisher
            # you can do whatever you like here or simply do nothing

            connections = pub.get_num_connections()
            if connections > 0:
                rospy.loginfo(
                    f'Published ROS topic /openhab/state/{item} with {value}')
                # pub.publish(msg)
                counter = counter + 1
            else:
                rate.sleep()

    def LocationState(self, event: ItemStateEvent):
        item = event.name
        value = event.value

        log.info("is LocationItem")
        msg = LocationState()
        splitted = value.split(",")
        msg.latitude = float(splitted[0])
        msg.longitude = float(splitted[1])
        msg.altitude = float(splitted[2])

        pub = rospy.Publisher(
            f'/openhab/items/{item}/state', LocationState, queue_size=1)

        for my_item in self.get_items(name=item):
            item_type = my_item

        if hasattr(item_type, 'last_update'):
            msg.header.stamp = rospy.Time.from_sec(parser.parse(
                str(item_type.last_update)).replace(tzinfo=timezone.utc).timestamp())
        else:
            msg.header.stamp = rospy.Time.now()
        msg.header.frame_id = "/base_link"
        msg.item = str(item)

        log.info(
            f'Published ROS topic /openhab/items/{item}/state with {value}')

        rate = rospy.Rate(1)
        counter = 0

        while counter < 1:
            # wait for a connection to publisher
            # you can do whatever you like here or simply do nothing

            connections = pub.get_num_connections()
            if connections > 0:
                rospy.loginfo(
                    f'Published ROS topic /openhab/state/{item} with {value}')
                # pub.publish(msg)
                counter = counter + 1
            else:
                rate.sleep()

    def NumberState(self, event: ItemStateEvent):
        item = event.name
        value = event.value

        log.info("is NumberItem")
        msg = NumberState()
        msg.state = float(value)

        pub = rospy.Publisher(
            f'/openhab/items/{item}/state', NumberState, queue_size=1)

        pub = rospy.Publisher(
            f'/openhab/items/{item}/state', SwitchState, queue_size=1)

        for my_item in self.get_items(name=item):
            item_type = my_item

        if hasattr(item_type, 'last_update'):
            msg.header.stamp = rospy.Time.from_sec(parser.parse(
                str(item_type.last_update)).replace(tzinfo=timezone.utc).timestamp())
        else:
            msg.header.stamp = rospy.Time.now()
        msg.header.frame_id = "/base_link"
        msg.item = str(item)

        log.info(
            f'Published ROS topic /openhab/items/{item}/state with {value}')

        rate = rospy.Rate(1)
        counter = 0

        while counter < 1:
            # wait for a connection to publisher
            # you can do whatever you like here or simply do nothing

            connections = pub.get_num_connections()
            if connections > 0:
                rospy.loginfo(
                    f'Published ROS topic /openhab/state/{item} with {value}')
                # pub.publish(msg)
                counter = counter + 1
            else:
                rate.sleep()

    def PlayerState(self, event: ItemStateEvent):
        item = event.name
        value = event.value

        log.info("is PlayerItem")
        msg = PlayerState()
        if value == "PLAY":
            msg.state = PlayerState.PLAY
        elif value == "PAUSE":
            msg.state = PlayerState.PAUSE
        elif value == "NEXT":
            msg.state = PlayerState.NEXT
        elif value == "PREVIOUS":
            msg.state = PlayerState.PREVIOUS
        elif value == "REWIND":
            msg.state = PlayerState.REWIND
        elif value == "FASTFORWARD":
            msg.state = PlayerState.FASTFORWARD

        pub = rospy.Publisher(
            f'/openhab/items/{item}/state', PlayerState, queue_size=1)

        for my_item in self.get_items(name=item):
            item_type = my_item

        if hasattr(item_type, 'last_update'):
            msg.header.stamp = rospy.Time.from_sec(parser.parse(
                str(item_type.last_update)).replace(tzinfo=timezone.utc).timestamp())
        else:
            msg.header.stamp = rospy.Time.now()
        msg.header.frame_id = "/base_link"
        msg.item = str(item)

        log.info(
            f'Published ROS topic /openhab/items/{item}/state with {value}')

        rate = rospy.Rate(1)
        counter = 0

        while counter < 1:
            # wait for a connection to publisher
            # you can do whatever you like here or simply do nothing

            connections = pub.get_num_connections()
            if connections > 0:
                rospy.loginfo(
                    f'Published ROS topic /openhab/state/{item} with {value}')
                # pub.publish(msg)
                counter = counter + 1
            else:
                rate.sleep()

    def RollershutterState(self, event: ItemStateEvent):
        item = event.name
        value = event.value

        log.info("is RollershutterItem")
        msg = RollershutterState()
        if isinstance(value, int):
            msg.isstate = False
            msg.ispercentage = True

            if 0 <= value <= 100:
                msg.state = int(value)
        elif isinstance(value, str):
            msg.isstate = True
            msg.ispercentage = False

            if value == "UP":
                msg.state = RollershutterState.PLAY
            elif value == "DOWN":
                msg.state = RollershutterState.PAUSE
            elif value == "STOP":
                msg.state = RollershutterState.NEXT
            elif value == "MOVE":
                msg.state = RollershutterState.PREVIOUS

        pub = rospy.Publisher(
            f'/openhab/items/{item}/state', RollershutterState, queue_size=1)

        for my_item in self.get_items(name=item):
            item_type = my_item

        if hasattr(item_type, 'last_update'):
            msg.header.stamp = rospy.Time.from_sec(parser.parse(
                str(item_type.last_update)).replace(tzinfo=timezone.utc).timestamp())
        else:
            msg.header.stamp = rospy.Time.now()
        msg.header.frame_id = "/base_link"
        msg.item = str(item)

        log.info(
            f'Published ROS topic /openhab/items/{item}/state with {value}')

        rate = rospy.Rate(1)
        counter = 0

        while counter < 1:
            # wait for a connection to publisher
            # you can do whatever you like here or simply do nothing

            connections = pub.get_num_connections()
            if connections > 0:
                rospy.loginfo(
                    f'Published ROS topic /openhab/state/{item} with {value}')
                # pub.publish(msg)
                counter = counter + 1
            else:
                rate.sleep()

    def StringState(self, event: ItemStateEvent):
        item = event.name
        value = event.value

        log.info("is StringItem")
        msg = StringState()
        msg.state = str(value)
        pub = rospy.Publisher(
            f'/openhab/items/{item}/state', StringState, queue_size=1)

        for my_item in self.get_items(name=item):
            item_type = my_item

        if hasattr(item_type, 'last_update'):
            msg.header.stamp = rospy.Time.from_sec(parser.parse(
                str(item_type.last_update)).replace(tzinfo=timezone.utc).timestamp())
        else:
            msg.header.stamp = rospy.Time.now()
        msg.header.frame_id = "/base_link"
        msg.item = str(item)

        log.info(
            f'Published ROS topic /openhab/items/{item}/state with {value}')

        rate = rospy.Rate(1)
        counter = 0

        while counter < 1:
            # wait for a connection to publisher
            # you can do whatever you like here or simply do nothing

            connections = pub.get_num_connections()
            if connections > 0:
                rospy.loginfo(
                    f'Published ROS topic /openhab/state/{item} with {value}')
                # pub.publish(msg)
                counter = counter + 1
            else:
                rate.sleep()

    def SwitchState(self, event: ItemStateEvent):
        item = event.name
        value = event.value

        log.info("is SwitchItem")
        msg = SwitchState()
        if value == "ON":
            msg.state = SwitchState.ON
        elif value == "OFF":
            msg.state = SwitchState.OFF

        pub = rospy.Publisher(
            f'/openhab/items/{item}/state', SwitchState, queue_size=1)

        for my_item in self.get_items(name=item):
            item_type = my_item

        if hasattr(item_type, 'last_update'):
            msg.header.stamp = rospy.Time.from_sec(parser.parse(
                str(item_type.last_update)).replace(tzinfo=timezone.utc).timestamp())
        else:
            msg.header.stamp = rospy.Time.now()
        msg.header.frame_id = "/base_link"
        msg.item = str(item)

        log.info(
            f'Published ROS topic /openhab/items/{item}/state with {value}')

        rate = rospy.Rate(1)
        counter = 0

        while counter < 1:
            # wait for a connection to publisher
            # you can do whatever you like here or simply do nothing

            connections = pub.get_num_connections()
            if connections > 0:
                rospy.loginfo(
                    f'Published ROS topic /openhab/state/{item} with {value}')
                # pub.publish(msg)
                counter = counter + 1
            else:
                rate.sleep()

    def ColorCallback(self, data):
        item = data.item

        if data.iscommand == True:
            if data.command == ColorCommand.ON or data.command == ColorCommand.OFF or data.command == ColorCommand.INCREASE or data.command == ColorCommand.DECREASE:
                value = data.command
            else:
                value = None
        elif data.ispercentage == True:
            if 0 <= data.percentage <= 100:
                value = data.percentage
        elif data.ishsb == True:
            value = (data.hue, data.saturation, data.brightness)

        rospy.loginfo(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')
        log.info(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')

        self.oh.send_command(item, value)

    def ContactCallback(self, data):
        item = data.item

        if data.command == ContactCommand.OPEN or data.command == ContactCommand.CLOSED:
            value = data.command

        rospy.loginfo(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')
        log.info(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')

        self.oh.send_command(item, value)

    def DateTimeCallback(self, data):
        item = data.item
        value = datetime.utcfromtimestamp(
            data.command.to_sec()).strftime("%Y-%m-%dT%H:%M:%SZ")

        rospy.loginfo(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')
        log.info(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')

        self.oh.post_update(item, value)

    def DimmerCallback(self, data):
        item = data.item

        if data.iscommand == True:
            if data.command == DimmerCommand.ON or data.command == DimmerCommand.OFF or data.command == DimmerCommand.INCREASE or data.command == DimmerCommand.DECREASE:
                value = data.command
        elif data.ispercentage == True:
            if 0 <= data.percentage <= 100:
                value = data.percentage

        rospy.loginfo(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')
        log.info(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')

        self.oh.send_command(item, value)

    def ImageCallback(self, data):
        item = data.item

        bridge = CvBridge()
        cv_image = bridge.imgmsg_to_cv2(
            data.command, desired_encoding='passthrough')

        retval, buffer = cv2.imencode('.jpg', cv_image)
        value = "data:image/jpg;base64" + \
            base64.b64encode(buffer).decode("utf-8")

        rospy.loginfo(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')
        log.info(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')

        self.oh.post_update(item, value)

    def LocationCallback(self, data):
        item = data.item

        value = str(data.latitude) + "," + \
            str(data.longitude) + "," + str(data.altitude)

        rospy.loginfo(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')
        log.info(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')

        self.oh.send_command(item, value)

    def NumberCallback(self, data):
        item = data.item

        if isinstance(data.number, float):
            value = data.command

        rospy.loginfo(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')
        log.info(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')

        self.oh.send_command(item, value)

    def PlayerCallback(self, data):
        item = data.item

        if data.command == PlayerCommand.PLAY or data.command == PlayerCommand.PAUSE or data.command == PlayerCommand.NEXT or data.command == PlayerCommand.PREVIOUS or data.command == PlayerCommand.REWIND or data.command == PlayerCommand.FASTFORWARD:
            value = data.command

        rospy.loginfo(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')
        log.info(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')

        self.oh.send_command(item, value)

    def RollershutterCallback(self, data):
        item = data.item

        if data.iscommand == True:
            if data.command == RollershutterCommand.UP or data.command == RollershutterCommand.DOWN or data.command == RollershutterCommand.STOP or data.command == RollershutterCommand.MOVE:
                value = data.command
        elif data.ispercentage == True:
            if 0 <= data.percentage <= 100:
                value = data.percentage

        rospy.loginfo(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')
        log.info(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')

        self.oh.send_command(item, value)

    def StringCallback(self, data):
        item = data.item

        if isinstance(data.command, str):
            value = data.command

        rospy.loginfo(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')
        log.info(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')

        self.oh.send_command(item, value)

    def SwitchCallback(self, data):
        item = data.item

        if data.command == SwitchCommand.ON or data.command == SwitchCommand.OFF:
            value = data.command

        rospy.loginfo(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')
        log.info(
            f'{rospy.get_caller_id()} Subscribed ROS topic /openhab/items/{item}/command with {value}')

        self.oh.send_command(item, value)

OpenHABBridge()

Inside ImageState I know I have an error. The rest should work fine. Please report any other errors.

I get as example following topics:

rostopic list
/openhab/items/testColor/command
/openhab/items/testColor/state
/openhab/items/testContact/command
/openhab/items/testContact/state
/openhab/items/testDateTime/command
/openhab/items/testDateTime/state
/openhab/items/testDimmer/command
/openhab/items/testDimmer/state
/openhab/items/testImage/command
/openhab/items/testLocation/command
/openhab/items/testNumber/command
/openhab/items/testPlayer/command
/openhab/items/testPlayer/state
/openhab/items/testRollershutter/command
/openhab/items/testRollershutter/state
/openhab/items/testString/command
/openhab/items/testString/state
/openhab/items/testSwitch/command
/openhab/items/testSwitch/state
/rosout
/rosout_agg

openhab_subscribers

The openhab_subscribers package is a ROS package within the catkin_ws. It uses ROS subscribers to subscribe from an example topic of the openhab_bridge. In this package there will be an example for each message type or item type.

Will be uploaded soon and also shown an example of how to do this via Exec Binding.

openhab_publishers

The openhab_publishers package is a ROS package within the catkin_ws. It uses ROS publishers to publish an example to a topic of the openhab_bridge. This should change via sendCommand the state of an item in openhab. In this package there will be an example for each message type or item type.

Will be uploaded soon and also shown an example of how to do this via Exec Binding.

test scenario

For testing ROS and its publisher and subscribers you can create following item, rules and sitemaps.

ros.items:

Group ROS

Color testColor (ROS)
Contact testContact "[%s]" (ROS)
DateTime testDateTime "[%1$tH:%1$tM]" (ROS)
Dimmer testDimmer (ROS)
Image testImage (ROS)
Location testLocation "HS Furtwangen" <house>  (ROS)
Number testNumber "[%.0f]" (ROS)
Player testPlayer (ROS)
Rollershutter testRollershutter (ROS)
String testString "[%s]" (ROS)
Switch testSwitch (ROS)

For each item type there was a test item. They can be populated with static values with following rule:

import java.util.Base64
import java.io.ByteArrayOutputStream
import java.net.URL

rule "Started"
when
    System started
then
    testColor.postUpdate("100, 100, 100")
    testContact.postUpdate(CLOSED)
    testDateTime.postUpdate(now.toLocalDateTime().toString())
    testDimmer.postUpdate(30)
    testNumber.postUpdate(50)
    testPlayer.postUpdate(PAUSE)
    testRollershutter.postUpdate(0)
    testString.postUpdate("Hello World")
    testSwitch.postUpdate(OFF)

    // 48.051437316054006, 8.207755911376244
    // latitude: 48.0501442
    // longitude: 8.2014192
    // elevation: 857.0
    testLocation.postUpdate(new PointType("48.051437316054006, 8.207755911376244, 857.0"))

    var userImageDataBytes = newByteArrayOfSize(0)
    var url = new URL("http://192.168.254.142:8080/static/webapps/Image.jpg")
    var byteStreamOutput = new ByteArrayOutputStream()
    var urlConnection = url.openConnection()
    var userpass = "openhabian" + ":" + "openhabian";
    var basicAuth = "Basic " + new String(Base64.getEncoder().encode(userpass.getBytes()));
    urlConnection.setRequestProperty ("Authorization", basicAuth);
    var inputStream = urlConnection.getInputStream()
    var n = 0
    var buffer = newByteArrayOfSize(1024)
    do {
        n = inputStream.read(buffer)
        if (n > 0)  {
            byteStreamOutput.write(buffer, 0, n)
        }
    } while (n > 0)
    userImageDataBytes = byteStreamOutput.toByteArray()

    var String encodedString = Base64.getMimeEncoder().encodeToString(userImageDataBytes).replaceAll("\\r\\n", "")
    var ImageTMP = "data:image/jpg;base64," + encodedString

    testImage.postUpdate(ImageTMP)	
end

Notice that inside $OH_CONF/html/webapps is an image called Image.jpg

import java.util.Base64
import java.io.ByteArrayOutputStream
import java.net.URL

rule "Started"
when
    Time cron "0 0/1 * * * ?"   // every minute
then
    testColor.postUpdate(NULL)
    testContact.postUpdate(NULL)
    testDateTime.postUpdate(NULL)
    testDimmer.postUpdate(NULL)
    testNumber.postUpdate(NULL)
    testPlayer.postUpdate(NULL)
    testRollershutter.postUpdate(NULL)
    testString.postUpdate(NULL)
    testSwitch.postUpdate(NULL)
    testLocation.postUpdate(NULL)
    testImage.postUpdate(NULL)

    testColor.postUpdate("100, 100, 100")
    testContact.postUpdate(CLOSED)
    testDateTime.postUpdate(now.toLocalDateTime().toString())
    testDimmer.postUpdate(30)
    testNumber.postUpdate(50)
    testPlayer.postUpdate(PAUSE)
    testRollershutter.postUpdate(0)
    testString.postUpdate("Hello World")
    testSwitch.postUpdate(OFF)

    // 48.051437316054006, 8.207755911376244
    // latitude: 48.0501442
    // longitude: 8.2014192
    // elevation: 857.0
    testLocation.postUpdate(new PointType("48.051437316054006, 8.207755911376244, 857.0"))

    var userImageDataBytes = newByteArrayOfSize(0)
    var url = new URL("http://192.168.254.142:8080/static/webapps/Image.jpg")
    var byteStreamOutput = new ByteArrayOutputStream()
    var urlConnection = url.openConnection()
    var userpass = "openhabian" + ":" + "openhabian";
    var basicAuth = "Basic " + new String(Base64.getEncoder().encode(userpass.getBytes()));
    urlConnection.setRequestProperty ("Authorization", basicAuth);
    var inputStream = urlConnection.getInputStream()
    var n = 0
    var buffer = newByteArrayOfSize(1024)
    do {
        n = inputStream.read(buffer)
        if (n > 0)  {
            byteStreamOutput.write(buffer, 0, n)
        }
    } while (n > 0)
    userImageDataBytes = byteStreamOutput.toByteArray()

    var String encodedString = Base64.getMimeEncoder().encodeToString(userImageDataBytes).replaceAll("\\r\\n", "")
    var ImageTMP = "data:image/jpg;base64," + encodedString

    testImage.postUpdate(ImageTMP)	
end

So that values change, which are then published via the openhab_bridge, I have created a rule that resets the values via cron job. Otherwise you can also make some changes via the sitemap. But not for all items (number, string etc.).

I kept the sitemap simple by simply specifying the ROS group.

sitemap Sitemap label="Sitemap" {
    Frame label="ROS" {
        Group item=ROS
    }
}

I think it might answer a few questions by playing with REST API, sending commands and state updates in different forms. For example, can a Dimmer type be updated with a decimal e.g. 56.5?

Can’t you feed the ROS messages from HABApp?
I thought that’s what you wanted to do?

Off course. You have to add to the $PYTHONPATH a directory from catkin_ws/devel that this would work.

I pushed yesterday to GitHub the openhab_msgs:

I also successfully created the Publisher and Subscriber in HABApp yesterday already. So with my Message Types. I then created an example for all items in openHAB. Looked at the output of the REST API, looked at how I can change the values, etc. For this I simply used rules. I also looked at how to add an image to an item, for example.

Then I went and copied out the Base64 string of the image (via the REST API), saved those to a file, and used Python and OpenCV to write how to convert an image back from Base64. From an OpenCV image you can convert an image for ROS sensor_msgs/Image and also vice versa via CvBridge. Means for the reverse conversion I must convert for openHAB at the end that whole again in Base64, then one can send also over ROS images to openHAB.

Theoretically I would not need the intermediate step via CvBridge if I knew more exactly how to feed the sensor_msgs/Image with data correctly. It should be possible to do this directly from Base64, instead of the intermediate step of converting the whole thing to OpenCV images first.

What I am still missing in the HABApp program is that the publishers and subscribers are converted accordingly and the message types are filled correctly.

@Michdo93, answer for your general questions, based on my actual knowledge - you might need to confront it with others.

  1. Do I still have to consider NULL and UNDEF in the enumerations?

Depends on use case. UnDefState.UNDEF or UnDefState.NULL is something you will see in several cases, ie. during initialization of the system or when conversion or some other calculation fails. This type is marker that item state can’t be determined used as a fallback, its not something you will normally be able to send to item.

  1. What happens if Color, Number or any other item is NULL or UNDEF? One possibility would be to publish nothing at all.

I think each item can have NULL or UNDEF state, but can’t be commanded with it (none of item types accepts UnDefType), these are reserved for above cases. If you, on ROS end, are interested in “lost state” then it might be worth to pass it from OH to ROS, but you can’t assume that same will work from other end, unless there is some tricky way habapp or mqtt binding is pushing NULL into item states.

  1. Do I want to return commands with NULL or UNDEF?

Most of UI stuff does not permit you sending NULL or UNDEF values. Most of use of UNDEF itself is internally within openHAB, and not UIs. Still, you can send NULL string via REST API and watch what’s going to happen. :slight_smile: [I suppose you will get HTTP 40x error]

  1. With string I could transfer NULL and UNDEF as string, but what do I do e.g. with float64 or with int8?

If you wish to transfer a undefined state then, I am afraid, it requires some handcrafted method as UnDefType is not a valid input for any of the item kinds currently used by openHAB.

  1. What dimensions do I need to consider?

Dimensions catalog is predefined - look at measurement kinds supported by UI: openhab-webui/bundles/org.openhab.ui/web/src/assets/item-types.js at 3.2.0 · openhab/openhab-webui · GitHub.

  1. How do I handle conversions?

On openHAB end all conversions are done automatically as it always able to receive a string value. Then, depending on item kind, there is a chain of “accepted command types” which are iterated to parse text. First which returns non null value wins. Lookt at org.openhab.core.types.TypeParser.

  1. How do I deal with minimum and maximum values? Also here it would be conceivable that I add bpsw. a field int8 min_value and a field int8 max_value. Since these are mandatory fields, they would always have to be entered. For dimmers, for example, with 0 and 100, unless there are other configurable options in openHAB. Can I accordingly also query min and max in openHAB?

openHAB does not limit minimum and maximum values, its usually a UI thing. Item as an generic instance can’t define limit. Channel or state definition to which item is linked, can define its own min/max values, but I think these are only used in UI to limit values entered by user, they are not checked at the runtime unless I am wrong.

  1. Upper and lower case. Are states like ON always capitalized? Are the commands also always capitalized?

Yes, they are case sensitive. An interesting fact is that almost each command is also a state. While there are distinguished in code, in practice they are exchangeable. :wink:

  1. Required and optional fields. Is this valid to work in ROS accordingly with boolean variables, whether I send e.g. a PERCENTAGE or a Command?

Can’t tell you how to work on ROS end, but openHAB core itself has a “cast” mechanism which permits converting commands or types between themselves. Each Type (OnOff, OpenClosed, Decimal, Percent) has a cast(Class<Type>) method which can be used to force conversion. For example you can convert DecimalType (number) into PercentType and vice versa.

Not to change anything about what you are doing and how you are going about it, but I believe there are a number of add-ons that work with C/C++ libraries via a JNI library (i think the Bluetooth library is one such). I know it gets tricky to do so across all OSs but ROS is only supported on one OS anyway so that shouldn’t matter right?

I just want to make sure this approach isn’t being ignored out of a mistaken belief that it cannot be done that way. Again, I’m not recommending that an add-on is the proper or best way.

No, when the value is converted to a PercentType an exception would be thrown. At least that happened the last time I tried it.

Just a point of clarification. You cannot command an Item to NULL or UNDEF but you can update an Item to NULL or UNDEF. Because of this NULL and UNDEF cannot be used as something sent to an Item from the UI, but you can postUpdate either to an Item through the REST API, from rules, or from Expire.

I wanted to make it absolutely clear that it is possible to use NULL and UNDEF so long as you are not using them as commands. The UI (and Profiles?) always send commands so NULL/UNDEF cannot be used from there.

No, calling PUT on the /items/{itemname}/state REST endpoint on any Item type with “NULL” or “UNDEF” will work. The Item will change to NULL/UNDEF without error (I just tested this). Nothing special is required.

I believe MQTT also filters incoming messages with it’s min/max properties. I don’t think it filters outgoing messages though.

The biggest exception one will normally encounter is OPEN/CLOSED which explicitly are not commands, only states. Some examples of things that are commands but not states include INCREASE/DECREASE, UP/DOWN, etc.

I think that helped me. I have not yet taken everything into account. But then I would have to publish logically with the States also for example NULL. I have not yet caught the case.

Except for the ImageType, I can now publish all of them. I have also found the error. I looked in the REST API to see what the image looks like:

data:image/jpg;base64,/9j/4AAQSkZJRgABAQEAYABg
...

I have an algorithm for what is behind it. In HABApp, unfortunately, I get something else and then my algorithm can not work.

b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00`\x00`\x00\x00\xff\xdb\x00C\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\xff\xdb\x00C\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\xff\xc0\x00\x11\x08\x01\xa4\x02v\x03\x01"\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x1e\x00\x00\x02\x03\x00\x02\x03\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x03\x00\x01\x04\x05\t\x06\x07\n\x08\xff\xc4\x00m\x10\x00\x02\x01\x02\x04\x03\x05\x

But that also leads me to another question. Are there different encodings for the images in openHAB or are they all base64?

So far I have only tested my publishers and no subscribers yet.

//Edit:

My Fault: I have to encode it first:

base64.b64encode(image_value)

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.