Input field for number/free text for openHAB UIs

Well,

This is a hack I am using. It is not the cleanest approach, but at least does the trick for me, so sharing in case it is useful for others.

I use a mix of text items, rules, and sitemap entries to simulate a “keypad” and a “input text item” on the android app (but it should work in other UIs also).

What you need:

  • A text item (of course, to receive the input)
  • A text item to be used (or mis-used :slight_smile:) to receive “key strokes”
  • Some sitemap entries to build the keypad
  • A rule to make it work

How it works:

  • An item is used to receive keystrokes. This item must be in the “TextInput” group for the rule to use it. The target text item name is specified in the Label for this keypad item (not the best use of labels either)
  • The sitemap uses a map to send text “keystrokes” to the keypad item
  • The rule updates the target item on each keystoke (there is a cursor implemented to edit the existing input)
  • When the user presses “done”, a command with the text is sent to the target item.

Benefits:

  • No unauthenticated rest api calls
  • Works wherever the sitemap is available (local, remote, etc)

Drawbacks:

  • The sitemap has to contain a full keypad map for every item needing this (copy & paste)
  • If the keypad is too wide, it will not fit on a portrait orientation screen (as in the snapshot above)
  • Concurrency: the item is not meant to be edited from two devices at the same time (for obvious reasons…)

The rule ("rules/textinput.rules"):

import org.eclipse.smarthome.model.script.ScriptServiceUtil

var registry = ScriptServiceUtil.getItemRegistry()

rule "Text Input"
when
    Member of TextInput received update
then
    var update = triggeringItem.state.toString()
    if (update == "") return;

    logDebug("TextInput", "{}: {}", triggeringItem.name, update)

    var target = registry.get(triggeringItem.label)

    if (target !== null) {
        var text = target.state.toString()
        if (text == "" || text == "NULL") text = "_"
        else if (!text.contains("_")) text += "_"

        if (update.length() == 1) {
                text = text.replaceFirst("_", update + "_")
        }
        else if (update == "RIGHT") {
                text = text.replaceFirst("_(.)", "$1_")
        }
        else if (update == "LEFT") {
                text = text.replaceFirst("(.)_", "_$1")
        }
        else if (update == "DELETE") {
                text = text.replaceFirst("._", "_")
        }
        else if (update == "DONE") {
                text = text.replaceFirst("_", "")
        }

        text = text.substring(0, 1).toUpperCase() + text.substring(1).toLowerCase().replace("  ", " ").split(" ").reduce([capitalized, word | capitalized + " " + word.substring(0, 1).toUpperCase() + word.substring(1)])

        if (text.contains("_")) target.postUpdate(text)
        else target.sendCommand(text.trim())
    }

    triggeringItem.postUpdate("")
end

A group (“items/textinput.items”):

// Group used in rule
Group TextInput

Some text items (“items/test.items”):

String TestText "[%s]"
String TestText_Input "TestText" <empty> (TextInput)

In the sitemap:

        Text item=TestText
        Switch item=TestText_Input label="" mappings=[1="1",2="2",3="3",4="4",5="5",6="6",7="7",8="8",9="9",0="0",DELETE="Del"]
        Switch item=TestText_Input label="" mappings=[Q="Q",W="W",E="E",R="R",T="T",Y="Y",U="U",I="I",O="O",P="P","'"="'"]
        Switch item=TestText_Input label="" mappings=[A="A",S="S",D="D",F="F",G="G",H="H",J="J",K="K",L="L","Ñ"="Ñ",DONE="Done"]
        Switch item=TestText_Input label="" mappings=[LEFT="<",Z="Z",X="X",C="C",V="V",B="B",N="N",M="M",RIGHT=">"," "=" "," "=" "]

PS: the rule is using the item registry to get item objects directly from the item name, without these items needing to be in a specific, known group. This is not a pretty much documented or known feature, but I find it quite useful in my rules.

2 Likes