Camera: Live feed with PTZ controls

Here is a widget for moving PTZ cameras. If you can improve on it then please post your changes. It can move a camera that either supports ONVIF presets, Relative movements and now also Continuous move (@Aspergillus thanks for contributing some code).

Steps to get this working:

  1. You need to either use one of these two features to auto create the items with the default naming.
    “Add Equipment to model”
    “Create Equipment from thing”
    If using textual config, you will need to either edit the widget or create your items to match the standard naming.
  2. Make sure when creating the Equipment you click ‘show advanced’ to see all the channels and tick the Pan, Tilt, Zoom, GoToPreset and MJPEG URL channels as a minimum.
  3. Select the Equipment/Item that is your desired camera in the setup of the widget.
  4. Optionally you can select an item you wish to switch on and off. Example the lights in the room that the camera is in. Alternatively it may be blinds or a motorized gate, the choice is yours and if you leave this blank the control disappears.
  5. If your camera does not support Relative movements, then you can hide those controls and just use Presets instead. If your camera supports it, you will see a named list of your presets appear that you can choose from that will take your camera directly to the selected preset location.

Thumb nail that when clicked opens up to bigger view.
PTZ

Setup

Widget Code

uid: ClickablePTZCamera
props:
  parameters:
    - context: item
      label: Select the Camera to Control
      name: camera
      required: true
      type: TEXT
    - label: Show Equipment Controls
      name: showSettings
      required: true
      type: BOOLEAN
    - label: Show PTZ Controls
      name: showControls
      required: true
      type: BOOLEAN
    - label: Show Stop Button
      name: showMoveStop
      required: true
      type: BOOLEAN
    - label: Show Preset Locations
      name: showPresets
      required: true
      type: BOOLEAN
    - label: Invert Tilt Control
      name: invertTilt
      required: true
      type: BOOLEAN
    - label: Invert Pan Control
      name: invertPan
      required: true
      type: BOOLEAN
    - label: Show Audio Alarms
      name: showAudioAlarms
      required: true
      type: BOOLEAN
    - label: Show Motion Alarms
      name: showMotionAlarms
      required: true
      type: BOOLEAN
    - context: item
      label: Item for Switch
      name: switchItem
      required: false
      type: TEXT
component: f7-card
config:
  class: no-margin
  expandable: true
  style:
    --f7-card-margin-horizontal: 0px
    border-radius: 6px
    height: 9rem
    width: 16rem
