mirror of
https://github.com/BetterCorp/BetterFrame.git
synced 2026-05-26 20:16:35 +00:00
feat(cameras): sync entity name on rename + ONVIF device name from GetDeviceInformation
Two fixes: 1. When admin renames a camera, the linked entity's name now syncs automatically so the entity list doesn't drift from the camera list. 2. ONVIF discovery now calls GetDeviceInformation before GetProfiles (best-effort, catches auth-gated devices). Pulls Manufacturer + Model and uses the combined string as the camera's proposed name instead of the raw IP. E.g. "Hikvision DS-2CD2146G2" instead of "192.168.74.8". Falls back to host IP when the device omits the info.
This commit is contained in:
parent
5edf9d4b0b
commit
9129613920
2 changed files with 36 additions and 4 deletions
|
|
@ -1317,6 +1317,14 @@ export function registerAdminRoutes(app: H3, deps: AdminDeps): void {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Sync entity name when camera name changes.
|
||||||
|
if (patch["name"]) {
|
||||||
|
const ent = deps.repo.getEntityForCamera(id);
|
||||||
|
if (ent && ent.name !== patch["name"]) {
|
||||||
|
deps.repo.updateEntity(ent.id, { name: patch["name"] } as any);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
notifyKiosks();
|
notifyKiosks();
|
||||||
deps.nodered.forward("camera.changed", { camera_id: id, event: "updated", source: "server" });
|
deps.nodered.forward("camera.changed", { camera_id: id, event: "updated", source: "server" });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -278,20 +278,21 @@ function profileGroupKey(profileName: string, sourceToken: string | null, stream
|
||||||
return match?.[1] ? `channel:${match[1]}` : "source:default";
|
return match?.[1] ? `channel:${match[1]}` : "source:default";
|
||||||
}
|
}
|
||||||
|
|
||||||
function groupProfiles(host: string, profiles: DiscoveredProfile[]): DiscoveredCamera[] {
|
function groupProfiles(host: string, deviceName: string | null, profiles: DiscoveredProfile[]): DiscoveredCamera[] {
|
||||||
const groups = new Map<string, DiscoveredProfile[]>();
|
const groups = new Map<string, DiscoveredProfile[]>();
|
||||||
for (const profile of profiles) {
|
for (const profile of profiles) {
|
||||||
const key = profileGroupKey(profile.profile_name, profile.source_token, profile.stream_uri);
|
const key = profileGroupKey(profile.profile_name, profile.source_token, profile.stream_uri);
|
||||||
groups.set(key, [...(groups.get(key) ?? []), profile]);
|
groups.set(key, [...(groups.get(key) ?? []), profile]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const base = deviceName || host;
|
||||||
const out: DiscoveredCamera[] = [];
|
const out: DiscoveredCamera[] = [];
|
||||||
let i = 1;
|
let i = 1;
|
||||||
for (const [key, group] of groups) {
|
for (const [key, group] of groups) {
|
||||||
const sourceToken = group.find((p) => p.source_token)?.source_token ?? null;
|
const sourceToken = group.find((p) => p.source_token)?.source_token ?? null;
|
||||||
const name = groups.size === 1
|
const name = groups.size === 1
|
||||||
? host
|
? base
|
||||||
: sourceToken ? `${host} ${sourceToken}` : `${host} camera ${String(i)}`;
|
: sourceToken ? `${base} ${sourceToken}` : `${base} camera ${String(i)}`;
|
||||||
out.push({
|
out.push({
|
||||||
name,
|
name,
|
||||||
source_token: sourceToken ?? (key.startsWith("channel:") ? key.slice("channel:".length) : null),
|
source_token: sourceToken ?? (key.startsWith("channel:") ? key.slice("channel:".length) : null),
|
||||||
|
|
@ -316,6 +317,29 @@ export async function discover(input: DiscoverInput): Promise<DiscoveredCamera[]
|
||||||
|
|
||||||
const header = wsseHeader(input.username, input.password);
|
const header = wsseHeader(input.username, input.password);
|
||||||
|
|
||||||
|
// ---- GetDeviceInformation (best-effort, for friendly device name) ---------
|
||||||
|
let deviceName: string | null = null;
|
||||||
|
try {
|
||||||
|
const devInfoEnv = buildEnvelope(header, `<tds:GetDeviceInformation xmlns:tds="http://www.onvif.org/ver10/device/wsdl"/>`);
|
||||||
|
const devInfoXml = await soap(
|
||||||
|
endpoint.deviceUrl,
|
||||||
|
"http://www.onvif.org/ver10/device/wsdl/GetDeviceInformation",
|
||||||
|
devInfoEnv,
|
||||||
|
timeoutMs,
|
||||||
|
input.soapTransport,
|
||||||
|
);
|
||||||
|
// Try Manufacturer + Model as combined name (e.g. "Hikvision DS-2CD2146G2")
|
||||||
|
const manufacturer = pickAll(devInfoXml, "Manufacturer")[0]?.trim() ?? null;
|
||||||
|
const model = pickAll(devInfoXml, "Model")[0]?.trim() ?? null;
|
||||||
|
if (manufacturer && model) {
|
||||||
|
deviceName = `${manufacturer} ${model}`;
|
||||||
|
} else {
|
||||||
|
deviceName = manufacturer ?? model ?? null;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Device info is optional — some cameras gate it behind auth or omit it.
|
||||||
|
}
|
||||||
|
|
||||||
// ---- GetProfiles -----------------------------------------------------------
|
// ---- GetProfiles -----------------------------------------------------------
|
||||||
const profilesEnv = buildEnvelope(header, `<trt:GetProfiles/>`);
|
const profilesEnv = buildEnvelope(header, `<trt:GetProfiles/>`);
|
||||||
const profilesXml = await soap(
|
const profilesXml = await soap(
|
||||||
|
|
@ -403,5 +427,5 @@ export async function discover(input: DiscoverInput): Promise<DiscoveredCamera[]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return groupProfiles(input.host, out);
|
return groupProfiles(input.host, deviceName, out);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue