How to fully integrate your Raspberry Pi digital picture frame into Home Assistant even showing the current image 2

How to fully integrate your Raspberry Pi digital picture frame into Home Assistant even showing the current image

The advantage of making something yourself rather than just going out and buying it off the shelf is that you can tailor everything to your exact requirements and ideas.

So when I set out to build a digital picture frame, I also wanted to integrate it tightly with my home automation system, “Home Assistant“.

The idea was to control the picture frame from within Home Assistant fully. This would include choosing the playback directory, seeing all the relevant Exif information, a map with the location where the photo was taken, and even showing the photo itself on display on the picture frame.

Paddy’s old PictureFrame script originated from a simple Pi3D demo file, and a lot of great features had been added over time. But its code also became very complex and needed a spring cleaning.

So, together with Paddy and Jeff, we completely rewrote the old Pi3D PictureFrame script and added lots of new features in the process. Amongst them was a perfect integration with Home Assistant!

If you are not yet familiar with PictureFrame, head over here first.

Tested with: Raspberry Pi OS March 2021 version, Raspberry Pi 2, 3, and 4, Pi3D 2.43, PictureFrame 2021.03.20, Home Assistant 2021.3.4, 1080p and 4K displays.

Benefits of a tight integration between Home Assistant and PictureFrame

The PictureFrame 2021 image viewer allows you to remote control many features and retrieve Exif data from the images. The new version now even has a web server that allows you to remotely view the currently displayed image.

The whole control panel in Home Assistant will look like this:

How to fully integrate your Raspberry Pi digital picture frame into Home Assistant even showing the current image 3

You can select the right controls and data fields that are of interest to you and create your own customized picture frame dash board.

Installation

The following assumes that you have successfully completed the PictureFrame basic installation and that you have Home Assistant running.

If you don’t use an external MQTT broker, you can install one on your Raspberry Pi by entering

sudo apt install -y mosquitto mosquitto-clients

Configuration of PictureFrame

The configuration of PictureFrame is made in configuration.yaml.

In the mqtt section enable MQTT and enter your IP and port.

mqtt:
  use_mqtt: True  # Set True, to enable mqtt
  server: 192.168.178.136 # Enter the IP where your MQTT is hosted
  port: 1883 # this works for most people
  login: "" # your mqtt user. If you have none, just leave empty
  password: ""  # password for mqtt user. If you have none, just leave empty
  tls: "" # this is for secure connections. If you don't know what this is, leave it empty
  device_id: "picframe" # unique id of device. Change if there is more than one PictureFrame

If you use the above settings, you only have to insert your IP and you can leave the rest as it is.

Directly below in the http section, enable it. If you are not using an SSL connection, the settings below should work for you.

http:
  use_http: True  # default=False. Set True to enable http 
  path: "/home/pi/picframe_data/html"  # path to where html files are located
  port: 9000     # port used to serve pages by http server < 1024 requires root which is *bad* idea
  use_ssl: False
  keyfile: ""   # private-key
  certfile: ""  # server certificate

Start PictureFrame and keep it running while we configure Home Assistant.

To check if MQTT works alright, you can use a tool like MQTT Explorer to see if the sensors and switches data are being transmitted.

How to fully integrate your Raspberry Pi digital picture frame into Home Assistant even showing the current image 4

If what you see looks similar to the above picture, you’re off to a good start.

Home Assistant uses “Sensors” and “Switches”. A switch can just be “ON” or “OFF” whereas a sensor can have specific values like dates, numbers, or strings.

Configuration of Home Assistant

If you haven’t worked with MQTT before, you need to add the MQTT Integration. In most cases, you only need to enter the IP of your MQTT server. The port is 1883 for most users.

The Options, “Auto Discovery” needs to be on, the rest you can ignore for the time being.

How to fully integrate your Raspberry Pi digital picture frame into Home Assistant even showing the current image 5
How to fully integrate your Raspberry Pi digital picture frame into Home Assistant even showing the current image 6
How to fully integrate your Raspberry Pi digital picture frame into Home Assistant even showing the current image 7

Click “Submit” and click on “devices” which will take you to the the “Devices” section. You should see “picframe”. Click on it.

