Skip to content

#140 PR3 Add image setting writes w/ conflict resolution & sub-endpoint fallback - #143

Merged
mezz64 merged 4 commits into
mezz64:masterfrom
baylanger:image-setting-writes
Nov 6, 2026
Merged

mezz64 merged 4 commits into
mezz64:masterfrom
baylanger:image-setting-writes

Conversation

@baylanger

@baylanger baylanger commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Adds writing of image settings w/ automatic conflict resolution: when the camera refuses a change because another setting is in the way, the logic switches that/those setting(s) off and applies the change, instead of returning an error.

Added functions

  • set_image_setting(path, value, channel=1): change one setting; conflicts are resolved automatically.
  • set_image_linked_setting(setting, value, channel=1): for a mode tied to an enabled flag (BLC mode, HLC mode); its off value turns the feature off.
  • put_image_settings(changes, channel=1): lower level, several values in one PUT, no conflict handling.
  • All return an ImageWriteResult: success, status_code, sub_status, also_changed, error_description.

Automatic conflict resolution

Some settings cannot be on together: WDR with BLC or HLC, and on some firmware HLC with WDR or defog. The camera refuses the change and names the conflict in its subStatusCode. set_image_setting() and set_image_linked_setting() - the conflict resolution kicks in:

  1. recognise the code;
  2. read current values and switch off only the blockers, in their own PUT;
  3. the caller receives a list showing what was switched off in ImageWriteResult.also_changed.
Camera's code When Switched off first
WDRNotDisable, MutexWithWDR BLC or HLC turned on while WDR is on WDR
MutexWithWDR WDR turned on while BLC or HLC is on BLC and HLC
BLCNotDisable / HLCNotDisable a change blocked by BLC / HLC being on BLC / HLC
HLCMutexWDROrDehaze (HTTP 403) WDR or defog turned on while HLC is on HLC
WDROrDehazeMutexHLC (HTTP 403) HLC turned on (also through HLC/HLCMode) while WDR or defog is on WDR and defog
result = client.set_image_setting("WDR/mode", "open")
result.success        # True
result.also_changed   # {"HLC/enabled": "false"}: HLC was on, so it was switched off first

Limits:

  • Only the codes above are resolved, and the HLC codes only for changes to WDR, defog or HLC. Any other refusal is returned unchanged, with a readable error_description.
  • If nothing is left to switch off, it stops instead of retrying.
  • put_image_settings() never resolves conflicts.
  • The table comes from two camera models.

Writing

  • A write reads the full document, changes only the target values as text and PUTs it back, because cameras reject re-serialised XML. The element is found by its full path, so a nested or same-named element elsewhere is never edited, and everything else is preserved byte for byte.
  • Cameras that reject writes to the full document with notSupport are retried on the feature's sub-endpoint (for example /ISAPI/Image/channels/1/Shutter) when all changes belong to one feature. Hikvision developer documents sub-endpoints for only some features (none for HLC or Scene).
  • A 200 with OK, "Reboot Required" (sub_status rebootRequired) or an empty body counts as applied.
  • error_description says "permission denied" only for lowPrivilege (or a 403 without a sub status).
  • Read-modify-write is not atomic: callers should serialise writes per device.

Other

  • Tests: They run the real client against a fake camera that enforces the conflicts, so request bodies, request order, retries and also_changed are checked.
  • Hardware: used on a DS-2CD2385G1 through the Home Assistant custom component, including the HLC/WDR conflict in both directions.
  • README: adds the write methods, an example and the conflict handling. The TODO "Support IR day/night status and ability to switch between day/night/auto" can be removed: set_image_setting("IrcutFilter/IrcutFilterType", "night") (or day, auto) does it.

#140 PR3

@baylanger baylanger changed the title Add image setting writes w/ conflict resolution & sub-endpoint fallback PR3 #140 #140 PR3 Add image setting writes w/ conflict resolution & sub-endpoint fallback Oct 20, 2026
@baylanger

Copy link
Copy Markdown
Contributor Author

Rebased w/ PR2 and small fixes. Ready for review.

@baylanger
baylanger force-pushed the image-setting-writes branch from 044344c to b842750 Compare October 29, 2026 02:46
@baylanger

baylanger commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

This PR is ready for review.... but the test failed w/ python 3.9.

3.9 reached EOL Oct. 2025, I'd think it's ok to drop it?

About the auto-resolution... I think it's good to have, if the end user wants to change something and it doesn't change due to a conflict, the person might not know how to troubleshoot and open an Issue. With auto-resolution, it's probably easier for someone to realize "example, changing setting A to On, B turned Off right away". Almost as simple as, 1 + 1 = 2 ;)

@baylanger

Copy link
Copy Markdown
Contributor Author

https://github.blog/changelog/2026-09-17-ubuntu-26-generally-available-and-latest-migration/

ubuntu-latest moved to ubuntu-26.04 and 3.9 isn't there anymore. If you still want to build w/ python 3.9 , need to pin the workflows to ubuntu-24.04.

@mezz64 mezz64 closed this Oct 29, 2026
@mezz64 mezz64 reopened this Oct 29, 2026
@mezz64 mezz64 closed this Oct 29, 2026
@mezz64 mezz64 reopened this Oct 29, 2026

@mezz64 mezz64 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the close/reopens, I was trying to get the updated tests to run.

Only thing i'd ask for cleanup on this is some better error reporting. Right now Reads raise ISAPIAuthError on a 401, but writes turn every response into an ImageWriteResult. Testing a 401 with an HTML body on the PUT, the result was success=False, status_code=401, sub_status='ok', error_description='ok', and the warning logged was "Setting … failed: ok". That happens because parse_response_status() uses "ok" when there's no subStatusCode, and an HTML error page parses as XML without one. A 401 on the feature-endpoint GET comes back as subEndpointGetFailed. You can't reliabily start reauthentication from either.

A fix could be to raise ISAPIAuthError on a 401 in the write path, as reads do. Only use "ok" as the fallback when the HTTP status is 200.

- 401: any request made while changing a setting (GET that reads
  current values or PUT that writes them) now raises ISAPIAuthError.
- 403 stays a failed result, since devices also use it for conflicts
  and notSupport.
- "ok" now means only a successful write. A failure with no
  subStatusCode gets "noSubStatus" and is logged as "HTTP <status>".
@baylanger

baylanger commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

Changes are in...

  • 401: any request made while changing a setting (GET that reads current values or PUT that writes them) now raises ISAPIAuthError.
  • 403 stays a failed result, since devices also use it for conflicts and notSupport.
  • "ok" now means only a successful write. A failure with no subStatusCode gets "noSubStatus" and is logged as "HTTP status".

@mezz64
mezz64 merged commit 6a38e55 into mezz64:master Nov 6, 2026
4 checks passed
@baylanger
baylanger deleted the image-setting-writes branch November 6, 2026 12:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants