obsbot-mcp 0.6.3 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -20
- package/dist/build-info.json +5 -0
- package/dist/device/manager.d.ts +8 -0
- package/dist/device/manager.js +37 -4
- package/dist/device/manager.js.map +1 -1
- package/dist/geometry/aim.d.ts +24 -13
- package/dist/geometry/aim.js +26 -13
- package/dist/geometry/aim.js.map +1 -1
- package/dist/ipc/build-id.d.ts +25 -0
- package/dist/ipc/build-id.js +60 -0
- package/dist/ipc/build-id.js.map +1 -0
- package/dist/ipc/client.d.ts +36 -0
- package/dist/ipc/client.js +106 -4
- package/dist/ipc/client.js.map +1 -1
- package/dist/ipc/coordinator.d.ts +91 -12
- package/dist/ipc/coordinator.js +293 -57
- package/dist/ipc/coordinator.js.map +1 -1
- package/dist/ipc/gate.d.ts +14 -0
- package/dist/ipc/gate.js +45 -0
- package/dist/ipc/gate.js.map +1 -0
- package/dist/ipc/owner.d.ts +27 -1
- package/dist/ipc/owner.js +67 -4
- package/dist/ipc/owner.js.map +1 -1
- package/dist/ipc/protocol.d.ts +30 -0
- package/dist/ipc/protocol.js +40 -0
- package/dist/ipc/protocol.js.map +1 -1
- package/dist/ipc/rendezvous.d.ts +37 -11
- package/dist/ipc/rendezvous.js +143 -30
- package/dist/ipc/rendezvous.js.map +1 -1
- package/dist/mcp/server.js +40 -12
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.d.ts +6 -0
- package/dist/mcp/tools.js +123 -17
- package/dist/mcp/tools.js.map +1 -1
- package/dist/tail2/api.d.ts +218 -0
- package/dist/tail2/api.js +267 -0
- package/dist/tail2/api.js.map +1 -0
- package/dist/tail2/mdns.d.ts +48 -0
- package/dist/tail2/mdns.js +216 -0
- package/dist/tail2/mdns.js.map +1 -0
- package/dist/tail2/registry.d.ts +100 -0
- package/dist/tail2/registry.js +200 -0
- package/dist/tail2/registry.js.map +1 -0
- package/dist/tail2/snapshot.d.ts +54 -0
- package/dist/tail2/snapshot.js +157 -0
- package/dist/tail2/snapshot.js.map +1 -0
- package/dist/tail2/tools.d.ts +7 -0
- package/dist/tail2/tools.js +370 -0
- package/dist/tail2/tools.js.map +1 -0
- package/dist/transport/helper-process.d.ts +11 -0
- package/dist/transport/helper-process.js +10 -0
- package/dist/transport/helper-process.js.map +1 -1
- package/dist/transport/linux.d.ts +59 -9
- package/dist/transport/linux.js +163 -21
- package/dist/transport/linux.js.map +1 -1
- package/dist/transport/macos.d.ts +8 -0
- package/dist/transport/macos.js +11 -0
- package/dist/transport/macos.js.map +1 -1
- package/dist/transport/transport.d.ts +22 -0
- package/dist/transport/transport.js +20 -0
- package/dist/transport/transport.js.map +1 -1
- package/dist/transport/windows.d.ts +6 -0
- package/dist/transport/windows.js +9 -0
- package/dist/transport/windows.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/native/prebuilt/darwin-arm64/obsbot-helper +0 -0
- package/native/prebuilt/darwin-x64/obsbot-helper +0 -0
- package/native/prebuilt/linux-x64/obsbot-helper +0 -0
- package/native/prebuilt/win32-x64/obsbot-helper.exe +0 -0
- package/package.json +4 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
export class SrtNotEnabledError extends Error {
|
|
4
|
+
constructor() {
|
|
5
|
+
super("the Tail 2's SRT output is not enabled. In OBSBOT Center switch the " +
|
|
6
|
+
"camera's streaming output to SRT (listener mode) first — note this " +
|
|
7
|
+
"disables NDI while active and does not survive a camera reboot. The " +
|
|
8
|
+
"listener serves immediately once enabled.");
|
|
9
|
+
this.name = "SrtNotEnabledError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const JPEG_SOI = 0xffd8;
|
|
13
|
+
/** Parse width/height out of a JPEG's SOF0/SOF2 marker (avoids a second ffprobe). */
|
|
14
|
+
const jpegDims = (buf) => {
|
|
15
|
+
let i = 2;
|
|
16
|
+
while (i + 9 < buf.length) {
|
|
17
|
+
if (buf[i] !== 0xff) {
|
|
18
|
+
i++;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const marker = buf[i + 1];
|
|
22
|
+
if (marker === 0xd8 || (marker >= 0xd0 && marker <= 0xd9)) {
|
|
23
|
+
i += 2;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const len = buf.readUInt16BE(i + 2);
|
|
27
|
+
if (marker === 0xc0 || marker === 0xc2) {
|
|
28
|
+
return { height: buf.readUInt16BE(i + 5), width: buf.readUInt16BE(i + 7) };
|
|
29
|
+
}
|
|
30
|
+
i += 2 + len;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
};
|
|
34
|
+
export async function srtSnapshot(host, opts = {}, spawnImpl = spawn) {
|
|
35
|
+
const maxDim = Math.round(opts.maxDim ?? 640);
|
|
36
|
+
const quality = Math.round(opts.quality ?? 80);
|
|
37
|
+
const port = opts.port ?? 5000;
|
|
38
|
+
const streamId = opts.streamId ?? "mainstream";
|
|
39
|
+
const timeoutMs = opts.timeoutMs ?? 20000;
|
|
40
|
+
const url = `srt://${host}:${port}?mode=caller&latency=120&streamid=${streamId}`;
|
|
41
|
+
const args = [
|
|
42
|
+
"-hide_banner", "-loglevel", "error",
|
|
43
|
+
"-rw_timeout", String(timeoutMs * 1000),
|
|
44
|
+
"-i", url,
|
|
45
|
+
"-frames:v", "1",
|
|
46
|
+
"-vf", `scale='min(${maxDim},iw)':-2`,
|
|
47
|
+
"-q:v", String(Math.max(2, Math.min(31, Math.round(31 - (quality * 29) / 100)))),
|
|
48
|
+
"-f", "image2", "-",
|
|
49
|
+
];
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const child = spawnImpl("ffmpeg", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
52
|
+
const chunks = [];
|
|
53
|
+
let stderr = "";
|
|
54
|
+
const timer = setTimeout(() => {
|
|
55
|
+
child.kill();
|
|
56
|
+
reject(new Error(`SRT snapshot timed out after ${timeoutMs + 5000}ms — SRT is likely off or mid-configuration in Center (settings changes require the output to be off). Re-enable it and retry.`));
|
|
57
|
+
}, timeoutMs + 5000);
|
|
58
|
+
child.stdout?.on("data", (c) => chunks.push(c));
|
|
59
|
+
child.stderr?.on("data", (c) => (stderr += c.toString()));
|
|
60
|
+
child.on("error", (e) => {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
reject(new Error(`ffmpeg is required for Tail 2 snapshots and failed to run: ${e.message}. Install it (winget install Gyan.FFmpeg) and retry.`));
|
|
63
|
+
});
|
|
64
|
+
child.on("close", (code) => {
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
const jpg = Buffer.concat(chunks);
|
|
67
|
+
if (code !== 0 || jpg.length < 4 || jpg.readUInt16BE(0) !== JPEG_SOI) {
|
|
68
|
+
reject(new Error(`SRT snapshot failed (ffmpeg exit ${code}): ${stderr.slice(0, 300) || "no frame"}`));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const dims = jpegDims(jpg);
|
|
72
|
+
resolve({
|
|
73
|
+
mime: "image/jpeg",
|
|
74
|
+
width: dims?.width ?? 0,
|
|
75
|
+
height: dims?.height ?? 0,
|
|
76
|
+
base64: jpg.toString("base64"),
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Grab a frame from the NDI Tools "NDI Webcam" DirectShow device (Windows
|
|
83
|
+
* only). NOT used by the snapshot tool automatically: on the reference
|
|
84
|
+
* machine (2026-09-26) the bridge's user-configured output slots delivered
|
|
85
|
+
* uniformly black frames to ffmpeg (YAVG=YMAX=16) while other slots hung,
|
|
86
|
+
* and probing the devices disrupts the app. Kept for manual/verified use —
|
|
87
|
+
* if a slot is confirmed live (e.g. previews correctly in OBS), this works.
|
|
88
|
+
* Never force-kill the Webcam app: that corrupts its output-slot config.
|
|
89
|
+
*/
|
|
90
|
+
export async function ndiWebcamSnapshot(opts = {}, spawnImpl = spawn) {
|
|
91
|
+
if (process.platform !== "win32")
|
|
92
|
+
return null;
|
|
93
|
+
// The virtual devices stay registered even when the bridge app is closed
|
|
94
|
+
// (and list four slots whose signal-bearing one varies across restarts —
|
|
95
|
+
// measured 2026-09-26), so without the app running an attempt is just a
|
|
96
|
+
// guaranteed hang. Guard on the process first.
|
|
97
|
+
const running = await new Promise((resolve) => {
|
|
98
|
+
const child = spawnImpl("tasklist", ["/FI", "IMAGENAME eq Webcam.exe", "/NH"], { stdio: ["ignore", "pipe", "ignore"] });
|
|
99
|
+
let out = "";
|
|
100
|
+
const timer = setTimeout(() => { child.kill(); resolve(false); }, 5000);
|
|
101
|
+
child.stdout?.on("data", (c) => (out += c.toString()));
|
|
102
|
+
child.on("error", () => { clearTimeout(timer); resolve(false); });
|
|
103
|
+
child.on("close", () => { clearTimeout(timer); resolve(/webcam\.exe/i.test(out)); });
|
|
104
|
+
});
|
|
105
|
+
if (!running)
|
|
106
|
+
return null;
|
|
107
|
+
const maxDim = Math.round(opts.maxDim ?? 640);
|
|
108
|
+
const quality = Math.round(opts.quality ?? 80);
|
|
109
|
+
const timeoutMs = opts.timeoutMs ?? 15000;
|
|
110
|
+
// Enumerate DirectShow video devices; find the NDI Webcam bridge.
|
|
111
|
+
const listed = await new Promise((resolve) => {
|
|
112
|
+
let out = "";
|
|
113
|
+
const child = spawnImpl("ffmpeg", ["-hide_banner", "-list_devices", "true", "-f", "dshow", "-i", "dummy"], { stdio: ["ignore", "ignore", "pipe"] });
|
|
114
|
+
const timer = setTimeout(() => { child.kill(); resolve(""); }, 10000);
|
|
115
|
+
child.stderr?.on("data", (c) => (out += c.toString()));
|
|
116
|
+
child.on("error", () => { clearTimeout(timer); resolve(""); });
|
|
117
|
+
child.on("close", () => { clearTimeout(timer); resolve(out); });
|
|
118
|
+
});
|
|
119
|
+
const m = /"([^"]*NDI Webcam[^"]*)"/i.exec(listed);
|
|
120
|
+
if (!m)
|
|
121
|
+
return null;
|
|
122
|
+
const device = m[1];
|
|
123
|
+
return new Promise((resolve) => {
|
|
124
|
+
const child = spawnImpl("ffmpeg", [
|
|
125
|
+
"-hide_banner", "-loglevel", "error",
|
|
126
|
+
"-rtbufsize", "100M",
|
|
127
|
+
"-f", "dshow", "-i", `video=${device}`,
|
|
128
|
+
"-frames:v", "1",
|
|
129
|
+
"-vf", `scale='min(${maxDim},iw)':-2`,
|
|
130
|
+
"-q:v", String(Math.max(2, Math.min(31, Math.round(31 - (quality * 29) / 100)))),
|
|
131
|
+
"-f", "image2", "-",
|
|
132
|
+
], { stdio: ["ignore", "pipe", "pipe"] });
|
|
133
|
+
const chunks = [];
|
|
134
|
+
const timer = setTimeout(() => { child.kill(); resolve(null); }, timeoutMs);
|
|
135
|
+
child.stdout?.on("data", (c) => chunks.push(c));
|
|
136
|
+
child.on("error", () => { clearTimeout(timer); resolve(null); });
|
|
137
|
+
child.on("close", () => {
|
|
138
|
+
clearTimeout(timer);
|
|
139
|
+
const jpg = Buffer.concat(chunks);
|
|
140
|
+
if (jpg.length < 4 || jpg.readUInt16BE(0) !== JPEG_SOI) {
|
|
141
|
+
resolve(null);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const dims = jpegDims(jpg);
|
|
145
|
+
resolve({
|
|
146
|
+
mime: "image/jpeg",
|
|
147
|
+
width: dims?.width ?? 0,
|
|
148
|
+
height: dims?.height ?? 0,
|
|
149
|
+
base64: jpg.toString("base64"),
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/** Standard NDI Tools install location, for the options report. */
|
|
155
|
+
export const NDI_WEBCAM_EXE = "C:\\Program Files\\NDI\\NDI 6 Tools\\Webcam\\Webcam.exe";
|
|
156
|
+
export const ndiToolsInstalled = () => existsSync(NDI_WEBCAM_EXE);
|
|
157
|
+
//# sourceMappingURL=snapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/tail2/snapshot.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ToolDef } from "../mcp/tools.js";
|
|
2
|
+
import type { Tail2Registry } from "./registry.js";
|
|
3
|
+
import { type SrtSnapshot } from "./snapshot.js";
|
|
4
|
+
export declare function createTail2Tools(registry: Tail2Registry, grab?: (host: string, o: {
|
|
5
|
+
maxDim: number;
|
|
6
|
+
quality: number;
|
|
7
|
+
}) => Promise<SrtSnapshot>): ToolDef[];
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { srtSnapshot, ndiToolsInstalled } from "./snapshot.js";
|
|
3
|
+
/**
|
|
4
|
+
* MCP tools for the OBSBOT Tail 2 (network camera — HTTP/WS control; its USB
|
|
5
|
+
* UVC mode is not used here, see TAIL2-PROTOCOL.md §11). A separate tool family from the Tiny 2's:
|
|
6
|
+
* the two cameras share semantics but not transports, and pretending otherwise
|
|
7
|
+
* would make every schema a lie somewhere. Where a tool mirrors a Tiny 2 tool
|
|
8
|
+
* (zoom, recenter, ai tracking) the semantics match on purpose; where the
|
|
9
|
+
* Tail 2 has no Tiny 2 counterpart (portrait rotation, roll bias) the tool is
|
|
10
|
+
* new.
|
|
11
|
+
*
|
|
12
|
+
* The `camera` selector here is a MAC address, device_name, or host — unlike
|
|
13
|
+
* the Tiny 2's serial-number selector. Omitted with exactly one Tail 2
|
|
14
|
+
* registered resolves to it; omitted with none tells the caller how to add
|
|
15
|
+
* one; omitted with several names them all.
|
|
16
|
+
*/
|
|
17
|
+
// Same defensive coercions as the Tiny 2 tools (see src/mcp/tools.ts): some
|
|
18
|
+
// MCP clients serialize numbers/booleans as strings, and z.coerce.boolean()
|
|
19
|
+
// would map "false" to true.
|
|
20
|
+
const num = () => z.preprocess((v) => (typeof v === "string" && v.trim() !== "" && !Number.isNaN(Number(v)) ? Number(v) : v), z.number());
|
|
21
|
+
const bool = () => z.preprocess((v) => (v === "true" ? true : v === "false" ? false : v), z.boolean());
|
|
22
|
+
const withCamera = (shape) => z.object({ ...shape, camera: z.string().optional() });
|
|
23
|
+
/**
|
|
24
|
+
* Bundle-verified enums from the camera's own web app (TAIL2-PROTOCOL.md §3).
|
|
25
|
+
* `none` is excluded from the tool's mode list — it is what `enabled:false`
|
|
26
|
+
* sends, and advertising it alongside `enabled` would make two ways to say
|
|
27
|
+
* the same thing, one of which is always wrong.
|
|
28
|
+
*/
|
|
29
|
+
const TAIL2_AI_MODES = [
|
|
30
|
+
"humanTrackingSingleMode",
|
|
31
|
+
"humanTrackingGroupMode",
|
|
32
|
+
"animalTrackingNormal",
|
|
33
|
+
"animalTrackingCloseUp",
|
|
34
|
+
"objectTracking",
|
|
35
|
+
"objectTrackingNormal",
|
|
36
|
+
"objectTrackingCloseUp",
|
|
37
|
+
];
|
|
38
|
+
const TAIL2_TRACK_SPEEDS = ["superLazy", "lazy", "slow", "fast", "crazy", "customized"];
|
|
39
|
+
const devicesSchema = z.object({});
|
|
40
|
+
const scanSchema = z.object({});
|
|
41
|
+
const statusSchema = withCamera({});
|
|
42
|
+
const infoSchema = withCamera({});
|
|
43
|
+
const zoomSchema = withCamera({
|
|
44
|
+
ratio: num().pipe(z.number().min(1.0).max(12.0)),
|
|
45
|
+
speed: num().pipe(z.number().int().min(1).max(10)).default(5),
|
|
46
|
+
});
|
|
47
|
+
const recenterSchema = withCamera({});
|
|
48
|
+
const portraitSchema = withCamera({ enable: bool() });
|
|
49
|
+
const rollBiasSchema = withCamera({ angle: num().pipe(z.number().min(-90).max(90)) });
|
|
50
|
+
const aiTrackSchema = withCamera({
|
|
51
|
+
enabled: bool(),
|
|
52
|
+
mode: z.enum(TAIL2_AI_MODES).default("humanTrackingSingleMode"),
|
|
53
|
+
});
|
|
54
|
+
const trackSpeedSchema = withCamera({ speed: z.enum(TAIL2_TRACK_SPEEDS) });
|
|
55
|
+
// Preset slots: the Tail 2's API is 0-based (ids 0–2) but the tools expose
|
|
56
|
+
// 1-based slots (1|2|3) to match the Tiny 2's preset tools, so a caller's
|
|
57
|
+
// mental model carries across both cameras.
|
|
58
|
+
const slotSchema = num().pipe(z.number().int().min(1).max(3));
|
|
59
|
+
const presetListSchema = withCamera({});
|
|
60
|
+
const presetSaveSchema = withCamera({ slot: slotSchema, name: z.string().max(40).optional() });
|
|
61
|
+
const presetSlotSchema = withCamera({ slot: slotSchema });
|
|
62
|
+
const presetRenameSchema = withCamera({ slot: slotSchema, name: z.string().max(40) });
|
|
63
|
+
const snapshotSchema = withCamera({
|
|
64
|
+
resolution: num().pipe(z.number().int().min(256).max(1920)).default(640),
|
|
65
|
+
quality: num().pipe(z.number().int().min(1).max(100)).default(80),
|
|
66
|
+
});
|
|
67
|
+
const PRESET_SHAPE_NOTE = `unlike the Tiny 2, save OVERWRITES an occupied slot (no create-once), and ` +
|
|
68
|
+
`there is no explicit-pose write in this API at all — a slot always captures ` +
|
|
69
|
+
`the camera's CURRENT pose (measured 2026-09-26; TAIL2-PROTOCOL.md §3)`;
|
|
70
|
+
const SETTLED_NOTE = `settled:false means the command was sent and acknowledged but the readback ` +
|
|
71
|
+
`had not caught up within the polling budget — the camera moves slower than ` +
|
|
72
|
+
`it acknowledges. Retry the read (or the command) rather than assuming failure.`;
|
|
73
|
+
export function createTail2Tools(registry, grab = (host, o) => srtSnapshot(host, o)) {
|
|
74
|
+
return [
|
|
75
|
+
{
|
|
76
|
+
name: "obsbot_tail2_devices",
|
|
77
|
+
description: "List registered OBSBOT Tail 2 cameras (network cameras; MAC-keyed). Each entry: " +
|
|
78
|
+
"mac (the identity — pass it as `camera` to any obsbot_tail2_* tool), name, and the " +
|
|
79
|
+
"host(s) it has answered on. Registration is not liveness: an offline camera still " +
|
|
80
|
+
"appears here; use obsbot_tail2_status to check one is reachable.",
|
|
81
|
+
schema: devicesSchema,
|
|
82
|
+
handler: async () => ({
|
|
83
|
+
cameras: registry.list().map((e) => ({ mac: e.mac, name: e.name, hosts: e.hosts })),
|
|
84
|
+
}),
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "obsbot_tail2_scan",
|
|
88
|
+
description: "Discover OBSBOT Tail 2 cameras on the network and register them. Listens for the " +
|
|
89
|
+
"camera's own mDNS announcements (~5s — it multicasts its MAC, name and IPs every " +
|
|
90
|
+
"few seconds, the same channel OBSBOT Center discovers by) and falls back to an HTTP " +
|
|
91
|
+
"subnet sweep only when nothing was heard (multicast-filtered networks; ~20s on a " +
|
|
92
|
+
"full /24). Returns the cameras found; they stay registered afterwards. Run this " +
|
|
93
|
+
"once to onboard a Tail 2 whose IP you don't know.",
|
|
94
|
+
schema: scanSchema,
|
|
95
|
+
handler: async () => {
|
|
96
|
+
const found = await registry.scan();
|
|
97
|
+
return {
|
|
98
|
+
found: found.map((e) => ({ mac: e.mac, name: e.name, hosts: e.hosts })),
|
|
99
|
+
registered: registry.list().length,
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: "obsbot_tail2_status",
|
|
105
|
+
description: "Read a Tail 2's full live status block (one WebSocket push): power, recording, " +
|
|
106
|
+
"portrait rotation, AI mode + tracking settings, zoom ratio, roll bias, focus modes, " +
|
|
107
|
+
"NDI/RTSP/SRT/RTMP enablement, SD card, preset list with poses (degrees + zoom " +
|
|
108
|
+
"ratio), and per-subsystem health (gimbal/AI/battery/lens/ToF). NOTE: no live " +
|
|
109
|
+
"yaw/pitch — the Tail 2 reports pose only via saved presets, so gimbal moves are " +
|
|
110
|
+
"open-loop (same limitation as the Tiny 2 on Linux).",
|
|
111
|
+
schema: statusSchema,
|
|
112
|
+
handler: async (args) => {
|
|
113
|
+
const { camera } = statusSchema.parse(args);
|
|
114
|
+
const { api } = await registry.resolve(camera);
|
|
115
|
+
return { camera: (await api.info()).mac, ...(await api.status()) };
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "obsbot_tail2_info",
|
|
120
|
+
description: "Read a Tail 2's identity and static configuration in one call: device_info (name, " +
|
|
121
|
+
"MAC, wired/wireless IPs), range (zoom 1.0–12.0, focus 1–100, exposure, white " +
|
|
122
|
+
"balance 2000–10000K, image adjust ranges), and networkconfig (NDI/stream encoder " +
|
|
123
|
+
"settings). Read-only.",
|
|
124
|
+
schema: infoSchema,
|
|
125
|
+
handler: async (args) => {
|
|
126
|
+
const { camera } = infoSchema.parse(args);
|
|
127
|
+
const { api } = await registry.resolve(camera);
|
|
128
|
+
return {
|
|
129
|
+
device: await api.info(),
|
|
130
|
+
ranges: await api.ranges(),
|
|
131
|
+
network: await api.networkConfig(),
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "obsbot_tail2_zoom",
|
|
137
|
+
description: "Set absolute zoom ratio on a Tail 2 (1.0–12.0 — twelve x, six times the Tiny 2's " +
|
|
138
|
+
"range; scale is the camera's own ratio, not a magnification multiple). speed 1–10 " +
|
|
139
|
+
"is REQUIRED by the firmware and defaults to 5. Zoom ramps mechanically; the tool " +
|
|
140
|
+
"polls the readback and returns settled:false if it hadn't arrived within ~5s — the " +
|
|
141
|
+
"command was still sent. Hardware-verified 2026-09-26.",
|
|
142
|
+
schema: zoomSchema,
|
|
143
|
+
handler: async (args) => {
|
|
144
|
+
const { ratio, speed, camera } = zoomSchema.parse(args);
|
|
145
|
+
const { api } = await registry.resolve(camera);
|
|
146
|
+
const r = await api.zoomSet(ratio, speed);
|
|
147
|
+
return { ok: true, ratio: r.ratio, settled: r.settled, note: SETTLED_NOTE };
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: "obsbot_tail2_recenter",
|
|
152
|
+
description: "Recenter a Tail 2's gimbal (yaw 0 / pitch 0 / roll 0). Returns as soon as the " +
|
|
153
|
+
"command is acknowledged — the Tail 2 exposes no live pose to verify arrival " +
|
|
154
|
+
"against (open-loop, like the Tiny 2 on Linux), and note that commanding a reset " +
|
|
155
|
+
"can drop AI tracking to none (observed once 2026-09-26; re-enable with " +
|
|
156
|
+
"obsbot_tail2_ai_track if that matters). Hardware-verified 2026-09-26.",
|
|
157
|
+
schema: recenterSchema,
|
|
158
|
+
handler: async (args) => {
|
|
159
|
+
const { camera } = recenterSchema.parse(args);
|
|
160
|
+
const { api } = await registry.resolve(camera);
|
|
161
|
+
await api.recenter();
|
|
162
|
+
return { ok: true };
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "obsbot_tail2_portrait",
|
|
167
|
+
description: "Rotate a Tail 2's barrel 90° for portrait framing (enable) or back to landscape " +
|
|
168
|
+
"(disable) — a motorized physical rotation no Tiny 2 has. Takes ~1.5s; the tool " +
|
|
169
|
+
"polls the status push for the new orientation and returns settled:false if the " +
|
|
170
|
+
"motor hadn't finished within ~5s. MEASURED 2026-09-26: while the motor is in " +
|
|
171
|
+
"motion the camera can acknowledge a write and silently drop it — if settled stays " +
|
|
172
|
+
"false, retry the command. Hardware-verified both directions.",
|
|
173
|
+
schema: portraitSchema,
|
|
174
|
+
handler: async (args) => {
|
|
175
|
+
const { enable, camera } = portraitSchema.parse(args);
|
|
176
|
+
const { api } = await registry.resolve(camera);
|
|
177
|
+
const r = await api.portraitSet(enable);
|
|
178
|
+
return { ok: true, portrait: r.switch_portrait, settled: r.settled, note: SETTLED_NOTE };
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: "obsbot_tail2_roll_bias",
|
|
183
|
+
description: "Set a Tail 2's roll trim angle in degrees (horizon correction / deliberate tilt), " +
|
|
184
|
+
"clamped to ±90. Unlike the Tiny 2 (whose roll slot is inert), this moves the " +
|
|
185
|
+
"image. Verified write+readback on hardware 2026-09-26.",
|
|
186
|
+
schema: rollBiasSchema,
|
|
187
|
+
handler: async (args) => {
|
|
188
|
+
const { angle, camera } = rollBiasSchema.parse(args);
|
|
189
|
+
const { api } = await registry.resolve(camera);
|
|
190
|
+
const r = await api.rollBiasSet(angle);
|
|
191
|
+
return { ok: true, angle: r.angle, settled: r.settled };
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: "obsbot_tail2_ai_track",
|
|
196
|
+
description: "Enable or disable a Tail 2's AI tracking. enabled:false stops tracking (mode `none`). " +
|
|
197
|
+
"enabled:true starts the chosen mode — default humanTrackingSingleMode (single-person " +
|
|
198
|
+
"framing, the camera's power-on default); also group human framing, animal tracking, " +
|
|
199
|
+
"and object tracking (the mode strings are the camera's own enum). NOTE: while " +
|
|
200
|
+
"tracking is active it owns the gimbal — manual moves fight it, and a recenter can " +
|
|
201
|
+
"drop tracking to none. Mode strings verified against firmware 7.2.13.1.",
|
|
202
|
+
schema: aiTrackSchema,
|
|
203
|
+
handler: async (args) => {
|
|
204
|
+
const { enabled, mode, camera } = aiTrackSchema.parse(args);
|
|
205
|
+
const { api } = await registry.resolve(camera);
|
|
206
|
+
const r = await api.aiModeSet(enabled ? mode : "none");
|
|
207
|
+
return {
|
|
208
|
+
ok: true,
|
|
209
|
+
mode: r.mode,
|
|
210
|
+
settled: r.settled,
|
|
211
|
+
...(r.settled ? {} : { note: SETTLED_NOTE }),
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: "obsbot_tail2_track_speed",
|
|
217
|
+
description: "Set a Tail 2's tracking follow speed: superLazy | lazy | slow | fast | crazy | " +
|
|
218
|
+
"customized — the camera's own six-speed enum (NOT the Tiny 2's standard/sport " +
|
|
219
|
+
"pair). Verified by readback on hardware 2026-09-26.",
|
|
220
|
+
schema: trackSpeedSchema,
|
|
221
|
+
handler: async (args) => {
|
|
222
|
+
const { speed, camera } = trackSpeedSchema.parse(args);
|
|
223
|
+
const { api } = await registry.resolve(camera);
|
|
224
|
+
const r = await api.trackSpeedSet(speed);
|
|
225
|
+
return { ok: true, speed: r.speed, settled: r.settled };
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: "obsbot_tail2_preset_list",
|
|
230
|
+
description: "List a Tail 2's three preset slots: occupied/empty, decoded name, and pose in " +
|
|
231
|
+
"degrees + zoom ratio (pitch/yaw/roll/ratio). Slot numbers are 1–3 (the camera's " +
|
|
232
|
+
"own ids are 0-based; mapped for consistency with the Tiny 2 preset tools).",
|
|
233
|
+
schema: presetListSchema,
|
|
234
|
+
handler: async (args) => {
|
|
235
|
+
const { camera } = presetListSchema.parse(args);
|
|
236
|
+
const { api } = await registry.resolve(camera);
|
|
237
|
+
const presets = await api.presetsGet();
|
|
238
|
+
return {
|
|
239
|
+
slots: [0, 1, 2].map((id) => {
|
|
240
|
+
const p = presets.find((x) => x.id === id);
|
|
241
|
+
return p
|
|
242
|
+
? { slot: id + 1, occupied: true, name: p.name, pose: { yaw: p.yaw, pitch: p.pitch, roll: p.roll, ratio: p.ratio } }
|
|
243
|
+
: { slot: id + 1, occupied: false, name: null, pose: null };
|
|
244
|
+
}),
|
|
245
|
+
};
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
name: "obsbot_tail2_preset_save",
|
|
250
|
+
description: "Save a Tail 2's CURRENT live pose into a slot (1–3): aim the camera first (via " +
|
|
251
|
+
"tracking, a recall, or physically), then save. name defaults to P1/P2/P3. NOTE: " +
|
|
252
|
+
PRESET_SHAPE_NOTE +
|
|
253
|
+
" — deleting first is unnecessary. Hardware-verified 2026-09-26.",
|
|
254
|
+
schema: presetSaveSchema,
|
|
255
|
+
handler: async (args) => {
|
|
256
|
+
const { slot, name, camera } = presetSaveSchema.parse(args);
|
|
257
|
+
const { api } = await registry.resolve(camera);
|
|
258
|
+
const r = await api.presetSave(slot - 1, name ?? `P${slot}`);
|
|
259
|
+
return { ok: true, slot, settled: r.settled, presets: r.presets };
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: "obsbot_tail2_preset_recall",
|
|
264
|
+
description: "Recall a Tail 2 preset slot (1–3): drives the gimbal and zoom to the saved pose. " +
|
|
265
|
+
"Refuses an empty slot. Arrival is verified through the only live observable — the " +
|
|
266
|
+
"zoom ratio from the status push — so gimbal axes are open-loop (the Tail 2 reports " +
|
|
267
|
+
"no live pose). Disable AI tracking first or it will fight the move. " +
|
|
268
|
+
"Hardware-verified 2026-09-26 (zoom 1.5 → saved 3.0 observed).",
|
|
269
|
+
schema: presetSlotSchema,
|
|
270
|
+
handler: async (args) => {
|
|
271
|
+
const { slot, camera } = presetSlotSchema.parse(args);
|
|
272
|
+
const { api } = await registry.resolve(camera);
|
|
273
|
+
const r = await api.presetRecall(slot - 1);
|
|
274
|
+
return { ok: true, slot, settled: r.settled, zoom: r.zoom };
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: "obsbot_tail2_preset_delete",
|
|
279
|
+
description: "Delete a Tail 2 preset slot (1–3), freeing it. Verified by the slot disappearing " +
|
|
280
|
+
"from the list.",
|
|
281
|
+
schema: presetSlotSchema,
|
|
282
|
+
handler: async (args) => {
|
|
283
|
+
const { slot, camera } = presetSlotSchema.parse(args);
|
|
284
|
+
const { api } = await registry.resolve(camera);
|
|
285
|
+
const r = await api.presetDelete(slot - 1);
|
|
286
|
+
return { ok: true, slot, settled: r.settled };
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
name: "obsbot_tail2_preset_rename",
|
|
291
|
+
description: "Rename a Tail 2 preset slot (1–3). Names travel base64 on the wire; the tool " +
|
|
292
|
+
"encodes/decodes transparently (up to 40 chars).",
|
|
293
|
+
schema: presetRenameSchema,
|
|
294
|
+
handler: async (args) => {
|
|
295
|
+
const { slot, name, camera } = presetRenameSchema.parse(args);
|
|
296
|
+
const { api } = await registry.resolve(camera);
|
|
297
|
+
const r = await api.presetRename(slot - 1, name);
|
|
298
|
+
return { ok: true, slot, settled: r.settled };
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
name: "obsbot_tail2_snapshot",
|
|
303
|
+
description: "Grab one still frame from a Tail 2 and return it as an image (for framing/lighting " +
|
|
304
|
+
"checks — and for measuring pixels). Source: the camera's SRT output via ffmpeg SRT " +
|
|
305
|
+
"caller (port 5000) — requires SRT listener mode in OBSBOT Center; exclusive with NDI, " +
|
|
306
|
+
"cleared by reboots, and Center only allows changing streaming settings while the output " +
|
|
307
|
+
"is off. Serves immediately once on. If SRT is off, returns a report of what it found " +
|
|
308
|
+
"with the available options and their costs (SRT toggle, NDI Webcam bridge caveats, " +
|
|
309
|
+
"NDI Tools install) so you can pick the cheapest path instead of dead-ending. " +
|
|
310
|
+
"resolution is the longest edge (256–1920, default 640); quality 1–100 (default 80). " +
|
|
311
|
+
"Needs ffmpeg. Frames are 16:9 1080p-sourced.",
|
|
312
|
+
schema: snapshotSchema,
|
|
313
|
+
handler: async (args) => {
|
|
314
|
+
const { resolution, quality, camera } = snapshotSchema.parse(args);
|
|
315
|
+
const entry = await registry.resolve(camera);
|
|
316
|
+
const status = await entry.api.status();
|
|
317
|
+
if (status.srt?.enable === true) {
|
|
318
|
+
const host = entry.hosts[0];
|
|
319
|
+
const snap = await grab(host, { maxDim: resolution, quality });
|
|
320
|
+
return {
|
|
321
|
+
content: [
|
|
322
|
+
{ type: "image", data: snap.base64, mimeType: snap.mime },
|
|
323
|
+
{
|
|
324
|
+
type: "text",
|
|
325
|
+
text: JSON.stringify({ width: snap.width, height: snap.height, source: "srt" }),
|
|
326
|
+
},
|
|
327
|
+
],
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
// SRT off. The NDI Webcam bridge is deliberately NOT attempted
|
|
331
|
+
// automatically: on the reference machine (2026-09-26) its configured
|
|
332
|
+
// output slots delivered uniformly black frames to ffmpeg while other
|
|
333
|
+
// slots hung — an auto-grab would either stall or return a confident
|
|
334
|
+
// black image. Probing the slots also disrupts the bridge app. Report
|
|
335
|
+
// options instead; SRT remains the autonomous path.
|
|
336
|
+
const ndiOn = status.ndi?.enable === true;
|
|
337
|
+
const tools = ndiToolsInstalled();
|
|
338
|
+
const lines = [
|
|
339
|
+
`No video source is currently grabbable from this Tail 2 (SRT off${ndiOn ? ", NDI on" : ", NDI off"}).`,
|
|
340
|
+
"",
|
|
341
|
+
"Options:",
|
|
342
|
+
`1. Enable SRT listener mode in OBSBOT Center for this camera (streaming settings; the toggle ` +
|
|
343
|
+
`disables NDI while active and is cleared by a camera reboot). Serves immediately once on — ` +
|
|
344
|
+
`this tool then grabs autonomously. Recommended for agent-driven use.`,
|
|
345
|
+
...(tools
|
|
346
|
+
? [
|
|
347
|
+
`2. NDI Webcam bridge (NDI Tools is installed): with NDI on and the bridge running, ` +
|
|
348
|
+
`its 'NDI Webcam' DirectShow output can be read with ffmpeg. NOT attempted automatically: ` +
|
|
349
|
+
`on the reference machine the configured slots delivered black frames and probing the ` +
|
|
350
|
+
`app's devices disrupts it. If you verify a slot carries live video (e.g. OBS can preview ` +
|
|
351
|
+
`it), this becomes a no-camera-changes source — but treat it as unverified until then.`,
|
|
352
|
+
` WARNING: never force-kill the Webcam app (taskkill /f) — that corrupts its output-slot ` +
|
|
353
|
+
`configuration; close it through its own UI.`,
|
|
354
|
+
]
|
|
355
|
+
: []),
|
|
356
|
+
`3. Install NDI Tools (https://ndi.video/tools/) for option 2, or an NDI-capable ffmpeg build.`,
|
|
357
|
+
];
|
|
358
|
+
return {
|
|
359
|
+
content: [
|
|
360
|
+
{
|
|
361
|
+
type: "text",
|
|
362
|
+
text: lines.join("\n"),
|
|
363
|
+
},
|
|
364
|
+
],
|
|
365
|
+
};
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
];
|
|
369
|
+
}
|
|
370
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/tail2/tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -60,6 +60,17 @@ export declare class HelperProcess {
|
|
|
60
60
|
value: number;
|
|
61
61
|
flags: number;
|
|
62
62
|
}>;
|
|
63
|
+
/**
|
|
64
|
+
* Time repeated GET_CUR reads of a camera control, in the helper (C), so the
|
|
65
|
+
* ~150us cache-vs-live signal is not buried by this JSON-RPC round trip. Used
|
|
66
|
+
* by LinuxTransport.livePoseReads to detect a live-reading kernel without
|
|
67
|
+
* moving the gimbal. Reads only.
|
|
68
|
+
*/
|
|
69
|
+
readLatency(property: number, samples?: number): Promise<{
|
|
70
|
+
meanUs: number;
|
|
71
|
+
minUs: number;
|
|
72
|
+
maxUs: number;
|
|
73
|
+
}>;
|
|
63
74
|
procAmpSet(property: number, value: number, flags: number): Promise<void>;
|
|
64
75
|
procAmpRange(property: number): Promise<{
|
|
65
76
|
min: number;
|
|
@@ -276,6 +276,16 @@ export class HelperProcess {
|
|
|
276
276
|
const resp = await this.rpc({ op: "camctrl_get", property });
|
|
277
277
|
return { value: resp.value, flags: resp.flags };
|
|
278
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Time repeated GET_CUR reads of a camera control, in the helper (C), so the
|
|
281
|
+
* ~150us cache-vs-live signal is not buried by this JSON-RPC round trip. Used
|
|
282
|
+
* by LinuxTransport.livePoseReads to detect a live-reading kernel without
|
|
283
|
+
* moving the gimbal. Reads only.
|
|
284
|
+
*/
|
|
285
|
+
async readLatency(property, samples = 40) {
|
|
286
|
+
const resp = await this.rpc({ op: "read_latency", property, samples });
|
|
287
|
+
return { meanUs: resp.meanUs, minUs: resp.minUs, maxUs: resp.maxUs };
|
|
288
|
+
}
|
|
279
289
|
async procAmpSet(property, value, flags) {
|
|
280
290
|
await this.rpc({ op: "procamp_set", property, value, flags });
|
|
281
291
|
}
|