fix: Should faithfully use WebRTC card PTZ format data_* (#2396)

- Closes: #2385


BREAKING CHANGE: Requires configuration change (automatic upgrade
offered) to move from (e.g.) `data_left_stop` to `data_end_left`
This commit is contained in:
Dermot Duffy
2026-03-07 10:00:39 -08:00
committed by GitHub
parent a81d72375d
commit 1d81e03b04
5 changed files with 249 additions and 21 deletions
+32
View File
@@ -540,6 +540,33 @@ const upgradePTZElementsToLive = function (): (data: unknown) => boolean {
};
};
// Upgrade old internal `data_*_stop` / `data_*_start` keys to
// WebRTC-compatible `data_end_*` / `data_start_*` ordering.
// WebRTC uses `data_start_left` / `data_end_left` (not `data_left_start` /
// `data_left_stop`).
// See: https://github.com/dermotduffy/advanced-camera-card/issues/2385
// See: https://github.com/AlexxIT/WebRTC/blob/master/custom_components/webrtc/www/webrtc-camera.js
const ptzIncorrectDataToWebRTCDataTransform = (data: unknown): unknown => {
if (typeof data !== 'object' || !data) {
return undefined;
}
let modified = false;
const out = { ...data };
for (const key of Object.keys(out)) {
const match = key.match(/^data_(.+)_(start|stop)$/);
if (match) {
const phase = match[2] === 'stop' ? 'end' : match[2];
const webrtcKey = `data_${phase}_${match[1]}`;
if (!(webrtcKey in out)) {
out[webrtcKey] = out[key];
}
delete out[key];
modified = true;
}
}
return modified ? out : undefined;
};
const ptzActionsToCamerasGlobalTransform = (data: unknown): unknown => {
if (typeof data !== 'object' || !data) {
return undefined;
@@ -967,4 +994,9 @@ const UPGRADES = [
'view.triggers.untrigger_seconds',
CONF_VIEW_TRIGGERS_UNTRIGGER_DELAY_SECONDS,
),
upgradeWithOverrides('cameras_global.ptz', ptzIncorrectDataToWebRTCDataTransform),
upgradeArrayOfObjects(
CONF_CAMERAS,
upgradeWithOverrides('ptz', ptzIncorrectDataToWebRTCDataTransform),
),
];
+42 -16
View File
@@ -6,32 +6,58 @@ export const ptzCameraConfigDefaults = {
c2r_delay_between_calls_seconds: 0.2,
};
// To avoid lots of YAML duplication, provide an easy way to just specify the
// service data as actions for each PTZ action, and it will be preprocessed
// into the full form. This also provides compatability with the AlexIT/WebRTC
// PTZ configuration.
const dataPTZFormatToFullFormat = function (suffix: string): (data: unknown) => unknown {
return (data) => {
// Converts WebRTC `data_*` shorthand keys into full `actions_*` perform-action
// objects (e.g. `data_start_left` → `actions_left_start`, `data_end_left` →
// `actions_left_stop`, `data_left` → `actions_left`, `data_home` → preset).
// See: https://github.com/AlexxIT/WebRTC/blob/master/custom_components/webrtc/www/webrtc-camera.js
const dataPTZFormatToFullFormat =
(suffix: string) =>
(data: unknown): unknown => {
if (!data || typeof data !== 'object' || !data['service']) {
return data;
}
const service = data['service'];
const out = { ...data };
Object.keys(data).forEach((key) => {
const match = key.match(/^data_(.+)$/);
const name = match?.[1];
if (name && !(`${suffix}${name}` in data)) {
for (const key of Object.keys(data)) {
const webrtc = key.match(/^data_(start|end)_(.+)$/);
const name = webrtc
? `${webrtc[2]}_${webrtc[1] === 'end' ? 'stop' : webrtc[1]}`
: key.match(/^data_(.+)$/)?.[1];
if (!name) {
continue;
}
// Route `data_home` into a `home` preset listed first so the PTZ
// home button (which activates the first preset) uses it.
if (suffix && name === 'home') {
const presets =
out['presets'] && typeof out['presets'] === 'object' ? out['presets'] : {};
if (!('home' in presets)) {
out['presets'] = {
home: {
action: 'perform-action',
perform_action: service,
data: out[key],
},
...presets,
};
}
} else if (!(`${suffix}${name}` in out)) {
out[`${suffix}${name}`] = {
action: 'perform-action',
perform_action: data['service'],
data: data[key],
perform_action: service,
data: out[key],
};
delete out[key];
delete out['service'];
}
});
delete out[key];
delete out['service'];
}
return out;
};
};
export const ptzCameraConfigSchema = z.preprocess(
dataPTZFormatToFullFormat('actions_'),