Items with multiple functions or states?


(Ben Elder) #1
  • Platform information:
    • Hardware: RPi2
    • OS: Openhabian
    • Java Runtime Environment: Whatever comes with Openhabian
    • openHAB version: 2.0.0, 2017-12-18T00:35:32
  • Issue of the topic: Two issues, please be kind to the new guy

I’ve setup the MQTT Binding and can publish/subscribe via an extra terminal session to many topics. I’ve created three things in my items file. One should be a simple switch, a Temperature sensor and an Item with multiple states.

First problem: The “simple” switch:
I am able to send the ON/OFF state from Openhab to the topic and see the message, however, If I publish from a console to the same topic, the dashboard (Basic or Classic UI) does not update the state. I’ve tried several things such as setting the variable to command or state.

Second problem: Item with multiple states:
I purchased an HDMI switch with a serial/RS232 port on it. My thought is to have OpenHAB be able to set the right input on the HDMI switch based on what input I wish to use. The serial communication is pretty simple, I just need to send "swi + " to the console. I have that part working and publishing to an MQTT topic. OpenHAB can see the port it’s on whether I publish to the topic, or I’ve also used the RPi’s GPIO to wire up a few buttons to essentially publish a message that the HDMI switch is listening for. See my buggy python script below.

I’ll attach my Items, Sitemap, Map and Python file for review. I figure the “simple switch” problem is something I’m doing wrong in the items file. For the HDMI switch, I don’t even know how to approach how to send to the MQTT topic one of eight items. Essentially is there an item that permits a drop-down list to set something?

I have not yet defined any Rules, but I know there is an Alexa binding. Ultimately I want to be able to walk into the room and say something to the sort of “Alexa, let’s play PS4” and it will turn on my Roku TV, set the right HDMI input on the TV and set the right output of my HDMI switcher.

I found a python library for Roku to turn on and set inputs, volume and all that stuff, I did not see a binding for it yet, but I think I can probably get it to leverage MQTT as well.

Items:

    Switch SmartPlug "Lamp [%s]" <poweroutlet> {mqtt=">[mqtt:sunroom/plug1:command:*:default],<[mqtt:/sunroom/plug1:command:default]"}
    Number Temperature "Room Temperature [%.1f]" {mqtt="<[mqtt:sunroom/temp:state:default]"}
    String Gaming "Playing [%s]" <sofa> {mqtt="<[mqtt:sunroom/switch:state:MAP(gaming.map)]"}

Sitemap:

    sitemap default label="Elder Home"
    {
    	Switch item=SmartPlug label="Lamp"
    	Text item=Temperature
    	Text item=Gaming
    }

Map:
swi01=PS3
swi02=PS4
swi03=XBOX 360
swi04=Nintendo Wii
swi05=Nintendo Switch
swi06=Super Nintendo Classic
swi07=Nintendo Classic
swi08=RCA Jacks

Still working out the bugs Python file:

    import time
    import serial
    import RPi.GPIO as GPIO
    import paho.mqtt.client as mqtt

    SWI01 = [17, 1]
    SWI02 = [23, 2]
    # SWI03 = [22, 3]
    # SWI04 = [23, 4]
    SWI05 = [24, 5]
    # SWI06 = [25, 6]
    # SWI07 = [26, 7]
    # SWI08 = [27, 8]

    GPIO.setmode(GPIO.BCM)    
    GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_UP)  
    GPIO.setup(24, GPIO.IN, pull_up_down=GPIO.PUD_UP)

    topic = "sunroom/switch"

    # GPIO Callbacks
    def port1_callback(channel):
    	client.publish(topic, "swi01")

    def port2_callback(channel):
    	client.publish(topic, "swi02")

    def port5_callback(channel):
    	client.publish(topic, "swi05")
    	
    # GPIO Button Interrupts
    GPIO.add_event_detect(SWI01[0], GPIO.FALLING, callback=port1_callback, bouncetime=300)  
    GPIO.add_event_detect(SWI02[0], GPIO.FALLING, callback=port2_callback, bouncetime=300)
    GPIO.add_event_detect(SWI05[0], GPIO.FALLING, callback=port5_callback, bouncetime=300)


    # MQTT Callbacks
    def on_connect(client, userdata, flags, rc):
    	if rc == 0:
    		print("Connected to MQTT Broker")
    		global Connected
    		Connected = True
    	else:
    		print("Connection failed")

    def on_message(client, userdata, message):
    	output = str(message.payload.decode("utf-8"))
    	print("received message = {}".format(output))
    	data = "{}\r".format(output)
    	ser.write(data.encode())

    Connected = False
    client = mqtt.Client("P1")
    client.connect("localhost")
    client.on_connect = on_connect
    client.on_message = on_message
    client.loop_start()

    ser = serial.Serial("/dev/ttyUSB0")
    ser.baudrate = 19200

    while Connected != True:    #Wait for MQTT connection
    	time.sleep(0.1)

    client.subscribe(topic)
    print("Subcribed to {}".format(topic))

    try:
    	print("Program is Running")
    	time.sleep(1000)
    except KeyboardInterrupt:  
    	GPIO.cleanup()
    	client.disconnect()
    	client.loop_stop()
    finally:
    	GPIO.cleanup()

