Widget to see history of pictures who rang the door bell

Just wanted to share with you a tiny widget (incl. scripts) where you can swipe through pictures taken from your camera triggered by your door bell.
image

Explanations see comments in code.
Note: minimum version v3.4.0 required - otherwise some compontents need to be replaces (see posts below by justin) and taphold does not work.

The following DSL rule gets executed on door bell button press:

rule "Door Bell"
when
	Item DoorBell_Status1Input changed from "OFF" to "ON" or
	Item DoorBell_Status1Input changed from "ON" to "OFF"
then
	var unread = vUnread.state as Number + 1
//this item is being used by the widget to display how many photos haven't been seen since the last reset:
	vUnread.sendCommand(unread)
//result of this bash script is an updated list of files names in the photo directory:
	val csvFiles = executeCommandLine(Duration.ofSeconds(1),"/etc/openhab/scripts/updateHistory.sh")
//convert list of file names to a json string containing description and file name for the widget:
	var jsonPhotos = csvFiles.split('\n').map[e | "{ \"caption\": \"" + e.split(".jpg").get(0).replace("~",", ").replace("-",":") + " Uhr\", \"url\": " + "\"/static/photos/" + e + "\"}"]
//store json string in this item:
	vFilenames.sendCommand(jsonPhotos.toString())
end

For easiness some of the code was shifted to a bash script updateHistory.sh:

folder="/etc/openhab/html/Photos" #this is where the photos are stored
# This string is used for the file name and description in the widget
now="$(LC_ALL=de_DE.utf8 date +"%A~%d.%m.~%H-%M-%S")" # Results in german notation Samstag~18.02.~20-22-01.
#for english weekday names use: now="$(date +"%A~%d.%m.~%H-%M-%S")"
#Download picture from camera and save it in the folder:
wget http://192.168.178.69/record/current.jpg -q -O $folder/$now.jpg
cd $folder
#for cleanup. This line deletes all old pictures and keeps the 20 latest ones.
ls -tp | tail -n +21 | xargs -I {} rm -- {} #ATTENTION: make 100% sure that you are in the correct folder. Otherwise all files in this folder will be deleted!
#create a string containing all pictures
ls $folder -1t

Widget:
when tapping a popup opens where you can slide through the photos. By intention I did not use f7-swiper because you cannot pinch to zoom.
On taphold action the number of unread photos is reset to 0

component: f7-card
config:
  style:
    --f7-button-hover-bg-color: transparent
    --f7-button-pressed-bg-color: gray
    --f7-button-text-transform: none
    --my-icon-label-size: 18px
    --my-icon-size: 45px
    --my-label-size: 24px
    -khtml-user-drag: none
    -moz-user-drag: none
    -moz-user-select: none
    -ms-user-select: none
    -o-user-drag: none
    -webkit-user-drag: none
    -webkit-user-select: none
    background-color: =(items.vUnread.state == '0')?'rgb(205,205,205)':'white'
    border-radius: 15px
    box-shadow: 0px 5px 10px rgba(0,0,0,0.15)
    height: 120px
    margin: 10px 5px 10px 5px
    max-height: 120px
    min-height: 120px
    padding: 0px
    user-drag: none
    user-select: none
slots:
  default:
    - component: oh-button
      config:
        action: photos
        actionPhotoBrowserConfig:
          exposition: true
          navbarOfText: von
          popupCloseLinkText: Schließen
          type: popup
        actionPhotos: =JSON.parse(@@'vFilenames')
        style:
          background-color: rgba(205,205,205,0.1)
          border-radius: 15px
          height: 100%
          padding: 0px
          width: 100%
        taphold_action: command
        taphold_actionCommand: "0"
        taphold_actionItem: vUnread
      slots:
        default:
          - component: table
            config:
              style:
                margin: 10px
                table-layout: fixed
                vertical-align: middle
                white-space: nowrap
            slots:
              default:
                - component: tr
                  slots:
                    default:
                      - component: td
                        config:
                          style:
                            text-align: left
                            width: var(--my-icon-size)
                        slots:
                          default:
                            - component: oh-icon
                              config:
                                icon: f7:bell
                                style:
                                  color: black
                                  font-size: var(--my-icon-size)
                                  vertical-align: middle
                                  width: var(--my-icon-size)
                      - component: td
                        slots:
                          default:
                            - component: f7-chip
                              config:
                                bgColor: blue
                                style:
                                  color: black
                                text: =@'vUnread'
                                visible: =(items.vUnread.state != '0') ? 'true':'false'
                - component: tr
                  slots:
                    default:
                      - component: td
                        slots:
                          default:
                            - component: Label
                              config:
                                colspan: 2
                                style:
                                  color: black
                                  font-size: var(--my-label-size)
                                  font-weight: normal
                                  text-align: left
                                text: Klingel
    - component: f7-icon
      config:
        f7: ellipsis_vertical
        style:
          font-size: 20px
          position: absolute
          right: 5px
          top: 12px
          vertical-align: middle
        textColor: gray

