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
}
}