(Rich Koshak) #2

Your topics are different. The outgoing is “sunroom/plug1” and the incoming is “/sunroom/plug1” (note the leading /).

But you have a bigger problem that will occur once you do fix that. You will have an infinite loop because when you send a message to the topic the Item will read in the new state as a command and then publish the new state as a command to the topic and then read the new state as a command …

Change “command” to “state” so when you read in the new state from the topic it will only update the Item’s state and not go back out to the MQTT binding and publish a new message to the topic, thereby cutting your feedback loop.

Usually users will separate the command and state topic so you can, for example:

Switch SmartPlug "Lamp [%s]" &lt;poweroutlet&gt; {mqtt="&gt;[mqtt:sunroom/plug1/cmd:command:*:default],&lt;[mqtt:sunroom/plug1/state:state:default]"}

which makes it even more difficult to accidentally fall into these sorts of feedback loops. It also makes it clear where the information is coming from. For example, the messages that get published to the state topic are coming from the device and those on the cmd topic comes from OH or anything else you have that can command the device. As a result, the state topic can become a sort of acknowledgement that the command was acted upon, in addition to capturing manual triggering of the device.

A Number Item or a String Item can have any number of values and on the sitemap you can use a Selection or Switch with mappings. I’m not sure what is the equivalent on HABPanel.

I don’t think Alexa supports such free natural text processing. I suspect you will have to say “Alexa, turn on PS4” and you will have to have a PS4 Switch Item tagged as switchable (or what ever Alexa uses) and a Rule to do all the actions that should occur.

Please use code fences for all code. How to use code fences


(Ben Elder) #3

Got the “Simple Switch” working. It makes sense now with the “state” topic and the “command” topic. Guess I should only use the command topic when updating from Openhab, and use the state topic to update from any other source?

For the HDMI switch, I have it working in both directions but not optimal. I did the “state” and “command” topics as well and Openhab now switches the input on the HDMI switch, but if I update the state from another source, it just places the text before the all of my selections, instead of updating the selection button.

Updated Items File:

Switch SmartPlug "Lamp [%s]" <poweroutlet> {mqtt=">[mqtt:sunroom/plug1:command:*:default],<[mqtt:sunroom/plug1/state:state:default]"}
Number Temperature "Room Temperature [%.1f]" {mqtt="<[mqtt:sunroom/temp:state:default]"}
String Gaming <sofa> {mqtt=">[mqtt:sunroom/switch:command:*:default], <[mqtt:sunroom/switch/state:state:MAP(gaming.map)]"}

Updated Sitemap File:

sitemap default label="Elder Home"
{
	Switch item=SmartPlug label="Lamp"
	Text item=Temperature
	Switch item=Gaming label="Gaming Selection" mappings=[swi01="PS3", swi02="PS4", swi03="XBOX360", swi04="Nintendo Wii", swi05="Nintendo Switch", swi06="Super Nintendo Classic", swi07="Nintendo Classic", swi08="RCA Jacks"]
}

Also, is there a way to use my “gaming.map” in the Sitemap file? It was not clear in any of the examples in the link sent?

Gaming Map File:

swi01=PS3
swi02=PS4
swi03=XBOX 360
swi04=Nintendo Wii
swi05=Nintendo Switch
swi06=Super Nintendo Classic
swi07=Nintendo Classic
swi08=RCA Jacks

Thanks for the tip on the codeblocks. I put my python code starting with the quad spaces, but the imports didn’t take for some reason. I used the python code block in the specific one outlined for it, fixed above. Also it has some slight edits for the “state” and “command” topics, below:

import time
import serial
import RPi.GPIO as GPIO
import paho.mqtt.client as mqtt