kudos to @JustinG , @rlkoshak , @ysc for their help

1 Like

Thanks for sharing, will have to check it out soon.

Can you make this easier to configure and then add to the marketplace?

I wrote a mp4 recording history widget which is found in the marketplace so feel free to copy any code from it or how it is setup to grab the items from a single equipment selection. Been meaning to create a version of it that does mjpeg Instead of the HLS for the live feed.

Thanks for sharing this great widget and code. I have added the rule and script and this seem to work nicely now. the items are updated, but the widget looks blank. I have copy/pasted the widget code from above, not changed anything. Am I missing something?

Bildschirm­foto 2023-03-02 um 09.29.25


I see the problem, text is missing in my template. I‘ll provide the changes this evening

Hey Mülli, if I copy paste the widget t works out of the box.
Maybe you did missed to copy the entire widget code?

thanks Oliver, no I tried again. No content from the widget is showing.

if I paste the content of the vFilenames item into the widget, the pictures are showing on click.

  • [{ “caption”: “Freitag, 03.03., 08:46:12 Uhr”, “url”: “/static/photos/Freitag~03.03.~08-46-12.jpg”}, { “caption”: “Freitag, 03.03., 08:46:10 Uhr”, “url”: “/static/photos/Freitag~03.03.~08-46-10.jpg”}, { “caption”: “Donnerstag, 02.03., 12:05:46 Uhr”, “url”: “/static/photos/Donnerstag~02.03.~12-05-46.jpg”}, { “caption”: “Donnerstag, 02.03., 12:05:44 Uhr”, “url”: “/static/photos/Donnerstag~02.03.~12-05-44.jpg”}, { “caption”: “Mittwoch, 01.03., 20:21:44 Uhr”, “url”: “/static/photos/Mittwoch~01.03.~20-21-44.jpg”}, { “caption”: “Mittwoch, 01.03., 20:21:36 Uhr”, “url”: “/static/photos/Mittwoch~01.03.~20-21-36.jpg”}, { “caption”: “Mittwoch, 01.03., 20:21:34 Uhr”, “url”: “/static/photos/Mittwoch~01.03.~20-21-34.jpg”}, { “caption”: “Mittwoch, 01.03., 20:21:28 Uhr”, “url”: “/static/photos/Mittwoch~01.03.~20-21-28.jpg”}, { “caption”: “Mittwoch, 01.03., 20:21:25 Uhr”, “url”: “/static/photos/Mittwoch~01.03.~20-21-25.jpg”}, { “caption”: “Mittwoch, 01.03., 20:19:39 Uhr”, “url”: “/static/photos/Mittwoch~01.03.~20-19-39.jpg”}, { “caption”: “@eaDir Uhr”, “url”: “/static/photos/@eaDir”}, { “caption”: “Mittwoch, 01.03., 20:04:52 Uhr”, “url”: “/static/photos/Mittwoch~01.03.~20-04-52.jpg”}, { “caption”: “Mittwoch, 01.03., 19:59:24 Uhr”, “url”: “/static/photos/Mittwoch~01.03.~19-59-24.jpg”}, { “caption”: “Mittwoch, 01.03., 19:55:00 Uhr”, “url”: “/static/photos/Mittwoch~01.03.~19-55-00.jpg”}]

btw. i am on Version 3.4.0 M1, this might be a root cause…

ok. step by step.
your widget is missing the icon, label text and f7-chip. did you delete them? If you deleted too much of slot components the widget will not work.
could you post your widget?
how do you trigger the rule? It is not triggered by the widget but by item linked to your door bell

That’s certainly part of the issue. The widget uses the @ItemName and @@ItemName shortcuts which were not introduced until later 3.4 milestones (M5?).

Replace @ItemName with the old

items.ItemName.displayState || items.ItemName.state

and @@ItemName with

items.ItemName.state

and that should help.

Thank you @JustinG , this helped already and the window with the pictures appears now, but indeed @Oliver2 the icon, label text and f7-chip are not showing and the taphold_action is not working with the widget. The rule for the doorbell is working well and the vUnread item is increasing with each ring, also the new pictures are stored as they should.

I have not deleted any code from your widget, this is my current version, only adjusted with the two changes @JustinG advised.

My guess is that there are additional components in that only work post 3.4.0. M1.