How to fully integrate your Raspberry Pi digital picture frame into Home Assistant even showing the current image 8
How to fully integrate your Raspberry Pi digital picture frame into Home Assistant even showing the current image 9
How to fully integrate your Raspberry Pi digital picture frame into Home Assistant even showing the current image 10

You can already see all the entities that are being sent from PictureFrame to Home Assistant.

How to fully integrate your Raspberry Pi digital picture frame into Home Assistant even showing the current image 11
How to fully integrate your Raspberry Pi digital picture frame into Home Assistant even showing the current image 12

Play around with some of the switches like e.g. “picframe_next” to move the PictureFrame to the next image to see if it works.

Once this works, we will configure several settings which allow us to fully control PictureFrame from within Home Assistant.

Installing the Helpers

Switches and sensors can be used directly in Home Assistant cards. For input elements like text fields or the brightness slider, you have to create the elements first. This is done with “helpers”. You can find them in the configuration section.

Unfortunately, Home Assistant doesn’t provide a YAML import for helpers. You have to create them over the GUI manually.

I will show you screenshots of each Helper so that you know exactly where you need to enter what information. Just click on “Add Helper”.

The Entity ID will be added automatically once you save the Helper.

Let’s start with the first one.

Helper picframe.time_delay

Add a Helper “Number”.

How to fully integrate your Raspberry Pi digital picture frame into Home Assistant even showing the current image 13

Helper picframe.fade_time

Add a Helper “Number”.

How to fully integrate your Raspberry Pi digital picture frame into Home Assistant even showing the current image 14

Helper picframe.brightness

Add a Helper “Number”.

How to fully integrate your Raspberry Pi digital picture frame into Home Assistant even showing the current image 15

Helper picframe_date_from

To pick a date, we must first define the local data format, which can be processed. You could use the date picker helper for date input, but it is limited to standard Unix time. While this is probably fine for most users, some may want to include scanned images from much earlier.

While this is possible, it is a bit more complicated because it requires a regular expression pattern (“regex”) for client-side valuation.

A regex pattern is a sequence of characters that specifies a search pattern. Such patterns are used by string-searching algorithms, or for input validation. So exactly what we need for specifying a date range.

Here are a few examples:

FormatRegex
dd.mm.yy^(0[1-9]|[12][0-9]|3[01])\.(0[1-9]|1[012])\.\d{4}$
YYYY-mm-dd ^\d{4}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$
mm/dd/YYYY^(0[1-9]|1[012])\/(0[1-9]|[12][0-9]|3[01])\/\d{4}$
dd/mm/YYYY^(0[1-9]|[12][0-9]|3[01])\/(0[1-9]|1[012])\/\d{4}$

Add a Helper “Text”.

How to fully integrate your Raspberry Pi digital picture frame into Home Assistant even showing the current image 16

Helper picframe_date_to

Add a Helper “Text”.

How to fully integrate your Raspberry Pi digital picture frame into Home Assistant even showing the current image 17

Helper picframe_location_filter

Add a Helper “Text”.

How to fully integrate your Raspberry Pi digital picture frame into Home Assistant even showing the current image 18

Helper picframe_tags_filter

Add a Helper “Text”.

How to fully integrate your Raspberry Pi digital picture frame into Home Assistant even showing the current image 19

Helper picframe_dir

Add a Helper “Dropdown”.

Under “Options” just add one dummy directory. It will later be overwritten with the actual content of your Pictures folder.

How to fully integrate your Raspberry Pi digital picture frame into Home Assistant even showing the current image 20

So, all in all your Helper selection should include all these:

How to fully integrate your Raspberry Pi digital picture frame into Home Assistant even showing the current image 21

Creating the Cards

Now go back to your Lovelace interface and add another view. You can always move the card later but for the moment, let’s keep it a separate view.

Images Card

This card shows you all the image metadata

Create a new card, pick “Entities”. You will see a few pre-filled entities which we are not going to use. Instead, click on “Show Code Editor”.

Erase the sample text in there and replace it with:

cards:
  - type: markdown
    content: >-
      {% if state_attr("sensor.picframe_image", "IPTC Object Name") -%}

      ## {{ state_attr("sensor.picframe_image", "IPTC Object Name") }}
       {%- else -%}
      ## {{ states("sensor.picframe_image") }}

      {%- endif %}

      {% if state_attr("sensor.picframe_image", "IPTC Caption/Abstract") -%}
         {{ state_attr("sensor.picframe_image", "IPTC Caption/Abstract") }}
      {% endif %}

      ![Image](http://192.168.178.178:9000/{{
      states('sensor.picframe_image') | urlencode }})

      {% if state_attr("sensor.picframe_image", "location") != None %}
        {{ state_attr("sensor.picframe_image", "location") }}
      {%- else -%}
        No GPS data.
      {%- endif %}
  - type: glance
    entities:
      - entity: switch.picframe_back
        tap_action:
          action: toggle
      - entity: switch.picframe_paused
        tap_action:
          action: toggle
      - entity: switch.picframe_next
        tap_action:
          action: toggle
      - entity: switch.picframe_shuffle
        tap_action:
          action: toggle
      - entity: switch.picframe_delete
        tap_action:
          action: toggle
    show_name: false
    show_state: false
    state_color: true
    show_icon: true
  - type: entities
    entities:
      - entity: sensor.picframe_image
        name: File
      - entity: sensor.picframe_image_date
        icon: 'mdi:calendar-clock'
        name: Date
      - type: attribute
        entity: sensor.picframe_image
        attribute: EXIF ExposureTime
        unit: sec
        icon: 'mdi:camera-timer'
        name: Exposure
      - type: attribute
        entity: sensor.picframe_image
        attribute: EXIF FNumber
        unit: f
        icon: 'mdi:camera-iris'
        name: Aperture
      - type: attribute
        entity: sensor.picframe_image
        attribute: EXIF ISOSpeedRatings
        icon: 'mdi:film'
        name: ISO
      - type: attribute
        entity: sensor.picframe_image
        attribute: EXIF FocalLength
        unit: iso
        icon: 'mdi:signal-distance-variant'
        name: Focal Length
      - type: attribute
        entity: sensor.picframe_image
        attribute: Image Model
        unit: mm
        icon: 'mdi:camera'
        name: Camera Model
      - type: attribute
        entity: sensor.picframe_image
        attribute: IPTC Keywords
        icon: 'mdi:tag'
        name: Keywords
  - aspect_ratio: '3:2'
    dark_mode: false
    default_zoom: 11
    entities:
      - entity: sensor.picframe_image
    type: map
    hours_to_show: 0
type: vertical-stack

Search for “![Image](http://192.168.178.178:9000/{{” and enter the IP address of your picture frame.

If you enabled SSL in PictureFrame’s webserver change "http” to “https".

Filter Card

This card is for filtering the images based on directory, locations, keywords, and dates.

Create a new Card, repeat the procedure above, and paste the text below into the YAML editor. Translate the “name” part to your local language if you like and ensure that the date format is the same as defined in the Helpers date_from and date_to.

entities:
  - entity: sensor.picframe_image_counter
    name: Images
  - entity: input_select.picframe_directory
    name: Directory
  - entity: input_text.picframe_tags_filter
    name: Keyword Search
  - entity: input_text.picframe_location_filter
    name: Location Search
  - entity: input_text.picframe_date_from
    name: Filter on date from (DD.MM.YYYY)
  - entity: input_text.picframe_date_to
    name: Filter on date up to (DD.MM.YYYY)
show_header_toggle: false
theme: Backend-selected
title: Filter
type: entities
state_color: true

Text Overlay Card

This card allows you to control the image information.

Create yet another card and paste this into the YAML editor.

type: entities
entities:
  - entity: switch.picframe_title_toggle
    name: Title
  - entity: switch.picframe_caption_toggle
    name: Caption
  - entity: switch.picframe_name_toggle
    name: File Name
  - entity: switch.picframe_location_toggle
    name: Location
  - entity: switch.picframe_date_toggle
    name: Capture Date
  - entity: switch.picframe_directory_toggle
    name: Directory
  - entity: switch.picframe_text_refresh
    name: Text refresh
title: Text-Overlay
state_color: true

Settings Card

This card controls the screen brightness, display on/off settings, and the image transition.

entities:
  - entity: switch.picframe_display
    name: Living Room Frame
  - entity: input_number.picframe_brightness
    name: Brightness
  - entity: input_number.picframe_time_delay
    name: Duration
  - entity: input_number.picframe_fade_time
    name: Fade Time
show_header_toggle: false
theme: Backend-selected
title: Digital Picture Frame
type: entities
state_color: true

Automations

The automation settings perform several useful tasks like changing the date format you are expecting in date_from / date_to helpers and managing the input variables overall.

The time format can be found here: docs.python.org datetime.

Instead of creating seven separate automations, you can append the code below to the automation.yaml file. The easiest way is to use the file editor in Home Assistant. Save and restart the Home Assistant Server once you are done.

- id: pic1607067277342
  alias: picframe_directory_get
  description: ''
  trigger:
  - platform: state
    entity_id: sensor.picframe_directory
  - platform: homeassistant
    event: start
  condition:
  - condition: or
    conditions:
    - condition: template
      value_template: '{{ ( states("sensor.picframe_directory") ) != ( states("input_select.picframe_directory")
        ) }}'
    - condition: template
      value_template: '{{ ( state_attr("sensor.picframe_directory", "directories")
        ) != ( state_attr("input_select.picframe_directory", "options") ) }}'
  action:
  - service: input_select.set_options
    data:
      options: '{{ state_attr(''sensor.picframe_directory'', ''directories'') }}'
    entity_id: input_select.picframe_directory
  - service: input_select.select_option
    data:
      option: '{{  states("sensor.picframe_directory") }}'
    entity_id: input_select.picframe_directory
  mode: single