SWI01 = [17, 1]
SWI02 = [23, 2]
# SWI03 = [22, 3]
# SWI04 = [23, 4]
SWI05 = [24, 5]
# SWI06 = [25, 6]
# SWI07 = [26, 7]
# SWI08 = [27, 8]

GPIO.setmode(GPIO.BCM)    
GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_UP)  
GPIO.setup(24, GPIO.IN, pull_up_down=GPIO.PUD_UP)

topic_state = "sunroom/switch/state"
topic_pub = "sunroom/switch"

# GPIO Callbacks
def port1_callback(channel):
	client.publish(topic_state, "swi01")

def port2_callback(channel):
	client.publish(topic_state, "swi02")

def port5_callback(channel):
	client.publish(topic_state, "swi05")
	
# GPIO Button Interrupts
GPIO.add_event_detect(SWI01[0], GPIO.FALLING, callback=port1_callback, bouncetime=300)  
GPIO.add_event_detect(SWI02[0], GPIO.FALLING, callback=port2_callback, bouncetime=300)
GPIO.add_event_detect(SWI05[0], GPIO.FALLING, callback=port5_callback, bouncetime=300)


# MQTT Callbacks
def on_connect(client, userdata, flags, rc):
	if rc == 0:
		print("Connected to MQTT Broker")
		global Connected
		Connected = True
	else:
		print("Connection failed")

def on_message(client, userdata, message):
	output = str(message.payload.decode("utf-8"))
	print("received message = {}".format(output))
	data = "{}\r".format(output)
	ser.write(data.encode())

Connected = False
client = mqtt.Client("P1")
client.connect("localhost")
client.on_connect = on_connect
client.on_message = on_message
client.loop_start()

ser = serial.Serial("/dev/ttyUSB0")
ser.baudrate = 19200

while Connected != True:    #Wait for MQTT connection
	time.sleep(0.1)

client.subscribe(topic_pub)
print("Subcribed to {}".format(topic_pub))

try:
	print("Program is Running")
	time.sleep(100000)
except KeyboardInterrupt:  
	GPIO.cleanup()
	client.disconnect()
	client.loop_stop()
finally:
	GPIO.cleanup()

(Rich Koshak) #4

No, anything that is telling the device to turn on/off or whatever uses the cmd topic. ONLY the device publishes to the state topic to tell OH or anything else that cares that the device has changed state.

That may be a problem with the UI updating. Does it show correctly when you refresh the page?

https://www.openhab.org/docs/configuration/items.html#state-transformation


(Ben Elder) #5

Thanks, you’ve been very helpful Rich!

I was able to get things going with the items file, but it seems like I should be able to refer to the map file in the sitemap file, instead of explicitly typing out the maps in the file.

Items File:

Switch SmartPlug "Lamp [%s]" <poweroutlet> {mqtt=">[mqtt:sunroom/plug1/command:command:*:default],<[mqtt:sunroom/plug1/state:state:default]"}
Number Temperature "Room Temperature [%.1f]" {mqtt="<[mqtt:sunroom/temp/state:state:default]"}
String Gaming <sofa> {mqtt=">[mqtt:sunroom/switch/command:command:*:default], <[mqtt:sunroom/switch/state:state:default]"}

Sitemap File:
sitemap default label=“Elder Home”

{
	Switch item=SmartPlug label="Lamp"
	Text item=Temperature
	Selection item=Gaming mappings=[swi01="PS3", swi02="PS4", swi03="XBOX360", swi04="Nintendo Wii", swi05="Nintendo Switch", swi06="Super Nintendo Classic", swi07="Nintendo Classic", swi08="RCA Jacks"]
}

it seems I should be able to just update the sitemap section with the map file, something to the sort of:

Selection item=Gaming mappings=[MAP(gaming.map)]

Are the transformation only useable on the items file? The pointer you sent me to seems to only have examples for the items file. I’m not seeing where anyone used it in the sitemap file.

I also tried removing the mappings from the Sitemaps file and added the description of:
"Playing [MAP(gaming.map):%s]" to the item. No Dice…

Right now, nothing is referring to my gaming.map file.


(Rich Koshak) #6

You cannot use a map file to define mappings.

Mappings have nothing to do with transformations. A transformation takes one single thing and transformed it too something else. Mappings is a way to provide buttons on the Sitemap. Beyond the map transform requiring a list of mappings and the mappings subelement requiring a similar list they have nothing in common.

You can use your gaming.map to transform the state of an item into a more human friendly form. You can use gaming.map to transform the data returned by a binding into a format that can be used by an item. You can use it in a rule to transform a value inside the Rule.