Here is a sample of my roller shutter blind set up:
UID: mqtt:topic:mqttbroker:2910e769b4
label: Blinds back 1-2
thingTypeUID: mqtt:topic
configuration:
payloadNotAvailable: online
transformationPattern:
- JSONPATH:$state
payloadAvailable: offline
bridgeUID: mqtt:broker:mqttbroker
location: House
channels:
- id: blind1backcontrol
channelTypeUID: mqtt:rollershutter
label: Blind 1 back control
configuration:
invert: true
stop: STOP
commandTopic: zigbee2mqtt/blind1back/set
stateTopic: zigbee2mqtt/blind1back
transformationPattern:
- JSONPATH:$.position
off: CLOSE
on: OPEN
- id: blind1backposition
channelTypeUID: mqtt:number
label: Blind 1 back position
configuration:
commandTopic: zigbee2mqtt/blind1back/set/position
min: 0
stateTopic: zigbee2mqtt/blind1back
transformationPattern:
- JSONPATH:$.position
max: 100
- id: blind1backstatus
channelTypeUID: mqtt:contact
label: Blind 1 back status regex
description: Put regex as STOP command was logging errors
configuration:
stateTopic: zigbee2mqtt/blind1back
transformationPattern:
- REGEX:(^((?!STOP).)*$)∩JSONPATH:$.state
off: CLOSE
on: OPEN