- id: pic1607067504666
  alias: picframe_directory_set
  description: ''
  trigger:
  - platform: state
    entity_id: input_select.picframe_directory
  condition: []
  action:
  - service: mqtt.publish
    data:
      topic: picframe/directory
      payload: '{{ states("input_select.picframe_directory") }}'
  mode: single
- id: pic1607456756183
  alias: picframe_fade_time_get
  description: ''
  trigger:
  - platform: state
    entity_id: sensor.picframe_fade_time
  condition:
  - condition: template
    value_template: '{{ states("input_number.picframe_fade_time") != states("sensor.picframe_fade_time")
      }}'
  action:
  - service: input_number.set_value
    data:
      value: '{{ states("sensor.picframe_fade_time") }}'
    entity_id: input_number.picframe_fade_time
  mode: single
- id: pic1607456736910
  alias: picframe_time_delay_get
  description: ''
  trigger:
  - platform: state
    entity_id: sensor.picframe_time_delay
  condition:
  - condition: template
    value_template: '{{ states("input_number.picframe_time_delay") != states("sensor.picframe_time_delay")
      }}'
  action:
  - service: input_number.set_value
    data:
      value: '{{ states("sensor.picframe_time_delay") }}'
    entity_id: input_number.picframe_time_delay
  mode: single
- id: pic1607456731052
  alias: picframe.date_from_get
  description: ''
  trigger:
  - platform: state
    entity_id: sensor.picframe_date_from
  condition:
  - condition: template
    value_template: '{{ as_timestamp(strptime(states("input_text.picframe_date_from"),"%d.%m.%Y"))
      | int != states("sensor.picframe_date_from")|int }}'
  action:
  - service: input_text.set_value
    data:
      value: '{{ states(''sensor.picframe_date_from'') | int | timestamp_custom(''%d.%m.%Y'')
        }}'
    entity_id: input_text.picframe_date_from
  mode: single
- id: pic1607456741584
  alias: picframe.date_to_get
  description: ''
  trigger:
  - platform: state
    entity_id: sensor.picframe_date_to
  condition:
  - condition: template
    value_template: '{{ as_timestamp(strptime(states("input_text.picframe_date_to"),"%d.%m.%Y"))
      | int != states("sensor.picframe_date_to")|int }}'
  action:
  - service: input_text.set_value
    data:
      value: '{{ states(''sensor.picframe_date_to'') | int | timestamp_custom(''%d.%m.%Y'')
        }}'
    entity_id: input_text.picframe_date_to
  mode: single
