obsbot-mcp 0.7.0 → 0.9.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 +111 -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/tail2/api.d.ts +381 -0
- package/dist/tail2/api.js +547 -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/move.d.ts +41 -0
- package/dist/tail2/move.js +79 -0
- package/dist/tail2/move.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 +772 -0
- package/dist/tail2/tools.js.map +1 -0
- 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,547 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
// The camera stores preset names as base64 of UTF-8 (decoded from the web
|
|
3
|
+
// bundle's own btoa-encoder, hardware-round-tripped 2026-09-26).
|
|
4
|
+
const b64encode = (s) => Buffer.from(s, "utf8").toString("base64");
|
|
5
|
+
const b64decode = (s) => Buffer.from(s, "base64").toString("utf8");
|
|
6
|
+
export class Tail2HttpError extends Error {
|
|
7
|
+
status;
|
|
8
|
+
path;
|
|
9
|
+
body;
|
|
10
|
+
constructor(status, path, body) {
|
|
11
|
+
super(`Tail 2 API ${path} failed with HTTP ${status}${body ? `: ${body}` : ""}`);
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.path = path;
|
|
14
|
+
this.body = body;
|
|
15
|
+
this.name = "Tail2HttpError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
19
|
+
export class Tail2Api {
|
|
20
|
+
baseUrl;
|
|
21
|
+
timeoutMs;
|
|
22
|
+
wsUrl;
|
|
23
|
+
constructor(opts) {
|
|
24
|
+
// Accept a bare host too — the common case is "192.168.0.132".
|
|
25
|
+
this.baseUrl = opts.baseUrl.startsWith("http") ? opts.baseUrl : `http://${opts.baseUrl}`;
|
|
26
|
+
this.timeoutMs = opts.timeoutMs ?? 5000;
|
|
27
|
+
this.wsUrl = `${this.baseUrl.replace(/^http/, "ws")}/ws/`;
|
|
28
|
+
}
|
|
29
|
+
async req(method, path, body) {
|
|
30
|
+
let res;
|
|
31
|
+
try {
|
|
32
|
+
res = await fetch(this.baseUrl + path, {
|
|
33
|
+
method,
|
|
34
|
+
headers: body !== undefined ? { "Content-Type": "application/json" } : undefined,
|
|
35
|
+
// A string body is used as-is: one endpoint (evbias) needs an exact
|
|
36
|
+
// JSON float literal that JSON.stringify cannot produce from a number.
|
|
37
|
+
body: body === undefined ? undefined : typeof body === "string" ? body : JSON.stringify(body),
|
|
38
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
throw new Error(`Tail 2 at ${this.baseUrl} is unreachable (${e instanceof Error ? e.message : String(e)}). ` +
|
|
43
|
+
`Check the camera is powered and on the network; if it changed IP, re-run obsbot_tail2_scan.`);
|
|
44
|
+
}
|
|
45
|
+
if (!res.ok) {
|
|
46
|
+
const text = await res.text().catch(() => "");
|
|
47
|
+
throw new Tail2HttpError(res.status, path, text.slice(0, 300));
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return (await res.json());
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
// HTTP-level success but a body that isn't JSON: this is some OTHER
|
|
54
|
+
// device's web server on that address, not a Tail 2. Same actionable
|
|
55
|
+
// shape as unreachable — the fix is a different address, not a retry.
|
|
56
|
+
throw new Error(`Tail 2 at ${this.baseUrl} did not reply with JSON for ${path} ` +
|
|
57
|
+
`(${e instanceof Error ? e.message : String(e)}) — the address is probably another ` +
|
|
58
|
+
`device, not a Tail 2. Re-run obsbot_tail2_scan to find the camera.`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* PUT/POST with ack checking. `body` may be an object (JSON.stringify'd)
|
|
63
|
+
* or a pre-built JSON string — evbias needs a float literal like `0.0`
|
|
64
|
+
* that stringifying a number cannot produce (measured 2026-09-30: JSON
|
|
65
|
+
* integers 0 and -1 get 400 "Invalid value type"; 0.0 and -1.0 apply).
|
|
66
|
+
*/
|
|
67
|
+
async send(method, path, body) {
|
|
68
|
+
const ack = await this.req(method, path, body);
|
|
69
|
+
// Every confirmed endpoint replies {"code":200,"err_idx":0}; a non-200
|
|
70
|
+
// code with HTTP 200 has not been observed, but checking it is free and
|
|
71
|
+
// makes an err_idx≠0 reply loud instead of silent.
|
|
72
|
+
if (typeof ack.code === "number" && ack.code !== 200) {
|
|
73
|
+
throw new Error(`${path} rejected: code=${ack.code} err_idx=${ack.err_idx ?? "?"}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Read `read()` until `match()` holds, on a bounded ladder. Returns
|
|
78
|
+
* `settled:false` (NOT a throw) when the ladder runs out — the command was
|
|
79
|
+
* sent and acknowledged, so the caller decides whether that is failure.
|
|
80
|
+
* Mirrors `obsbot_zoom_uvc`'s `settled` contract.
|
|
81
|
+
*/
|
|
82
|
+
async verified(read, match, opts = {}) {
|
|
83
|
+
const attempts = opts.attempts ?? 6;
|
|
84
|
+
const delayMs = opts.delayMs ?? 400;
|
|
85
|
+
let value = await read();
|
|
86
|
+
for (let i = 1; i < attempts && !match(value); i++) {
|
|
87
|
+
await sleep(delayMs);
|
|
88
|
+
value = await read();
|
|
89
|
+
}
|
|
90
|
+
return { settled: match(value), value };
|
|
91
|
+
}
|
|
92
|
+
// ---- reads ---------------------------------------------------------------
|
|
93
|
+
info() {
|
|
94
|
+
return this.req("GET", "/camera/sdk/device_info");
|
|
95
|
+
}
|
|
96
|
+
/** One-shot status: connect, take the first push, close. No persistent socket. */
|
|
97
|
+
status() {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const ws = new WebSocket(this.wsUrl, { handshakeTimeout: this.timeoutMs });
|
|
100
|
+
// One winner across message/error/close/timer. NOT removeAllListeners():
|
|
101
|
+
// the camera keeps pushing its ~1 Hz status while our close handshake is
|
|
102
|
+
// in flight, and a late teardown frame can raise 'error' AFTER we've
|
|
103
|
+
// stripped the handler — an unhandled 'error' on a WebSocket kills the
|
|
104
|
+
// process (measured on live hardware 2026-09-26: "Invalid WebSocket
|
|
105
|
+
// frame: invalid status code 12592"). The settled flag makes stray
|
|
106
|
+
// events no-ops; success tears the socket down with terminate() so no
|
|
107
|
+
// further frames are parsed at all.
|
|
108
|
+
let settled = false;
|
|
109
|
+
const settle = (fn) => {
|
|
110
|
+
if (settled)
|
|
111
|
+
return;
|
|
112
|
+
settled = true;
|
|
113
|
+
clearTimeout(timer);
|
|
114
|
+
fn();
|
|
115
|
+
};
|
|
116
|
+
const timer = setTimeout(() => settle(() => {
|
|
117
|
+
ws.terminate();
|
|
118
|
+
reject(new Error(`Tail 2 status channel (${this.wsUrl}) timed out. Retry this call.`));
|
|
119
|
+
}), this.timeoutMs + 2000);
|
|
120
|
+
ws.on("error", (e) => settle(() => reject(new Error(`Tail 2 status channel failed: ${e.message}`))));
|
|
121
|
+
ws.on("close", () => settle(() => reject(new Error("status channel closed before a push arrived"))));
|
|
122
|
+
ws.on("message", (data) => {
|
|
123
|
+
try {
|
|
124
|
+
const parsed = JSON.parse(data.toString());
|
|
125
|
+
settle(() => {
|
|
126
|
+
ws.terminate();
|
|
127
|
+
resolve(parsed);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
settle(() => reject(e instanceof Error ? e : new Error(String(e))));
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
ranges() {
|
|
137
|
+
return this.req("GET", "/camera/sdk/range");
|
|
138
|
+
}
|
|
139
|
+
networkConfig() {
|
|
140
|
+
return this.req("GET", "/camera/sdk/networkconfig");
|
|
141
|
+
}
|
|
142
|
+
imageState() {
|
|
143
|
+
return this.req("GET", "/camera/sdk/image");
|
|
144
|
+
}
|
|
145
|
+
// ---- PTZ -----------------------------------------------------------------
|
|
146
|
+
zoomGet() {
|
|
147
|
+
return this.req("GET", "/camera/sdk/ptz/zoom");
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Absolute zoom ratio on the Tail 2's own 1.0–12.0 scale (`GET range`).
|
|
151
|
+
* `speed` is REQUIRED by the firmware (a ratio-only PUT is a 400) — both
|
|
152
|
+
* fields MEASURED 2026-09-26.
|
|
153
|
+
*/
|
|
154
|
+
async zoomSet(ratio, speed, verify = {}) {
|
|
155
|
+
await this.send("PUT", "/camera/sdk/ptz/zoom", { ratio, speed });
|
|
156
|
+
// The zoom motor ramps; 4.5s of polling covers a full 1x→12x sweep at
|
|
157
|
+
// default speed without hanging the call on a wedged motor.
|
|
158
|
+
const v = await this.verified(() => this.zoomGet(), (r) => Math.abs(r.ratio - ratio) <= 0.01, { attempts: 10, delayMs: 500, ...verify });
|
|
159
|
+
return { settled: v.settled, ratio: v.value.ratio };
|
|
160
|
+
}
|
|
161
|
+
/** Gimbal recenter (`POST ptz/reset`). Open-loop: the Tail 2 reports no live pose. */
|
|
162
|
+
async recenter() {
|
|
163
|
+
await this.send("POST", "/camera/sdk/ptz/reset", {});
|
|
164
|
+
}
|
|
165
|
+
// ---- gimbal speed control (the joystick primitive) -------------------------
|
|
166
|
+
//
|
|
167
|
+
// POST /camera/sdk/ptz/gimbalcontrol {stop, pitch, roll, yaw} — from the
|
|
168
|
+
// vendor REST doc (SDKs/obsbot_tail_2_res_tful.zip), hardware-verified
|
|
169
|
+
// 2026-09-30: each axis −178..178, sign = direction, magnitude = SPEED.
|
|
170
|
+
// stop:true halts. This is a continuous speed command with no endpoint of
|
|
171
|
+
// its own — the camera keeps moving until stopped.
|
|
172
|
+
gimbalSpeed(yaw, pitch, roll) {
|
|
173
|
+
return this.send("POST", "/camera/sdk/ptz/gimbalcontrol", {
|
|
174
|
+
stop: false,
|
|
175
|
+
pitch,
|
|
176
|
+
roll,
|
|
177
|
+
yaw,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
gimbalStop() {
|
|
181
|
+
return this.send("POST", "/camera/sdk/ptz/gimbalcontrol", {
|
|
182
|
+
stop: true,
|
|
183
|
+
pitch: 0,
|
|
184
|
+
roll: 0,
|
|
185
|
+
yaw: 0,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
gimbalInvertGet() {
|
|
189
|
+
return this.req("GET", "/camera/sdk/ptz/gimbalinvert");
|
|
190
|
+
}
|
|
191
|
+
/** Reverse (invert) control directions. Readback-verified like every write. */
|
|
192
|
+
async gimbalInvertSet(enable, verify = {}) {
|
|
193
|
+
await this.send("PUT", "/camera/sdk/ptz/gimbalinvert", { enable });
|
|
194
|
+
const v = await this.verified(() => this.gimbalInvertGet(), (r) => r.enable === enable, verify);
|
|
195
|
+
return { settled: v.settled, enable: v.value.enable };
|
|
196
|
+
}
|
|
197
|
+
// ---- pose readback via the preset API ----------------------------------------
|
|
198
|
+
//
|
|
199
|
+
// The API reports no live pose directly, but `preset set` captures the LIVE
|
|
200
|
+
// pose into a slot and `GET preset` returns it in degrees. Save-to-scratch
|
|
201
|
+
// → read → delete is therefore a pose sensor (measured 2026-09-30: it
|
|
202
|
+
// caught a 7.9° yaw move exactly). The scratch slot must be EMPTY — save
|
|
203
|
+
// overwrites and there is no pose-by-value write to restore a clobbered
|
|
204
|
+
// preset, so a full preset bank is a hard error, not a guess.
|
|
205
|
+
/** Marker name for pose-probe scratch slots, so crashed probes are reusable. */
|
|
206
|
+
static POSE_PROBE_NAME = "pose-probe";
|
|
207
|
+
async readPose(verify = {}) {
|
|
208
|
+
let scratch;
|
|
209
|
+
const list = await this.presetsGet();
|
|
210
|
+
// A leftover probe slot from a crashed run is ours by name — delete it
|
|
211
|
+
// first and wait for it to be GONE, so the freshness check below (slot
|
|
212
|
+
// APPEARS after our save) cannot match its stale contents.
|
|
213
|
+
const leftover = list.find((p) => p.name === Tail2Api.POSE_PROBE_NAME);
|
|
214
|
+
if (leftover) {
|
|
215
|
+
await this.presetOp("delete", leftover.id);
|
|
216
|
+
await this.verified(() => this.presetsGet(), (l) => !l.some((x) => x.id === leftover.id), { attempts: 6, delayMs: 350, ...verify });
|
|
217
|
+
}
|
|
218
|
+
const fresh = await this.presetsGet();
|
|
219
|
+
const used = new Set(fresh.map((p) => p.id));
|
|
220
|
+
scratch = [0, 1, 2].find((id) => !used.has(id));
|
|
221
|
+
if (scratch === undefined) {
|
|
222
|
+
throw new Error("all three preset slots are occupied — the pose read needs an empty scratch slot. " +
|
|
223
|
+
"Delete one preset (obsbot_tail2_preset_delete) and retry.");
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
await this.presetOp("set", scratch, Tail2Api.POSE_PROBE_NAME);
|
|
227
|
+
const v = await this.verified(() => this.presetsGet(), (l) => {
|
|
228
|
+
const p = l.find((x) => x.id === scratch);
|
|
229
|
+
return p !== undefined && p.name === Tail2Api.POSE_PROBE_NAME;
|
|
230
|
+
}, { attempts: 8, delayMs: 350, ...verify });
|
|
231
|
+
const p = v.value.find((x) => x.id === scratch);
|
|
232
|
+
if (!p)
|
|
233
|
+
throw new Error("pose probe: the scratch slot vanished before it could be read");
|
|
234
|
+
return { yaw: p.yaw, pitch: p.pitch, roll: p.roll, ratio: p.ratio };
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
// Best-effort cleanup; must not mask the pose result.
|
|
238
|
+
await this.presetOp("delete", scratch).catch(() => { });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// ---- rotation (no Tiny 2 equivalent) --------------------------------------
|
|
242
|
+
/**
|
|
243
|
+
* Motorized 90° barrel rotation. Verified against the WS push rather than a
|
|
244
|
+
* REST readback (none exists) — and the readback MUST be tolerant of the
|
|
245
|
+
* motor taking ~1.5s, plus the swallowed-write hazard during motion.
|
|
246
|
+
*/
|
|
247
|
+
async portraitSet(enable, verify = {}) {
|
|
248
|
+
await this.send("POST", "/camera/sdk/switch_portrait", { enable });
|
|
249
|
+
const v = await this.verified(() => this.status(), (s) => s.switch_portrait === enable, { attempts: 12, delayMs: 450, ...verify });
|
|
250
|
+
return { settled: v.settled, switch_portrait: v.value.switch_portrait === true };
|
|
251
|
+
}
|
|
252
|
+
rollBiasGet() {
|
|
253
|
+
return this.req("GET", "/camera/sdk/ptz/rollbias");
|
|
254
|
+
}
|
|
255
|
+
/** Roll trim in degrees. MEASURED write + readback 2026-09-26. */
|
|
256
|
+
async rollBiasSet(angle, verify = {}) {
|
|
257
|
+
await this.send("PUT", "/camera/sdk/ptz/rollbias", { angle });
|
|
258
|
+
const v = await this.verified(() => this.rollBiasGet(), (r) => Math.abs(r.angle - angle) <= 0.01, verify);
|
|
259
|
+
return { settled: v.settled, angle: v.value.angle };
|
|
260
|
+
}
|
|
261
|
+
// ---- AI tracking -----------------------------------------------------------
|
|
262
|
+
aiModeGet() {
|
|
263
|
+
return this.req("GET", "/camera/sdk/ai/workmode");
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* `mode` strings are the camera's own enum (bundle-verified, TAIL2-PROTOCOL
|
|
267
|
+
* §3): none | humanTrackingSingleMode | humanTrackingGroupMode |
|
|
268
|
+
* animalTrackingNormal | animalTrackingCloseUp | objectTracking |
|
|
269
|
+
* objectTrackingNormal | objectTrackingCloseUp.
|
|
270
|
+
*/
|
|
271
|
+
async aiModeSet(mode, verify = {}) {
|
|
272
|
+
await this.send("PUT", "/camera/sdk/ai/workmode", { mode });
|
|
273
|
+
const v = await this.verified(() => this.aiModeGet(), (r) => r.mode === mode, verify);
|
|
274
|
+
return { settled: v.settled, mode: v.value.mode };
|
|
275
|
+
}
|
|
276
|
+
trackSpeedGet() {
|
|
277
|
+
return this.req("GET", "/camera/sdk/ai/trackspeed");
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* The Tail 2's own six-speed enum (bundle-verified): superLazy | lazy |
|
|
281
|
+
* slow | fast | crazy | customized. NOT the Tiny 2's standard/sport pair.
|
|
282
|
+
*/
|
|
283
|
+
async trackSpeedSet(speed, verify = {}) {
|
|
284
|
+
await this.send("PUT", "/camera/sdk/ai/trackspeed", { speed });
|
|
285
|
+
const v = await this.verified(() => this.trackSpeedGet(), (r) => r.speed === speed, verify);
|
|
286
|
+
return { settled: v.settled, speed: v.value.speed };
|
|
287
|
+
}
|
|
288
|
+
// ---- record / capture --------------------------------------------------------
|
|
289
|
+
//
|
|
290
|
+
// From the vendor REST doc, hardware-verified 2026-09-30: record is a
|
|
291
|
+
// {"recording":"on"|"off"} switch (GET read back "off" with no SD card —
|
|
292
|
+
// state is readable regardless; starting without media will fail), and
|
|
293
|
+
// capture is a body-less POST trigger.
|
|
294
|
+
recordGet() {
|
|
295
|
+
return this.req("GET", "/camera/sdk/record/control");
|
|
296
|
+
}
|
|
297
|
+
async recordSet(on, verify = {}) {
|
|
298
|
+
const want = on ? "on" : "off";
|
|
299
|
+
await this.send("PUT", "/camera/sdk/record/control", { recording: want });
|
|
300
|
+
const v = await this.verified(() => this.recordGet(), (r) => r.recording === want, verify);
|
|
301
|
+
return { settled: v.settled, recording: v.value.recording };
|
|
302
|
+
}
|
|
303
|
+
/** Still-photo trigger. Needs storage; the ack says the camera accepted it. */
|
|
304
|
+
captureTrigger() {
|
|
305
|
+
return this.send("POST", "/camera/sdk/capture/trigger", {});
|
|
306
|
+
}
|
|
307
|
+
// ---- focus ---------------------------------------------------------------------
|
|
308
|
+
//
|
|
309
|
+
// af/mode is {"mode":"afc"|"afs"|"mf"}; af/motorposition is 0-100 and is
|
|
310
|
+
// MODE-GATED: HTTP 500 while the mode is not mf (measured — the value does
|
|
311
|
+
// not exist outside manual focus).
|
|
312
|
+
focusModeGet() {
|
|
313
|
+
return this.req("GET", "/camera/sdk/image/af/mode");
|
|
314
|
+
}
|
|
315
|
+
async focusModeSet(mode, verify = {}) {
|
|
316
|
+
await this.send("PUT", "/camera/sdk/image/af/mode", { mode });
|
|
317
|
+
const v = await this.verified(() => this.focusModeGet(), (r) => r.mode === mode, verify);
|
|
318
|
+
return { settled: v.settled, mode: v.value.mode };
|
|
319
|
+
}
|
|
320
|
+
focusPositionGet() {
|
|
321
|
+
return this.req("GET", "/camera/sdk/image/af/motorposition");
|
|
322
|
+
}
|
|
323
|
+
async focusPositionSet(position, verify = {}) {
|
|
324
|
+
await this.send("PUT", "/camera/sdk/image/af/motorposition", { position });
|
|
325
|
+
const v = await this.verified(() => this.focusPositionGet(), (r) => Math.abs(r.position - position) <= 1, verify);
|
|
326
|
+
return { settled: v.settled, position: v.value.position };
|
|
327
|
+
}
|
|
328
|
+
// ---- tap-to-focus / tap-to-track ------------------------------------------------
|
|
329
|
+
//
|
|
330
|
+
// Both take NORMALIZED frame coordinates (0.01-0.99, top-left 0.01/0.01),
|
|
331
|
+
// so a pixel from obsbot_tail2_snapshot divides by frameWidth/frameHeight
|
|
332
|
+
// and lands directly — the geometry-free aiming loop.
|
|
333
|
+
focusWindowGet() {
|
|
334
|
+
return this.req("GET", "/camera/sdk/image/af/windowcenter");
|
|
335
|
+
}
|
|
336
|
+
/** Move the focus window AND start a point focus (per the vendor doc). AFC/AFS only. */
|
|
337
|
+
async focusWindowSet(x, y, verify = {}) {
|
|
338
|
+
await this.send("PUT", "/camera/sdk/image/af/windowcenter", { x, y });
|
|
339
|
+
const v = await this.verified(() => this.focusWindowGet(), (r) => Math.abs(r.x - x) <= 0.01 && Math.abs(r.y - y) <= 0.01, verify);
|
|
340
|
+
return { settled: v.settled, x: v.value.x, y: v.value.y };
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Tap-to-track (MEASURED 2026-09-30, undocumented in the Tail 2's own doc):
|
|
344
|
+
* POST a normalized coordinate and the camera engages tracking on the
|
|
345
|
+
* subject there — from mode none it armed humanTrackingSingleMode on its
|
|
346
|
+
* own. Fire-and-ack; which mode engages depends on what (if anything) is
|
|
347
|
+
* at the coordinate, so the caller reads ai mode back when it matters.
|
|
348
|
+
*/
|
|
349
|
+
async targetSelect(x, y) {
|
|
350
|
+
await this.send("POST", "/camera/sdk/ai/workmode/normaltrack/targetselect", { x, y });
|
|
351
|
+
}
|
|
352
|
+
// ---- exposure --------------------------------------------------------------------
|
|
353
|
+
//
|
|
354
|
+
// mode manual|auto. In AUTO: auto/mode global|face (face-priority AE) and
|
|
355
|
+
// evbias (-3.0..3.0 in 0.3-ish steps). In MANUAL: iso and shutter ("1/N"
|
|
356
|
+
// string). The manual values are MODE-GATED: HTTP 500 while exposure is
|
|
357
|
+
// auto (measured). Manual ISO read back 894 on first switch — a live-AE
|
|
358
|
+
// inherited value, NOT the doc's "increment of 100"; the range is real but
|
|
359
|
+
// the granularity claim is not.
|
|
360
|
+
exposureModeGet() {
|
|
361
|
+
return this.req("GET", "/camera/sdk/image/exposure/mode");
|
|
362
|
+
}
|
|
363
|
+
async exposureModeSet(mode, verify = {}) {
|
|
364
|
+
await this.send("PUT", "/camera/sdk/image/exposure/mode", { mode });
|
|
365
|
+
const v = await this.verified(() => this.exposureModeGet(), (r) => r.mode === mode, verify);
|
|
366
|
+
return { settled: v.settled, mode: v.value.mode };
|
|
367
|
+
}
|
|
368
|
+
exposureAutoModeGet() {
|
|
369
|
+
return this.req("GET", "/camera/sdk/image/exposure/auto/mode");
|
|
370
|
+
}
|
|
371
|
+
async exposureAutoModeSet(mode, verify = {}) {
|
|
372
|
+
await this.send("PUT", "/camera/sdk/image/exposure/auto/mode", { mode });
|
|
373
|
+
const v = await this.verified(() => this.exposureAutoModeGet(), (r) => r.mode === mode, verify);
|
|
374
|
+
return { settled: v.settled, mode: v.value.mode };
|
|
375
|
+
}
|
|
376
|
+
exposureEvbiasGet() {
|
|
377
|
+
return this.req("GET", "/camera/sdk/image/exposure/auto/compensation");
|
|
378
|
+
}
|
|
379
|
+
async exposureEvbiasSet(evbias, verify = {}) {
|
|
380
|
+
// The firmware rejects JSON integers here (400 "Invalid value type" —
|
|
381
|
+
// measured: 0 and -1 rejected, 0.0 and -1.0 applied), and some integer
|
|
382
|
+
// bodies ACK without ever applying. Always send a decimal-point float.
|
|
383
|
+
await this.send("PUT", "/camera/sdk/image/exposure/auto/compensation", `{"evbias":${evbias.toFixed(1)}}`);
|
|
384
|
+
const v = await this.verified(() => this.exposureEvbiasGet(), (r) => Math.abs(r.evbias - evbias) < 0.05, verify);
|
|
385
|
+
return { settled: v.settled, evbias: v.value.evbias };
|
|
386
|
+
}
|
|
387
|
+
exposureIsoGet() {
|
|
388
|
+
return this.req("GET", "/camera/sdk/image/exposure/manual/iso");
|
|
389
|
+
}
|
|
390
|
+
async exposureIsoSet(iso, verify = {}) {
|
|
391
|
+
await this.send("PUT", "/camera/sdk/image/exposure/manual/iso", { iso });
|
|
392
|
+
const v = await this.verified(() => this.exposureIsoGet(), (r) => Math.abs(r.iso - iso) < 1, verify);
|
|
393
|
+
return { settled: v.settled, iso: v.value.iso };
|
|
394
|
+
}
|
|
395
|
+
exposureShutterGet() {
|
|
396
|
+
return this.req("GET", "/camera/sdk/image/exposure/manual/shuttertime");
|
|
397
|
+
}
|
|
398
|
+
async exposureShutterSet(shutter, verify = {}) {
|
|
399
|
+
await this.send("PUT", "/camera/sdk/image/exposure/manual/shuttertime", { shutter });
|
|
400
|
+
const v = await this.verified(() => this.exposureShutterGet(), (r) => r.shutter === shutter, verify);
|
|
401
|
+
return { settled: v.settled, shutter: v.value.shutter };
|
|
402
|
+
}
|
|
403
|
+
// ---- image style / hdr / white balance ---------------------------------------------
|
|
404
|
+
//
|
|
405
|
+
// style/<control> GETs return {mode, <control>} (the mode rides along);
|
|
406
|
+
// style/mode GET returns the whole bundle. Values are absolute 0-100.
|
|
407
|
+
styleGet() {
|
|
408
|
+
return this.req("GET", "/camera/sdk/image/style/mode");
|
|
409
|
+
}
|
|
410
|
+
async styleSet(control, value, verify = {}) {
|
|
411
|
+
await this.send("PUT", `/camera/sdk/image/style/${control}`, { [control]: value });
|
|
412
|
+
const v = await this.verified(() => this.req("GET", `/camera/sdk/image/style/${control}`), (r) => r[control] === value, verify);
|
|
413
|
+
return { settled: v.settled, value: v.value[control], mode: v.value.mode };
|
|
414
|
+
}
|
|
415
|
+
async styleModeSet(mode, verify = {}) {
|
|
416
|
+
// The PUT takes the mode with the full value bundle — send the current
|
|
417
|
+
// values alongside so a mode switch never stomps the adjustments.
|
|
418
|
+
const current = await this.styleGet();
|
|
419
|
+
await this.send("PUT", "/camera/sdk/image/style/mode", { ...current, mode });
|
|
420
|
+
const v = await this.verified(() => this.styleGet(), (r) => r.mode === mode, verify);
|
|
421
|
+
return { settled: v.settled, mode: v.value.mode };
|
|
422
|
+
}
|
|
423
|
+
hdrGet() {
|
|
424
|
+
return this.req("GET", "/camera/sdk/image/hdr/control");
|
|
425
|
+
}
|
|
426
|
+
async hdrSet(on, verify = {}) {
|
|
427
|
+
const want = on ? "on" : "off";
|
|
428
|
+
await this.send("PUT", "/camera/sdk/image/hdr/control", { control: want });
|
|
429
|
+
const v = await this.verified(() => this.hdrGet(), (r) => r.control === want, verify);
|
|
430
|
+
return { settled: v.settled, control: v.value.control };
|
|
431
|
+
}
|
|
432
|
+
wbConfigGet() {
|
|
433
|
+
return this.req("GET", "/camera/sdk/image/whitebalance/config");
|
|
434
|
+
}
|
|
435
|
+
async wbConfigSet(mode, temperature, verify = {}) {
|
|
436
|
+
// Temperature only matters in manual mode (doc), but sending the current
|
|
437
|
+
// one alongside is harmless and keeps the readback honest.
|
|
438
|
+
const current = await this.wbConfigGet();
|
|
439
|
+
await this.send("PUT", "/camera/sdk/image/whitebalance/config", {
|
|
440
|
+
mode,
|
|
441
|
+
temperature: temperature ?? current.temperature,
|
|
442
|
+
});
|
|
443
|
+
const v = await this.verified(() => this.wbConfigGet(), (r) => r.mode === mode && (temperature === undefined || Math.abs(r.temperature - temperature) <= 1), verify);
|
|
444
|
+
return { settled: v.settled, mode: v.value.mode, temperature: v.value.temperature };
|
|
445
|
+
}
|
|
446
|
+
// ---- streaming output select ---------------------------------------------------
|
|
447
|
+
//
|
|
448
|
+
// {"control":"ndi"|"rtsp"|"srt"|"off"} — exactly ONE active output. Setting
|
|
449
|
+
// srt displaces ndi (the exclusivity TAIL2-PROTOCOL §7a/§8 already
|
|
450
|
+
// measured); this is the programmatic way to arm SRT for snapshots.
|
|
451
|
+
streamControlGet() {
|
|
452
|
+
return this.req("GET", "/camera/sdk/ndi-rtsp-srt/control");
|
|
453
|
+
}
|
|
454
|
+
async streamControlSet(control, verify = {}) {
|
|
455
|
+
await this.send("PUT", "/camera/sdk/ndi-rtsp-srt/control", { control });
|
|
456
|
+
const v = await this.verified(() => this.streamControlGet(), (r) => r.control === control, verify);
|
|
457
|
+
return { settled: v.settled, control: v.value.control };
|
|
458
|
+
}
|
|
459
|
+
// ---- tracking extra / audio ------------------------------------------------------
|
|
460
|
+
onlyMeGet() {
|
|
461
|
+
return this.req("GET", "/camera/sdk/ai/human/onlyme");
|
|
462
|
+
}
|
|
463
|
+
async onlyMeSet(enable, verify = {}) {
|
|
464
|
+
await this.send("PUT", "/camera/sdk/ai/human/onlyme", { enable });
|
|
465
|
+
const v = await this.verified(() => this.onlyMeGet(), (r) => r.enable === enable, verify);
|
|
466
|
+
return { settled: v.settled, enable: v.value.enable };
|
|
467
|
+
}
|
|
468
|
+
audioVolumeGet() {
|
|
469
|
+
return this.req("GET", "/camera/sdk/audio/input/volume");
|
|
470
|
+
}
|
|
471
|
+
async audioVolumeSet(volume, verify = {}) {
|
|
472
|
+
await this.send("PUT", "/camera/sdk/audio/input/volume", { volume });
|
|
473
|
+
const v = await this.verified(() => this.audioVolumeGet(), (r) => Math.abs(r.volume - volume) <= 1, verify);
|
|
474
|
+
return { settled: v.settled, volume: v.value.volume };
|
|
475
|
+
}
|
|
476
|
+
audioMuteGet() {
|
|
477
|
+
return this.req("GET", "/camera/sdk/audio/input/mute");
|
|
478
|
+
}
|
|
479
|
+
async audioMuteSet(enable, verify = {}) {
|
|
480
|
+
await this.send("PUT", "/camera/sdk/audio/input/mute", { enable });
|
|
481
|
+
const v = await this.verified(() => this.audioMuteGet(), (r) => r.enable === enable, verify);
|
|
482
|
+
return { settled: v.settled, enable: v.value.enable };
|
|
483
|
+
}
|
|
484
|
+
// ---- presets ----------------------------------------------------------------
|
|
485
|
+
/**
|
|
486
|
+
* A saved pose: angles in DEGREES plus zoom ratio — the same units the
|
|
487
|
+
* tools speak. `name` is base64 on the wire; encode/decode lives here so
|
|
488
|
+
* callers never see it raw.
|
|
489
|
+
*/
|
|
490
|
+
async presetsGet() {
|
|
491
|
+
const r = await this.req("GET", "/camera/sdk/ptz/preset");
|
|
492
|
+
return (r.presetList ?? []).map((p) => ({ ...p, name: b64decode(p.name) }));
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* `PUT ptz/preset {"operation": ..., "id": ..., "name": base64}` — one
|
|
496
|
+
* endpoint, four operations (grammar decoded from the camera's own web
|
|
497
|
+
* bundle and hardware-verified 2026-09-26; see TAIL2-PROTOCOL.md §3).
|
|
498
|
+
* IDs are 0-based (0–2); the tools expose 1-based slots for consistency
|
|
499
|
+
* with the Tiny 2's preset tools.
|
|
500
|
+
*
|
|
501
|
+
* `set` saves the CURRENT live pose — there is NO explicit-pose write
|
|
502
|
+
* anywhere in this API (measured), and `set` on an occupied slot
|
|
503
|
+
* OVERWRITES it (unlike the Tiny 2's create-once slots).
|
|
504
|
+
*/
|
|
505
|
+
async presetOp(operation, id, name) {
|
|
506
|
+
await this.send("PUT", "/camera/sdk/ptz/preset", {
|
|
507
|
+
operation,
|
|
508
|
+
id,
|
|
509
|
+
...(name !== undefined ? { name: b64encode(name) } : {}),
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Save the current live pose into slot `id`. The preset LIST lags the
|
|
514
|
+
* write by up to ~1s (measured), so verification is a ladder, and the
|
|
515
|
+
* pose itself can't be verified at all — the camera reports no live pose,
|
|
516
|
+
* so "the slot exists afterwards" is the honest success criterion.
|
|
517
|
+
*/
|
|
518
|
+
async presetSave(id, name, verify = {}) {
|
|
519
|
+
await this.presetOp("set", id, name);
|
|
520
|
+
const v = await this.verified(() => this.presetsGet(), (list) => list.some((p) => p.id === id), { attempts: 6, delayMs: 350, ...verify });
|
|
521
|
+
return { settled: v.settled, presets: v.value };
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Recall slot `id`. Physical arrival is verifiable through exactly one
|
|
525
|
+
* observable: the preset's zoom ratio landing in the live zoom_ratio (the
|
|
526
|
+
* WS push carries it). The gimbal axes themselves are open-loop.
|
|
527
|
+
*/
|
|
528
|
+
async presetRecall(id, verify = {}) {
|
|
529
|
+
const target = (await this.presetsGet()).find((p) => p.id === id);
|
|
530
|
+
if (!target)
|
|
531
|
+
throw new Error(`preset slot ${id} is empty — nothing to recall`);
|
|
532
|
+
await this.presetOp("call", id);
|
|
533
|
+
const v = await this.verified(() => this.zoomGet(), (r) => Math.abs(r.ratio - target.ratio) <= 0.05, { attempts: 14, delayMs: 450, ...verify });
|
|
534
|
+
return { settled: v.settled, zoom: v.value.ratio };
|
|
535
|
+
}
|
|
536
|
+
async presetDelete(id, verify = {}) {
|
|
537
|
+
await this.presetOp("delete", id);
|
|
538
|
+
const v = await this.verified(() => this.presetsGet(), (list) => !list.some((p) => p.id === id), { attempts: 6, delayMs: 350, ...verify });
|
|
539
|
+
return { settled: v.settled, presets: v.value };
|
|
540
|
+
}
|
|
541
|
+
async presetRename(id, name, verify = {}) {
|
|
542
|
+
await this.presetOp("rename", id, name);
|
|
543
|
+
const v = await this.verified(() => this.presetsGet(), (list) => (list.find((p) => p.id === id)?.name ?? null) === name, { attempts: 6, delayMs: 350, ...verify });
|
|
544
|
+
return { settled: v.settled, presets: v.value };
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/tail2/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Passive mDNS discovery for the OBSBOT Tail 2.
|
|
3
|
+
*
|
|
4
|
+
* The Tail 2 has no mDNS query responder — it ANNOUNCES. Every few seconds it
|
|
5
|
+
* multicasts a response-framed packet to 224.0.0.251:5353 (IPv4, and the same
|
|
6
|
+
* digest over IPv6 link-local) carrying, in the TXT record of its
|
|
7
|
+
* `_remo_mdns._tcp.local` service, a full device digest: both MACs, both IPs,
|
|
8
|
+
* the hex-encoded device name, battery and sleep flags. Measured 2026-09-30 by
|
|
9
|
+
* capturing OBSBOT Center's discovery: Center sent no queries either — the
|
|
10
|
+
* announcements alone are how everything on the LAN finds the camera.
|
|
11
|
+
* TAIL2-PROTOCOL.md §2a.
|
|
12
|
+
*
|
|
13
|
+
* So discovery here is a listen, not a sweep: bind 5353 with SO_REUSEADDR
|
|
14
|
+
* (the OS's own responder holds the port on every platform; multicast
|
|
15
|
+
* delivery to multiple REUSEADDR sockets is uniform across Windows, Linux
|
|
16
|
+
* and macOS — it is how NDI tools coexist), join the group on every
|
|
17
|
+
* non-internal IPv4 interface, and collect Tail 2-shaped announcements.
|
|
18
|
+
* TAIL2-PROTOCOL.md §7a.
|
|
19
|
+
*/
|
|
20
|
+
/** What one camera's announcement tells us — identity straight from the camera. */
|
|
21
|
+
export interface Tail2Announcement {
|
|
22
|
+
mac: string;
|
|
23
|
+
name: string;
|
|
24
|
+
/** Wired first, then wireless, then the address the packet arrived from. */
|
|
25
|
+
hosts: string[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Extract a Tail 2 announcement from one mDNS packet, or null for anything
|
|
29
|
+
* else on the wire. Detection is by the digest's own shape — TXT strings that
|
|
30
|
+
* JSON-parse into an object carrying a `ble_mac` (the registry's identity
|
|
31
|
+
* MAC) plus a reachable IP — so other devices' mDNS traffic parses to nothing
|
|
32
|
+
* and registers nothing. `srcIp` is where the packet came from; it becomes a
|
|
33
|
+
* host of last resort and a live-reachability hint.
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseTail2Announcement(buf: Buffer, srcIp?: string): Tail2Announcement | null;
|
|
36
|
+
/** Accumulates announcements across packets, keyed by MAC. Testable without
|
|
37
|
+
* sockets: push() buffers, results() the deduplicated list. */
|
|
38
|
+
export declare function createAnnouncementCollector(): {
|
|
39
|
+
push(buf: Buffer, srcIp?: string): void;
|
|
40
|
+
results(): Tail2Announcement[];
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Listen for Tail 2 announcements for `ms` milliseconds (the camera repeats
|
|
44
|
+
* every few seconds, so 5s is ample) and return what was heard. Never throws:
|
|
45
|
+
* a bind failure or a network without multicast yields an empty list, and the
|
|
46
|
+
* caller falls back to the HTTP sweep.
|
|
47
|
+
*/
|
|
48
|
+
export declare function listenForTail2Announcements(ms?: number): Promise<Tail2Announcement[]>;
|