Input field for number/free text for openHAB UIs

You need to use VPN tunnel or make some NAT in your internet router.

Hi,
thanks Mark, all works perfect in OH2. Is it possible to extend the code for only four digit input (PIN number) and delete the input filed after putting 4 keypad inputs ?
Can you supress AJAX values, that no last values are displayed ?
Thanks.

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

Jozef,
I can’t help you. It may be possible, but I don’t know Javascript. I just managed to make this work from the starting point provided eirlier in the thread.

OK thanks.

I encountered a problem in OpenHab 2.4 stable with this nice trick. I have been using it to input an email address into String in BasicUI, see original post. Since 2.4 the formatting has changed and messes up the html view. I think this is due to the changes in Webview, which now adds an icon and text title AND it adds a new ‘div’ with is 2 columns. It stopped as well showing the Icon. See attached screenshot.


My html knowledge isn’t good enough to fix this. Any hints ?

1 Like

Hi Mark,
I’m beginner, I see your example very well, but I haven’t comprehend how to configure “PVEmeter1” and “PVEmeter2” items.
Can you explain this?
Thanks.

This workaround is great and gives me the ability to do many things with it in the future. Thanks for the implementation, it works great!
One thing though, I have set mine up to look like a number pad. The # and * symbols on the phone pad are replaced with del and done. If I enter the number 1 and hit done, the sent value is 1_, until I hit done one more time.

How would you go about having the _ removed? I cannot just have spaces because of the replacement code you have. I am not familiar enough to figure it out. Would it be possible to only have the leading _ if nothing is in the field? How would you substitute whatever number or letter is currently in the field for the _ after the first entry?

thanks for any help. This is a pretty great workaround.

Hi Mark,
I have implemented your script and it works without problems. Changed to input field from type “text” to “password” - so it will mask the input.
Your are suing several style sheets like “…/basicui/mdl/material.min.css” - I don’t find them in my installation. Could you please provide them?
Thanks for your reply
Georg

1 Like

Got mine working as below
Screenshot from 2020-01-13 22-18-39

Using code from Mherwege, in this link. Everything works great in 2.5.6. Thanks!

Minor issue, any idea why the excessive padding in the Sitemap? (Its fine on Android app, but this is the screenshot from Chrome.) Also, any idea why these empty images or how to suppress them?

BTW for RaspberryPi, these are the paths & URLs:
Path for html files: /etc/openhab2/html/file.html
URL: http://10.0.0.10:8080/static/index.html

Thanks!

default.sitemap:

Text item=openhab2_sm label="Admin" {
			Frame label="Climate Settings" {
				Webview url="http://10.0.0.10:8080/static/textinput.html?item=config_low_temp&label=Low Temp Alert Threshold (f)"
				Webview url="http://10.0.0.10:8080/static/textinput.html?item=config_high_temp&label=High Temp Alert Threshold (f)"
				Webview url="http://10.0.0.10:8080/static/textinput.html?item=config_low_humidity_home&label=Low Humidity Alert (home)"
				Webview url="http://10.0.0.10:8080/static/textinput.html?item=config_high_humidity_home&label=High Humidity Alert (home)"
				Webview url="http://10.0.0.10:8080/static/textinput.html?item=config_low_humidity_prop&label=Low Humidity Alert (properties)"
				Webview url="http://10.0.0.10:8080/static/textinput.html?item=config_high_humidity_prop&label=High Humidity Alert (properties)"
			}
			Frame label="Property Settings" {
				Webview url="http://10.0.0.10:8080/static/textinput.html?item=openhab2_Name&label=openhab2%20Name"
				Switch item=AlarmEnabled_Door_openhab2
				Switch item=AlarmEnabled_Motion_openhab2
				Webview url="http://10.0.0.10:8080/static/textinput.html?item=openhab3_Name&label=openhab3%20Name"
				Switch item=AlarmEnabled_Door_openhab3
				Switch item=AlarmEnabled_Motion_openhab3
				Webview url="http://10.0.0.10:8080/static/textinput.html?item=openhab4_Name&label=openhab4%20Name"
				Switch item=AlarmEnabled_Door_openhab4
				Switch item=AlarmEnabled_Motion_openhab4
			}
		}