- id: pic1607456720004
  alias: picframe_fade_time_set
  description: ''
  trigger:
  - platform: state
    entity_id: input_number.picframe_fade_time
  condition:
  - condition: template
    value_template: '{{ states("input_number.picframe_fade_time") != states("sensor.picframe_fade_time")
      }}'
  action:
  - service: mqtt.publish
    data:
      topic: picframe/fade_time
      payload: '{{ states("input_number.picframe_fade_time") }}'
  mode: single
- id: pic1607467027848
  alias: picframe_time_delay_set
  description: ''
  trigger:
  - platform: state
    entity_id: input_number.picframe_time_delay
  condition:
  - condition: template
    value_template: '{{ states("input_number.picframe_time_delay") != states("sensor.picframe_time_delay")
      }}'
  action:
  - service: mqtt.publish
    data:
      topic: picframe/time_delay
      payload: '{{ states("input_number.picframe_time_delay") }}'
  mode: single
- id: pic1607457675153
  alias: picframe.date_to_set
  description: ''
  trigger:
  - platform: state
    entity_id: input_text.picframe_date_to
  condition:
  - condition: template
    value_template: '{{ as_timestamp(strptime(states("input_text.picframe_date_to"),"%d.%m.%Y"))
      | int != states("sensor.picframe_date_to")|int }}'
  action:
  - service: mqtt.publish
    data:
      topic: picframe/date_to
      payload: "{% if states(\"input_text.picframe_date_to\") != \"\" -%}   \n\
        \  {{ as_timestamp(strptime(states(\"input_text.picframe_date_to\"),\"\
        %d.%m.%Y\")) |     int }} \n{%- endif %}"
  - condition: template
    value_template: '{{  states("input_text.picframe_date_to") == "" }}'
  - service: input_text.set_value
    data:
      value: '{{ states(''sensor.picframe_date_to'') | int | timestamp_custom(''%d.%m.%Y'')
        }}'
    entity_id: input_text.picframe_date_to
  mode: single
- id: pic1607466674037
  alias: picframe.date_from_set
  description: ''
  trigger:
  - platform: state
    entity_id: input_text.picframe_date_from
  condition:
  - condition: template
    value_template: '{{ as_timestamp(strptime(states("input_text.picframe_date_from"),"%d.%m.%Y"))
      | int != states("sensor.picframe_date_from")|int }}'
  action:
  - service: mqtt.publish
    data:
      topic: picframe/date_from
      payload: "{% if states(\"input_text.picframe_date_from\") != \"\" -%}   \n\
        \  {{ as_timestamp(strptime(states(\"input_text.picframe_date_from\"),\"\
        %d.%m.%Y\")) |     int }} \n{%- endif %}"
  - condition: template
    value_template: '{{ states("input_text.picframe_date_from") == "" }}'
  - service: input_text.set_value
    data:
      value: '{{ states(''sensor.picframe_date_from'') | int | timestamp_custom(''%d.%m.%Y'')
        }}'
    entity_id: input_text.picframe_date_from
  mode: single
- id: pic1613336726633
  alias: picframe_brightness_get
  description: ''
  trigger:
  - platform: state
    entity_id: sensor.picframe_brightness
  condition:
  - condition: template
    value_template: '{{ states("input_number.picframe_brightness") | int  != states("sensor.picframe_brightness")
      | int * 100 }}'
  action:
  - service: input_number.set_value
    data:
      value: '{{ states("sensor.picframe_brightness") | int * 100 }}'
    entity_id: input_number.picframe_brightness
  mode: single
- id: pic1613367010761
  alias: picframe_brightness_set
  description: ''
  trigger:
  - platform: state
    entity_id: input_number.picframe_brightness
  condition:
  - condition: template
    value_template: '{{ states("input_number.picframe_brightness") | int != states("sensor.picframe_brightness")
      | int * 100 }}'
  action:
  - service: mqtt.publish
    data:
      topic: picframe/brightness
      payload: '{{ states("input_number.picframe_brightness") | float / 100.0 }}'
  mode: single
- id: pic1613395679608
  alias: picframe_location_filter_set
  description: ''
  trigger:
  - platform: state
    entity_id: input_text.picframe_location_filter
  condition: []
  action:
  - service: mqtt.publish
    data:
      topic: picframe/location_filter
      payload: '{{ states("input_text.picframe_location_filter") }}'
  mode: single
