Custom widget: Keypad

Keypad.widget.json (2.4 KB)

Hope it will be of use to someone.
I was looking at building a DIY home alarm system fully running on openHAB2 (installed on raspberry PI)
The GPIO for contacts, PIR, siren etc. and 7” wall mounted panels for Key Pads, Panic button etc., which are located around the house.

As there is already GPIO binding I’ve just focused on a widget for the keypad that can set an item state (ON/OFF) if alarm is set or not and rules to monitor GPIO state if alarm is ON.

The Widget contains Numeric Key Pad Buttons with a clear and Enter Button.

The widget links to one of your items for entered value and also has a max input length field for user’s choice this can be left empty and will ignore monitoring length. Items used are;

String Alarm_Code "Alarm Code"
String Entered_Code "Entered Code"
Switch Alarm_State "Alarm State"

Below is an example of keypad updating Alarm State (for this example rule is set to 5 second delay when setting alarm ON to allow someone get out the house this can be adjusted to suit)

Here is a simple rule I am using to check if the entered code is the same as the Alarm code.

var Timer set_timer = null
rule "Alarm_Code_Entered"
when
	Item Entered_Code received update
then
if (Entered_Code.state.toString==Alarm_Code.state.toString)
{     
	if(Alarm_State.state==ON)
	{
		//Turn off Alarm instantly
		Alarm_State.postUpdate(OFF)
	}
	else if (Alarm_State.state==OFF)
	{
		if (set_timer != null)
		{
			set_timer.cancel()
			set_timer = null
		}
		else
		{
			//Give user 15 seconds to get out and close door
			set_timer = createTimer(now.plusSeconds(5)) [|
				Alarm_State.postUpdate(ON)
			set_timer = null
			]
		}
	}
}
end

Let me know what you think also If you feel it would be worth creating a binding for a Home Alarm system tell us also.

11 Likes

Awesome work @Murpher!
I’d suggest a masking feature for the entered numbers.

Great work @Murpher !
Also auto reset the the typed code after few seconds of inactivity would be a nice feature!

Thanks @kubawolanin and @villaRob. There both great ideas.

I’ve added a css frame around the numbers just to clean it up for the meantime.

What do you think the best method is to add them features. Can you add script into the widget code editor?

My first thought is I can fork the GitHub habpanel repository and create a Keypad widget through that with them features

Very nice @Murpher, thanks for sharing!
Unfortunately you can’t add Javascript code in the current state, since it can be injected externally it would be a security issue.

Hi how can I import this to habpanel OH1.8.3?

Hi @Nguy_n_Phuc_Anh_D_ng

Sorry I only use openHAB 2 so not fully sure if it’s the same method @ysc might be able to confirm.

If you download the the Keypad.widget.json file https://community.openhab.org/t/custom-widget-keypad/18154/4

Then in your HABPanel click the edit on your desired dashboard.

Next click the Add Widget (top right) and then click the custom widget cog

Then click Import… and select the downloaded keypad json file that you downloaded

Hope that helps

Unfortunately I can’t see any custom widgets selection in my habpanel, what should I do?

But anyway, I have edited your keypad widget and turn it into web code like this, I hope it could help someone who is using OH1.8.x like me:


<div ng-form="myKeyPad" ng-init="myCode=''"><div class="col-sm-12"><div 
     ng-style="{
      'background-color': '#E3DDE9'
     }"
     class="template-container"
     style="position:absolute;width:291px; height: 40px;top:-50px;left:6px">
  <div class="template-contents">
    <div style="
            margin-top: 0px;
            margin-left: 0px;
            "><label style="font-size:30px;color: black;font-family: Comic Sans MS, cursive, sans-serif;">{{ myCode | limitTo: config.max_length }}{{myCode.length > config.max_length ? '' : ''}}</label>