slots:
  default:
    - component: f7-card-content
      config:
        class: card-opened-fade-out
        style:
          background-image: ="url('"+items[props.camera + "_MJPEG_URL"].state+"')"
          background-repeat: no-repeat
          background-size: 17rem 10rem
          border-radius: 6px
          color: var(--f7-card-header-text-color)
          z-index: -1
    - component: f7-card-content
      config:
        class: card-opened-fade-in
        style:
          background-image: ="url('"+items[props.camera + "_MJPEG_URL"].state+"')"
          background-position: center
          background-repeat: no-repeat
          background-size: 100% auto
          color: var(--f7-card-header-text-color)
          z-index: -1
    - component: oh-link
      config:
        action: toggle
        actionCommand: ON
        actionCommandAlt: OFF
        actionItem: =props.switchItem
        class: card-prevent-open
        iconF7: power
        iconSize: 22
        style:
          color: "=(items[props.switchItem].state === 'OFF') ? 'white' : (items[props.switchItem].state === '0,0,0') ? 'white' : (items[props.switchItem].state === '0') ? 'white' : 'cyan'"
          margin: 0em 0.2em
          opacity: 0.5
        visible: =props.switchItem !== undefined
    - component: oh-link
      config:
        action: command
        actionCommand: "=(props.invertPan) ? 'DECREASE' : 'INCREASE'"
        actionItem: =props.camera + "_Pan"
        class: card-prevent-open
        iconF7: arrow_left
        iconSize: 22
        style:
          color: var(--f7-card-header-text-color)
          margin: 0 0.2em
          opacity: 0.5
        visible: =props.showControls === true
    - component: oh-link
      config:
        action: command
        actionCommand: "=(props.invertTilt) ? 'INCREASE' : 'DECREASE'"
        actionItem: =props.camera + "_Tilt"
        class:
          - card-prevent-open
        iconF7: arrow_up
        iconSize: 22
        style:
          color: var(--f7-card-header-text-color)
          margin: 0 0.2em
          opacity: 0.5
        visible: =props.showControls === true
    - component: oh-link
      config:
        action: command
        actionCommand: OFF
        actionItem: =props.camera + "_Pan"
        class:
          - card-prevent-open
        iconF7: stop_circle
        iconSize: 22
        style:
          color: var(--f7-card-header-text-color)
          margin: 0 0.2em
          opacity: 0.5
        visible: =props.showMoveStop === true
    - component: oh-link
      config:
        action: command
        actionCommand: "=(props.invertTilt) ? 'DECREASE' : 'INCREASE'"
        actionItem: =props.camera + "_Tilt"
        class: card-prevent-open
        iconF7: arrow_down
        iconSize: 22
        style:
          color: var(--f7-card-header-text-color)
          margin: 0 0.2em
          opacity: 0.5
        visible: =props.showControls === true
    - component: oh-link
      config:
        action: command
        actionCommand: "=(props.invertPan) ? 'INCREASE' : 'DECREASE'"
        actionItem: =props.camera + "_Pan"
        class: card-prevent-open
        iconF7: arrow_right
        iconSize: 22
        style:
          color: var(--f7-card-header-text-color)
          margin: 0 0.2em
          opacity: 0.5
        visible: =props.showControls === true
    - component: oh-link
      config:
        action: command
        actionCommand: INCREASE
        actionItem: =props.camera + "_Zoom"
        class: card-prevent-open
        iconF7: plus
        iconSize: 22
        style:
          color: var(--f7-card-header-text-color)
          margin: 0 0.2em
          opacity: 0.5
        visible: =props.showControls === true
    - component: oh-link
      config:
        action: command
        actionCommand: DECREASE
        actionItem: =props.camera + "_Zoom"
        class:
          - card-prevent-open
        iconF7: minus
        iconSize: 22
        style:
          color: var(--f7-card-header-text-color)
          margin: 0 0.2em
          opacity: 0.5
        visible: =props.showControls === true    
    - component: oh-link
      config:
        action: options
        actionItem: =props.camera + "_Go_To_Preset"
        class: card-prevent-open
        iconF7: list_number
        iconSize: 22
        style:
          color: var(--f7-card-header-text-color)
          margin: 0 0.2em
          opacity: 0.5
        visible: =props.showPresets === true
    - component: oh-link
      config:
        action: group
        actionGroupPopupItem: =props.camera
        class: card-prevent-open
        iconF7: gear_alt
        iconSize: 22
        style:
          color: var(--f7-card-header-text-color)
          margin: 0 0.2em
          opacity: 0.5
        visible: =props.showSettings === true
    - component: oh-link
      config:
        iconF7: eye
        iconSize: 18
        style:
          color: var(--f7-card-header-text-color)
          opacity: "=(items[props.camera + '_Motion_Alarm'].state === 'ON') ? '0.5' : '0'"
          position: absolute
          right: 0.2rem
          top: 7.8rem
        visible: =props.showMotionAlarms === true
    - component: oh-link
      config:
        iconF7: ear
        iconSize: 18
        style:
          color: var(--f7-card-header-text-color)
          left: 0rem
          opacity: "=(items[props.camera + '_Audio_Alarm'].state === 'ON') ? '0.5' : '0'"
          position: absolute
          top: 7.7rem
        visible: =props.showAudioAlarms === true
12 Likes

Added new ability to click on a gear icon to open up all the camera controls so you have access to PTZ via ABSOLUTE controls on a slider and the ability to turn alarms on and off and far more.

1 Like

Added some changes to first post widget code that make the opened view look nicer and the controls also work now on expanded view.

1 Like

Improved the code in the first post again.

Controls now open up to a larger size so they are easier to press.
Works better across iPhone and android tablets.

Great widget, thanks.

On the light theme, the controls are not visible when opened, as they are placed on the white space above the image.

Changing the
color: white

for the controls to
color: var(--f7-card-header-text-color)
seem to fix this, or at least makes it better.

I’m sure there are other ways to do this, but I am just getting started with widgets…

1 Like

Hope this isnt a dumb question but the camera itself is this an assumption the IPcamera binding has been used to create the equipment?

Yes as how else can you move a camera if it is not with the ip camera binding? There is a similar widget that is for non movable cameras that are not using the binding as it takes URL for the config setup.

Feel free to use any of the widgets to create your own however your setup works.

Is there any way to pop-up this widget or the other one without PTZ, by switching virtual item?

1 Like

Cool widget. I added invertPan, because tilt and pan are both reversed for my camera.

Strangely, GotoPreset does not work. It does not find the item (404 in the browser console), but the item is named correctly “CameraName_GotoPreset” and is working in the settings (gear icon) popup.

