iobroker.agent-dvr 0.1.0 → 0.2.1

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/build/main.js CHANGED
@@ -282,6 +282,7 @@ window.ADVscan=scan;scan();
282
282
  `.trim();
283
283
  class AgentDvr extends utils.Adapter {
284
284
  pollTimer = void 0;
285
+ pollBusy = false;
285
286
  refreshTimer = void 0;
286
287
  authHeader = null;
287
288
  baseUrl = "";
@@ -291,6 +292,7 @@ class AgentDvr extends utils.Adapter {
291
292
  ptzActive = /* @__PURE__ */ new Map();
292
293
  widgetSig = {};
293
294
  profileSig = "";
295
+ lastCamNames = [];
294
296
  lastEventFn = {};
295
297
  camAspect = {};
296
298
  devById = /* @__PURE__ */ new Map();
@@ -352,47 +354,87 @@ class AgentDvr extends utils.Adapter {
352
354
  callback();
353
355
  }
354
356
  onMessage(obj) {
355
- if (!obj || obj.command !== "getGo2rtcStreams") {
357
+ if (!obj) {
356
358
  return;
357
359
  }
358
- void this.fetchGo2rtcStreams().then((streams) => {
359
- this.sendTo(obj.from, obj.command, { streams }, obj.callback);
360
- });
360
+ if (obj.command === "getGo2rtcStreams") {
361
+ const overrideUrl = typeof obj.message === "object" && obj.message !== null ? obj.message.url : void 0;
362
+ void this.fetchGo2rtcStreams(overrideUrl).then((result) => {
363
+ this.sendTo(obj.from, obj.command, result, obj.callback);
364
+ });
365
+ } else if (obj.command === "getAgentDvrCameras") {
366
+ this.sendTo(obj.from, obj.command, { cameras: this.lastCamNames }, obj.callback);
367
+ } else if (obj.command === "getAvailableSources") {
368
+ void this.fetchAvailableSources().then((result) => {
369
+ this.sendTo(obj.from, obj.command, result, obj.callback);
370
+ });
371
+ }
361
372
  }
362
- fetchGo2rtcStreams() {
363
- return new Promise((resolve) => {
364
- const url = this.config.go2rtcUrl;
365
- if (!url) {
366
- resolve([]);
367
- return;
368
- }
369
- let target;
370
- try {
371
- target = new URL("/api/streams", url);
372
- } catch {
373
- resolve([]);
374
- return;
375
- }
373
+ async fetchAvailableSources() {
374
+ const deadline = new Promise(
375
+ (res) => this.setTimeout(() => res({ result: [{ source: "!", name: "Timeout" }] }), 5e3)
376
+ );
377
+ return Promise.race([this._doFetchAvailableSources(), deadline]);
378
+ }
379
+ async _doFetchAvailableSources() {
380
+ const rows = [];
381
+ for (const c of this.lastCamNames) {
382
+ rows.push({ source: "AgentDVR", name: `${c.name} (${c.key})` });
383
+ }
384
+ const { streams } = await this.fetchGo2rtcStreams();
385
+ for (const s of streams) {
386
+ rows.push({ source: "go2rtc", name: s });
387
+ }
388
+ if (!rows.length) {
389
+ rows.push({ source: "\u2014", name: "No sources found (adapter has not polled yet)" });
390
+ }
391
+ return { result: rows };
392
+ }
393
+ fetchGo2rtcStreams(overrideUrl) {
394
+ const url = (overrideUrl || this.config.go2rtcUrl || "").replace(/^https:\/\//i, "http://");
395
+ if (!url) {
396
+ return Promise.resolve({ streams: [], error: "go2rtcUrl not configured" });
397
+ }
398
+ let target;
399
+ try {
400
+ target = new URL("/api/streams", url);
401
+ } catch {
402
+ return Promise.resolve({ streams: [], error: `Invalid URL: ${url}` });
403
+ }
404
+ let httpReq;
405
+ const fetch = new Promise((resolve) => {
376
406
  const mod = target.protocol === "https:" ? https : http;
377
- const req = mod.get(target.toString(), (res) => {
407
+ httpReq = mod.get(target.toString(), (res) => {
378
408
  let body = "";
379
409
  res.on("data", (c) => {
380
410
  body += c.toString();
381
411
  });
382
412
  res.on("end", () => {
383
413
  try {
384
- resolve(Object.keys(JSON.parse(body) || {}));
414
+ const parsed = JSON.parse(body);
415
+ const streams = Object.keys(parsed || {});
416
+ resolve({
417
+ streams,
418
+ error: streams.length === 0 ? "go2rtc reachable but no streams configured" : void 0
419
+ });
385
420
  } catch {
386
- resolve([]);
421
+ resolve({
422
+ streams: [],
423
+ error: `Invalid JSON response from go2rtc (HTTP ${res.statusCode})`
424
+ });
387
425
  }
388
426
  });
389
- res.on("error", () => resolve([]));
390
- });
391
- req.setTimeout(4e3, () => {
392
- req.destroy();
427
+ res.on("error", (e) => resolve({ streams: [], error: `Stream error: ${e.message}` }));
393
428
  });
394
- req.on("error", () => resolve([]));
429
+ httpReq.on("error", (e) => resolve({ streams: [], error: `Connection error: ${e.message}` }));
395
430
  });
431
+ const timeout = new Promise(
432
+ (resolve) => this.setTimeout(() => {
433
+ httpReq == null ? void 0 : httpReq.destroy();
434
+ resolve({ streams: [], error: `Timeout fetching ${target.toString()}` });
435
+ }, 3e3)
436
+ );
437
+ return Promise.race([fetch, timeout]);
396
438
  }
397
439
  onStateChange(id, state) {
398
440
  if (!state || state.ack !== false) {
@@ -1119,93 +1161,103 @@ class AgentDvr extends utils.Adapter {
1119
1161
  // ---- main poll ----
1120
1162
  async poll() {
1121
1163
  var _a, _b;
1122
- const res = await this.apiGet("/command/getObjects");
1123
- const json = asJson(res.data);
1124
- if (!res.ok || !json) {
1125
- await this.setStateAsync("info.connection", false, true);
1126
- await this.setStateAsync("system.online", { val: false, ack: true });
1127
- this.log.warn(`AgentDVR unreachable: ${res.error || "invalid response"}`);
1164
+ if (this.pollBusy) {
1165
+ this.log.debug("poll() skipped \u2014 previous run still in progress");
1128
1166
  return;
1129
1167
  }
1130
- await this.setStateAsync("info.connection", true, true);
1131
- await this.setStateAsync("system.online", { val: true, ack: true });
1132
- if (json.settings) {
1133
- await this.flattenWrite(json.settings, "system.settings", 0);
1134
- }
1135
- if (this.config.storeRawJson) {
1136
- const clean = { ...json };
1137
- delete clean.icons;
1138
- await this.writeLeaf("system.raw_getObjects", JSON.stringify(clean).slice(0, 6e4));
1139
- }
1140
- const devices = findDevices(json);
1141
- await this.setStateAsync("system.cameraCount", { val: devices.filter((d) => d.ot === 2).length, ack: true });
1142
- const activeFolders = new Set(devices.map((d) => this.deviceFolder(d)));
1143
- for (const d of devices) {
1144
- await this.buildDevice(d);
1145
- }
1146
- await this.pruneStaleDevices(activeFolders);
1147
- const stats = asJson((await this.apiGet("/command/getSystemStats")).data);
1148
- if (stats) {
1149
- await this.flattenWrite(stats, "system.stats", 0);
1150
- const gb = parseSizeGb(stats.disk_free);
1151
- if (gb !== null) {
1152
- await this.writeLeaf("system.disk_free_gb", gb);
1168
+ this.pollBusy = true;
1169
+ try {
1170
+ const res = await this.apiGet("/command/getObjects");
1171
+ const json = asJson(res.data);
1172
+ if (!res.ok || !json) {
1173
+ await this.setStateAsync("info.connection", false, true);
1174
+ await this.setStateAsync("system.online", { val: false, ack: true });
1175
+ this.log.warn(`AgentDVR unreachable: ${res.error || "invalid response"}`);
1176
+ return;
1153
1177
  }
1154
- }
1155
- const status = asJson((await this.apiGet("/command/getStatus")).data);
1156
- if (status) {
1157
- await this.flattenWrite(status, "system.status", 0);
1158
- }
1159
- if (this.config.enableSystemControls && Array.isArray(json.profiles)) {
1160
- const states = {};
1161
- let activeInd = null;
1162
- for (const p of json.profiles) {
1163
- if (p && typeof p === "object") {
1164
- const po = p;
1165
- const ind = (_b = (_a = po.id) != null ? _a : po.ind) != null ? _b : po.index;
1166
- const pname = po.name;
1167
- if (typeof ind === "number" && (typeof pname === "string" || typeof pname === "number")) {
1168
- states[ind] = String(pname);
1169
- if (po.active === true) {
1170
- activeInd = ind;
1171
- }
1172
- }
1178
+ await this.setStateAsync("info.connection", true, true);
1179
+ await this.setStateAsync("system.online", { val: true, ack: true });
1180
+ if (json.settings) {
1181
+ await this.flattenWrite(json.settings, "system.settings", 0);
1182
+ }
1183
+ if (this.config.storeRawJson) {
1184
+ const clean = { ...json };
1185
+ delete clean.icons;
1186
+ await this.writeLeaf("system.raw_getObjects", JSON.stringify(clean).slice(0, 6e4));
1187
+ }
1188
+ const devices = findDevices(json);
1189
+ this.lastCamNames = devices.filter((d) => d.ot === 2).map((d) => ({ key: this.deviceFolder(d), name: d.name }));
1190
+ await this.setStateAsync("system.cameraCount", { val: devices.filter((d) => d.ot === 2).length, ack: true });
1191
+ const activeFolders = new Set(devices.map((d) => this.deviceFolder(d)));
1192
+ for (const d of devices) {
1193
+ await this.buildDevice(d);
1194
+ }
1195
+ await this.pruneStaleDevices(activeFolders);
1196
+ const stats = asJson((await this.apiGet("/command/getSystemStats")).data);
1197
+ if (stats) {
1198
+ await this.flattenWrite(stats, "system.stats", 0);
1199
+ const gb = parseSizeGb(stats.disk_free);
1200
+ if (gb !== null) {
1201
+ await this.writeLeaf("system.disk_free_gb", gb);
1173
1202
  }
1174
1203
  }
1175
- if (Object.keys(states).length > 0) {
1176
- const sig = JSON.stringify(states);
1177
- if (sig !== this.profileSig) {
1178
- this.profileSig = sig;
1179
- await this.extendObjectAsync("system.profile.selector", { common: { states } });
1180
- await this.setStateAsync("system.profile.list", { val: sig, ack: true });
1204
+ const status = asJson((await this.apiGet("/command/getStatus")).data);
1205
+ if (status) {
1206
+ await this.flattenWrite(status, "system.status", 0);
1207
+ }
1208
+ if (this.config.enableSystemControls && Array.isArray(json.profiles)) {
1209
+ const states = {};
1210
+ let activeInd = null;
1211
+ for (const p of json.profiles) {
1212
+ if (p && typeof p === "object") {
1213
+ const po = p;
1214
+ const ind = (_b = (_a = po.id) != null ? _a : po.ind) != null ? _b : po.index;
1215
+ const pname = po.name;
1216
+ if (typeof ind === "number" && (typeof pname === "string" || typeof pname === "number")) {
1217
+ states[ind] = String(pname);
1218
+ if (po.active === true) {
1219
+ activeInd = ind;
1220
+ }
1221
+ }
1222
+ }
1181
1223
  }
1182
- if (activeInd !== null) {
1183
- await this.setStateAsync("system.profile.selector", { val: activeInd, ack: true });
1224
+ if (Object.keys(states).length > 0) {
1225
+ const sig = JSON.stringify(states);
1226
+ if (sig !== this.profileSig) {
1227
+ this.profileSig = sig;
1228
+ await this.extendObjectAsync("system.profile.selector", { common: { states } });
1229
+ await this.setStateAsync("system.profile.list", { val: sig, ack: true });
1230
+ }
1231
+ if (activeInd !== null) {
1232
+ await this.setStateAsync("system.profile.selector", { val: activeInd, ack: true });
1233
+ }
1184
1234
  }
1185
1235
  }
1186
- }
1187
- if (this.config.enableOverview) {
1188
- const cams = devices.filter((d) => d.ot === 2);
1189
- const ovId = "overview";
1190
- if (!this.ensuredFolders.has(ovId)) {
1191
- await this.setObjectNotExistsAsync(ovId, {
1192
- type: "state",
1193
- common: {
1194
- name: "Overview (all cameras)",
1195
- type: "string",
1196
- role: "html",
1197
- read: true,
1198
- write: false,
1199
- def: ""
1200
- },
1201
- native: {}
1202
- });
1203
- this.ensuredFolders.add(ovId);
1236
+ if (this.config.enableOverview) {
1237
+ const cams = devices.filter((d) => d.ot === 2);
1238
+ const ovId = "overview";
1239
+ if (!this.ensuredFolders.has(ovId)) {
1240
+ await this.setObjectNotExistsAsync(ovId, {
1241
+ type: "state",
1242
+ common: {
1243
+ name: "Overview (all cameras)",
1244
+ type: "string",
1245
+ role: "html",
1246
+ read: true,
1247
+ write: false,
1248
+ def: ""
1249
+ },
1250
+ native: {}
1251
+ });
1252
+ this.ensuredFolders.add(ovId);
1253
+ }
1254
+ await this.setStateAsync(ovId, { val: this.buildOverviewHtml(cams), ack: true });
1204
1255
  }
1205
- await this.setStateAsync(ovId, { val: this.buildOverviewHtml(cams), ack: true });
1256
+ await this.setStateAsync("system.lastUpdate", { val: (/* @__PURE__ */ new Date()).toISOString(), ack: true });
1257
+ await this.setStateAsync("system.lastPoll", { val: Date.now(), ack: true });
1258
+ } finally {
1259
+ this.pollBusy = false;
1206
1260
  }
1207
- await this.setStateAsync("system.lastUpdate", { val: (/* @__PURE__ */ new Date()).toISOString(), ack: true });
1208
- await this.setStateAsync("system.lastPoll", { val: Date.now(), ack: true });
1209
1261
  }
1210
1262
  scheduleRefresh() {
1211
1263
  if (this.refreshTimer !== void 0) {