</div>
</div>
</div></div>
  <div class="col-sm-4 pd-b" style="top:0px;left:-10px">
    <button ng-click="myCode= myCode + 1" class="btn btn-default btn-lg fullWidth" style="width:90px;height:40px;margin: auto;text-align:center;font-family: Comic Sans MS, cursive, sans-serif;">1</button></div>
  <div class="col-sm-4 pd-b" style="top:0px;left:-10px"><button ng-click="myCode= myCode + 2" class="btn btn-default btn-lg fullWidth" style="width:90px;height:40px;margin: auto;text-align:center;font-family: Comic Sans MS, cursive, sans-serif;">2</button></div><div class="col-sm-4 pd-b" style="top:0px;left:-10px"><button ng-click="myCode= myCode + 3" class="btn btn-default btn-lg fullWidth" style="width:90px;height:40px;margin: auto;text-align:center;font-family: Comic Sans MS, cursive, sans-serif;">3</button></div><div class="col-sm-4 pd-b" style="top:5px;left:-10px"><button ng-click="myCode= myCode + 4" class="btn btn-default btn-lg fullWidth" style="width:90px;height:40px;margin: auto;text-align:center;font-family: Comic Sans MS, cursive, sans-serif;">4</button></div><div class="col-sm-4 pd-b" style="top:5px;left:-10px"><button ng-click="myCode= myCode + 5" class="btn btn-default btn-lg fullWidth" style="width:90px;height:40px;margin: auto;text-align:center;font-family: Comic Sans MS, cursive, sans-serif;">5</button></div><div class="col-sm-4 pd-b" style="top:5px;left:-10px"><button ng-click="myCode= myCode + 6" class="btn btn-default btn-lg fullWidth" style="width:90px;height:40px;margin: auto;text-align:center;font-family: Comic Sans MS, cursive, sans-serif;">6</button></div><div class="col-sm-4 pd-b" style="top:10px;left:-10px"><button ng-click="myCode= myCode + 7" class="btn btn-default btn-lg fullWidth" style="width:90px;height:40px;margin: auto;text-align:center;font-family: Comic Sans MS, cursive, sans-serif;">7</button></div><div class="col-sm-4 pd-b" style="top:10px;left:-10px"><button ng-click="myCode= myCode + 8" class="btn btn-default btn-lg fullWidth" style="width:90px;height:40px;margin: auto;text-align:center;font-family: Comic Sans MS, cursive, sans-serif;">8</button></div><div class="col-sm-4 pd-b" style="top:10px;left:-10px"><button ng-click="myCode= myCode + 9" class="btn btn-default btn-lg fullWidth" style="width:90px;height:40px;margin: auto;text-align:center;font-family: Comic Sans MS, cursive, sans-serif;">9</button></div><div class="col-sm-4" style="top:15px;left:-10px"><button class="btn btn-danger btn-lg fullWidth" ng-click="myCode=''" style="width:90px;height:40px;margin:auto;text-align: center;font-family: Comic Sans MS, cursive, sans-serif;">CLEAR</button></div><div class="col-sm-4" style="top:15px;left:-10px"><button ng-click="myCode= myCode + 0" class="btn btn-default btn-lg fullWidth" style="width:90px;height:40px;margin: auto;text-align:center;font-family: Comic Sans MS, cursive, sans-serif;">0</button></div><div class="col-sm-4" style="top:15px;left:-10px">  <button class="btn btn-success btn-lg" style="width:90px;height:40px;margin:auto;text-align: center;font-family: Comic Sans MS, cursive, sans-serif;"
  ng-click="sendCmd('Entered_Code', myCode);myCode=''">ENTER</button>
</div></div>

I think it’s even cooler, hope you like it

1 Like

@Murpher , where do you set the pasword?
In the rule-file itself? What would be the safest way to do so?

@Marijn_Oudijk, I’m current setting this in a startup rule, I’m away from my pi but think the rule is something like this;

rule "set password"
when
	System started
then
	Alarm_Code.postUpdate('1234')
	Alarm_State.postUpdate('ON')
end

Currently I haven’t got round to it as my raspberry pi is on a UPS.

But if you add persistence to them two items Alarm_Code & Alarm_State (i’m looking at using mapdb “restoreOnStartup” when I get round to it), that will keep the Code/State values set on a restart.

Might not be the safest way as you put it but works for me.

Works like a charm setting the password.

I did notice that, if you limit the entering to 4 digits but you push 5 before confirming, the 4 digits are shown in the screen but the 5 digits are entered into the string for evaluation.

Glad that worked for you.

Thanks for pointing that out. The error was I wasn’t limiting the string on the submit only in the display. I’ve sorted that out now