- id: pic1616758160875
  alias: picframe_tags_filter_set
  description: ''
  trigger:
  - platform: state
    entity_id: input_text.picframe_tags_filter
  condition: []
  action:
  - service: mqtt.publish
    data:
      topic: picframe/tags_filter
      payload: '{{ states("input_text.picframe_tags_filter") }}'
  mode: single
- id: pic1613567985293
  alias: picframe_location_filter_get
  description: ''
  trigger:
  - platform: state
    entity_id: sensor.picframe_location_filter
  condition:
  - condition: template
    value_template: "{% if is_state('sensor.picframe_location_filter', 'None')\
      \ %}\n      {{ states('input_text.picframe_location_filter') != '' }}\n{%\
      \ else %}\n      {{ states('input_text.picframe_location_filter') != states('sensor.picframe_location_filter')\
      \ }}.\n{% endif %}"
  action:
  - service: input_text.set_value
    data:
      value: '{{ states("sensor.picframe_location_filter") }}'
    entity_id: input_text.picframe_location_filter
  mode: single
- id: pic1613673115253
  alias: picframe_location_tags_get
  description: ''
  trigger:
  - platform: state
    entity_id: sensor.picframe_tags_filter
  condition:
  - condition: template
    value_template: "{% if is_state('sensor.picframe_tags_filter', 'None') %}\n\
      \      {{ states('input_text.picframe_tags_filter') != '' }}\n{% else %}\n\
      \      {{ states('input_text.picframe_tags_filter') != states('sensor.picframe_tags_filter')\
      \ }}.\n{% endif %}"
  action:
  - service: input_text.set_value
    data:
      value: '{{ states("sensor.picframe_tags_filter") }}'
    entity_id: input_text.picframe_tags_filter
  mode: single

Home Assistant Configuration.yaml

This refers to the configuration.yaml of Home Assistant not the file of the same name in PictureFrame.

Open configuration.yaml and add the following paragraph.

sensor:
  - platform: template
    sensors:
      picframe_image_date:
        value_template: "{{ state_attr('sensor.picframe_image', 'EXIF DateTimeOriginal') | int | timestamp_custom('%d. %b. %Y %H:%M') }}"
        friendly_name: "Date"

The time format could be found here: docs.python.org datetime

Finally, restart Home Assistant.

Go to the Automations again and trigger the “picframe_directory_get” automation manually once by clicking “Execute”.

Go to your Home Assistant dashboard. As soon as the next image change happens on PictureFrame, the data will be shown.

How to integrate more than one picture frame

Setting up two or more picture frames is very simple.

In the configuration.yaml of your other picture frame, give it a different name than the default “picframe”.

Then repeat all the above steps but replace every instance of “picframe” with your new name. This means, helper, cards, and automations. It is easily done with Find&Replace with an editor like Sublime Text.

Note that you will have to change the ID of the every duplicate automation to something different. The easiest is to just change “pic” to “pictv” or whatever you have in mind. But it can be any string as long as it is unique.

- id: 'pic1613673115253'

If you don’t, the automations will be ignored by Home Assistant. And don’t forget to add a second sensor in the Home Assistant configuration.yaml file and to restart.

Troubleshooting

If everything works, but you can’t see the image, make sure that you are either using HTTP or HTTPS on both Home Assistant and in the settings in PictureFrame.

The image will only work in your local network. If you are using e.g. Nabu Casa to access your Home Assistant instance, you won’t be able to see the image.

Also, it may take “one round” of images for the database to have all the information on keywords and locations.

If you can’t enter keywords or locations, click on the icon and enter your value there. You only need to do it once, it will be fine afterwards.

Conclusion

Integrating PictureFrame into Home Assistant allows to use all the great smart list features and to see the metadata extracted from the images. It really makes the software a lot easier to use overall.

I admit that it’s a bit of work, but it’s not hard, and with the detailed instructions in this article, it should be easy for you to achieve this high integration level between PictureFrame 2021 and Home Assistant.

Enjoy!

Was this post helpful?

Scroll to Top