6ceaadfbf2
park-buzi field observation: after a power cut the Hikvision cameras reboot at the 1970 epoch (no/dead RTC battery, no NTP) and stay there until a human logs into the web UI (which silently pushes the browser clock) — corrupting the snapshot OSD timestamps (the evidence trail) and ANPR push times meanwhile. The host is the site's time authority (offline-first, no NTP infra): - Device monitor triggers a sync at each camera's offline→ready edge — exactly the power-restored moment — plus a 24h backstop; the attempt is stamped before the async call so a failing camera retries at backstop cadence, never every poll. - HikvisionCamera.syncClock: GET /ISAPI/System/time; drift ≤60s → leave alone; beyond (or unparseable = infinite drift) → PUT timeMode=manual with the site wall-clock now WITH explicit utc offset (localIsoWithOffset), echoing the camera's timeZone verbatim — correct the clock, never fight its tz/DST config. - Jumps >1h (the power-cut signature) log warn (persisted to app_logs); small corrections info. Capability-guarded (isClockSyncable) — hikvision only; dahua's CGI has no such endpoint. - http-digest generalised to digestRequest (GET/PUT/POST + body); the handshake was already method-aware. digestGet delegates unchanged. 8 new tests: in-sync no-op, 1970 PUT shape (manual + host instant + echoed tz), unparseable→sync, failed-set surfaces, dahua non-capability, DST-both-sides pins on the offset formatter. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
25 lines
954 B
TypeScript
25 lines
954 B
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { localIsoWithOffset } from "./device-monitor.js";
|
|
|
|
// The camera clock-sync sends the SITE's wall-clock now with an explicit UTC offset
|
|
// (ISAPI localTime) — the offset is what makes the instant unambiguous regardless of
|
|
// the camera's own tz/DST config. Pin the DST both-sides behaviour for the site tz.
|
|
|
|
describe("localIsoWithOffset (camera clock sync payload)", () => {
|
|
it("Tirane summer = +02:00 (CEST)", () => {
|
|
expect(localIsoWithOffset("Europe/Tirane", new Date("2026-07-07T10:00:00Z"))).toBe(
|
|
"2026-07-07T12:00:00+02:00",
|
|
);
|
|
});
|
|
it("Tirane winter = +01:00 (CET)", () => {
|
|
expect(localIsoWithOffset("Europe/Tirane", new Date("2026-01-15T10:00:00Z"))).toBe(
|
|
"2026-01-15T11:00:00+01:00",
|
|
);
|
|
});
|
|
it("UTC = +00:00", () => {
|
|
expect(localIsoWithOffset("UTC", new Date("2026-07-07T10:00:00Z"))).toBe(
|
|
"2026-07-07T10:00:00+00:00",
|
|
);
|
|
});
|
|
});
|