Shinobi

Setting up Home Assistant with Shinobi

The Shinobi NVR custom component for Home Assistant creates a camera, an image, a status sensor, an object-count sensor, enable/recording switches, and (where PTZ Control is enabled) a set of PTZ buttons for every monitor. It also browses recordings through Home Assistant's Media Browser and fires real-time shinobi_detector_trigger events over Shinobi's own socket.io server.

  • Home Assistant 2024.1.0 or newer.
  • A reachable Shinobi server and an API Key — see "Creating an API Key" below for exactly which permissions it needs.
  • The Group Key (ke) the API Key belongs to.

Copy the custom_components/shinobi folder into Home Assistant's config/custom_components/ directory (or add it as a custom repository in HACS), then restart Home Assistant.

Create a dedicated API Key for Home Assistant rather than reusing an existing one, so its permissions can be scoped to exactly what the integration uses. In Shinobi: Account → API. Every permission below was confirmed against the actual route handlers in Shinobi's source, not assumed from the permission's name:

  • Get Monitors — list monitors. Required for the integration to load at all, and for it to discover new/removed cameras.
  • Control Monitors — the Enabled/Recording switches, set_mode service, PTZ buttons and ptz service, and the trigger_motion service.
  • Watch Stream — the camera entity's live view, both Home Assistant's built-in stream dialog and the embed_url attribute.
  • Watch Snapshot — the camera entity's still picture.
  • Watch Videos — despite the name, this one permission also gates detection events (the Motion binary sensor and the Object Count sensor both read from Shinobi's events endpoint), the Media Browser's recordings list, and the Latest Frame image entity (both read from Shinobi's timelapse-frame index).
  • Auth via Socketoptional, only for real-time push notifications. Without it Shinobi rejects the socket.io connection outright and the integration silently falls back to polling only (a warning is logged, nothing breaks).
  • Create API Keysoptional, only for real-time push notifications, and easy to miss. Shinobi's "get one API key's own details" endpoint is gated behind this permission even for a read-only lookup of your own key. The integration calls it once at startup to resolve the key's own uid, which Shinobi's socket.io handshake requires alongside the key itself.

Not needed for anything the integration does: Delete Videos, Edit Monitors, Get Alarms, Edit Alarms, Get Logs.

  • In Home Assistant: Settings → Devices & Services → Add Integration → Shinobi NVR.
  • Enter the Host, Port (default 8080), API Key, and Group Key.
  • Every monitor Shinobi returns for that Group Key is created as a device automatically — no per-camera setup step.

Each monitor becomes a standard Home Assistant camera entity: live view through HA's built-in stream dialog, a JPEG snapshot as the entity picture, and casting support. On modest hardware this built-in dialog can look jittery — it re-muxes Shinobi's HLS output through Home Assistant's own stream component, an extra hop Shinobi's own player doesn't need. For a smoother live view, use the embed method described below instead of the camera entity's default dialog.

Every camera.* entity exposes an embed_url attribute (Developer Tools → States) — Shinobi's own embeddable player page. It runs hls.js/flv.js directly in the browser, so there is no re-muxing on the Home Assistant side at all.

http://[SHINOBI HOST]:[PORT]/[API KEY]/embed/[GROUP KEY]/[MONITOR ID]/fullscreen|jquery|gui
  • jqueryrequired. The embed player script references jQuery at load time and never starts without it; Shinobi only loads jQuery when this flag is present.
  • fullscreen — adds a fullscreen toggle button, useful inside an iframe.
  • gui — adds the stream-chrome CSS (timestamp overlay, unmute button).

If the embed page loads but the video never starts, and the server is reached through a reverse proxy or a different hostname than Shinobi auto-detects, add relative to the addon list and a ?host= query parameter pointing at the address the browser should use to reach Shinobi:

http://[SHINOBI HOST]:[PORT]/[API KEY]/embed/[GROUP KEY]/[MONITOR ID]/fullscreen|jquery|gui|relative?host=http://[SHINOBI HOST]:[PORT]/

Shinobi has no push notification for "a monitor was added" or "a monitor was deleted", so this is handled by polling — the following happens automatically, without restarting Home Assistant or reloading the integration:

  • New monitor added in Shinobi — gets its full set of entities (camera, image, sensors, switches, and PTZ buttons if applicable) within one poll cycle (default 10 seconds).
  • Monitor deleted in Shinobi — its device and every entity belonging to it are removed from Home Assistant within one poll cycle. They are not merely marked unavailable; the device disappears from Settings → Devices.

One case is not automatic: changing a setting on a monitor that Home Assistant already knows about — most notably, turning PTZ Control on or off for an existing monitor. Since Home Assistant only decides whether to create PTZ buttons the first time it sees a given monitor id, toggling Control later does not retroactively add or remove them. To pick up that kind of in-place change, reload the integration:

  • Settings → Devices & Services → Shinobi NVR → ⋮ → Reload.

A clean per-camera card — embedded live stream on top, a row of tiles for status and controls below — is a vertical-stack with a Webpage (iframe) card followed by a Grid of Tile cards. Use the camera's own embed_url attribute for the iframe, and swap your_camera_name below for the actual entity id slug shown in Developer Tools → States.

type: vertical-stack
cards:
  - type: iframe
    url: >-
      http://[SHINOBI HOST]:[PORT]/[API KEY]/embed/[GROUP KEY]/[MONITOR ID]/fullscreen|jquery|gui
    aspect_ratio: 56%
  - type: grid
    columns: 2
    square: false
    cards:
      - type: tile
        entity: switch.your_camera_name_recording
      - type: tile
        entity: switch.your_camera_name_enabled
      - type: tile
        entity: sensor.your_camera_name_status
      - type: tile
        entity: binary_sensor.your_camera_name_motion

For several cameras, adding one card per camera by hand gets old fast. On an editable dashboard, use the ⋮ menu → Edit in YAML (view-level, not per-card) and paste one vertical-stack block like the one above per camera.

Home Assistant fires a shinobi_detector_trigger event on the bus for every motion/object detection, in real time rather than on the polling interval, using Shinobi's own socket.io detector_on mechanism — no MQTT broker or topic configuration needed. Event data includes monitor_id, time, reason, confidence, matrices, and a best-effort snapshot_url (resolved from Shinobi's timelapse-frame index around the moment of detection, populated only for genuine object detections).

trigger:
  - platform: event
    event_type: shinobi_detector_trigger
    event_data:
      reason: object
condition:
  - condition: template
    value_template: "{{ 'person' in trigger.event.data.matrices | map(attribute='tag') | list }}"
action:
  - service: notify.mobile_app_your_phone
    data:
      message: "Person detected on {{ trigger.event.data.monitor_id }}"
      data:
        image: "{{ trigger.event.data.snapshot_url }}"
  • Embed iframe loads but stays blank — the jquery addon flag is missing from the URL. embed_url already includes it; this only matters if the URL was built by hand.
  • A recently added camera has no entities yet — wait one poll cycle (default 10 seconds), or lower the polling interval in the integration's Options.
  • A camera's PTZ buttons didn't appear after enabling Control in Shinobi — reload the integration (see "Keeping the Camera List in Sync" above).
  • shinobi_detector_trigger never fires — confirm the API Key has Auth via Socket enabled in Shinobi (Account → API); Home Assistant logs a warning at startup if it cannot resolve a uid for the key.
  • snapshot_url is often null — expected on a monitor with a sparse timelapse-capture interval; Home Assistant only searches a window around the detection's own timestamp, it does not take a fresh snapshot. Configure a shorter timelapse interval on monitors used for notification images.

ShinobiDocs

All content is property of their respective owners.