ROS iot_bridge for openHAB 3

Good morning everyone,

thanks to GitHub user corb555, there was an iot_bridge to openHAB 2 for the Robot Operating System (ROS) that ran for ROS Kinetic (Ubuntu 16.04) and ROS Lunar (Ubuntu 14.04). I set out to customize it. This includes an adaptation to openHAB 3 for ROS Kinetic (Ubuntu 16.04) and ROS Noetic (Ubuntu 20.04). It should probably also work for ROS Melodic (Ubuntu 18.04), because it runs in Python 2 and Python 3. ROS Kinetic still uses Python 2 and ROS Noetic Python 3. With ROS Melodic you could use both Python 2 and Python 3. Depends on the particular installation and configuration. ROS packages in Python don’t need to be compiled, thank goodness. If the program were written in C++, it would be unthinkable that I would say that the package would also run in ROS Melodic without having tested it.

What does the iot_bridge do?

It connects to the Rest API of openHAB 3 and reacts e.g. to changing states. These are then published via a ROS Publisher. For this the diagnostic_msgs/KeyValue are used. And vice versa the program goes and accesses ROS Subscriber. After a subscription, the status of an item is then changed via sendCommand using the Rest API of openHAB 3.

If you like, the iot_bridge feeds a ROS robot with the information from openHAB items. This can accordingly execute a program and publish there e.g. to the entpsrechendes Topic, which would subscribe and in openHAB a sendCommand to this item will execute.

With this program ROS can interact with openHAB and vice versa.

What does the iot_bridge not do?

The iot_bridge does not start ROS nor does it execute a single ROS program via rosrun or several ROS programs via roslaunch which are entered accordingly in a launch file. For this use case you can use the Exec Binding or the executeCommandLine action of openHAB. You have to connect to the computer via SSH and then start the program accordingly.

The iot_bridge actually only establishes the bridge from openHAB to ROS. A program that uses the states of the items must first subscribe to the publisher of the iot_bridge, use the data and possibly publish it again to the subscriber of the iot_bridge.

Here is a link to the diagnost_msgs/KeyValue documentation:
http://docs.ros.org/en/noetic/api/diagnostic_msgs/html/msg/KeyValue.html

Here is a link to my kinetic-devel Branch:

Here is a link to my noetic-devel Branch:

Changes compared to the original repository
from corb555

From

        self.iot_host = rospy.get_param(BASENAME + '/host', "localhost")
        self.iot_port = rospy.get_param(BASENAME + '/port', 8080)
        self.username = rospy.get_param(BASENAME + '/username', "openhab")
        self.password = rospy.get_param(BASENAME + '/password', "")
        self.poll_rate = rospy.get_param(BASENAME + '/pollrate', 2)

I have done the following:

        self.iot_host = rospy.get_param(BASENAME + '/host')
        self.iot_port = rospy.get_param(BASENAME + '/port')
        self.username = rospy.get_param(BASENAME + '/username')
        self.password = rospy.get_param(BASENAME + '/password')
        self.poll_rate = rospy.get_param(BASENAME + '/pollrate', 2)

So username and password must be obtained from iot_bridge/config/items.yaml as well.

To access openHAB 3 the header of

        """ Header for OpenHAB REST request """
        return {
            "Authorization": "Basic %s" % self.cmd.auth,
            # "X-Atmosphere-Transport": "streaming",
            # "X-Atmosphere-tracking-id": self.iot_rest_id,
            "X-Atmosphere-Framework": "1.0",
            "Accept": "application/json"}

to be adapted to

return {"Accept": "application/json"}

The difference between Python 2 and Python 3 (that is, between the kinetic-devel and noetic-devel branches) is this:

        self.auth = None
        if self.params.username is not None or len(self.params.username) != 1:
            if self.params.password is not None or len(self.params.password) != 1:
                self.auth = base64.encodestring(
                    '%s:%s' % (self.params.username, self.params.password)
                    ).replace('\n', '')
            else:
                self.auth = base64.encodestring(
                '%s:%s' % (self.params.username, "")
                    ).replace('\n', '')
        else:
            self.auth = None
        self.auth = None
        if self.params.username is not None or len(self.params.username) != 1:
            if self.params.password is not None or len(self.params.password) != 1:
                self.auth = base64.encodestring((
                    '%s:%s' % (self.params.username, self.params.password)
                    ).replace('\n', '').encode()).decode().strip()
            else:
                self.auth = base64.encodestring((
                '%s:%s' % (self.params.username, "")
                    ).replace('\n', '').enconde()).decode().strip()
        else:
            self.auth = None

In addition, of course, in the first line

#!/usr/bin/python3

instead of

#!/usr/bin/python

So that ROS Noetic can build that also the following was changed in the CMakeLists.txt:

ROS Kinetic:

cmake_minimum_required(VERSION 2.8.3)

ROS Noetic:

cmake_minimum_required(VERSION 3.0.2)

I don’t know by heart what minimum is needed for ROS Melodic. This would be the only change there.

Usage

I assume that openHAB 3 and one of the three mentioned ROS distributions is installed.

cd catkin_ws/src
git clone --branch <noetic-|kinetic->devel https://github.com/Michdo93/iot_bridge.git
cd ..
catkin_make

Please decide for noetic-devel or kinetic-devel accordingly.

Next, adjust the iot_bridge/config/items.yaml regarding hostname, port, username and password of openHAB.

In the last step create an item file or edit an existing file. The ROS group must be created once and every item that should publish its information via ROS must be assigned to this group.

For example /etc/openhab/ros.items:

Group ROS (All)
String ROS_Status "ROS [%s]"
Switch Light_GF_Corridor_Ceiling  "Ceiling"  (GF_Corridor, Lights, ROS)
Switch Light_GF_Bathroom (GF_Bathroom, Lights, ROS)

You can start the program with: roslaunch iot_bridge iot.launch

Further information can be found in the ROS Wiki of the old iot_bridge:

http://wiki.ros.org/iot_bridge

Examination

With the command rosnode list you will receive

/iot
/rosout

The /rosout node is automatically start it the roscore runs. This node is for logging to the shell script.

The command rosnode info /iot you will receive following informations:

Node [/iot]
Publications: 
 * /iot_updates [diagnostic_msgs/KeyValue]
 * /rosout [rosgraph_msgs/Log]

Subscriptions: 
 * /iot_command [unknown type]
 * /iot_set [unknown type]

Services: 
 * /iot/get_loggers
 * /iot/set_logger_level


contacting node http://ubuntu:36987/ ...
Pid: 27346
Connections:
 * topic: /rosout
    * to: /rosout
    * direction: outbound
    * transport: TCPROS

With the command rostopic list you will receive:

/iot_command
/iot_set
/iot_updates
/rosout
/rosout_agg

rostopic info /iot_command :

Type: diagnostic_msgs/KeyValue

Publishers: None

Subscribers: 
 * /iot (http://ubuntu:36987/)

rostopic info /iot_set:

Type: diagnostic_msgs/KeyValue

Publishers: None

Subscribers: 
 * /iot (http://ubuntu:36987/)

rostopic info /iot_updates:


Type: diagnostic_msgs/KeyValue

Publishers: 
 * /iot (http://ubuntu:36987/)

Subscribers: None

Conclusion

Maybe it makes sense to write a publisher and subscriber for each item itself, so that ROS programs can access and use specific data.