Sure. I made a working roku remote that I can insert as a iframe into habpanel. Works great on iPad and PC. The keyboard function uses the devices on-screen keyboard but keyboard does not work properly in android OS. This dynamically finds your roku(s) and finds the installed apps. Here is the screen shot.
exec.things file
Thing exec:command:getRokus [command="/etc/openhab2/scripts/searchRokus.py"]
roku.items file
String rokuCommand
String rokuDiscoveryResponse {channel="exec:command:getRokus:output"}
Switch discoverRokus {channel="exec:command:getRokus:run"}
roku.rules file
rule "Send Roku Command"
when
Item rokuCommand received update
then
sendHttpPostRequest(rokuCommand.state.toString)
logInfo("Roku", rokuCommand.state.toString)
end
/etc/openhab2/scripts/searchRokus.py
#!/usr/bin/python
import sys
import socket
import re
import xml.etree.ElementTree as ET
import urllib2
import json
ssdpRequest = "M-SEARCH * HTTP/1.1\r\n" + \
"HOST: 239.255.255.250:1900\r\n" + \
"Man: \"ssdp:discover\"\r\n" + \
"MX: 5\r\n" + \
"ST: roku:ecp\r\n\r\n";
socket.setdefaulttimeout(8)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
sock.sendto(ssdpRequest, ("239.255.255.250", 1900))
response = "["
while True:
try:
name = "Unknown"
model = "Unknown"
privateListening = "Unknown"
resp = sock.recv(1024)
matchObj = re.match(r'.*USN: uuid:roku:ecp:([\w\d]{12}).*LOCATION: (http://.*/).*', resp, re.S)
serialNumber = matchObj.group(1)
address = matchObj.group(2)
url = address + "query/device-info"
tree = ET.parse(urllib2.urlopen(url))
root = tree.getroot()
name = root.find('user-device-name').text
model = root.find('model-name').text
privateListening = root.find('supports-private-listening').text
url = address + "query/apps"
tree = ET.parse(urllib2.urlopen(url))
root = tree.getroot()
response+='{"name": "' + str(name) + '", "model": "' + str(model) + '", "serial": "' + str(serialNumber) + '", "address": "' + str(address) + '", "privateListening": "' + str(privateListening) + '", "apps": ['
for app in root.findall('app'):
appName = app.text
appId = app.get('id')
response+='{"appName": "' + str(appName) + '", "appId": "' + str(appId) + '"},'
response= response[:-1]
response+=']},'
except socket.timeout:
break
response= response[:-1]
response+=']'
print response
/etc/openhab2/html/roku.html
(url: http://OpenHabIP:8080/static/roku.html)
<html>
<head>
<script src="javascript/jquery-3.2.1.min.js"></script>
<script src="javascript/roku.js"></script>
<link rel="stylesheet" href="style/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="style/roku.css">
</head>
<body>
<input id="hiddenBox" type="text" autocorrect="off"/>
<div id="rokuControllerContainer">
<div id="deviceSelectorContainer">
<h3 style="color: white;">Roku Controller</h3>
<ul id="customRadio"></ul>
</div>
<div id="controlButtonsContainer">
<div id='Backspace' class="control glyphicon glyphicon-arrow-left btn btn-default"></div>
<div id='Home' class="control glyphicon glyphicon-home btn btn-default"></div></br>
<div id='Up' class="control glyphicon glyphicon-menu-up btn btn-default"></div></br>
<div id='Left' class="control glyphicon glyphicon-menu-left btn btn-default"></div>
<div id='Select' class="control glyphicon glyphicon-ok-sign btn btn-default"></div>
<div id='Right' class="control glyphicon glyphicon-menu-right btn btn-default"></div></br>
<div id='Down' class="control glyphicon glyphicon-menu-down btn btn-default"></div></br>
<div id='InstantReplay' class="control glyphicon glyphicon-repeat btn btn-default"></div>
<div id='Info' class="control glyphicon btn btn-default"><div style="height:24px;width: 19.98px;line-height: 43px">*</div></div>
<div id="keyboardAccess" class="btn btn-default">Keyboard</div></br>
<div id='Rev' class="control glyphicon glyphicon-backward btn btn-default"></div>
<div id='Play' class="control glyphicon glyphicon-play btn btn-default"></div>
<div id='Fwd' class="control glyphicon glyphicon-forward btn btn-default"></div><br><br>
</div>
<div id="appsContainer"></div>
</div>
</body>
</html>
/etc/openhab2/html/javascript/roku.js
$(document).ready(function(){
discoverRokus();
//changes state of rokuCommand to the selected button
$('.control').click(function () {;
//when keyboard button is click then place cursor in textbox that is off screen to get on-screen keyboard on mobile devices to pop-up, when clicked again take cursor out of field
$('#keyboardAccess').click(function () {;
//captures keypresses in text box for keyboard input - This is know to not work properly in android devices
$('#hiddenBox').keyup(function (e) {
var keynum;
var key;
if(window.event) { // IE
keynum = e.keyCode;
} else if(e.which){ // Netscape/Firefox/Opera
keynum = e.which;
}
if (keynum == 13) {
key = "ENTER";
} else if(keynum == 8){
key = "BACKSPACE";
} else {
var inBox = $('#hiddenBox').val();
key = "LIT_" + inBox.substr(inBox.length - 1);
}
$.ajax({
type : "PUT",
url : "http://192.168.1.45:8080/rest/items/rokuCommand/state",
data : currentAddress + "keypress/" + key,
headers : { "Content-Type": "text/plain" }
});
});
});
availableRokus = [];
function getState(item){
function setState( item ,txtNewState ){
function sendCommand( item , txtCommand ){
function discoverRokus() {
console.log("Discovering Roku's");
$('#customRadio').html('<span style="color: white;">Discovering Rokus</span>');
//Turns this item on to start running script
$.ajax({
type : "POST",
url : "http://192.168.1.45:8080/rest/items/discoverRokus",
data : "ON",
headers : { 'Content-Type': 'text/plain' }
}).done( function(data){
//while the state is ON (command is running) then do nothing
while (getState('discoverRokus') == 'ON'){}
$('#customRadio').html('');
var counter = 0;
//get JSON response and turn it into javascript object
rokuDiscoveryResponse = getState('rokuDiscoveryResponse');
rokusObject = JSON.parse(rokuDiscoveryResponse);
$('#customRadio').html('');
currentName = rokusObject[0]['name'];
currentAddress = rokusObject[0]['address'];
currentSerial = rokusObject[0]['serial'];
currentPrivateListening = rokusObject[0]['privateListening'];
currentModel = rokusObject[0]['model'];
if (currentName == 'None') {
currentName = 'Unknown';
}
var checked = "checked";
for (var key in rokusObject) {
if (rokusObject[key].name == 'None') {
rokusObject[key].name = 'Unknown';
}
$('#customRadio').append('<li><input type="radio" id="option-' + key + '" name="selector" ' + checked + '/><label for="option-' + key + '">' + rokusObject[key].name + '</label></li>');
checked = null;
}
$('#customRadio li input[type=radio]').click(function () {
var key = $("input[name='selector']:checked").attr('id');
key = key.substr(key.length - 1);
currentName = rokusObject[key]['name'];
currentAddress = rokusObject[key]['address'];
currentSerial = rokusObject[key]['serial'];
currentPrivateListening = rokusObject[key]['privateListening'];
currentModel = rokusObject[key]['model'];
loadApps(key);
});
loadApps(0);
}).fail( function(jqXHR, textStatus ){
console.log( "Failure: " + textStatus );
});
}
function loadApps(rokuKey) {
$('#appsContainer').html("");
for (var appKey in rokusObject[rokuKey].apps) {
var currentAppId = rokusObject[rokuKey]['apps'][appKey].appId;
var currentAppName = rokusObject[rokuKey]['apps'][appKey].appName;
$('#appsContainer').append('<img alt="' + currentAppId + '" class="appIcon btn btn-default" src="' + currentAddress + 'query/icon/' + currentAppId + '"/>');
}
$('.appIcon').click(function () {
var appId = $(this).attr('alt');
$.ajax({
type : "PUT",
url : "http://192.168.1.45:8080/rest/items/rokuCommand/state",
data : currentAddress + "launch/" + appId,
headers : { "Content-Type": "text/plain" }
});
});
}
/etc/openhab2/html/style/roku.css
body{
background-color: #456;
text-align: center;
}
#rokuControllerContainer{
text-align: center;
width: 100%;
height: 500px;
}
#deviceSelectorContainer{
text-align: center;
}
#customRadio{
list-style: none;
position: relative;
margin: 0 auto;
padding: 0;
height: 45px;
}
/*style untouched button*/
#customRadio li{
color: #AAAAAA;
display: inline-block;
position: relative;
height: 45px;
background-color: gray;
text-align: center;
margin: 0 4px;
}
#customRadio li input[type=radio]{
position: absolute;
visibility: hidden;
}
#customRadio li label{
display: block;
position: relative;
font-weight: 300;
font-size: 12px;
line-height: 45px;
padding: 0 20px;
margin: 0 auto;
height: 45px;
z-index: 9;
cursor: pointer;
-webkit-transition: all 0.25s linear;
}
/*Style hover over button*/
#customRadio li:hover label{
color: #FFFFFF;
}
/*style selected button*/
input[type=radio]:checked ~ label{
color: white;
background-color: #0db9f0;
}
.glyphicon {
font-size: 20px;
margin: 3px;
color: white;
}
#controlButtonsContainer{
text-align: center;
}
.appIcon{
width: 30%;
}
#appsContainer{
height: 155px;
overflow-y: scroll;
width: 80%;
margin: 0 auto;
max-width: 450px;
}
.control{
background-color: purple;
border: 0;
color: white;
}
.btn-default:hover{
background-color: purple;
border: 0;
}
#Up{
margin-bottom: -5px;
border-bottom: 0;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
#Left{
margin-right: -7px;
border-right: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
#Select{
border-radius: 0;
border: initial;
}
#Right{
margin-left: -7px;
border-left: 0;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
#Down{
margin-top: -4px;
border-top: 0;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
#Info{
font-family: Arial, Helvetica, sans-serif;
font-size: 40px;
overflow: hidden;
width: 46px;
height: 42px;
}
#keyboardAccess{
border: 0;
color: white;
background-color: purple;
position: relative;
height: 42px;
line-height: 31px;
}
#hiddenBox{
height: 20px;
width: 100px;
position: absolute;
top: -25px;
}
custom habpanel widget html
<div>
<iframe style="width: 100%;height: 600px;" src="/static/roku.html" frameBorder="0">
</iframe>
</div>
other files needed…
JQuery
/etc/openhab2/html/javascript/jquery-3.2.1.min.js
Bootstrap v3.3.7
I use fonts and css folder and copied them into /etc/openhab2/html/style/bootstrap/