Pilot your Ezviz cameras (and light bulbs) with this module. It is used by:
- The official Ezviz integration in Home Assistant
- The EZVIZ (Beta) custom integration for Home Assistant
You can also use it directly from the command line for quick checks and scripting.
- Inspect device and connection status in table or JSON form
- Control cameras: PTZ, privacy/sleep/audio/IR/state LEDs, alarm settings
- Control light bulbs: toggle, status, brightness and color temperature
- Dump raw pagelist and device infos JSON for exploration/debugging
- Reuse a saved session token (no credentials needed after first login)
From PyPI:
pip install pyezvizapiAfter installation, a pyezvizapi command is available on your PATH.
If you are running from a clone of this repository or using the helper scripts directly, ensure these packages are available:
pip install requests paho-mqtt pycryptodome# See available commands and options
pyezvizapi --help
# First-time login and save token for reuse
pyezvizapi -u YOUR_EZVIZ_USERNAME -p YOUR_EZVIZ_PASSWORD --save-token devices status
# Subsequent runs can reuse the saved token (no credentials needed)
pyezvizapi devices status --json- Username/password:
-u/--usernameand-p/--password - Token file:
--token-file(defaults toezviz_token.jsonin the current directory) - Save token:
--save-tokenwrites the current token after login - Saved token precedence: if
--token-filecontains a session token, the CLI reuses it even when username/password are also supplied. This preserves MFA/elevated session state; a fresh credential login may require MFA again. - MFA: The CLI prompts for a code if required by your account
- Region:
-r/--regionoverrides the default region (apiieu.ezvizlife.com)
Examples:
# First-time login and save token locally
pyezvizapi -u YOUR_EZVIZ_USERNAME -p YOUR_EZVIZ_PASSWORD --save-token devices status
# Reuse saved token (no credentials)
pyezvizapi devices status --json- Default: human-readable tables (for list/status views)
- JSON: add
--jsonfor easy parsing and editor-friendly exploration
All commands are subcommands of the module runner:
pyezvizapi <command> [options]Person-friendly media save commands for scripts and Home Assistant
shell_command usage. These write to local filesystem paths and print a small
JSON result when --json is used.
# Save a short direct-local live clip. MPEG-TS uses FFmpeg remuxing with copy.
pyezvizapi --token-file ezviz_token.json --json save clip \
--serial ABC123 --channel 1 --duration 10s \
--output /config/www/ezviz/front.ts
# Save a direct-local encrypted battery-camera clip.
pyezvizapi --token-file ezviz_token.json --json save clip \
--serial ABC123 --duration 10s --decrypt-video \
--output /config/www/ezviz/front.ts
# Save from the full local HCNetSDK command-port media path on port 8000.
pyezvizapi --token-file ezviz_token.json --json save clip \
--source hcnetsdk-command-port --serial ABC123 --host 192.0.2.10 \
--hcnetsdk-command-frames-file /config/ezviz/ABC123-port8000-frames.json \
--duration 10s --output /config/www/ezviz/front.ts
# Trigger a snapshot and save the returned image locally.
pyezvizapi --token-file ezviz_token.json --json save image \
--serial ABC123 --channel 1 \
--output /config/www/ezviz/front.jpg
# Save a known alarm/snapshot image URL without triggering a new capture.
pyezvizapi --token-file ezviz_token.json --json save image \
--serial ABC123 --image-url "https://..." \
--output /config/www/ezviz/alarm.jpgsave clip uses the direct-local 9010/9020 SDK path and fetches the LAN
endpoint/CAS tuple from the authenticated client by default. Use
--source hcnetsdk-command-port for the full local port-8000 media path when
you have the complete HCNetSDK bootstrap command frames from a trusted local
implementation; the release package consumes those frames and keeps APK/Frida
tooling under tools/apk-re. save image triggers the camera capture endpoint
unless --image-url is supplied, then downloads and decrypts EZVIZ encrypted
image payloads when needed.
Integrations can use the same behavior directly from client.py without
shelling out:
result = client.save_clip(
"ABC123",
"/config/www/ezviz/front.ts",
duration_seconds=10,
source="local-sdk",
)
image = client.save_image(
"ABC123",
"/config/www/ezviz/front.jpg",
channel=1,
)Use source="hcnetsdk-command-port", host="192.0.2.10", command_port=8000,
and hcnetsdk_command_frames=(...) when an integration already has the complete
full-local HCNetSDK port-8000 command bootstrap frames. For native-style flows
that use several short command sockets before the media socket, pass
hcnetsdk_command_plan=HcNetSdkCommandPortMultiSocketPlan(...) or use the CLI
--hcnetsdk-command-plan-file JSON form with socket steps/sockets. If the
frames were captured on another device, pass hcnetsdk_local_ip or
--hcnetsdk-local-ip so the encoded client LAN IP word is patched for the
current host. Both helpers accept a local path or a binary file object and
return a small result dict with the output name, byte count, content type, and
source metadata.
Lower-level command-port callers can use HcNetSdkCommandPortClient.login() to
generate the reboot-safe port-8000 RSA/challenge login sequence directly. The
returned HcNetSdkCommandPortLoginSession exposes the 4-byte session id, device
serial, decrypted challenge, password seed, and follow-up command auth seed from
the second login response header. Use hcnetsdk_command_port_auth_word() with
that session id, auth seed, decrypted challenge, command id, and the native
addend/counter value to generate post-login command header auth words, or
hcnetsdk_command_port_control_frame() to build a complete 0x63 post-login
control frame when the command-specific body tail is known. The helper keeps
the login session id in the same network order in both the 0x63 control body
and auth-word generation. Use
hcnetsdk_command_port_control_template_from_frame() to strip a captured or
generated 0x63 frame down to the reusable command id/body tail before
rebuilding it with fresh session values. A set of those templates can be grouped
as HcNetSdkCommandPortGeneratedSocketStep /
HcNetSdkCommandPortGeneratedMultiSocketPlan and rendered into a concrete
HcNetSdkCommandPortMultiSocketPlan after a fresh login. If you already have a
concrete plan, hcnetsdk_command_port_generated_plan_from_socket_plan() can
extract a generated template plan from it and infer session-relative addend
deltas when the original auth seed and challenge key are supplied. The offline
CLI helper pyezvizapi stream hcnetsdk-command-plan-generate --input plan.json
performs the same conversion for JSON plan files and can write directly to a
generated-plan file for later live runs. The clip
helpers can consume a generated plan directly with
hcnetsdk_command_generated_plan plus hcnetsdk_command_password, or the CLI
equivalents --hcnetsdk-command-generated-plan-file and
--hcnetsdk-command-password; this runs a fresh command-port login and renders
the session-relative templates before opening the native-style media sockets.
For the app-observed LAN preview sequence, callers can skip the JSON plan file
and use hcnetsdk_command_port_native_lan_live_view_plan() directly, or pass
--hcnetsdk-command-native-plan app-lan-live-view in the CLI.
Generated-plan JSON templates may set
"body_tail_transform": "play_login_today" on the native 0x111040
play-login template to refresh the captured date words to the current local
date before rendering the frame.
For the native-style EZVIZ HCNetSDK media step (0x30000), plan JSON defaults
to not reading a control response from the media socket. Native leaves the
64-byte IMKH reply on the media socket and treats it as prefix before the
first $ media frame; consuming it as a control response can produce a stream
that reaches media but fails to settle like the app. Set "read_responses": true/"response_reads": 1 only when deliberately running a diagnostic variant.
For generated plans captured from the native LAN preview path, the practical
CLI shape is:
pyezvizapi --json save clip \
--source hcnetsdk-command-port \
--serial ABC123 \
--host 192.0.2.10 \
--output front.ts \
--duration 20 \
--hcnetsdk-command-native-plan app-lan-live-view \
--hcnetsdk-command-password "$EZVIZ_LAN_PASSWORD" \
--hcnetsdk-local-ip 192.0.2.20 \
--hcnetsdk-h264-skip-initial-idr-windows 1The built-in app-lan-live-view plan currently supports --channel 1 only
because the app-observed command tails include channel-1 fields. It keeps the
media step on the native-prefix behavior above, refreshes native 0x111040
play-login date words at render time, sends bounded 0x30006 media keepalives,
and issues the app-observed 0x90100 I-frame request after the media socket is
open.
Use --hcnetsdk-command-metadata-output during command-port saves to write a
sanitized JSON summary of command ids, response lengths/header fields, and
first-media shape for diagnostics without storing frame bodies or credentials.
For command frames, it also reports bounded nonzero 32-bit samples from the
command-specific body tail while skipping the session-bound client IP/session
prefix. When metadata output is enabled, the save path also records packet
counts plus elapsed timing, lengths, and SHA-256 hashes for a bounded sample of
media packets, along with a bounded IDMX/H.264 frame-shape summary that reports
counts, NAL/FU-A types, RTP-like sequence/timestamp continuity, and hashes
without storing media bytes. Media-socket keepalive send attempts are recorded
as bounded command id/timing/success metadata when a plan includes keepalives.
For offline Frida artifacts, the hcnetsdk-command-dump-summary stream helper
can summarize dumped
ezviz-hcnetsdk-command-frame-*.bin files and raw
ezviz-hcnetsdk-inbound-media-*.bin media dumps. The output includes command
counts, bounded command-body samples, scanned command-port media-frame counts,
and the same bounded IDMX/H.264 frame-shape metadata used by live command-port
capture diagnostics.
When collecting a fresh Frida command-shape trace, set
EZVIZ_FRIDA_WITH_STREAM_TRANSFORM=1 on
tools/apk-re/frida/run-ezviz-hcnetsdk-command-shape-hook to load the
stream-transform hook in the same Frida session. This captures command frames,
raw command-port inbound media, and PlayM4 input dumps from the same native run
so their sanitized summaries can be compared directly. The same runner accepts
EZVIZ_HCNETSDK_TARGET_SERIAL, EZVIZ_HCNETSDK_TARGET_IP,
EZVIZ_HCNETSDK_TARGET_PORT, EZVIZ_HCNETSDK_TARGET_PASSWORD,
EZVIZ_HCNETSDK_DUMP_COMMAND_FRAMES,
EZVIZ_HCNETSDK_COMMAND_DUMP_DIR,
EZVIZ_HCNETSDK_DUMP_INBOUND_MEDIA_CHUNKS,
EZVIZ_HCNETSDK_INBOUND_MEDIA_DUMP_DIR, and
EZVIZ_HCNETSDK_INBOUND_MEDIA_DUMP_MAX_BYTES to inject the target and dump
settings before the hook loads. EZVIZ_HCNETSDK_TARGET_PASSWORD is only used
inside the Frida-triggered app process when the app has no saved LAN password
for the target serial; the hook logs only the password length. For
LAN-device-list diagnostics,
EZVIZ_HCNETSDK_FORCE_PREVIEW_AFTER_LOGIN=1 can additionally route a successful
LAN login into the native preview activity so command-port media and PlayM4
input hooks fire without manual tablet interaction.
If a command-port camera emits corrupt startup IDR refreshes before stabilizing,
--hcnetsdk-h264-skip-initial-idr-windows N can drop the first N
IDR-started H.264 windows before remuxing clear IDMX media. Native HCNetSDK
startup traces can include non-video IDMX prelude records on RTP payload types
104/112 before clear H.264 starts on payload type 96, followed by a large
explicit keyframe request. When FFmpeg reports warnings only in that first real
IDR window, --hcnetsdk-h264-skip-initial-idr-windows 1 mirrors the practical
native-player tolerance and yields a warning-free remux once a later IDR is
available. For a more robust startup workaround,
--hcnetsdk-h264-trim-to-clean-idr-window samples IDR
windows with FFmpeg and remuxes from the first one that decodes without H.264
errors. Because the output begins at the selected clean window, the resulting
clip can be shorter than the requested capture duration. Add
--hcnetsdk-h264-clean-idr-preroll-seconds N with the trim option to overcapture
for up to N extra seconds before trimming; this gives generated command-port
sessions time to stabilize while preserving more of the requested clean clip.
Use --hcnetsdk-h264-clean-idr-max-windows N when long unstable starts need
more than the default 32 IDR windows checked.
If you need to preserve the requested clip duration instead of trimming after
capture, use --hcnetsdk-h264-wait-for-clean-idr-window; this discards startup
media until a decodable IDR window is found, then starts the requested duration
window. Bound that pre-capture wait with
--hcnetsdk-h264-clean-idr-wait-seconds N. Some cameras expose very sparse or
persistently corrupt IDR refreshes on generated command-port sessions even
after the media socket remains stable; in that case use the native-prefix media
plan above for a playable remux and keep clean-IDR wait as a diagnostic.
Experimental plan JSON can set
read_first_media_immediately on the media socket step to drain one media
packet before later short command sockets, which is useful when comparing native
startup timing. Plan steps can also set delay_after_commands_seconds to keep a
socket open briefly after its command/response exchange before the next step is
run; this is intended for matching native command-port startup pacing during
diagnostics. Media steps with keepalive templates can set
keepalive_initial_delay_seconds to override the default first-keepalive delay;
use 0.0 to send the first keepalive immediately, matching native HCNetSDK
startup traces.
For native player dumps that are already H.264 Annex-B bytes,
summarize_h264_annexb_units() reports the same kind of bounded NAL-unit
length/type/hash metadata without printing media contents.
summarize_h264_annexb_idr_windows() additionally reports IDR-started GOP
window offsets, lengths, and hashes. The CLI can run the same offline diagnostic
without cloud credentials:
pyezvizapi stream h264-annexb-summary \
--input native-addtovideodata.h264 \
--decode-idr-windowsThe optional decode check feeds each sampled IDR-started window to FFmpeg and records only the return code and bounded stderr lines. The same bounded decode check is available for native command-port dump folders:
pyezvizapi stream hcnetsdk-command-dump-summary \
--command-frame-dir native-dumps/command \
--inbound-media-file native-dumps/command/ezviz-hcnetsdk-inbound-media-fd116.bin \
--playm4-input-dir native-dumps/playm4 \
--decode-idr-windowsCommand payload templates still vary by native call, so callers remain responsible for supplying the correct generated template plan.
- Actions:
device,status,switch,connection - Examples:
# Table view
pyezvizapi devices status
# JSON view
pyezvizapi devices status --jsonSample table columns include:
name | status | device_category | device_sub_category | sleep | privacy | audio | ir_led | state_led | local_ip | local_rtsp_port | battery_level | alarm_schedules_enabled | alarm_notify | Motion_Trigger
The CLI also computes a switch_flags map for each device (all switch states by name, e.g. privacy, sleep, sound, infrared_light, light, etc.).
Experimental VTM cloud stream helpers. Packet tracing prints sanitized metadata only. Dumping writes VLC-friendly MPEG-TS by default using FFmpeg -c copy remuxing only, with --format raw available for unchanged VTM payloads. Encrypted packets fail unless --allow-encrypted is set. Some battery cameras keep the VTM channel unencrypted but encrypt the video NAL payloads inside MPEG-PS; use --decrypt-video to decrypt those HEVC/H.264 NAL payloads with the camera encrypt key before writing or remuxing.
# Inspect stream packet metadata without printing media bytes
pyezvizapi stream trace --serial ABC123 --channel 1 --max-packets 20 --json-lines
# Dump a VLC-playable MPEG-TS capture to a file
pyezvizapi stream dump --serial ABC123 --channel 1 --duration 1m --output stream.ts
# Decrypt encrypted battery-camera HEVC video while dumping
pyezvizapi stream dump --serial ABC123 --channel 1 --duration 30s --decrypt-video --output stream.ts
# Pipe raw MPEG-PS payloads directly into FFmpeg and remux to MPEG-TS
pyezvizapi stream dump --serial ABC123 --channel 1 --format raw | \
ffmpeg -f mpeg -i pipe:0 -c copy -f mpegts stream.ts
# Serve a local MPEG-TS URL for Home Assistant/FFmpeg clients
pyezvizapi stream proxy --serial ABC123 --channel 1 --listen-port 8558
# Serve a decrypted local MPEG-TS URL for encrypted battery cameras
pyezvizapi stream proxy --serial ABC123 --channel 1 --decrypt-video --listen-port 8558The dump command captures one minute by default. Use --duration 30s, --duration 2min, or --duration 0 for unlimited capture; --max-packets can still be used as an additional stop limit. MPEG-TS output requires FFmpeg and remuxes the camera payload with codec copy only; it does not transcode video or audio. The proxy serves http://<host>:8558/<serial>.ts by default. Each HTTP client opens a fresh VTM stream and remuxes it through FFmpeg. Keep the proxy bound to loopback unless you put it behind an authenticated reverse proxy or otherwise restrict access; the stream URL is not authenticated by pyezvizapi.
Direct-local SDK streaming is also available for devices that expose the local
9010/9020 SDK setup. Authenticated clients can fetch the LAN endpoint, CAS
local-control tuple, and camera media decrypt key explicitly:
pyezvizapi --token-file ezviz_token.json stream local-sdk-keys \
--serial ABC123456That command intentionally prints secrets for setup/debug use. For normal
streaming, prefer fetching CAS directly with --fetch-cas and let
--decrypt-video retrieve the media key from the authenticated client. You can
also save that JSON to a protected file and pass it back with
--credentials-file, supply values for one run, or use
EZVIZ_LOCAL_OPERATION_CODE and EZVIZ_LOCAL_CAS_KEY:
The Python implementation matches the normal app live-view wire shape: local
SDK frames are 32-byte header + AES-CBC/PKCS#5 body + 32-byte ASCII MD5 ciphertext trailer, then the 9020 stream port emits $ interleaved RTP carrying
MPEG-PS payloads.
pyezvizapi stream local-sdk-dump \
--credentials-file local-sdk-credentials.json \
--decrypt-video --decrypt-codec encrypted-header \
--format mpegts --output local.ts \
--metadata-output local.metadata.jsonpyezvizapi stream local-sdk-dump \
--host 192.0.2.10 --serial ABC123456 \
--operation-code "$EZVIZ_LOCAL_OPERATION_CODE" \
--cas-key "$EZVIZ_LOCAL_CAS_KEY" \
--media-key-hex "$EZVIZ_LOCAL_MEDIA_KEY_HEX" \
--decrypt-video --decrypt-codec hevc-encrypted-header \
--inner-address 192.0.2.20 --inner-port 9020 \
--format mpegts --output local.ts \
--metadata-output local.metadata.jsonIf your token contains CAS service metadata, --fetch-cas can fetch the
operation-code/key tuple through authenticated EZVIZ CAS instead of requiring
those two values on the command line. When --decrypt-video is used without
--media-key or --media-key-hex, the CLI fetches the camera media key with
the authenticated client:
pyezvizapi --token-file ezviz_token.json stream local-sdk-dump \
--host 192.0.2.10 --serial ABC123456 --fetch-cas \
--inner-address 192.0.2.20 --inner-port 9020 \
--format mpegts --output local.tsUse --cas-serial when the cloud CAS lookup needs a different serial form
than the local SDK IV/device serial used for the stream setup. The default
--receiver-shape app uses the self-closing attribute XML seen in normal EZVIZ
live-view traces; use --receiver-shape structured when you need the older
nested NatAddress/InnerAddress receiver XML shape.
The local SDK client binds the command socket source port to the advertised
receiver port before connecting to the camera command port, matching the
normal app's bind(0.0.0.0:<ReceiverInfo Port>) -> connect(<camera>:9010)
sequence.
For encrypted local media, --media-key accepts printable media keys and
--media-key-hex/EZVIZ_LOCAL_MEDIA_KEY_HEX accepts binary native media keys
encoded as hex. This media key is separate from the CAS key used for the
local-SDK control-channel envelope.
The --credentials-file form accepts the JSON shape printed by
local-sdk-keys: serial, endpoint.host, endpoint.command_port,
endpoint.stream_port, cas.operation_code, cas.key, cas.encrypt_type, and
optional media_key or media_key_hex. Explicit CLI arguments override matching
credential-file fields.
The optional metadata JSON contains only non-secret response commands, statuses,
body classifications, XML tag names, and first-media shape; it deliberately does
not include request bodies, keys, operation codes, UUIDs, timestamps, or payload
bytes.
Library callers that just want bytes can use
copy_local_sdk_stream_from_client(client, serial, output, ...). It fetches the
LAN endpoint and CAS tuple, opens the local 9010/9020 stream, optionally fetches
the media decrypt key when decrypt_video=True, and writes either mpegps or
mpegts output before closing the sockets. Lower-level callers can still use
open_local_sdk_stream_from_client() with
copy_local_stream_to_mpegps()/copy_local_stream_to_mpegts() for clear local
media, or copy_local_stream_to_decrypted_mpegps() /
copy_local_stream_to_decrypted_mpegts() for encrypted local media. Decrypting
captures must use a bounded duration_seconds or max_packets value; the decrypt
transform buffers MPEG-PS data so it can handle NALs split across local RTP
packets.
This is separate from the proprietary HCNetSDK command protocol on port 8000.
pyezvizapi includes native-Python framing/remux primitives for caller-supplied
command-port frames, including the port-8000 $ media length format and clear
H.264 IDMX payloads. The person-facing save clip --source hcnetsdk-command-port wrapper can consume those complete command frames and
write a local MPEG-TS clip. The generated command-port login helper covers the
initial RSA/challenge session setup and exposes the native command auth seed;
hcnetsdk_command_port_auth_word() covers the native post-login command-word
transform, and hcnetsdk_command_port_control_frame() wraps that transform into
a complete 0x63 control frame for callers that already know the command body
tail. hcnetsdk_command_port_control_template_from_frame() extracts reusable
control templates from captured/generated frames while dropping session-bound
client IP and session-id fields.
Fetch cloud clip descriptors used by the EZVIZ app download path. The returned metadata can include seqId, storageVersion, fileSize, crypt, keyChecksum, and the native SDK streamUrl host/port.
# List recent cloud clips
pyezvizapi cloud_videos --serial ABC123 --channel 1 --limit 10
# Hydrate details and emit JSON for further investigation
pyezvizapi --json cloud_videos --serial ABC123 --channel 1 --limit 5 --details
# Download a cloud clip. Direct HTTP(S) URLs are saved as-is; native streamUrl
# clips are fetched through the pure-Python cloud replay protocol and decrypted.
pyezvizapi cloud_video_download --serial ABC123 --channel 1 --seq-id 12345 \
--output clip.ps --encrypted-output clip.tmp
# Decrypt a previously captured native .tmp file locally in Python
pyezvizapi cloud_video_decrypt --serial ABC123 --input clip.tmp --output clip.psSome cloud clips only expose the EZVIZ native SDK streamUrl host/port in videoDetails. For regular cloud-storage clips, cloud_video_download now fetches /v3/cameras/ticketInfo, downloads the encrypted cloud replay .tmp stream over TLS, and applies the local Python PS/NAL decrypt transform. --encrypted-output keeps the encrypted .tmp for comparison.
cloud_video_decrypt is the pure-Python transform step for captured cloud .tmp files. Prefer --serial so pyezvizapi fetches the camera encrypt key without putting it in shell history; --key is available for offline/manual experiments. By default, --decrypt-codec auto detects HEVC, H.264 with an encrypted NAL header, or H.264 with a clear one-byte NAL header. You can still force --decrypt-codec hevc, --decrypt-codec h264, --decrypt-codec h264-clear-header, or --decrypt-codec h264-encrypted-header for manual experiments. h264 remains the backwards-compatible alias for clear-header H.264.
Fetch SD-card playback record descriptors using the same record-list endpoints exposed by the EZVIZ app.
# List recent SD-card playback records
pyezvizapi sdcard_videos --serial ABC123 --channel 1 \
--start-time "2026-05-10T21:50:00" --stop-time "2026-05-10T21:55:00"
# Try the app's common/intelligent record endpoints when the default v2 path is empty
pyezvizapi --json sdcard_videos --serial ABC123 --source common \
--start-time "2026-05-10T21:50:00" --stop-time "2026-05-10T21:55:00"SD-card records are descriptors for native playback/download. The public API does not currently expose a direct HTTP media URL for every record; use stream dump for live VTM capture or cloud_video_download when cloud videoDetails includes an HTTP(S) URL.
Requires --serial.
- Actions:
status,move,move_coords,unlock-door,unlock-gate,switch,alarm,select - Examples:
# Camera status
pyezvizapi camera --serial ABC123 status
# PTZ move
pyezvizapi camera --serial ABC123 move --direction up --speed 5
# Move by coordinates
pyezvizapi camera --serial ABC123 move_coords --x 0.4 --y 0.6
# Switch setters
pyezvizapi camera --serial ABC123 switch --switch privacy --enable 1
# Alarm settings (push notify, sound level, do-not-disturb)
pyezvizapi camera --serial ABC123 alarm --notify 1 --sound 2 --do_not_disturb 0
# Battery camera work mode
pyezvizapi camera --serial ABC123 select --battery_work_mode POWER_SAVE- Actions:
status - Example:
pyezvizapi devices_light statusSet global defence mode for the account/home.
pyezvizapi home_defence_mode --mode HOME_MODEConnect to Ezviz MQTT push notifications using the current session token. Use --debug to see connection details.
pyezvizapi mqttFor quick experimentation, a small helper script is included which can use a saved token file or prompt for credentials with MFA and save the session token:
# With a previously saved token file
python examples/mqtt_listener.py --token-file ezviz_token.json
# Interactive login, then save token for next time
python examples/mqtt_listener.py --save-token
# Explicit credentials (not recommended for shared terminals)
python examples/mqtt_listener.py -u USER -p PASS --save-tokenDump the complete raw pagelist JSON. Great for exploring unknown fields in an editor (e.g. Notepad++).
pyezvizapi pagelist > pagelist.jsonDump the processed device infos mapping (what the integration consumes). Optionally filter to one serial:
# All devices
pyezvizapi device_infos > device_infos.json
# Single device
pyezvizapi device_infos --serial ABC123 > ABC123.jsonpyezvizapi camera --serial BAXXXXXXX-BAYYYYYYY unlock-door
pyezvizapi camera --serial BAXXXXXXX-BAYYYYYYY unlock-gateValidate RTSP credentials by issuing a DESCRIBE request. Falls back from Basic to Digest auth automatically.
python examples/rtsp_auth_test.py <IP> <USER> <PASS> --uri /Streaming/Channels/101On success, the script prints a confirmation. On failure it raises one of:
InvalidHost: Hostname/IP or port issueAuthTestResultFailed: Invalid credentials
Install the project with development dependencies:
python -m pip install -U pip wheel
python -m pip install -e .[dev]Run the same local validation used by CI:
ruff check .
codespell pyezvizapi tests README.md pyproject.toml .github
pip-audit --progress-spinner off
mypy --install-types --non-interactive .
pyright pyezvizapi
pytest --cov=pyezvizapi --cov-report=term-missing --cov-report=xml --cov-fail-under=85
python -m build
twine check dist/*
python -m pip checkRun style fixes where possible:
ruff check --fix .Before committing, remove generated artifacts so they do not leak into PRs:
rm -rf dist build *.egg-info .coverage coverage.xml .pytest_cache .mypy_cache .ruff_cache
find . -type d -name __pycache__ -prune -exec rm -rf {} +Tests should be offline by default. Do not add tests that require EZVIZ credentials, real cameras, cloud calls, or live network access. Prefer small fixtures and fakes for request builders, status parsing, MQTT payload handling, and Home Assistant integration contracts.
There is no official API documentation. Much of this is inferred from Ezviz mobile app behavior (Android/iOS). Some regions operate on separate endpoints; US example: apiius.ezvizlife.com.
Example:
pyezvizapi -u username@domain.com -p PASS@123 -r apius.ezvizlife.com devices statusFor advanced troubleshooting or new feature research, MITM proxy tools like mitmproxy/Charles/Fiddler can be used to inspect traffic from the app (see community guides for SSL unpinning and WSA usage).
Contributions are welcome — the API surface is large and there are many improvements possible.
See CONTRIBUTING.md for development setup, local validation, offline-test expectations, and cleanup guidance before opening a PR.
For questions, bug reports, and feature requests, see SUPPORT.md for what to include and what not to post publicly.
For changes that affect Home Assistant integrations, see docs/home-assistant-contract.md before renaming methods, changing status keys, or altering auth/MQTT behavior.
For vulnerability reports or security-sensitive behavior, see SECURITY.md. Please do not post credentials, MFA codes, tokens, or private camera URLs in public issues.
We follow SemVer when publishing the library. See CHANGELOG.md and repository tags for released versions.
Release tags should match the package version in pyproject.toml using the form v<version> (for example, v1.0.4.7). The PyPI publish workflow validates this before uploading distributions.
Recommended release flow:
- Bump
project.versioninpyproject.tomland updateCHANGELOG.mdunderUnreleasedduring normal PR work. - Run Prepare Release with the bare version, then review and merge the generated changelog PR.
- Run Upload Python Package manually with the same bare version. That workflow validates the version, builds and smoke-tests the wheel/CLI, publishes to PyPI, then creates the matching GitHub release/tag.
Apache 2.0 — see LICENSE.md.
Draft versions: 0.0.x