From 513566c89e46b96e48425d7c08c8ab6a9f4648ed Mon Sep 17 00:00:00 2001 From: Julian Cuni Date: Sat, 27 Jun 2026 22:55:02 +0200 Subject: [PATCH] =?UTF-8?q?chore(debug):=20add=20test-post-camera-events.p?= =?UTF-8?q?y=20=E2=80=94=20a=20dumb=20HTTP=20sink=20for=20camera=20pushes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A tiny stdlib HTTP server that logs every request (source IP, method, path, full body, JPEG part stripped) to verify whether a Hikvision camera actually POSTs its Alarm Server events — independent of our app's parsing/acceptance. It cracked the 2026-06-27 "auto-exit" investigation: proved the exit camera was sending NOTHING (corrupt config DB), then later that it sent plain VMD without targetType=vehicle. python3 test-post-camera-events.py [port] # default 8099 Point a camera's Alarm Server at this host:port; drive a car. A line from the camera IP = it sends (debug downstream); silence = the camera isn't POSTing. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V --- test-post-camera-events.py | 44 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 test-post-camera-events.py diff --git a/test-post-camera-events.py b/test-post-camera-events.py new file mode 100644 index 0000000..2d8dcae --- /dev/null +++ b/test-post-camera-events.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +import sys +from datetime import datetime +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer + +PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8099 + + +class Sink(BaseHTTPRequestHandler): + def _log(self, method: str) -> None: + ts = datetime.now().strftime("%H:%M:%S") + src = self.client_address[0] + clen = int(self.headers.get("Content-Length", 0) or 0) + body = self.rfile.read(clen) if clen else b"" + ctype = self.headers.get("Content-Type", "-") + print(f"\n=== {ts} {method} {self.path} from {src} ===", flush=True) + print(f" Content-Type: {ctype} ({clen} bytes)", flush=True) + text = body.decode("utf-8", "replace") + cut = text.find('Content-Type: image/jpeg') + if cut != -1: + text = text[:cut] + "\n [...JPEG image part omitted...]" + print(" body:\n" + text, flush=True) + self.send_response(200) + self.send_header("Content-Length", "2") + self.end_headers() + self.wfile.write(b"OK") + + def do_POST(self): + self._log("POST") + + def do_GET(self): + self._log("GET") + + def do_PUT(self): + self._log("PUT") + + def log_message(self, *args): + pass + + +if __name__ == "__main__": + print(f"camera-event post test listening on 0.0.0.0:{PORT}", flush=True) + print("Ctrl-C to stop.\n", flush=True) + ThreadingHTTPServer(("0.0.0.0", PORT), Sink).serve_forever()