Thanks, it’s working but when i link this items to a channel (mqtt binding) new value is not send to the channel /mqtt broker ;( any idea how to fix it?

to post command you can change this line

PUT->POST


 function httpPut(theUrl, theValue)
    {
        var xmlHttp = new XMLHttpRequest();
        xmlHttp.open("POST", theUrl, true);

and remove “/state”

 <!-- url = "http://" + openHabianPI + ":8080/rest/items/" + getParam('item') + "/state"; -->

        url = "http://" + openHabianPI + ":8080/rest/items/" + getParam('item');

@Jacek_Kaczmarczyk Thank you for this.

I have made the script a bit more general. It now supports both commands and state updates. If you add &command to the url, it will send a command, otherwise do a state update.

There are also a few more formatting options. Before, it was assuming numbers and these were stripped from their fractional parts. Also UOM’s for QuantityTypes were lost. One could take this much further, but it gives me sufficient options to make it look and behave a bit nicer.

  • Adding &type=text will not do any formatting.
  • Adding &type=int or &type=integer or &round will round to the nearest integer, but still preserve and show the UOM if there is one.

Here is the new code:

<html class="ui-icons-enabled">

<script>
    function getParam(param) {
        var qs = (function(a) {
            if (a == "") return {};
            var b = {};
            for (var i = 0; i < a.length; ++i) {
                var p=a[i].split('=', 2);
                if (p.length == 1)
                    b[p[0]] = "";
                else
                    b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
            }
            return b;
        }) (window.location.search.substr(1).split('&'));       
        return qs[param];
    }
    function httpGet(theUrl, type, round) {
        var xmlHttp = new XMLHttpRequest();
        xmlHttp.onreadystatechange = function() {
    		if (this.readyState == 4 && this.status == 200) {
				if (type == "text" | !round)
					currentValue = xmlHttp.responseText;
				else {
					currentValue = xmlHttp.responseText.split(' ')[1];
					currentValue = Math.round(parseFloat(xmlHttp.responseText)) + (currentValue ? " " + currentValue : "");
				}
		    	document.getElementById('textInput').value = currentValue;
		    }
		};
        xmlHttp.open("GET", theUrl, true);
        xmlHttp.setRequestHeader("Accept", "text/plain");
        xmlHttp.send();
        return 
    }
    function httpPut(theUrl, theValue) {
        var xmlHttp = new XMLHttpRequest();
        xmlHttp.open("PUT", theUrl, true);
        xmlHttp.setRequestHeader("Content-type", "text/plain");
        xmlHttp.setRequestHeader("Accept", "application/json");
        xmlHttp.send(theValue);
    }
    function httpPost(theUrl, theValue) {
        var xmlHttp = new XMLHttpRequest();
        xmlHttp.open("POST", theUrl, true);
        xmlHttp.setRequestHeader("Content-type", "text/plain");
        xmlHttp.setRequestHeader("Accept", "application/json");
        xmlHttp.send(theValue);
    }
	function getValue() {
        url = "../rest/items/" + getParam('item') + "/state";
		type = getParam('type') | "text";
		round = getParam('round') !== 'Undefined' | getParam('type') == 'int' | getParam('type') == 'integer' ? true : false;
        httpGet(url, type, round);
    }
	function sendValue() {
		if (getParam('command') !== 'Undefined')
			postCommand();
		else
			updateState();
	}
	function updateState() {
        url = "../rest/items/" + getParam('item') + "/state";
        httpPut(url, document.getElementById('textInput').value);
    }
	function postCommand() {
        url = "../rest/items/" + getParam('item');
        httpPost(url, document.getElementById('textInput').value);
    }
</script>

<head>
	<link rel="stylesheet" type="text/css" href="../basicui/mdl/material.min.css" />
	<link rel="stylesheet" type="text/css" href="../basicui/material-icons.css" />
	<link rel="stylesheet" type="text/css" href="../basicui/roboto.css" />
	<link rel="stylesheet" type="text/css" href="../basicui/smarthome.css" />
	<script src="../basicui/mdl/material.min.js"></script>
	
	<style>
		form {
			margin-bottom: 0;
		}
		.mdl-form__row {
			border-bottom-style: none;
			height: auto;
			padding-top: 0;
			padding-right: 0;
			padding-bottom: 0;
			padding-left: 0;
		}
	</style>
</head>
<body class="mdl-color-text--grey-700" data-icon-type="svg">
	<form action="JavaScript:sendValue()">
		<div class="mdl-form__row mdl-cell mdl-cell--6-col mdl-cell--8-col-tablet">
			<span class="mdl-form__icon" id=icon>
			</span>
			<div class="mdl-form__label" id=label>
			</div>
			<div>
				<input type="text" name="textInput" id="textInput">
			</div>
			<div>
				<input type="submit" value="Submit">
			</div>
		</div>
		<script>
			document.getElementById('label').innerHTML = getParam('label');
			document.getElementById('icon').innerHTML = "<img data-icon=line src=../icon/" + getParam('icon') + "?format=svg/>";
			document.getElementById('textInput').value = getValue();
		</script>
	</form>
</body>
1 Like

is there a way to get rid of an static .sitemap url/ip of the item ? i use vpn to connect and if i use this html in .sitemaps it works only for local OR vpn ip (base on url provided in sitemap)

there is also sth wrong with extracted values to basicUI
item.state = “00:00/12°C 09:00/15°C 11:30/15°C 12:30/15°C 17:30/19°C 22:00/19°C”

input value in basicui “0 09:00/15°C”

and one more question is it possible to expose it via openhab app (cloud connector) i don’t see this items in the sitemap via app (but visible via desktop)

This is related to the use of the full IP address. The sitemap element is a webview. If the internet address of the webview element is not accessible, it will not show. That is the case if you use a local IP address over the cloud connector. You could in theory use any internet address, but it has to be accessible. Opening ports on your router for this and forwarding http traffic to the openHAB server wouldn’t be a good idea, as it would expose your full system. This limitation has nothing to do with the script, but with the simple fact of using a webview element.

Can you show the webview element definition in your sitemap that generates this response? Are you sure you cannot scroll the input field to show the rest? Your value could be too long for the size of the input box. Please also check with the REST API (development tools) the item has the value you state and did not get cut anywhere else. I don’t immediately see anything obvious where the value would be cut by the script if you just use the &text parameter (and not any of the others).

ok, got it working in sitemap without specifiying IP
url="…/static/textInput.html?

1 Like

Sure

Webview label="Dni robocze" url="../static/textInput.html?item=RadiatorAC1_WorkdaysSchedule&type=text"
                                        Webview label="Dni wolne" url="../static/textInput.html?item=RadiatorAC1_HolidaysSchedule&type=text"

item value is 100% longer, input field is not allowing to scroll it