Here is a widget with which the presence and absence (also the duration of the absence) can be displayed.
ScrewPlate will be a template set with various widgets, all of which are highly customizable in a unified style. More information on GitHub
Since version 1.2, you no longer need to use an extra rule. You just need to create an item (DateTime) with a profile (timestamp of the last change) and select it to display the time of absence.
Deprecated: I use the following DSL rule to calculate the duration of absence.
if (Handy_Online.state == OFF) { val lastUpdate1 = Handy_Online.lastUpdate val awaySince1 = lastUpdate1.until(now, java.time.temporal.ChronoUnit.MINUTES) VI_Handy_AwaySince.postUpdate(awaySince1) logInfo(“Presence”,"AwaySince: "+ awaySince1) } else { VI_Handy_AwaySince.postUpdate(0) }
And what’s the full data time? It uses both the date and time portion of the timestamp. If you are only updating the time it won’t show anything more recent than that date.
All I can say is what I posted continues to work for me.
This is a DateTime Item? Not a String? That doesn’t look like the normal formatting for a DateTime Item’s state which should look like “2025-10-23T15:27:13.123-0200”.
What is the actual state of the Item? Meaning what do you see in events.log? What do you see in Settings → Items? What do you see if you log out the state of the Item in a rule?
The thing comes from the network binding and the item represents the ON/OFF status of the cell phone. In addition, there is an item in the timestamp of the last change with the above-mentioned profile.
I’m not sure I understand the question. fromNow() does format it as a phrase (e.g. “five minutes ago”). I’m not sure you have any additional formatting control over that though. There is a link to the dayjs docs on the expressions page in the OH docs which will tell you everything you can do with it.
Since widget version 1.2, you no longer need to use an extra rule. You just need to create an item (DateTime) with a profile (timestamp of the last change) and select it to display the time of absence.
I made a change to it that makes it easier to adopt the widget to a different number of persons - using the array definition method from Garbage Collection - comma separated lists.
uid: Card_Template_Presence
tags:
- Card Template Presence
- Customizable Template
- ScrewPlate
- Version 1.3
props:
parameters:
- description: "Input without 'px' (Default: none)"
label: Card Height
name: cardHeight
required: false
type: TEXT
groupName: cardGroup
- description: "Input without 'px' (Default: 20px)"
label: Card Border Radius
name: cardBorderRadius
required: false
type: TEXT
groupName: cardGroup
- description: Input like '#FFFFFF' or 'red', 'yellow' etc.
label: Card Border Color
name: cardBorderColor
required: false
type: TEXT
groupName: cardGroup
- description: "Input without 'px' (Default: 3px)"
label: Card Border Size
name: cardBorderSize
required: false
type: TEXT
groupName: cardGroup
- description: Input like '#FFFFFF' or 'red', 'yellow' etc.
label: Card Background Color
name: cardBgColor
required: false
type: TEXT
groupName: cardGroup
- default: "false"
label: Card Background Image - Show on Card
name: cardBgImageShow
required: false
type: BOOLEAN
groupName: cardGroup
- description: Input file name for Background Image (uploaded to /html/ folder)
label: Card Background Image
name: cardBgImage
required: false
type: TEXT
groupName: cardGroup
- default: "false"
label: Icon - Show on Card
name: iconShow
required: false
type: BOOLEAN
groupName: iconGroup
- description: Use oh:iconName (openHAB icon), f7:iconName (Framework7 icon),
material:iconName (Material icon) or iconify:iconSet:iconName
label: Icon
name: iconName
required: false
type: TEXT
groupName: iconGroup
- description: Input like '#FFFFFF' or 'red', 'yellow' etc.
label: Icon Color
name: iconColor
required: false
type: TEXT
groupName: iconGroup
- description: "Input without 'px' (Default: 30px)"
label: Icon Size
name: iconSize
required: false
type: TEXT
groupName: iconGroup
- default: "false"
label: Header - Show on Card
name: headerShow
required: false
type: BOOLEAN
groupName: headerGroup
- description: Input Text to diplay on Header
label: Header
name: headerText
required: false
type: TEXT
groupName: headerGroup
- description: Input like '#FFFFFF' or 'red', 'yellow' etc.
label: Header Text Color
name: headerTextColor
required: false
type: TEXT
groupName: headerGroup
- description: "Input without 'px' (Default: 25px)"
label: Header Text Size
name: headerTextSize
required: false
type: TEXT
groupName: headerGroup
- default: "false"
label: Footer - Show on Card
name: footerShow
required: false
type: BOOLEAN
groupName: footerGroup
- description: Input Text to diplay on Footer
label: Footer
name: footerText
required: false
type: TEXT
groupName: footerGroup
- description: Input like '#FFFFFF' or 'red', 'yellow' etc.
label: Footer Text Color
name: footerTextColor
required: false
type: TEXT
groupName: footerGroup
- description: "Input without 'px' (Default: 16px)"
label: Footer Text Size
name: footerTextSize
required: false
type: TEXT
groupName: footerGroup
- description: Input like '#FFFFFF' or 'red', 'yellow' etc.
label: Content Text Color
name: contentTextColor
required: false
type: TEXT
groupName: contentGroup
- description: "Input without 'px' (Default: 18px)"
label: Content Text Size
name: contentTextSize
required: false
type: TEXT
groupName: contentGroup
- description: "Input without 'px' (Default: 30px)"
label: Content Icon Size
name: contentIconSize
required: false
type: TEXT
groupName: contentGroup
- description: Input like '#FFFFFF' or 'red', 'yellow' etc.
label: Content Icon Color
name: contentIconColor
required: false
type: TEXT
groupName: contentGroup
- description: "Input text value for present (Default: present)"
label: Present Text Value
name: presentTextValue
required: false
type: TEXT
groupName: customGroup
- description: Input like '#FFFFFF' or 'red', 'yellow' etc.
label: Present Icon Color (only usable if the icon is transparent)
name: presentIconColor
required: false
type: TEXT
groupName: customGroup
- description: "Input text value for absence (Default: absent)"
label: Absence Text Value
name: absenceTextValue
required: false
type: TEXT
groupName: customGroup
- description: Input like '#FFFFFF' or 'red', 'yellow' etc.
label: Absence Icon Color (only usable if the icon is transparent)
name: absenceIconColor
required: false
type: TEXT
groupName: customGroup
- default: "false"
description: Display your own labels for the people
label: Person Label - Show on Card
name: personLabelShow
required: false
type: BOOLEAN
groupName: customGroup
- description: Labels for persons - to display Name; comma separated list
label: Person Labels List
name: personLabelArray
required: false
type: TEXT
groupName: customGroup
- description: Use oh:iconName (openHAB icon), f7:iconName (Framework7 icon),
material:iconName (Material icon) or iconify:iconSet:iconName; comma
separated list
label: Persons Icon List
name: personIconArray
required: false
type: TEXT
groupName: customGroup
- description: Absence person item - to display State; comma separated list
name: personItemList
required: false
type: TEXT
groupName: customGroup
- description: "Absence person - display time since absent or display the absence
text (default: false)"
label: Absence Time Person - Show on Card
name: personShowAbsentTime
required: false
type: BOOLEAN
groupName: customGroup
- description: Absence person - to display time since absence; comma separated list
name: personItemLastUpdateList
required: false
type: TEXT
groupName: customGroup
parameterGroups:
- name: cardGroup
label: Card Settings
description: All settings to customize the card layout
- name: iconGroup
label: Icon Settings
description: All settings to customize the icon
- name: headerGroup
label: Header Settings
description: All settings to customize the header area
- name: footerGroup
label: Footer Settings
description: All settings to customize the content area
- name: contentGroup
label: Content Settings
description: All settings to customize the content area
- name: customGroup
label: Custom Settings
description: All settings to customize the custom objects
timestamp: Dec 30, 2025, 6:35:57 PM
component: f7-card
config:
style:
--f7-card-bg-color: "=props.cardBgColor ? props.cardBgColor : (themeOptions.dark
=== 'dark' ? '#232324' : 'white')"
--f7-card-border-radius: "=props.cardBorderRadius ? (props.cardBorderRadius) + 'px' : '20px'"
--f7-card-margin-horizontal: 4px
--f7-card-margin-vertical: 4px
--f7-card-outline-border-color: "=props.cardBorderColor ? props.cardBorderColor
: (themeOptions.dark === 'dark' ? '#c95826' : '#c95826')"
background-color: var(--f7-card-bg-color)
background-image: "=props.cardBgImageShow== 'false' ? '' :
'linear-gradient(rgba(255,255,255,0.6), rgba(255,255,255,0.6)),
url(/static/' + props.cardBgImage + ')'"
border: "=props.cardBorderSize ? (props.cardBorderSize)+'px solid
var(--f7-card-outline-border-color)' : '6px solid
var(--f7-card-outline-border-color)'"
color: "=props.contentTextColor ? props.contentTextColor : (themeOptions.dark
=== 'dark' ? 'white' : 'black')"
font-size: "=props.contentTextSize ? (props.contentTextSize)+'px' : '18px'"
slots:
default:
- component: f7-card-header
config:
style:
--f7-card-header-border-color: transparent
--f7-card-header-font-size: "=props.headerTextSize ? (props.headerTextSize) + 'px' : '25px'"
--f7-card-header-font-weight: 800
--f7-card-header-min-height: "=props.headerShow == 'true' ? '35px' : '0px'"
--f7-card-header-text-color: "=props.headerTextColor ? props.headerTextColor :
(themeOptions.dark === 'dark' ? 'white' : 'black')"
justify-content: flex-start
margin-left: "=(Number.parseInt(props.cardBorderRadius) > 90) ? '30px' :
(Number.parseInt(props.cardBorderRadius) > 50) ? '10px' : '0px'"
slots:
default:
- component: oh-icon
config:
icon: "=props.iconName ? props.iconName : 'f7:house'"
style:
color: "=props.iconColor ? props.iconColor : (themeOptions.dark === 'dark' ?
'white' : 'black')"
padding-right: 10px
visible: "=props.iconShow ? props.iconShow : props.iconShow"
width: "=props.iconSize ? (props.iconSize) + 'px' : '30px'"
- component: Label
config:
text: "=props.headerText ? props.headerText : 'Header'"
visible: "=props.headerShow ? props.headerShow : props.headerShow"
- component: f7-card-content
config:
style:
align-items: center
display: flex
height: "=props.cardHeight ? (props.cardHeight) + 'px' : ''"
justify-content: center
margin-left: "=(Number.parseInt(props.cardBorderRadius) > 90) ? '30px' :
(Number.parseInt(props.cardBorderRadius) > 50) ? '10px' : '0px'"
margin-top: -10px
slots:
default:
- component: f7-block
config:
style:
margin-top: -10px
text-align: center
width: 100%
slots:
default:
- component: f7-row
slots:
default:
- component: oh-repeater
config:
for: listitem
fragment: true
in: =props.personLabelArray.split(",")
slots:
default:
- component: f7-col
slots:
default:
- component: Label
config:
text: =loop.listitem
visible: "=props.personLabelShow ? props.personLabelShow :
props.personLabelShow"
- component: f7-row
config:
style:
margin-top: 10px
slots:
default:
- component: oh-repeater
config:
for: listitem
fragment: true
in: =props.personIconArray.split(",")
slots:
default:
- component: f7-col
slots:
default:
- component: oh-icon
config:
action: analyzer
actionAnalyzerChartType: day
actionAnalyzerCoordSystem: time
actionAnalyzerItems:
- '=props.personItemList.split(",")[loop.listitem_idx]'
height: "=props.contentIconSize ? (props.contentIconSize) + 'px' : '30px'"
icon: =loop.listitem
style:
color: "=(@@loop.listitem == 'ON' ? (props.presentIconColor ?
props.presentIconColor : 'green') :
(props.absenceIconColor ?
props.absenceIconColor : 'red')) :
props.contentIconColor ?
props.contentIconColor :
(themeOptions.dark === 'dark' ?
'white' : 'black')"
margin-top: 5px
- component: f7-row
config:
class:
- text-align-center
slots:
default:
- component: oh-repeater
config:
for: listitem
fragment: true
in: =props.personItemList.split(",")
slots:
default:
- component: f7-col
slots:
default:
- component: Label
config:
style:
margin-top: 5px
text: "=@@loop.listitem == 'ON' ? (props.presentTextValue ?
props.presentTextValue : 'present')
:(props.personShowAbsentTime
? dayjs(items[props.personItemLastUpda\
teList.split(',')[loop.listitem_idx]].s\
tate).fromNow() :
(props.absenceTextValue ?
props.absenceTextValue : 'absent'))"
- component: f7-card-footer
config:
style:
--f7-card-footer-border-color: transparent
--f7-card-footer-font-size: "=props.footerTextSize ? (props.footerTextSize) + 'px' : '16px'"
--f7-card-footer-font-weight: 500
--f7-card-footer-min-height: "=props.footerShow == 'true' ? '20px' : '0px'"
--f7-card-footer-text-color: "=props.footerTextColor ? props.footerTextColor :
(themeOptions.dark === 'dark' ? 'white' : 'black')"
margin-left: "=(Number.parseInt(props.cardBorderRadius) > 90) ? '30px' :
(Number.parseInt(props.cardBorderRadius) > 50) ? '10px' : '0px'"
slots:
default:
- component: Label
config:
text: "=props.footerText ? props.footerText : 'Footer'"
visible: "=props.footerShow ? props.footerShow : props.footerShow"
'''