It Doesn't Read the Value, It Counts Your Writes
The camera had one job: accept a value and keep it. It accepted the request, returned a success, and handed back something else entirely. Then it did that again, with the same body, and gave me a third answer.
The problem started as a frozen picture where a video should be. A live camera view that renders one frame and never moves is a specific kind of insult — the camera is online, the credentials are correct, the path is valid, and the thing still doesn’t animate. Two causes were plausible: the transport, and the codec. It turned out to be both. The integration handed out a stream URL in a scheme nothing on the receiving end could open, and behind it the camera was encoding in a codec a browser won’t decode without help.
Fixing the transport was the easy half. The codec was the interesting half, because the vendor’s own app offered no option for it. The app presented a settings screen with resolution, frame rate, bitrate — and no note about the profile the encoder was using. So I stopped looking at the app and went at the camera’s HTTP API directly, which exposes the encoder settings as a small JSON document. You read it, you change a field, you write it back.
I read it. The profile was sitting at the bottom of its range, and the codec was the one I didn’t want. I wrote the document back with the codec changed and the profile set to the value I actually wanted. The response came back with the new codec and the old profile.
I wrote it again — identical body, same field, same value. The response came back with the profile one step up, and the codec still correct. A third write, identical again, and the profile advanced another step. Three writes, one value sent, three different results.
The field wasn’t being read. It was being stepped. Every write advanced a counter through the profile list in order, regardless of what the request asked for. Which means the field exists, is documented, is writable, and is functionally decorative — and the only way to land on a specific value is to write repeatedly and read the answer each time until it matches.
That inverts the usual rule. Retrying an unchanged request until it behaves is normally a superstition that hides a real bug. Here it was the only correct operation, and it was the response — not the status code — that told me when to stop. A 200 confirms the request arrived. It says nothing at all about whether the device did what the request asked.
Two things I’m keeping from this. The first: a successful write that returns a different state than the one you sent is not noise, it’s a fingerprint. Devices that behave that way are keeping their own state machine and treating your request as a nudge, and no amount of reading the docs will reveal that — only comparing what you sent against what came back, field by field.
The second: the vendor app is not a complete inventory of what a device can do. The absence of a setting in an interface is an absence of UI, not an absence of a capability. This camera could always have been set the way I wanted. It just had no button for it, and no honest answer when asked directly.
It’s running on the profile I wanted now, and it’s stable. I still don’t know why the field exists if nothing consults it. I’ve decided not to ask.