I user a widget to control the blinds.
uid: Remote_curtain_control_notimer_counter
tags: []
props:
parameters:
- context: Text
description: Title of the card
label: Title
name: rollerTitle
required: false
type: TEXT
- context: item
description: Rollershutter Item
label: Rollershutter Item
name: groupItem
required: true
type: TEXT
- context: item
description: Rollershutter position
label: Rollershutter Position
name: blindposition
required: true
type: TEXT
- context: item
description: Rollershutter position counter
label: Rollershutter position counter
name: closedcounter
required: true
type: TEXT
timestamp: Jul 18, 2025, 9:43:38 AM
component: f7-card
config:
style:
background: '=themeOptions.dark === "dark" ? "rgb(40, 40, 40)" : "rgb(255, 255, 210)"'
border-radius: var(--f7-card-expandable-border-radius)
box-shadow: 5px 5px 15px 1px rgba(0,0,0,0.3)
margin-left: 5pxcon
margin-right: 5px
slots:
default:
- component: f7-card-content
config:
style:
position: relative
z-index: 3
slots:
default:
- component: f7-row
config:
style:
margin-bottom: -10px
margin-left: 0px
margin-right: 0px
slots:
default:
- component: f7-col
config:
style:
margin-left: 0px
margin-right: 0px
width: 60px
slots:
default:
- component: oh-icon
config:
height: 50px
icon: =(items[props.blindposition].state >
89)?('blinds-90'):(items[props.blindposition].state
>
79)?('blinds-80'):(items[props.blindposition].state
>
69)?('blinds-70'):(items[props.blindposition].state
>
59)?('blinds-60'):(items[props.blindposition].state
>
49)?('blinds-50'):(items[props.blindposition].state
>
39)?('blinds-40'):(items[props.blindposition].state
>
29)?('blinds-30'):(items[props.blindposition].state
>
19)?('blinds-20'):(items[props.blindposition].state
> 9)?('oh:blinds-10'):('oh:blinds-0')
style:
color: orange
- component: f7-col
config:
style:
margin-left: 60px
margin-right: 0px
position: absolute
width: 80%
slots:
default:
- component: f7-row
slots:
default:
- component: Label
config:
style:
color: '=themeOptions.dark === "dark" ? "white" : "black"'
font-size: 15px
font-weight: 500
text: =props.rollerTitle
- component: f7-row
config:
style:
align-items: right
display: flex
flex-direction: row
slots:
default:
- component: f7-chip
config:
style:
background: =Math.round(items[props.blindposition].state)=="0"?"Green":"orange"
color: black
font-weight: 500
text: '=Math.round(items[props.blindposition].state) == "100" ? "Open" :
Math.round(items[props.groupItem].state) ==
"100" ? "Closed" :
Math.round(items[props.blindposition].state) +
"% open"'
- component: f7-row
slots:
default:
- component: Label
config:
style:
color: '=themeOptions.dark === "dark" ? "white" : "black"'
font-size: 15px
font-weight: 500
text: '="Shut: " + Math.round(items[props.closedcounter].state) + " times"'
- component: f7-col
config:
style:
margin-left: 0px
margin-right: 0px
width: 90px
slots:
default:
- component: oh-link
config:
action: command
actionCommand: UP
actionFeedback: UP pressed
actionItem: =props.groupItem
iconColor: orange
iconF7: arrow_left_circle
iconSize: 28
style:
background: '=themeOptions.dark === "dark" ? "rgb(40, 40, 40)" : "rgb(240, 240,
240)"'
z-index: 98
- component: oh-link
config:
action: command
actionCommand: STOP
actionFeedback: STOP pressed
actionItem: =props.groupItem
iconF7: stop_circle
iconSize: 28
style:
background: '=themeOptions.dark === "dark" ? "rgb(40, 40, 40)" : "rgb(240, 240,
240)"'
z-index: 98
- component: oh-link
config:
action: command
actionCommand: DOWN
actionFeedback: DOWN pressed
actionItem: =props.groupItem
iconColor: orange
iconF7: arrow_right_circle
iconSize: 28
style:
background: '=themeOptions.dark === "dark" ? "rgb(40, 40, 40)" : "rgb(240, 240,
240)"'
z-index: 98
- component: f7-card-content
config:
class: align-items-center alignt-text-center justify-content-center
style:
margin-left: 15px
margin-right: 15px
position: relative
z-index: 2
visible: =(vars.PresetVisible) == true
slots:
default:
- component: f7-row
slots:
default:
- component: oh-slider
config:
color: orange
item: =props.blindposition
max: 100
min: 0
step: "1"
style:
background: trasparent
color: black
font-size: 13px
- component: f7-row
config:
style:
margin-top: 15px
slots:
default:
- component: oh-button
config:
action: command
actionCommand: 25
actionItem: =props.blindposition
round: true
style:
background: '=items[props.groupItem].state >= "20" &&
items[props.groupItem].state <= "30" ? "orange" :
"rgb(210, 210, 210)"'
color: black
z-index: 98
text: 25
- component: oh-button
config:
action: command
actionCommand: 40
actionItem: =props.blindposition
round: true
style:
background: '=items[props.groupItem].state >= "35" &&
items[props.groupItem].state <= "45" ? "orange" :
"rgb(210, 210, 210)"'
color: black
z-index: 98
text: 40
- component: oh-button
config:
action: command
actionCommand: 50
actionItem: =props.blindposition
round: true
style:
background: '=items[props.groupItem].state >= "45" &&
items[props.groupItem].state <= "55" ? "orange" :
"rgb(210, 210, 210)"'
color: black
z-index: 98
text: 50
- component: oh-button
config:
action: command
actionCommand: 75
actionItem: =props.blindposition
round: true
style:
background: '=items[props.groupItem].state >= "70" &&
items[props.groupItem].state <= "80" ? "orange" :
"rgb(210, 210, 210)"'
color: black
z-index: 98
text: 75
- component: oh-button
config:
action: command
actionCommand: 90
actionItem: =props.blindposition
round: true
style:
background: '=items[props.groupItem].state >= "85" &&
items[props.groupItem].state <= "95" ? "orange" :
"rgb(210, 210, 210)"'
color: black
z-index: 98
text: 90
- component: f7-card-content
config:
class: align-items-center alignt-text-center justify-content-center
style:
margin-bottom: -50px
margin-left: 15px
margin-right: 15px
margin-top: -50px
position: relative
z-index: 0
visible: =(vars.ChartVisible) == true
slots:
default:
- component: oh-chart
config:
chartType: day
options:
backgroundColor: transparent
periodVisible: false
slots:
grid:
- component: oh-chart-grid
config:
containLabel: false
series:
- component: oh-time-series
config:
barWidth: 1
color: orange
gridIndex: 0
item: =props.blindposition
type: line
xAxisIndex: 0
yAxisIndex: 0
xAxis:
- component: oh-time-axis
config:
axisPointer:
show: false
gridIndex: 0
yAxis:
- component: oh-value-axis
config:
gridIndex: 0
min: 0
name: " % open"
- component: f7-card-footer
config:
style:
background: rgb(245, 218, 137)
border-radius: 0 0 10px 10px
height: auto
slots:
default:
- component: f7-block
config:
style:
bottom: 3px
display: flex
left: 0
position: absolute
slots:
default:
- component: oh-button
config:
action: variable
actionVariable: PresetVisible
actionVariableValue: =! (vars.PresetVisible)
slots:
default:
- component: oh-icon
config:
icon: iconify:mdi:window-shutter-auto
style:
color: "=(vars.PresetVisible ? 'rgb(255, 128, 0)' : 'rgb(106, 106, 106)')"
width: 25px
- component: oh-button
config:
action: variable
actionVariable: ChartVisible
actionVariableValue: =! (vars.ChartVisible)
slots:
default:
- component: oh-icon
config:
icon: iconify:ant-design:bar-chart-outlined
style:
color: "=(vars.ChartVisible ? 'rgb(255, 128, 0)' : 'rgb(106, 106, 106)')"
width: 25px
Also if you use the zigbee2mqtt frontend/web you can easliy set friendly names:
frontend:
enabled: true
port: 8082
host: 0.0.0.0
Good luck. Hope some of that helps.
zigbee2mqtt is good once you get used to it. I am using a docker version of it.