Hello all,
Basically I am working to create a single virtual universal remote widget to control everything in my home theatre setup (Projector/Receiver/KODI mainly today). I stumbled across and built upon this excellent Harmony widget as a starting point for my first custom widget (thanks a lot to @craigers and @matt1 here!):
https://community.openhab.org/t/oh3-harmony-widget/112172/7?u=d0t
To get started I was able to rapidly modify the widgets in the thread above, add some additional configurations, and create a single remote that could do mostly what I was after - controlling KODI for playback/pause/stop, and a rule for the power button which would power on/off both the PC/Receiver/BenQ Projector (thanks @mlobstein!), and have volume/mute controlled by the Receiver. It was relatively easy to wire in via YAML and control a set of devices/items mapped in the widgets config, by adding new Widget config options (Props > Parameters) for the items I wanted to control. But is it possible to dynamically switch Props configuration within a Widget, based on other options?
For example, in my screenshot below, I’d like the Screen button in the top right and corner popup and allow the user to select the device they want to control (defined when ‘picking’ which things to control in the Widget config), so its possible to more generically control and navigate through each devices menus using the same virtual remote/buttons. At the same time, I understand how this can ‘break down’ quick as you consider the many different types of things/items/states and how the mapping could be very different… And It’s unclear to me if handling such mappings are even possible today within the Widget definition itself, beyond just picking items. (i.e. ideally nesting of the configuration so that the selected device could map all Widget buttons to a different set of items)
After heavily revising the UI, here is where I am currently at, which was strongly inspired by my favorite physical remote - the Harmony One:
For you OH3 UI Widget guru’s out there - would you recommend that I just continue forward with one to one mappings for each button/item in the Widget configuration and drop the thought of device switching for now?
Resources
(edit - @ysc: Added the code from post #4) & updated the screenshot
uid: harmonyOne-v5
tags: []
props:
parameters:
- context: item
description: Select the Displays Power Channel (i.e. Projector or Television power switch)
label: Select Item
name: power
required: true
type: TEXT
- context: item
description: Select the item to use for Mute (i.e. Receiver)
label: Select Item
name: mute
required: true
type: TEXT
- context: item
description: Select the item to use for Volume (i.e. Receiver)
label: Select Item
name: volume
required: true
type: TEXT
- context: item
description: Select the item to use for Player Controls (i.e. KODI MediaControl)
label: Select Item
name: player
required: true
type: TEXT
- context: item
description: Select the item to use for Actions (i.e. KODI InputAction, a.k.a Execute An Action)
label: Select Item
name: inputaction
required: true
type: TEXT
- context: item
description: Select the item to display on the Virtual Screen (i.e. KODI Title)
label: Select Item
name: screen
required: true
type: TEXT
- context: item
description: Select the item to display when Virtual Screen is unavailable (i.e. Date/Time).
label: Select Item
name: date
required: true
type: TEXT
parameterGroups: []
timestamp: Mar 7, 2021, 5:55:58 PM
component: f7-card
config:
style:
background-color: rgb(0, 0, 0)
--f7-card-margin-horizontal: 0px
border-radius: 50px
width: 20rem
height: 44rem
box-shadow: 0px 1px 4px -2px
text-shadow: 0px -1px
slots:
default:
- component: Label
config:
style:
position: absolute
left: 125px
top: 15px
color: white
font-size: 18px
line-height: 32px
white-space: nowrap
overflow: hidden
text-overflow: ellipsis
text-shadow: -1px 1px 1px hsl(0,0%,66%)
- component: oh-button
config:
iconF7: power
iconSize: 15
#textColor: white
bgColor: black
action: command
actionCommand: "=(items[props.power].state === 'OFF') ? 'ON' : 'OFF' "
actionItem: =props.power
style:
position: absolute
left: 20px
top: 15px
border-radius: 12px
box-shadow: 0px 2px 5px
color: "=(items[props.power].state === 'OFF') ? 'white' : 'red' "
height: 34px
background-image: "linear-gradient(360deg, #1C1C1C 10%, #494949 360%)"
- component: oh-link
config:
style:
position: absolute
top: 110px
left: 5px
color: white
iconF7: arrowtriangle_left_fill
iconSize: 30
action: command
actionCommand: PLAY
actionItem: =props.harmony+"_PlayerControl"
- component: oh-button
config:
#text: "=(items[props.screen] && items[props.screen].state !== undefined ? items[props.screen].state : items[props.date].state)"
#"=(props.forecastHours === undefined) ? 12 : Number(props.forecastHours)"
#text: "=(props.screen !== undefined) ? props.screen : props.date"
#text: "=(items[props.date].state)"
#text: "=(items[props.screen] !== undefined) ? =(items[props.screen].state) : =(items[props.date].state)"
text: "=(items[props.screen].state !== 'UNDEF' ? items[props.screen].state : items[props.date].state)"
#text: "=(items[props.screen].state && items[props.screen].state !== 'UNDEF' ? items[props.screen].state : items[props.date].state)"
raised: true
large: true
textColor: white
bgColor: black
#action: options
#actionItem: =props.harmony+"_CurrentActivity"
style:
position: absolute
right: 35px
top: 60px
width: 250px
height: 130px
#word-wrap: break-word
word-break: break-word
white-space: pre-wrap
line-height: 1.5
box-shadow: inset 0 0 2px
- component: oh-link
config:
style:
position: absolute
top: 110px
right: 5px
color: white
iconF7: arrowtriangle_right_fill
iconSize: 30
action: command
actionCommand: PLAY
actionItem: =props.harmony+"_PlayerControl"
- component: f7-badge
config:
bgColor: black
style:
position: absolute
left: 130px
top: 550px
width: 60px
height: 120px
border-radius: 12px
box-shadow: 0px 2px 5px
background-image: "linear-gradient(360deg, #1C1C1C 10%, #494949 360%)"
- component: oh-link
config:
style:
position: absolute
top: 550px
left: 132px
color: white
border-bottom: 1px solid gray
iconF7: play
iconSize: 60
action: command
actionCommand: PLAY
actionItem: =props.player
- component: oh-link
config:
style:
position: absolute
top: 625px
left: 148px
color: white
iconF7: pause
iconSize: 25
action: command
actionCommand: PAUSE
actionItem: =props.player
- component: f7-badge
config:
bgColor: black
style:
position: absolute
left: 30px
top: 550px
width: 90px
height: 30px
border-radius: 12px
box-shadow: 0px 2px 5px
background-image: "linear-gradient(360deg, #1C1C1C 10%, #494949 360%)"
- component: oh-link
config:
style:
position: absolute
top: 597px
left: 65px
color: white
iconF7: backward_end_alt
iconSize: 25
action: command
actionCommand: PREVIOUS
actionItem: =props.player
- component: f7-badge
config:
bgColor: black
style:
position: absolute
left: 30px
top: 595px
width: 90px
height: 30px
border-radius: 12px
box-shadow: 0px 2px 5px
background-image: "linear-gradient(360deg, #1C1C1C 10%, #494949 360%)"
- component: oh-link
config:
style:
position: absolute
top: 552px
left: 65px
color: white
iconF7: backward
iconSize: 25
action: command
actionCommand: REWIND
actionItem: =props.player
- component: f7-badge
config:
bgColor: black
style:
position: absolute
right: 30px
top: 595px
width: 90px
height: 30px
border-radius: 12px
box-shadow: 0px 2px 5px
background-image: "linear-gradient(360deg, #1C1C1C 10%, #494949 360%)"
- component: oh-link
config:
style:
position: absolute
top: 597px
right: 65px
color: white
iconF7: forward_end_alt
iconSize: 25
action: command
actionCommand: NEXT
actionItem: =props.player
- component: f7-badge
config:
bgColor: black
style:
position: absolute
right: 30px
top: 550px
width: 90px
height: 30px
border-radius: 12px
box-shadow: 0px 2px 5px
background-image: "linear-gradient(360deg, #1C1C1C 10%, #494949 360%)"
- component: oh-link
config:
style:
position: absolute
top: 552px
right: 65px
color: white
iconF7: forward
iconSize: 25
action: command
actionCommand: FASTFORWARD
actionItem: =props.player
- component: f7-badge
config:
bgColor: black
style:
position: absolute
left: 30px
top: 640px
width: 90px
height: 30px
border-radius: 12px
box-shadow: 0px 2px 5px
background-image: "linear-gradient(360deg, #1C1C1C 10%, #494949 360%)"
- component: oh-link
config:
style:
position: absolute
top: 642px
left: 65px
color: red
iconF7: circle_fill
iconSize: 25
action: command
actionCommand: record
actionItem: =props.inputaction
- component: f7-badge
config:
bgColor: black
style:
position: absolute
right: 30px
top: 640px
width: 90px
height: 30px
border-radius: 12px
box-shadow: 0px 2px 5px
background-image: "linear-gradient(360deg, #1C1C1C 10%, #494949 360%)"
- component: oh-link
config:
style:
position: absolute
top: 642px
right: 65px
color: white
iconF7: stop_fill
iconSize: 25
action: command
actionCommand: stop
actionItem: =props.inputaction
- component: f7-badge
config:
bgColor: black
style:
position: absolute
right: 50px
top: 210px
width: 60px
height: 30px
border-radius: 12px
box-shadow: 0px 2px 5px
$transform: skew(20deg)
background-image: "linear-gradient(360deg, #1C1C1C 10%, #494949 360%)"
- component: oh-link
config:
style:
position: absolute
top: 215px
right: 60px
color: white
text: MENU
action: command
actionCommand: contextmenu
actionItem: =props.inputaction
- component: f7-badge
config:
bgColor: black
style:
position: absolute
left: 144px
top: 195px
width: 32px
height: 50px
border-radius: 12px
box-shadow: 0px 2px 5px
background-image: "linear-gradient(360deg, #1C1C1C 10%, #494949 360%)"
- component: oh-link
config:
style:
position: absolute
top: 200px
left: 150px
color: white
border-bottom: 1px solid gray
iconF7: arrowtriangle_up
iconSize: 20
action: command
actionCommand: pageup
actionItem: =props.inputaction
- component: oh-link
config:
style:
position: absolute
top: 222px
left: 150px
color: white
iconF7: arrowtriangle_down
iconSize: 20
action: command
actionCommand: pagedown
actionItem: =props.inputaction
- component: f7-badge
config:
bgColor: black
style:
position: absolute
left: 50px
top: 210px
width: 60px
height: 30px
border-radius: 12px
box-shadow: 0px 2px 5px
$transform: skew(-45deg)
background-image: "linear-gradient(360deg, #1C1C1C 10%, #494949 360%)"
- component: oh-link
config:
style:
position: absolute
top: 215px
left: 66px
color: white
text: EXIT
action: command
actionCommand: back
actionItem: =props.inputaction
- component: f7-badge
config:
bgColor: black
style:
position: absolute
left: 30px
top: 260px
width: 60px
height: 40px
border-radius: 12px
box-shadow: 0px 2px 5px
transform: skew(-45deg)
background-image: "linear-gradient(360deg, #1C1C1C 10%, #494949 360%)"
- component: oh-link
config:
style:
position: absolute
top: 267px
left: 47px
color: white
iconF7: speaker_3
iconSize: 25
action: command
actionCommand: INCREASE
actionItem: =props.volume
- component: f7-badge
config:
bgColor: black
style:
position: absolute
left: 125px
top: 255px
width: 70px
height: 40px
border-radius: 50%
- component: oh-link
config:
style:
position: absolute
top: 267px
left: 143px
color: white
z-index: 2
iconF7: arrowtriangle_up
iconSize: 35
action: command
actionCommand: up
actionItem: =props.inputaction
- component: f7-badge
config:
bgColor: black
style:
position: absolute
right: 30px
top: 260px
width: 60px
height: 40px
border-radius: 12px
box-shadow: 0px 2px 5px
transform: skew(45deg)
background-image: "linear-gradient(360deg, #1C1C1C 10%, #494949 360%)"
- component: oh-link
config:
style:
position: absolute
top: 268px
right: 47px
color: white
iconF7: arrow_up_square
iconSize: 25
action: command
actionCommand: channelup
actionItem: =props.inputaction
- component: f7-badge
config:
bgColor: black
style:
position: absolute
left: 65px
top: 315px
width: 60px
height: 70px
border-radius: 50%
- component: oh-link
config:
style:
position: absolute
top: 333px
left: 71px
color: white
z-index: 2
iconF7: arrowtriangle_left
iconSize: 35
action: command
actionCommand: left
actionItem: =props.inputaction
- component: f7-badge
config:
bgColor: black
style:
position: absolute
left: 127px
top: 318px
width: 65px
height: 65px
border-radius: 50%
z-index: 2
- component: f7-badge
config:
bgColor: gray
style:
position: absolute
left: 80px
top: 270px
width: 160px
height: 160px
transform: rotate(45deg)
border-radius: 50px/50px
background-image: "linear-gradient(300deg, #1C1C1C 10%, #494949 360%)"
z-index: 1
- component: f7-badge
config:
bgColor: black
style:
position: absolute
left: 130px
top: 320px
width: 60px
height: 60px
border-radius: 60px
background-image: "linear-gradient(360deg, #1C1C1C 1%, #494949 90%)"
z-index: 3
- component: oh-link
config:
text: OK
style:
font-size: 18px
position: absolute
top: 337px
left: 148px
color: white
z-index: 3
action: command
actionCommand: select
actionItem: =props.inputaction
- component: f7-badge
config:
bgColor: black
style:
position: absolute
right: 65px
top: 315px
width: 60px
height: 70px
border-radius: 50%
- component: oh-link
config:
style:
position: absolute
top: 333px
right: 71px
color: white
iconF7: arrowtriangle_right
iconSize: 35
action: command
actionCommand: right
actionItem: =props.inputaction
- component: f7-badge
config:
bgColor: black
style:
position: absolute
left: 125px
top: 380px
width: 70px
height: 60px
border-radius: 50%
- component: oh-link
config:
style:
position: absolute
top: 400px
left: 142px
color: white
iconF7: arrowtriangle_down
iconSize: 35
action: command
actionCommand: down
actionItem: =props.inputaction
- component: f7-badge
config:
bgColor: black
style:
position: absolute
left: 30px
top: 400px
width: 60px
height: 40px
border-radius: 12px
box-shadow: 0px 2px 5px
transform: skew(45deg)
background-image: "linear-gradient(360deg, #1C1C1C 10%, #494949 360%)"
- component: oh-link
config:
style:
position: absolute
top: 406px
left: 47px
color: white
iconF7: speaker_1
iconSize: 25
action: command
actionCommand: DECREASE
actionItem: =props.volume
- component: f7-badge
config:
bgColor: black
style:
position: absolute
right: 30px
top: 400px
width: 60px
height: 40px
border-radius: 12px
box-shadow: 0px 2px 5px
transform: skew(-45deg)
background-image: "linear-gradient(360deg, #1C1C1C 10%, #494949 360%)"
- component: oh-link
config:
style:
position: absolute
top: 408px
right: 47px
color: white
iconF7: arrow_down_square3
iconSize: 25
action: command
actionCommand: channeldown
actionItem: =props.inputaction
- component: f7-badge
config:
bgColor: black
style:
position: absolute
left: 55px
top: 455px
width: 60px
height: 30px
border-radius: 12px
box-shadow: 0px 2px 5px
background-image: "linear-gradient(360deg, #1C1C1C 10%, #494949 360%)"
- component: oh-link
config:
style:
position: absolute
top: 457px
left: 72px
color: "=(items[props.mute].state === 'OFF') ? 'white' : 'red' "
iconF7: speaker_slash_rtl
iconSize: 25
action: command
actionCommand: "=(items[props.mute].state === 'OFF') ? 'ON' : 'OFF' "
actionItem: =props.mute
- component: f7-badge
config:
bgColor: black
style:
position: absolute
right: 55px
top: 455px
width: 60px
height: 30px
border-radius: 12px
box-shadow: 0px 2px 5px
$transform: skew(-25deg)
background-image: "linear-gradient(360deg, #1C1C1C 10%, #494949 360%)"
- component: oh-link
config:
style:
position: absolute
top: 457px
right: 73px
color: white
iconF7: arrow_uturn_left
iconSize: 25
action: command
actionCommand: back
actionItem: =props.inputaction
- component: f7-badge
config:
bgColor: black
style:
position: absolute
left: 80px
top: 500px
width: 60px
height: 30px
border-radius: 12px
box-shadow: 0px 2px 5px
background-image: "linear-gradient(360deg, #1C1C1C 10%, #494949 360%)"
- component: oh-link
config:
style:
position: absolute
top: 505px
left: 90px
color: white
text: GUIDE
action: command
actionCommand: osd
actionItem: =props.inputaction
- component: f7-badge
config:
bgColor: black
style:
position: absolute
right: 80px
top: 500px
width: 60px
height: 30px
border-radius: 12px
box-shadow: 0px 2px 5px
background-image: "linear-gradient(360deg, #1C1C1C 10%, #494949 360%)"
- component: oh-link
config:
style:
position: absolute
top: 505px
right: 94px
color: white
text: INFO
action: command
actionCommand: info
actionItem: =props.inputaction