uid: Doorbell-Pictures_V2
tags: []
props:
  parameters: []
  parameterGroups: []
timestamp: Mar 3, 2023, 11:57:52 AM
component: f7-card
config:
  style:
    --f7-button-hover-bg-color: transparent
    --f7-button-pressed-bg-color: gray
    --f7-button-text-transform: none
    --my-icon-label-size: 18px
    --my-icon-size: 45px
    --my-label-size: 24px
    -khtml-user-drag: none
    -moz-user-drag: none
    -moz-user-select: none
    -ms-user-select: none
    -o-user-drag: none
    -webkit-user-drag: none
    -webkit-user-select: none
    background-color: =(items.vUnread.state == '0')?'rgb(205,205,205)':'white'
    border-radius: 15px
    box-shadow: 0px 5px 10px rgba(0,0,0,0.15)
    height: 120px
    margin: 10px 5px 10px 5px
    max-height: 120px
    min-height: 120px
    padding: 0px
    user-drag: none
    user-select: none
slots:
  default:
    - component: oh-button
      config:
        action: photos
        actionPhotoBrowserConfig:
          exposition: true
          navbarOfText: von
          popupCloseLinkText: Schließen
          type: popup
        actionPhotos: =JSON.parse(items.vFilenames.state)
        style:
          background-color: rgba(205,205,205,0.1)
          border-radius: 15px
          height: 100%
          padding: 0px
          width: 100%
        taphold_action: command
        taphold_actionCommand: "0"
        taphold_actionItem: vUnread
      slots:
        default:
          - component: table
            config:
              style:
                margin: 10px
                table-layout: fixed
                vertical-align: middle
                white-space: nowrap
            slots:
              default:
                - component: tr
                  slots:
                    default:
                      - component: td
                        config:
                          style:
                            text-align: left
                            width: var(--my-icon-size)
                        slots:
                          default:
                            - component: oh-icon
                              config:
                                icon: f7:bell
                                style:
                                  color: black
                                  font-size: var(--my-icon-size)
                                  vertical-align: middle
                                  width: var(--my-icon-size)
                      - component: td
                        slots:
                          default:
                            - component: f7-chip
                              config:
                                bgColor: blue
                                style:
                                  color: black
                                text: =items.vUnread.displayState || items.vUnread.state
                                visible: =(items.vUnread.state != '0') ? 'true':'false'
                - component: tr
                  slots:
                    default:
                      - component: td
                        slots:
                          default:
                            - component: Label
                              config:
                                colspan: 2
                                style:
                                  color: black
                                  font-size: var(--my-label-size)
                                  font-weight: normal
                                  text-align: left
                                text: Klingel
    - component: f7-icon
      config:
        f7: ellipsis_vertical
        style:
          font-size: 20px
          position: absolute
          right: 5px
          top: 12px
          vertical-align: middle
        textColor: gray

Sorry, you’re right I should have caught the other piece the first time.

The html tags as components was added at the same time as the @ shortcuts, so those will not be working in your version. You can also (mostly) fix those.

Any place where there is -component: [some html tag], change the component to an f7-row instead and add to the component’s config the tag property with the name of the html tag you want.

For example:

          - component: table
            config:
              style:
                margin: 10px
                table-layout: fixed
                vertical-align: middle
                white-space: nowrap

would become

          - component: f7-row
            config:
              tag: table
              style:
                margin: 10px
                table-layout: fixed
                vertical-align: middle
                white-space: nowrap

This is not a 100% perfect change. The new structure will be the same, but the rows will have the f7 row class applied to them which may result in some slightly different styling.

Thanks once again @JustinG .

image

I will nevertheless use this opportunity to update from my current version to benefit from the added stuff post 3.4.0 M1. The taphold_action feature is one example that is still missing, so the counter is not resettable right now. So far 3.4.0M1 was running very well and stable so I was reluctant to change anything like never change a running system.

Thanks for your comments. I added a comment regarding minimum version number in the first post.

1 Like

I just updated to 3.4.2. release build and the widget code works well!

@Oliver2 thanks for sharing this widget. It inspired me to create my version of the photo history widget. My goal was to avoid bash scripts and manipulating images on the file system
Here is my widget implementation

Items:
Image doorbell_ftp_image - item is linked to FTP Upload channel
String doorbell_camera_history - items

The rule which is triggered on each image upload:

Widget:

uid: CameraHistory
tags: []
props:
  parameters:
    - context: item
      label: Image History Item
      name: history
      required: false
      type: TEXT
    - label: Mjpeg Camera Url
      name: liveUrl
      required: false
      type: TEXT
    - label: Hls Camera Url
      name: hlsUrl
      required: false
      type: TEXT