I’ve update the link in the first post with the fix.

1 Like

little addon for the keypad…

</style>
<div ng-if="itemValue('AlarmSysStatus')=='1'">
  <span style="color: green; font-size: 20pt"><b>DISARMED</b></span>
</div>
<div ng-if="itemValue('AlarmSysStatus')=='2'">
  <span style="color: red; font-size: 20pt"><b>ARMED STAY</b></span>
</div>
<div ng-if="itemValue('AlarmSysStatus')=='3'">
  <span style="color: red; font-size: 20pt"><b>ARMED AWAY</b></span>
</div><br>
1 Like

My Version…

// Alarm Keypad
rule "KeyPad-Disarm"
when
    Item AlarmArmDisarmKeypad received command 86421
then
    AlarmArmDisarm.sendCommand(1)
    logInfo("Security", "LOG: KeyPad Command 1 Received")
end


// Alarm Keypad
rule "KeyPad-Arm-Away"
when
    Item AlarmArmDisarmKeypad received command 86422
then
    AlarmArmDisarm.sendCommand(2)
    logInfo("Security", "LOG: KeyPad Command 2 Received")
end


// Alarm Keypad
rule "KeyPad-Arm-Stay"
when
    Item AlarmArmDisarmKeypad received command 86423
then
    AlarmArmDisarm.sendCommand(3)
    logInfo("Security", "LOG: KeyPad Command 3 Received")
end

Ok,

So i just changed mine to the opposite of above. I added three new buttons above 1,2,3. They are Disarm, Away ,Stay. So you hit one of them first which adds that number first say 1 for disarm. Then you add your 4 digit pass so it ends up being 18642 like so…

// Alarm Keypad
rule "KeyPad-Arm-Away"
when
    Item AlarmArmDisarmKeypad received command 18642
then
    AlarmArmDisarm.sendCommand(2)
    logInfo("Security", "LOG: KeyPad Command 2 Received")
end

Dear All

please can you for me it doesn’t work,

I’m a newbye with OH so please can you post the complete file .rules and the complete file .items to add?

furthermore in the json file I note that in configuration tab there is the item “enterd_value”, items in your previous post is Entered_Code

where to define the humber for max_lenght?

please tell me where to find a step by step guide to add keypad widget.

thanks in advance for your support

AlarmKeypad.widget.json (3.8 KB)

I used your code to make this. This works with the alarmdecoder binding. It works much more like the physical keypad where it transmits 1 button press at a time. The stay, away, bypass and chime text changes color depending on their relative status. It also has the text output from keypad and alarm status icon. The alarm status will show 1 of 3 statuses: Ready, Not Ready, Armed.

My items file was just the example from here with the addition of the following item.

Number alarmDigit "alarmDigit" (gPanel)  {alarmdecoder="SEND#1=1,2=2,3=3,4=4,5=5,6=6,7=7,8=8,9=9,10=*,0=0,11=POUND", autoupdate="false"}

Also found that this code works fine without defining settings within the custom widget.

5 Likes

As per @Hawkevilcat I’ve been using the design where it transmits 1 button press at a time. In fact James thanks for the widget as it has much nicer styling than mine so I’ve adopted it to suit.

I am not using the alarmdecoder though so I have a rule to do the processing as below. It also addresses the requirement to mask the display and clear entered characters after a time out period.

You will need to create 3 items to handle the key press:
String item vrt_Key_Press **
String item vrt_Key_Entry
String item vrt_Key_Entry_Save

** You do need to use a widget like the one James has created, but it needs some small modifications to work with my rule code:

  1. You don’t need to create the ‘alarmDigit’ item.
  2. The widget sends each press to ‘alarmDigit’ change that to ‘vrt_Key_Press’
  3. I use CLEAR and ENTER rather than * and #, in the widget you’ll find it sends 10 and 11 respectively, change to send ‘CLEAR’ and ‘ENTER’
  4. I don’t use the ‘alarmPanelStatusAway’ item that is part of the alarmdecoder so the I’ve stripped the text out from below the numbers. Each to there own though :slight_smile:
  5. At the top change ‘alarmPanelDisplay’ to ‘vrt_Key_Entry’

