better video ptz stuf

This commit is contained in:
legop3
2026-07-11 18:25:30 -04:00
parent e4ada54cf4
commit 0bb3f89472
9 changed files with 306 additions and 174 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -78,7 +78,7 @@
<script defer src="https://analytics.otter.land/script.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land"></script>
<script defer src="https://analytics.otter.land/recorder.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land" data-sample-rate="0.15" data-mask-level="moderate" data-max-duration="300000"></script>
<title>Roomba Rover</title>
<script type="module" crossorigin src="/assets/index-Bi6EaYls.js"></script>
<script type="module" crossorigin src="/assets/index-B8x6P1kX.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-MAURNhur.css">
</head>
<body>
@@ -7,6 +7,7 @@ const { issueCommand } = require('../commandService');
const homeAssistantService = require('../homeAssistantService');
const neatoService = require('../neatoService');
const liftService = require('../liftService');
const ptzCameraService = require('../ptzCameraService');
const {
HEADLIGHT_DISABLE_ACTION,
LASER_DISABLE_ACTION,
@@ -103,6 +104,17 @@ async function disableAllRoverLasers() {
return { action: 'disableRoverLasers', attempted, failed };
}
async function disablePtzEmitters() {
/*
The PTZ camera has its own light APIs and ownership rules, so the idle
service delegates the actual Reolink calls to ptzCameraService instead of
pretending they are rover commands. This keeps idleService responsible only
for "idle fired; run cleanup actions" and keeps camera-specific payload
details beside the rest of the PTZ integration.
*/
return ptzCameraService.disableEmittersForIdle();
}
async function sendNeatoHome() {
try {
await neatoService.sendHome();
@@ -126,6 +138,7 @@ const idleActions = [
// dockAllRovers,
disableAllRoverHeadlights,
disableAllRoverLasers,
disablePtzEmitters,
sendNeatoHome,
raiseLift,
];
@@ -756,6 +756,81 @@ async function setIr(socket, payload = {}) {
});
}
async function disableEmittersForIdle() {
/*
Idle cleanup is a server-owned safety action, not a user control action, so
it intentionally does not go through requireOperator(). If nobody is using
the camera, the system still needs a way to leave every camera-side emitter
in a known off state.
*/
if (!enabled) {
return { action: 'disablePtzEmitters', skipped: true, reason: 'ptzDisabled' };
}
await initialize();
if (!state.initialized) {
return {
action: 'disablePtzEmitters',
success: false,
error: state.error || 'PTZ camera is not ready',
};
}
return serializeVendorState(async () => {
const client = await ensureReolinkClient();
const lightPayload = { channel: 0, state: spotlightCameraStateForLogicalOn(false) };
const irPayload = { channel: 0, state: normalizeIrState('off') };
const failures = [];
/*
Set the public state before the API calls finish so the UI immediately
reflects the idle policy. If a camera call fails, the result still records
that failure and the next vendor refresh can correct the optimistic state.
*/
state.light = normalizeSpotlightState({
...(state.light || {}),
...lightPayload,
on: false,
});
state.ir = {
...(state.ir || {}),
...irPayload,
};
emitChange('idle-emitters-off-pending');
try {
await client.api('SetWhiteLed', { WhiteLed: lightPayload });
} catch (err) {
failures.push({ control: 'spotlight', error: err.message });
}
try {
await client.api('SetIrLights', { IrLights: irPayload });
} catch (err) {
failures.push({ control: 'ir', error: err.message });
}
/*
Read back once after the writes so stale optimistic state does not linger
forever. The existing spotlight button path delays verification because it
is user-facing and frequently toggled; idle fires rarely, so one ordered
refresh keeps the final state simple.
*/
try {
await refreshVendorState();
} catch (err) {
failures.push({ control: 'refresh', error: err.message });
}
emitChange('idle-emitters-off');
return {
action: 'disablePtzEmitters',
success: failures.length === 0,
failures,
};
});
}
function canRequestLiveVideo(socket) {
if (!enabled || !passesMode(socket)) return false;
if (state.operatorSocketId === socket?.id) return true;
@@ -973,6 +1048,7 @@ module.exports = {
ptzCameraEvents: events,
getPublicState,
canRequestLiveVideo,
disableEmittersForIdle,
getReplaySource: () => enabled && isReplayEnabled()
? { type: 'ptz', id: PTZ_CAMERA_ID, label: cameraConfig.name || 'PTZ Camera' }
: null,