timestamp: Mar 21, 2023, 1:35:02 PM
component: f7-block
config:
  style:
    height: 100%
    margin: 0px
    max-height: 100%
    max-width: 100%
    padding: 0px
    width: 100%
slots:
  default:
    - component: oh-image
      config:
        lazy: true
        lazyFadeIn: true
        style:
          bottom: 0px
          display: block
          height: auto
          left: 0px
          margin: auto
          margin-left: auto
          margin-right: auto
          max-height: 100%
          max-width: 100%
          padding: 0px
          position: absolute
          right: 0px
          top: 0px
          width: auto
        url: =props.liveUrl
        visible: =props.liveUrl !== undefined
    - component: oh-video
      config:
        style:
          bottom: 0px
          display: block
          height: auto
          left: 0px
          margin: auto
          margin-left: auto
          margin-right: auto
          max-height: 100%
          max-width: 100%
          padding: 0px
          position: absolute
          right: 0px
          top: 0px
          width: auto
        url: =props.hlsUrl
        visible: =props.hlsUrl !== undefined
    - component: oh-link
      config:
        action: photos
        actionPhotoBrowserConfig: '{ "lazy": true, "type": "popup", "navbarShowCount": true}'
        actionPhotos: =vars.urls
        color: orange
        style:
          pointer-events: none
          position: absolute
          right: 10px
          top: 10px
        visible: =props.history !== undefined && JSON.parse('[' + @props.history + ']').length != 0
      slots:
        default:
          - component: oh-link
            config:
              action: command
              actionCommand: " "
              actionItem: =props.history
              style:
                pointer-events: none
            slots:
              default:
                - component: oh-link
                  config:
                    action: variable
                    actionVariable: urls
                    actionVariableValue: ='[' + @props.history + ']'
                    badge: =JSON.parse('[' + @props.history + ']').length
                    badgeColor: orange
                    style:
                      pointer-events: auto
                    text: " "

Snapshots:
Live camera view


You’ll see a photo gallery with history images by clicking on the history counter in the top right corner. Also, it will clear the history item

WARNING: This implementation has a downside, it stores all history images content in one string item and performance is not good when you have more than 100 unseen images. To fix it I’m working on API extension to put only URLs in history instead of image itself

Thanks to @JustinG for multiple actions on one click

Thank you for sharing your cool widget @Oliver2 !
It took my a while to adapt the widget and the rule to OpenHab 4.0.2 and the rules via new JavaScript/ECMAScript 262 Edition 11 (ECMAScript 2022+).

To make it easier for the next one I share my working result here for everyone:

openhab/conf/items/klingel.items

Number                  vUnread             "Ungelesene Klingelmeldungen"   
String                  vFilenames          "Snapshop Dateinnamenliste"

openhab/conf/automation/js/klingel.js

rules.JSRule({
    name: "Alarm Türklingel",
    description: "Tuerklingel aber niemand zu Hause",
    triggers: [
        triggers.ItemStateChangeTrigger('Kamera_Tuerklingel_LastCall')
    ],
    execute: (event) => {
        if (items.getItem("AnwesenheitPerson1_Delayed").state === "OFF") {
            let status = "Niemand zu Hause!"
            /* Daten für Klingel_Ueberblick Widget sammeln */
            var unread = items.getItem("vUnread").numericState + 1
            //this item is being used by the widget to display how many photos haven't been seen since the last reset:
            items.getItem("vUnread").sendCommand(unread)
            //result of this bash script is an updated list of files names in the photo directory:            
            var csvFiles = actions.Exec.executeCommandLine(time.Duration.ofSeconds(1), "/openhab/conf/scripts/updateHistory.sh");
            //convert list of file names to a json string containing description and file name for the widget:
            var jsonPhotos = "[" + csvFiles.split('\n').slice(0, -1).map((e) => "{ \"caption\": \"" + e.split(".jpg")[0].replace("~", ", ").replace("~", ", ").replace("-", ":").replace("-", ":") + " Uhr\", \"url\": " + "\"/static/tuerklingel/" + e + "\"}") + "]"
            //store json string in this item:
            items.getItem("vFilenames").sendCommand(jsonPhotos)
        }
    },
    tags: ["Alarm"],
    id: "Alarm_Tuerklingel"
});

openhab/conf/scripts/updateHistory.sh

