A read-only Home Assistant integration for NX Witness and compatible VMS systems like Nx Meta. It adds your cameras as Home Assistant entities, motion binary sensors, a recorded clip browser, and an optional Lovelace card with a timeline you can drag.
Read-only means the integration only ever reads from the server. It never changes cameras, recordings, or server settings. The one exception is session-token auth, which has to create a login session on the server to get a token. Basic auth avoids even that.
- A camera entity for each NX Witness camera
- A motion binary sensor per camera, with a look-back window you can set
- A recorded clip browser in the Home Assistant media browser
- An optional Lovelace card with live view, a draggable timeline, and archive seek
- Video wall support: one card can render a whole NX Witness video wall
- Read-only: the integration does not modify anything on the NX Witness server
- Home Assistant 2026.3 or newer
- An NX Witness server that Home Assistant can reach
- An NX Witness account (a read-only account is recommended)
- Click the button above, or open HACS and go to Integrations.
- Open the menu in the top right and select Custom repositories.
- Add
https://github.com/lolstring/ha-nxwitnessas an Integration. - Search for NX Witness and install it.
- Restart Home Assistant.
- Go to Settings > Devices & Services > Add Integration and search for NX Witness.
- Copy
custom_components/nxwitnessinto your Home Assistant/config/custom_components/folder. - Restart Home Assistant.
- Go to Settings > Devices & Services > Add Integration and search for NX Witness.
Everything is set up through the UI. When you add the integration you are asked for:
| Field | Description |
|---|---|
| Host | IP or hostname of your NX Witness server |
| Port | Default is 7001 |
| Use HTTPS | Turn on for secure connections |
| Verify SSL | Turn this off for self-signed certificates |
| Authentication mode | Basic (read-only, preferred) or Session token |
| Username / Password | Your NX Witness credentials |
| Recent motion window | How many seconds back to check for recent motion |
NX 5 / NX 6 note: newer NX Witness servers turn off Basic auth by default and require HTTPS. On a stock NX 6 server, use Session token auth and turn on Use HTTPS. Basic auth only works if you have turned it back on in the server settings.
After setup, click Configure on the integration card to change:
| Option | Description |
|---|---|
| Enable camera entities | Create a camera entity per camera |
| Enable motion sensors | Create a motion binary sensor per camera |
| Enable clip browser | Show recorded clips in the media browser |
| Enabled cameras | Pick which cameras to expose |
| Motion window | Seconds to look back when checking for motion (5 to 3600) |
| Motion detection source | Motion for server-side detection, Recording for in-camera or analytics detection, or Analytics |
| Scan interval | How often to refresh camera and layout data, in seconds (5 to 300) |
| Default stream | Primary, Secondary, or Auto (the Auto value is stored as undefined, not auto) |
| Live stream format | MP4, WebM, MPEG-TS, or MJPEG (the MPEG-TS value is mpegts) |
| Live stream resolution | For example 1080p or 720p |
| Clip format | Format used for media browser clips |
| Clip FPS | Frame rate for media browser clips (1 to 60) |
| Clip look-back days | How many days of clips to list in the media browser (1 to 90) |
The repository includes an optional Lovelace card at www/nxwitness-camera-card/nxwitness-camera-card.js. It gives you a full-width view with hover controls, a draggable timeline, archive seek, and video wall support.
- Copy
www/nxwitness-camera-card/nxwitness-camera-card.jsto/config/www/nxwitness-camera-card/on your Home Assistant instance. - Go to Settings > Dashboards > Resources and add:
- URL:
/local/nxwitness-camera-card/nxwitness-camera-card.js - Type: JavaScript module
- URL:
- Reload your browser.
type: custom:nxwitness-camera-card
entity_id: camera.front_doorThe card reads the camera entity's attributes to find the camera ID and config entry, so that is all you need.
type: custom:nxwitness-camera-card
entity_id: camera.front_door
title: Front Door
stream: primary # primary or secondary
format: mp4 # mp4 or webm
resolution: 1080p
duration_ms: 300000 # archive clip length in ms (5 minutes by default)
show_name: true
show_timestamp: true
show_play: true
show_live: true
show_snapshot: true
show_mute: true
show_quality: true
show_fullscreen: trueThe card plays into a normal HTML video element, so use mp4 or webm. The mpegts and mjpeg formats are for the integration's stream options, not this card.
type: custom:nxwitness-camera-card
mode: video-wall
config_entry_id: YOUR_CONFIG_ENTRY_ID
video_wall_id: YOUR_VIDEO_WALL_ID
title: All CamerasIn video wall mode there is no camera entity to read, so you must pass config_entry_id as well as video_wall_id. The easiest way to get both is the visual card editor, which has dropdowns for the integration and the video wall. To set them by hand, get video_wall_id from the nxwitness.get_video_walls service (see Services below), and get config_entry_id from Settings > Devices & Services, by opening the NX Witness entry and copying the ID from the URL.
List every motion sensor in one trigger. trigger.to_state always points at the sensor that fired, so a single automation covers all cameras. The snapshot_url attribute is a signed URL to a still from the NX Witness server, taken at the moment the motion started. It is not a live frame grabbed when the notification is sent.
alias: NX Witness motion alert
trigger:
- platform: state
entity_id:
- binary_sensor.front_door_motion
- binary_sensor.garage_motion
- binary_sensor.driveway_motion
to: "on"
action:
- action: notify.mobile_app_your_phone
data:
title: Motion detected
message: "Motion on {{ trigger.to_state.name }}"
data:
image: "{{ trigger.to_state.attributes.snapshot_url }}"
tag: "{{ trigger.to_state.entity_id }}"
when: >
{{ (trigger.to_state.attributes.last_motion_start_ms | int / 1000) | int }}iOS live preview: add
entity_id: camera.front_door(matching the camera that fired) insidedata:to show a live camera feed in the notification.
The clips_media_source attribute holds the media browser path for that camera's recent clips. It is only present when the clip browser is enabled.
alias: NX Witness motion with clip link
trigger:
- platform: state
entity_id:
- binary_sensor.front_door_motion
- binary_sensor.garage_motion
to: "on"
action:
- action: notify.mobile_app_your_phone
data:
title: "Motion on {{ trigger.to_state.name }}"
message: Tap to review the recording.
data:
image: "{{ trigger.to_state.attributes.snapshot_url }}"
url: >
/media-browser/{{ trigger.to_state.attributes.clips_media_source }}alias: NX Witness motion light
trigger:
- platform: state
entity_id: binary_sensor.driveway_motion
to: "on"
action:
- action: light.turn_on
target:
entity_id: light.driveway
- wait_for_trigger:
- platform: state
entity_id: binary_sensor.driveway_motion
to: "off"
- action: light.turn_off
target:
entity_id: light.drivewayalias: NX Witness motion log
trigger:
- platform: state
entity_id:
- binary_sensor.front_door_motion
- binary_sensor.garage_motion
to: "on"
action:
- action: rest_command.log_motion
data:
camera: "{{ trigger.to_state.name }}"
timestamp: "{{ now().isoformat() }}"All services are read-only and return a response. They do not change anything on the NX Witness server. If you have more than one NX Witness entry configured, pass config_entry_id to pick a specific server.
| Service | Description |
|---|---|
nxwitness.get_stored_files |
List stored files on the server |
nxwitness.get_layouts |
Return layout metadata |
nxwitness.get_video_walls |
Return video wall metadata |
nxwitness.get_video_wall_render_plan |
Expand a video wall into its tiles (used by the card) |
action: nxwitness.get_layouts
data:
include_layout_items: true
response_variable: nx_layoutsaction: nxwitness.get_video_walls
response_variable: nx_wallsThe response includes an id field for each video wall. Use that as video_wall_id in your card config.
action: nxwitness.get_video_wall_render_plan
data:
video_wall_id: "your-video-wall-uuid"
response_variable: nx_planaction: nxwitness.get_stored_files
data:
path: ""
response_variable: nx_files- Go to Settings > Devices & Services.
- Find the NX Witness entry.
- Open the menu and choose Delete.
If you installed through HACS, you can also remove it from HACS and restart. For a manual install, delete custom_components/nxwitness from your /config directory and restart.
The integration does not store any data outside the config entry, so deleting the entry removes everything.
Open the repo in the dev container (.devcontainer/devcontainer.json), then use the one-command scripts:
| Command | What it does |
|---|---|
scripts/setup |
Install dev dependencies |
scripts/develop |
Run Home Assistant from source with this integration loaded, debug logging, and a debugpy listener on :5678. It generates a ./config folder, which is git-ignored. |
scripts/lint |
black then ruff --fix |
scripts/test |
Run the unit suite with coverage (extra args are passed through) |
Fast loop: run scripts/develop, open http://localhost:8123, and add the NX Witness integration. Edit code, press Ctrl-C, and re-run. Or attach the VS Code "Home Assistant (develop)" debug config for breakpoints. Test coverage must stay at or above 95%, which scripts/test enforces.
Prod-like loop (docker image): the dev container also defines a homeassistant service that uses the official Home Assistant image with the component bind-mounted read-only. Copy the sample config first:
cp -r local-testing/config.sample local-testing/config # bash
Copy-Item -Recurse local-testing/config.sample local-testing/config # PowerShell
docker compose up -d homeassistant
Because the mount is read-only, apply Python changes with the "Restart Home Assistant" task (or docker compose restart homeassistant). To debug, attach the "Python: Attach to Home Assistant (docker)" config. The sample config turns on debugpy.
A gated end-to-end stack runs the integration against a real containerised NX Witness (Meta VMS) server, with the bundled Testcamera running inside it. It does not run in the default pytest suite (the e2e marker is filtered out in pyproject.toml) and is started on its own:
pwsh scripts/e2e.ps1 # Windows
bash scripts/e2e.sh # Linux and macOS
See local-testing/e2e/README.md for the one-time setup. The prebuilt image is pulled for you. You only supply a sample clip and, optionally, a trial license that enables the motion and clip tests.