Edit:
When I use this widget, then a cookie with name “” and value “secure” is being set.
Unfortunately, if any cookie with an empty name is present, the OH 3 authentication does not work anymore. I am being logged out and cannot log in anymore, until I delete this cookie.
This cookie is probably being set by the MJPEG stream URL of my Amcrest camera?

This widget and the binding both do not create cookies, if the openHAB framework creates them I do not know and you are better off asking for help in your own thread with a subject that is more broad so more people see it.

Very strange, the widget only sends commands to the event bus as does the mainUI controls that the gear icon opens, the binding then reads and acts on these commands from the event bus. This rules out the binding as the cause of this problem. I just looked up what my item is called and it is CameraName_GoToPreset note the capital T in To. Did you create the item using textual config? If so this is a big reason to change to using the UI to create them for you.
FYI the default naming is grabbed from the Label and not the channel ID.

Thanks, did not spot the capital “T”.

When I delete and recreate the point with the default name, I get “CameraName_gotoPreset”. I think I created the thing with OH 2.5, probably the default name of the item has changed in OH 3.

Anyway, now that I know the issue, I can name it correctly and it works in the widget.

Hi that looks very nice in your screenshot.
I’m trying same, but it’s not displaying, just a blank widget - any idea how I can check what is the issue?


In the overview dashboard

config:
  label: Overview
blocks:
  - component: oh-block
    config: {}
    slots:
      default:
        - component: oh-grid-cells
          config: {}
          slots:
            default:
              - component: widget:ClickablePTZCamera
                config:
                  camera: IPCamera01
masonry:
  - component: oh-masonry
    slots:
      default: []

My Camera is online and the MPEGURL is working when accessed individually.

Hello, @matt1 please are you able to post your thing/item code, followed the steps but not able to reach the widget showing something. Thanks

@Dharvin_D - In my setup, while the FOSCAM camera is accessible over HTTP/HTTPS, for some reason the OH Widget only works when you load the OH Main UI over HTTP. By default, that would be:

http://<server-ip>:8080/

Hope that helps!
.

1 Like

@matt1 thanks → that was the issue, switching to http is displaying the camera feed!
Now am up to work on getting the PTZ working

I would like to get this to actual discussion again. I used the camera wifget that did this in OH2.5. Now i cant find anything like that for OH3 with the camera stream popup when a trigger is switched.

Thnx

Hey guys.
Is there any way to use the snapshot URL (snapshots.mjpeg) as video source?
Currently only ipcamera.mjpeg is being used, which for my Amcrest is the sub-stream and only available in ugly blocky 640x480.
What I tried so far:

  • Putting the snapshots URL in “MJPEG URL” → resulting in no image at all
  • Putting the snapshots URL in “Snapshot URL” → resulting in “Connection Timeout: Check your IP and PORT are correct and the camera can be reached”

Any help appreciated.

Yes just change the first url that is marked to ‘card-opened-fade-out’.

I used to have it that way and only opened the full MJPE stream when clicked, but found it annoying to move the camera when it has a 1 second delay on the picture as my cameras all create mjpeg streams onboard. I guess you could hide the controls until the camera is clicked and it opens up. Have a play and see what you can come up with.

Open your own thread if you need help and give photos of what your trying to do.

1 Like

When I try to install this or Clickable camera widget I keep getting this error:

16:21:29.392 [ERROR] [mmunity.CommunityUIWidgetAddonHandler] - Unable to parse YAML: Unrecognized field “default” (class org.openhab.core.config.core.dto.ConfigDescriptionParameterDTO), not marked as ignorable (22 known properties: “readOnly”, “max”, “limitToOptions”, “groupName”, “name”, “stepsize”, “context”, “defaultValue”, “min”, “label”, “filterCriteria”, “verify”, “type”, “description”, “options”, “required”, “advanced”, “unit”, “multipleLimit”, “pattern”, “multiple”, “unitLabel”])
at [Source: (StringReader); line: 10, column: 23] (through reference chain: org.openhab.core.ui.components.RootUIComponent[“props”]->org.openhab.core.config.core.dto.ConfigDescriptionDTO[“parameters”]->java.util.ArrayList[1]->org.openhab.core.config.core.dto.ConfigDescriptionParameterDTO[“default”])
16:21:29.394 [ERROR] [mmunity.CommunityUIWidgetAddonHandler] - Widget from marketplace is invalid: Unable to parse YAML

Installing “Custom rollershutter widget with preset configuration” went without error. Tried with 3.2 and 3.2 snapshot. Any idea?