folder="/openhab/conf/html/tuerklingel" #this is where the photos are stored
# This string is used for the file name and description in the widget
now="$(LC_ALL=de_DE.utf8 date +"%A~%d.%m.~%H-%M-%S")" # Results in german notation Samstag~18.02.~20-22-01.
#for english weekday names use: now="$(date +"%A~%d.%m.~%H-%M-%S")"
#Download picture from camera and save it in the folder:
wget http://192.168.0.2:9080/ipcamera/tuerklingel/ipcamera.jpg -q -O $folder/$now.jpg
cd $folder
#for cleanup. This line deletes all old pictures and keeps the 20 latest ones.
ls -tp | tail -n +21 | xargs -I {} rm -- {} #ATTENTION: make 100% sure that you are in the correct folder. Otherwise all files in this folder will be deleted!
#create a string containing all pictures
ls $folder -1t

UI Widget

uid: Klingel_Ueberblick
tags: []
props:
  parameters: []
  parameterGroups: []
timestamp: Aug 25, 2023, 10:01:03 AM
component: f7-card
config:
  style:
    --f7-button-hover-bg-color: transparent
    --f7-button-pressed-bg-color: gray
    --f7-button-text-transform: none
    --my-icon-label-size: 18px
    --my-icon-size: 45px
    --my-label-size: 24px
    -khtml-user-drag: none
    -moz-user-drag: none
    -moz-user-select: none
    -ms-user-select: none
    -o-user-drag: none
    -webkit-user-drag: none
    -webkit-user-select: none
    border-radius: 8px
    box-shadow: 5px 5px 10px rgba(0,0,0,0.15)
    border: 1px solid lightgrey
    user-drag: none
    user-select: none
slots:
  default:
    - component: oh-button
      config:
        action: photos
        actionPhotoBrowserConfig:
          exposition: true
          navbarOfText: von
          popupCloseLinkText: Schließen
          type: popup
        actionPhotos: =JSON.parse(@@'vFilenames')
        style:
          background-color: rgba(205,205,205,0.1)
          border-radius: 15px
          height: 100%
          padding: 0px
          width: 100%
        taphold_action: command
        taphold_actionCommand: "0"
        taphold_actionItem: vUnread
      slots:
        default:
          - component: table
            config:
              style:
                margin: 10px
                table-layout: fixed
                vertical-align: middle
                white-space: nowrap
            slots:
              default:
                - component: tr
                  slots:
                    default:
                      - component: td
                        config:
                          style:
                            text-align: left
                            width: var(--my-icon-size)
                        slots:
                          default:
                            - component: oh-icon
                              config:
                                icon: f7:bell
                                style:
                                  color: black
                                  font-size: var(--my-icon-size)
                                  vertical-align: middle
                                  width: var(--my-icon-size)
                      - component: td
                        slots:
                          default:
                            - component: f7-chip
                              config:
                                bgColor: red
                                style:
                                  color: white
                                text: =@'vUnread'
                                visible: =(items.vUnread.state != '0') ? 'true':'false'
                - component: tr
                  slots:
                    default:
                      - component: td
                        slots:
                          default:
                            - component: Label
                              config:
                                colspan: 2
                                style:
                                  color: black
                                  font-size: var(--my-label-size)
                                  font-weight: normal
                                  text-align: left
                                text: Klingel
    - component: f7-icon
      config:
        f7: ellipsis_vertical
        style:
          font-size: 20px
          position: absolute
          right: 5px
          top: 12px
          vertical-align: middle
        textColor: gray

With the following extra code you can send an telegram message to you when someone rang the door bell and you are not at home:

            let telegramAlarmAction = actions.Things.getActions("telegram", "telegram:telegramBot:botAlarm")
            telegramAlarmAction.sendTelegramPhoto(
                items.getItem("Kamera_Tuerklingel_BildUrl").state,
                "Haustürklingel. "
            )

Next will use the TelegramQuery to pick an audio answer to the person in fron of the door. Also I would like to activate/deactivae the door bell in the widget and show the status.

Glad you like it and yes, that was on my todo to add telegram :slight_smile:
I also converted my script to JS:

var unread = items.vKlingelUnread.numericState + 1;
items.vKlingelUnread.postUpdate(unread);
var csvFiles = actions.Exec.executeCommandLine(time.Duration.ofSeconds(1),"/etc/openhab/scripts/updatePhotoHistory.sh","klingel");
var jsonArray = [];
csvFiles.split('\n').forEach(e => {
    var newElement = {};
    if (e != "") jsonArray.push({caption: e.split(".jpg")[0].replaceAll("~",", ").replaceAll("-",":") + " Uhr", url: "/static/klingel/" + e});
});
var myJSON = JSON.stringify(jsonArray);
items.vKlingelFilenames.sendCommand(myJSON.toString());

JS gives us better array and JSON functionalities so that I was able to simplify the code a bit.

Thanks, this is a great idea!