The “main_widget” project is a series of widgets to create a new mobile view for controlling your openHAB installation. For full functionality, you will need to install all widgets listed in the development thread.
Installation:
Add the non semantic tag “Floor” to all your floors and if you like, also add the widgetOrderIndex to themAdd the name of the floor group 8e.g. gGroundfloor) as a new non semantic tag to each room in that particular floor- If you want a specific ordering in the floors and/or rooms menu, please add the widgetOrderIndex as metadata to your items in the model.
- Install all needed widgets for this projekt from the marketplace.
- Create an new layout page in openHAB MainUI
- Enter the code tab for the new page and paste the following code
config:
label: openPage
sidebar: true
hideNavbar: true
showFullscreenIcon: false
hideSidebarIcon: true
style:
--f7-block-padding-horizontal: 0px
--f7-navbar-height: 0
blocks:
- component: oh-block
config:
stylesheet: |
.block:first-child{
margin-top: 15px;
}
slots:
default:
- component: oh-grid-row
config: {}
slots:
default:
- component: oh-grid-col
config: {}
slots:
default:
- component: widget:main_widget
config:
locationTitle:
scenesGroup:
securityGroup:
securityMode:
referencePIN:
masonry: null
grid: []
configure the main_widget on your new page with the missing information (some items/groups need to be definded.
Screenshots
Changelog
Version 0.4
- removed need to press room button after selecting room from floors menu
- removed usage of seperat floors and rooms card, therefore introduced “FloorsAndRooms”
- introduced usage of uiSemantics for labels, see
[semantic,ui] Using enriched semantic to ease UI creation
Version 0.3
- fixed top and bottom spacing [@Nic0205]
- changed bottom tabs from button to oh link for further improvements [@Nic0205]
- refactored repeater usage to remove need of additional tags [@hmerk]
- code splitting, moved informationa middle part to separate widgets. [@hmerk]
Version 0.3
- fixed menu sorting and coloring
- changed margins to better use screen space
Version 0.2
- added menu entry sorting for floors and rooms [@Nic0205]
- new config options for scene and security group and security mode [@hmerk]
Version 0.1
- initial release
Resources
uid: main_widget
tags: []
props:
parameters:
- description: Set default color for Text
label: Text color
name: textColor
required: false
type: TEXT
- defaultValue: RGB(96, 96, 96)
description: Set default color for Text
label: Bottom-Navbar-Color
name: bnavColor
required: false
type: TEXT
- defaultValue: Somewhere
description: Name for your weather location
label: Weather Location
name: locationTitle
required: false
type: TEXT
- context: item
description: Name of scenes group item
label: Scenes Group
name: scenesGroup
required: false
type: TEXT
- description: Hide Security menu
label: hide
name: hideSecurity
required: false
type: BOOLEAN
groupName: security
parameterGroups:
- name: security
label: Security Configuration Parameters
component: f7-block
config:
style:
--f7-button-text-color: "#8c8c8c"
--menu-text-color: '= (!props.textColor) ? (themeOptions.dark=="light")? black : white : props.textColor'
--opmw-menu-text-color: =(themeOptions.dark=="light")?("#8C8C8C"):("#848484")
background: =(themeOptions.dark=="light") ? ("white"):("black")
display: flex
flex-direction: column
height: calc(100vh - var(--f7-toolbar-height) - var(--f7-safe-area-bottom) - var(--f7-safe-area-top))
justify-content: space-between
margin: 0
padding: 0
width: 100%
stylesheet: >
.selected_menu_item {
color: var(--menu-text-color);
text-decoration-color: #F8BB00 !important;
text-decoration: underline;
text-underline-offset: 2px;
font-weight: bold;
}
.unselected_menu_item {
color: #8C8C8C;
font-weight: 300;
}
.selected_bottom_navbar_item {
color: white;
font-size: 12px;
height: auto;
--f7-button-bg-color: transparent;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 1%;
padding-bottom: 1%;
}
.unselected_bottom_navbar_item {
color: #8c8c8c;
font-size: 12px;
height: auto;
--f7-button-bg-color: transparent
display: flex;
flex-direction: column;
padding-top: 2%;
padding-bottom: 2%;
}
slots:
default:
- component: f7-block
config:
style:
flex: 0 0 auto
overflow: scroll
slots:
default:
- component: f7-segmented
config:
style:
flex: 1 1 auto
gap: 10px
justify-content: center
margin-left: 2%
margin-right: 2%
slots:
default:
- component: oh-repeater
config:
for: baseMenu
fragment: true
in:
- name: Home
- name: Floors
- name: Rooms
map: loop.baseMenu.name
sourceType: array
slots:
default:
- component: oh-button
config:
action: variable
actionVariable: objVar
actionVariableValue:
selectSection: ="SECTION" + loop.baseMenu_idx
class: '=vars.objVar.selectSection=="SECTION" + loop.baseMenu_idx ? "selected_menu_item" : "unselected_menu_item"'
large: true
style:
flex: 0 1 auto
font-size: 24px
font-weight: 200
width: auto
text: =loop.baseMenu
- component: f7-row
config:
style:
align-items: center
display: flex
flex-direction: row
flex-wrap: nowrap
height: 2em
justify-content: space-between
width: 100%
visible: false
slots:
default:
- component: oh-button
config:
action: variable
actionVariable: buttonIndexHome
actionVariableValue: =(vars.buttonIndexHome || 0) + 1
iconF7: chevron_left
style:
color: "#F8BB00"
flex: 0 0 auto
height: 2em
- component: oh-repeater
config:
fetchMetadata: metadata,semantics,widgetOrder
filter: (loop.menuArray.metadata) && (loop.menuArray.metadata.semantics.value).includes("Floor")
for: menuArray
fragment: true
itemTags: ","
map: loop.menuArray_source
sourceType: itemsWithTags
slots:
default:
- component: f7-row
config:
style:
display: flex
height: 2em
justify-content: center
overflow: hidden
slots:
default:
- component: oh-repeater
config:
filter: loop.menuArray_idx == 0
for: menuButtonHome
fragment: true
in: =loop.menuArray
map: ((loop.menuButtonHome.metadata && loop.menuButtonHome.metadata.widgetOrder && loop.menuButtonHome.metadata.widgetOrder.value) || 0).toString().padStart(3,'0') + '&' + loop.menuButtonHome_idx
slots:
default:
- component: oh-button
config:
action: variable
actionVariable: objVar
actionVariableValue:
floor: =loop.menuArray[loop.menuButtonHome_source.sort()[loop.menuButtonHome_idx].split('&')[1]].name
selectSection: = vars.objVar.selectSection
style:
flex: 0 0 auto
height: 2em
order: =(((vars.buttonIndexHome || 0) % loop.menuButtonHome_source.length) + loop.menuButtonHome_source.length + loop.menuButtonHome_idx) % loop.menuButtonHome_source.length
text: =loop.menuArray[loop.menuButtonHome_source.sort()[loop.menuButtonHome_idx].split('&')[1]].label
- component: oh-button
config:
action: variable
actionVariable: buttonIndexHome
actionVariableValue: =(vars.buttonIndexHome || 0) - 1
iconF7: chevron_right
style:
color: "#F8BB00"
flex: 0 0 auto
height: 2em
- component: f7-row
config:
style:
align-items: center
display: flex
flex-direction: row
flex-wrap: nowrap
height: 2em
justify-content: center
width: 100%
visible: '=vars.objVar ? (vars.objVar.selectSection=="SECTION1") ? true : false : false'
slots:
default:
- component: oh-button
config:
action: variable
actionVariable: buttonIndexFloor
actionVariableValue: =(vars.buttonIndexFloor || 0) + 1
iconF7: chevron_left
style:
color: "#F8BB00"
flex: 0 0 auto
height: 2em
- component: oh-repeater
config:
fetchMetadata: metadata,semantics,widgetOrder
filter: (loop.menuArray.metadata) && (loop.menuArray.metadata.semantics.value).includes("Floor")
for: menuArray
fragment: true
itemTags: ","
map: loop.menuArray_source
sourceType: itemsWithTags
slots:
default:
- component: f7-row
config:
style:
display: flex
height: 2em
justify-content: center
overflow: hidden
slots:
default:
- component: oh-repeater
config:
filter: loop.menuArray_idx == 0
for: menuButtonFloor
fragment: true
in: =loop.menuArray
map: ((loop.menuButtonFloor.metadata && loop.menuButtonFloor.metadata.widgetOrder && loop.menuButtonFloor.metadata.widgetOrder.value) || 0).toString().padStart(3,'0') + '&' + loop.menuButtonFloor_idx
slots:
default:
- component: oh-button
config:
action: variable
actionVariable: objVar
actionVariableValue:
floor: =loop.menuArray[loop.menuButtonFloor_source.sort()[loop.menuButtonFloor_idx].split('&')[1]].name
selectSection: SECTION2
class: '=(vars.objVar.floor ==(loop.menuArray[loop.menuButtonFloor_source.sort()[loop.menuButtonFloor_idx].split("&")[1]].name)) ? "selected_menu_item" : "unselected_menu_item"'
style:
flex: 0 0 auto
height: 2em
order: =(((vars.buttonIndexFloor || 0) % loop.menuButtonFloor_source.length) + loop.menuButtonFloor_source.length + loop.menuButtonFloor_idx) % loop.menuButtonFloor_source.length
text-underline-offset: 4px
text: =loop.menuArray[loop.menuButtonFloor_source.sort()[loop.menuButtonFloor_idx].split('&')[1]].label
- component: oh-button
config:
action: variable
actionVariable: buttonIndexFloor
actionVariableValue: =(vars.buttonIndexFloor || 0) - 1
iconF7: chevron_right
style:
color: "#F8BB00"
flex: 0 0 auto
height: 2em
- component: f7-row
config:
style:
align-items: center
display: flex
flex-direction: row
flex-wrap: nowrap
height: 2em
justify-content: center
width: 100%
visible: '=vars.objVar ? (vars.objVar.selectSection=="SECTION2") ? true : false : false'
slots:
default:
- component: oh-button
config:
action: variable
actionVariable: buttonIndexRoom
actionVariableValue: =(vars.buttonIndexRoom || 0) + 1
iconF7: chevron_left
style:
color: "#F8BB00"
flex: 0 0 auto
height: 2em
- component: oh-repeater
config:
fetchMetadata: metadata,semantics,widgetOrder
filter: (loop.menuArrayRoom.metadata) && (loop.menuArrayRoom.metadata.semantics.value).includes("Room")
for: menuArrayRoom
fragment: true
groupItem: =vars.objVar.floor
map: loop.menuArrayRoom_source
sourceType: itemsInGroup
visible: "=!vars.objVar ? false : (vars.objVar.selectSection==SECTION1) ? false : true"
slots:
default:
- component: f7-row
config:
style:
display: flex
height: 2em
justify-content: center
overflow: hidden
slots:
default:
- component: oh-repeater
config:
filter: loop.menuArrayRoom_idx == 0
for: menuButtonRoom
fragment: true
in: =loop.menuArrayRoom
map: ((loop.menuButtonRoom.metadata && loop.menuButtonRoom.metadata.widgetOrder && loop.menuButtonRoom.metadata.widgetOrder.value) || 0).toString().padStart(3,'0') + '&' + loop.menuButtonRoom_idx
slots:
default:
- component: oh-button
config:
action: variable
actionVariable: objVar
actionVariableValue:
floor: =vars.objVar.floor
room: =loop.menuArrayRoom[loop.menuButtonRoom_source.sort()[loop.menuButtonRoom_idx].split('&')[1]].name
selectSection: =vars.objVar.selectSection
class: '=(vars.objVar.room ==(loop.menuArrayRoom[loop.menuButtonRoom_source.sort()[loop.menuButtonRoom_idx].split("&")[1]].name)) ? "selected_menu_item" : "unselected_menu_item"'
style:
flex: 0 0 auto
height: 2em
order: =(((vars.buttonIndexRoom || 0) % loop.menuButtonRoom_source.length) + loop.menuButtonRoom_source.length + loop.menuButtonRoom_idx) % loop.menuButtonRoom_source.length
text-underline-offset: 4px
text: =loop.menuArrayRoom[loop.menuButtonRoom_source.sort()[loop.menuButtonRoom_idx].split('&')[1]].label
- component: oh-repeater
config:
fetchMetadata: metadata,semantics,widgetOrder
filter: (loop.menuArrayRoom.metadata) && (loop.menuArrayRoom.metadata.semantics.value).includes("Room") && (loop.menuArrayRoom.metadata.semantics.config.hasLocation == vars.objVar.floor)
for: menuArrayRoom
fragment: true
itemTags: ","
map: loop.menuArrayRoom_source
sourceType: itemsWithTags
visible: "=!vars.objVar ? false : (vars.objVar.selectSection==SECTION1) ? false : true"
slots:
default:
- component: f7-row
config:
style:
display: flex
height: 2em
justify-content: center
overflow: hidden
slots:
default:
- component: oh-repeater
config:
filter: loop.menuArrayRoom_idx == 0
for: menuButtonRoom
fragment: true
in: =loop.menuArrayRoom
map: ((loop.menuButtonRoom.metadata && loop.menuButtonRoom.metadata.widgetOrder && loop.menuButtonRoom.metadata.widgetOrder.value) || 0).toString().padStart(3,'0') + '&' + loop.menuButtonRoom_idx
slots:
default:
- component: oh-button
config:
action: variable
actionVariable: objVar
actionVariableValue:
floor: =vars.objVar.floor
room: =loop.menuArrayRoom[loop.menuButtonRoom_source.sort()[loop.menuButtonRoom_idx].split('&')[1]].name
selectSection: =vars.objVar.selectSection
class: '=(vars.objVar.room ==(loop.menuArrayRoom[loop.menuButtonRoom_source.sort()[loop.menuButtonRoom_idx].split("&")[1]].name)) ? "selected_menu_item" : "unselected_menu_item"'
style:
flex: 0 0 auto
height: 2em
order: =(((vars.buttonIndexRoom || 0) % loop.menuButtonRoom_source.length) + loop.menuButtonRoom_source.length + loop.menuButtonRoom_idx) % loop.menuButtonRoom_source.length
text-underline-offset: 4px
text: =loop.menuArrayRoom[loop.menuButtonRoom_source.sort()[loop.menuButtonRoom_idx].split('&')[1]].label
- component: oh-button
config:
action: variable
actionVariable: buttonIndexRoom
actionVariableValue: =(vars.buttonIndexRoom || 0) - 1
iconF7: chevron_right
style:
color: "#F8BB00"
flex: 0 0 auto
height: 2em
- component: f7-row
config:
style:
align-items: center
display: flex
flex-direction: row
flex-wrap: nowrap
height: 2em
justify-content: center
width: 100%
visible: "=!vars.objVar ? false : !vars.objVar.floor ? false : true"
slots:
default:
- component: oh-repeater
config:
fetchMetadata: semantics,metadata,widgetOrder
filter: loop.floorItem.name==vars.objVar.floor
for: floorItem
itemTags: ","
sourceType: itemsWithTags
slots:
default:
- component: f7-chip
config:
style:
background: "#F8BB00"
text: =loop.floorItem.label
- component: widget:main_widget_Home_Card
config:
locationTitle: =props.locationTitle
referencePIN: =props.referencePIN
scenesGroup: =props.scenesGroup
securityGroup: =props.securityGroup
securityMode: =props.securityMode
- component: widget:main_widget_FloorsAndRooms
- component: f7-block
config:
style:
background: =props.bnavColor
border-radius: 0px 0px 10px 10px
flex: 0 0 auto
margin-bottom: -4em
overflow: scroll
visible: '=vars.objVar ? (vars.objVar.selectSection=="SECTION0") ? true : false : false'
slots:
default:
- component: f7-segmented
config:
style:
display: flex
flex-direction: row
justify-content: space-around
slots:
default:
- component: oh-link
config:
action: variable
actionVariable: objVar
actionVariableValue:
floor: =vars.objVar.floor
room: =vars.objVar.room
selectSection: =vars.objVar.selectSection
selectThing: Security
badgeColor: '=(vars.objVar && (vars.objVar.selectThing=="Security")) ? "orange" : "red"'
class: '=(vars.objVar && (vars.objVar.selectThing=="Security")) ? "selected_bottom_navbar_item" : "unselected_bottom_navbar_item"'
icon-f7: shield_fill
iconBadge: =(Number(items.gDoorsOpen.state)+Number(items.gWindowsOpen.state)+Number(items.motionDetected.state))
text: Security
- component: oh-link
config:
action: variable
actionVariable: objVar
actionVariableValue:
floor: =vars.objVar.floor
room: =vars.objVar.room
selectSection: =vars.objVar.selectSection
selectThing: Scenes
class: '=(vars.objVar && (vars.objVar.selectThing=="Scenes")) ? "selected_bottom_navbar_item" : "unselected_bottom_navbar_item"'
icon-f7: rectangle_on_rectangle_angled
text: Scenes
- component: oh-link
config:
action: variable
actionVariable: objVar
actionVariableValue:
floor: =vars.objVar.floor
room: =vars.objVar.room
selectSection: =vars.objVar.selectSection
selectThing: Appliances
class: '=(vars.objVar && (vars.objVar.selectThing=="Weather")) ? "selected_bottom_navbar_item" : "unselected_bottom_navbar_item"'
iconMaterial: power
text: Appliances
- component: oh-link
config:
action: variable
actionVariable: objVar
actionVariableValue:
floor: =vars.objVar.floor
room: =vars.objVar.room
selectSection: =vars.objVar.selectSection
selectThing: Energy
badgeColor: green
class: '=(vars.objVar && (vars.objVar.selectThing=="Energy")) ? "selected_bottom_navbar_item" : "unselected_bottom_navbar_item"'
icon-f7: bolt_fill
iconBadge: ""
text: Energy
- component: f7-block
config:
style:
background: =props.bnavColor
border-radius: 0px 0px 10px 10px
flex: 0 0 auto
margin-bottom: -4em
overflow: scroll
visible: '=vars.objVar ? (vars.objVar.selectSection=="SECTION0") ? false : true : false'
slots:
default:
- component: f7-segmented
config:
style:
display: flex
flex-direction: row
justify-content: space-around
slots:
default:
- component: oh-link
config:
action: variable
actionVariable: objVar
actionVariableValue:
floor: =vars.objVar.floor
room: =vars.objVar.room
selectSection: =vars.objVar.selectSection
selectThing: Lights
class: '=(vars.objVar && (vars.objVar.selectThing=="Lights")) ? "selected_bottom_navbar_item" : "unselected_bottom_navbar_item"'
icon-f7: lightbulb_fill
text: Lights
- component: oh-link
config:
action: variable
actionVariable: objVar
actionVariableValue:
floor: =vars.objVar.floor
room: =vars.objVar.room
selectSection: =vars.objVar.selectSection
selectThing: Rollers
class: '=(vars.objVar && (vars.objVar.selectThing=="Rollers")) ? "selected_bottom_navbar_item" : "unselected_bottom_navbar_item"'
icon-f7: archivebox
text: Shades
- component: oh-link
config:
action: variable
actionVariable: objVar
actionVariableValue:
floor: =vars.objVar.floor
room: =vars.objVar.room
selectSection: =vars.objVar.selectSection
selectThing: Climate
class: '=(vars.objVar && (vars.objVar.selectThing=="Climate")) ? "selected_bottom_navbar_item" : "unselected_bottom_navbar_item"'
icon-f7: snow
text: Climate
- component: oh-link
config:
action: variable
actionVariable: objVar
actionVariableValue:
floor: =vars.objVar.floor
room: =vars.objVar.room
selectSection: =vars.objVar.selectSection
selectThing: Appliances
class: '=(vars.objVar && (vars.objVar.selectThing=="Appliances")) ? "selected_bottom_navbar_item" : "unselected_bottom_navbar_item"'
iconMaterial: power
text: Appliances