To handle the validation of any code entered I have a group of ‘users’. If you want to do a different validation then you can change the allMembers.filter lines and adjust to suit your own item check, plenty of examples above.

My group and ‘user’ structure is simple enough though so if you want it create:
Group item grp_Users_PIN
String item usr_Yourname (and add it to the group grp_Users_PIN)

The rule file does also deal with:
vrt_Alarm_Message - simple string item I display to the user, not on the widget but you can add if you want it on the widget or elsewhere/nowhere
vrt_Alarm_State - happens to also be a string item but has a whole host of stuff behind it. Basically only used when a valid code is entered so it’s the easy bit you need to customise to suit your environment.

That’s about it once you’ve initialised your user(s).

Future nice to do’s:

  1. I’d really like to encrypt the PINs rather than store them in clear text. One way salted hash sort of thing, any sample code or suggestions welcome :wink:
  2. Maybe identify user levels so that the alarm can be reset/cleared from any state, admins basically.
// Monitors for key press and reset cache after timeout
var Timer alarmKeyResetTimer = null

rule "Alarm key press system started reset."
when
  System started
then
  vrt_Key_Entry.postUpdate("")
  vrt_Key_Entry_Save.postUpdate("")
end

rule "Alarm key press..."
when
  Item vrt_Key_Press received update
then
logDebug("Alarm", "keyPress state: " + vrt_Key_Press.state)

  if (vrt_Key_Entry.state == null || vrt_Key_Entry_Save.state == null) {
    vrt_Key_Entry.postUpdate("")
    vrt_Key_Entry_Save.postUpdate("")
  }

  if (vrt_Key_Press.state == "CLEAR") {
    // Clear pressed (some like to use *)
    vrt_Key_Entry.postUpdate("")
    vrt_Key_Entry_Save.postUpdate("")
  } else if (vrt_Key_Press.state == "ENTER") {
    // Enter pressed (some like to use #)

    var Boolean validPIN = new Boolean(false)
    grp_Users_PIN.allMembers.filter(user | user.state == vrt_Key_Entry_Save.state).forEach[ StringItem filteredItem |
logInfo("Alarm", "Valid alarm code entered by user: " + filteredItem.name)
      validPIN = true
    ]

    if (validPIN == true) {
      vrt_Alarm_Message.postUpdate("Code OK")
      // Code validated there fore disarm the alarm
      if (vrt_Alarm_State.state == "ARMING" || vrt_Alarm_State.state == "ARMED" || vrt_Alarm_State.state == "DISARMING") {
        vrt_Alarm_State.sendCommand("DISARMED")
      }
    } else {
      vrt_Alarm_Message.postUpdate("Incorrect code")
    }

    vrt_Key_Entry.postUpdate("")
    vrt_Key_Entry_Save.postUpdate("")
  } else {
    // Received a key press, capture it
    vrt_Key_Entry_Save.postUpdate(vrt_Key_Entry_Save.state.toString() + vrt_Key_Press.state.toString())
    var String tempStr = vrt_Key_Entry.state.toString()

    // These two lines allow a briefly display of the entered character
    // I've taken them out because you'd need to make the whole routine thread safe to really use them
    //vrt_Key_Entry.postUpdate(tempStr + vrt_Key_Press.state.toString())
    //Thread::sleep(300)

    vrt_Key_Entry.postUpdate(tempStr + "*")
  }

  // Clear any existing timer
  alarmKeyResetTimer?.cancel
  alarmKeyResetTimer = null

  // If any text in the buffer then set a timer to clear it if no further presses
  if (vrt_Key_Press.state != null && vrt_Key_Press.state.toString().length() > 0) {
    alarmKeyResetTimer = createTimer(now.plusSeconds(20), [ | 
      alarmKeyResetTimer = null
//logDebug("Alarm", "Reset alarm key pad entry")
      vrt_Key_Entry.postUpdate("")
      vrt_Key_Entry_Save.postUpdate("")
    ])
  }

end


1 Like

To hide the values of the key presses

This can be done by adding either the line
-webkit-text-security: square;
or
-webkit-text-security: disc;

to the .mask code in the style section it should then read
.mask{
border: 1px solid #ccc;
background: #fff;
color:#333;
width:100%;
height: 45px;
-webkit-text-security: square;
}