iobroker.agent-dvr 0.0.3 → 0.0.4
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.de.md +3 -0
- package/README.md +4 -0
- package/build/main.js +2 -2
- package/build/main.js.map +1 -1
- package/io-package.json +14 -1
- package/package.json +1 -1
package/README.de.md
CHANGED
|
@@ -171,6 +171,9 @@ Der State `snapshot_b64` enthält das aktuelle Kamerabild als `data:image/jpeg;b
|
|
|
171
171
|
Placeholder for the next version (at the beginning of the line):
|
|
172
172
|
### **WORK IN PROGRESS**
|
|
173
173
|
-->
|
|
174
|
+
### 0.0.4 (2026-06-27)
|
|
175
|
+
* (ipod86) fix: DP-Rollen korrigiert: snapshot_b64 → state (E1008), Profil-Selektor → level (E1011)
|
|
176
|
+
|
|
174
177
|
### 0.0.3 (2026-06-27)
|
|
175
178
|
* (ipod86) feat: Profil-Selektor — liest Profile aus getObjects, beschreibbarer Dropdown mit aktivem Profil bei jedem Poll
|
|
176
179
|
* (ipod86) feat: snapshot_b64-State (media.picture) immer vorhanden + manueller Refresh-Button; Auto-Poll optional
|
package/README.md
CHANGED
|
@@ -173,6 +173,10 @@ The `snapshot_b64` state stores the current camera frame as a `data:image/jpeg;b
|
|
|
173
173
|
Placeholder for the next version (at the beginning of the line):
|
|
174
174
|
### **WORK IN PROGRESS**
|
|
175
175
|
-->
|
|
176
|
+
### 0.0.4 (2026-06-27)
|
|
177
|
+
* (ipod86) fix: snapshot_b64 role corrected to `state` (E1008)
|
|
178
|
+
* (ipod86) fix: profile selector role corrected to `level` (E1011)
|
|
179
|
+
|
|
176
180
|
### 0.0.3 (2026-06-27)
|
|
177
181
|
* (ipod86) feat: profile selector — reads profiles from getObjects, writable dropdown with active profile reflected on every poll
|
|
178
182
|
* (ipod86) feat: snapshot_b64 state (media.picture) always present per camera + manual refresh button; auto-poll optional
|
package/build/main.js
CHANGED
|
@@ -514,7 +514,7 @@ class AgentDvr extends utils.Adapter {
|
|
|
514
514
|
await this.ensurePath(id);
|
|
515
515
|
await this.setObjectNotExistsAsync(id, {
|
|
516
516
|
type: "state",
|
|
517
|
-
common: { name, type: "number", role: "
|
|
517
|
+
common: { name, type: "number", role: "level", read: true, write: true, states },
|
|
518
518
|
native: {}
|
|
519
519
|
});
|
|
520
520
|
await this.setStateAsync(id, { val: 0, ack: true });
|
|
@@ -661,7 +661,7 @@ class AgentDvr extends utils.Adapter {
|
|
|
661
661
|
common: {
|
|
662
662
|
name: "Snapshot (Base64)",
|
|
663
663
|
type: "string",
|
|
664
|
-
role: "
|
|
664
|
+
role: "state",
|
|
665
665
|
read: true,
|
|
666
666
|
write: false
|
|
667
667
|
},
|
package/build/main.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/main.ts"],
|
|
4
|
-
"sourcesContent": ["import * as utils from '@iobroker/adapter-core';\nimport * as http from 'node:http';\nimport { getWidgetLabels, type WidgetLabels } from './lib/widget-i18n';\n\n// =====================================================================\n// Agent DVR -> ioBroker Adapter\n// API reference: https://ispysoftware.github.io/Agent_API/ (v7.5)\n// =====================================================================\n\n// ---- Types ----\n\ninterface ApiResult {\n\tok: boolean;\n\tdata?: string;\n\terror?: string;\n}\n\ninterface AgentDvrEvent {\n\tfn?: string;\n\ttg?: string;\n\tc?: number;\n\td?: number | string;\n\tsb?: number;\n\t[key: string]: unknown;\n}\n\ninterface Device {\n\toid: number | string;\n\tot: number;\n\tname: string;\n\traw: Record<string, unknown>;\n}\n\ntype RegistryKind = 'cam' | 'sys' | 'ptz' | 'ptzHold' | 'push' | 'setProfile' | 'snapshotB64';\n\ninterface RegistryEntry {\n\tkind: RegistryKind;\n\tpath?: string | null;\n\tparams?: string[];\n\toid?: number | string;\n\tot?: number;\n\tdir?: number;\n\tfid?: string;\n}\n\n// ---- Commands ----\n\nconst CAM_COMMANDS = [\n\t{ id: 'record', path: '/command/record', params: ['oid', 'ot'], name: 'Start recording' },\n\t{ id: 'recordStop', path: '/command/recordStop', params: ['oid', 'ot'], name: 'Stop recording' },\n\t{ id: 'recordRestart', path: '/command/recordRestart', params: ['oid', 'ot'], name: 'Restart recording' },\n\t{ id: 'triggerRecord', path: '/command/triggerRecord', params: ['oid', 'ot'], name: 'Trigger recording (timeout)' },\n\t{ id: 'snapshot', path: '/command/snapshot', params: ['oid'], name: 'Take snapshot' },\n\t{ id: 'detect', path: '/command/detect', params: ['oid', 'ot'], name: 'Trigger motion detection' },\n\t{ id: 'alertOn', path: '/command/alertOn', params: ['oid', 'ot'], name: 'Arm alerts' },\n\t{ id: 'alertOff', path: '/command/alertOff', params: ['oid', 'ot'], name: 'Disarm alerts' },\n\t{ id: 'switchOn', path: '/command/switchOn', params: ['oid', 'ot'], name: 'Switch device on' },\n\t{ id: 'switchOff', path: '/command/switchOff', params: ['oid', 'ot'], name: 'Switch device off' },\n\t{ id: 'objectDetectOn', path: '/command/objectdetecton', params: ['oid', 'ot'], name: 'Enable object detection' },\n\t{\n\t\tid: 'objectDetectOff',\n\t\tpath: '/command/objectdetectoff',\n\t\tparams: ['oid', 'ot'],\n\t\tname: 'Disable object detection',\n\t},\n\t{ id: 'recOnAlert', path: '/command/recordOnAlertOn', params: ['oid', 'ot'], name: 'Record on alert: on' },\n\t{ id: 'recOnDetect', path: '/command/recordOnDetectOn', params: ['oid', 'ot'], name: 'Record on detect: on' },\n\t{ id: 'purge', path: '/command/purge', params: ['oid', 'ot'], name: 'Purge folder (caution!)' },\n] as const;\n\nconst SYS_COMMANDS = [\n\t{ id: 'arm', path: '/command/arm', name: 'Arm system' },\n\t{ id: 'disarm', path: '/command/disarm', name: 'Disarm system' },\n\t{ id: 'allOn', path: '/command/allOn', name: 'All devices on' },\n\t{ id: 'allOff', path: '/command/allOff', name: 'All devices off' },\n\t{ id: 'reloadConfig', path: '/command/reloadConfig', name: 'Reload config' },\n\t{ id: 'reloadObjects', path: '/command/reloadObjects', name: 'Reload objects' },\n\t{ id: 'runStorageMgmt', path: '/command/runStorageMgmt', name: 'Run storage management' },\n\t{ id: 'blockExternal', path: '/command/blockExternal', name: 'Block external access' },\n\t{ id: 'unblockExternal', path: '/command/unblockExternal', name: 'Unblock external access' },\n\t{ id: 'restart', path: '/command/restart', name: 'Restart Agent DVR' },\n\t{ id: 'refresh', path: null, name: 'Refresh data now' },\n] as const;\n\nconst PTZ_DIRS = [\n\t{ id: 'left', dir: 1, hold: true },\n\t{ id: 'upLeft', dir: 2, hold: true },\n\t{ id: 'up', dir: 3, hold: true },\n\t{ id: 'upRight', dir: 4, hold: true },\n\t{ id: 'right', dir: 5, hold: true },\n\t{ id: 'downRight', dir: 6, hold: true },\n\t{ id: 'down', dir: 7, hold: true },\n\t{ id: 'downLeft', dir: 8, hold: true },\n\t{ id: 'zoomIn', dir: 9, hold: true },\n\t{ id: 'zoomOut', dir: 10, hold: true },\n\t{ id: 'stop', dir: 11, hold: false },\n\t{ id: 'center', dir: 0, hold: false },\n] as const;\n\nconst SKIP_KEYS = ['icons', 'image', 'thumbnail', 'base64'];\n\n// ---- Module-level helpers ----\n\nfunction sanitize(s: unknown): string {\n\treturn (\n\t\tString(s)\n\t\t\t.replace(/[\\s.[\\]*?\"'`,;:/\\\\]+/g, '_')\n\t\t\t.replace(/[^A-Za-z0-9_-]/g, '_')\n\t\t\t.replace(/_+/g, '_')\n\t\t\t.replace(/^_+|_+$/g, '') || 'x'\n\t);\n}\n\nfunction asJson(data: unknown): Record<string, unknown> | null {\n\tif (data == null) {\n\t\treturn null;\n\t}\n\tif (typeof data === 'object') {\n\t\treturn data as Record<string, unknown>;\n\t}\n\tif (typeof data === 'string') {\n\t\ttry {\n\t\t\treturn JSON.parse(data) as Record<string, unknown>;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\treturn null;\n}\n\nfunction toStr(v: unknown): string {\n\tif (v == null) {\n\t\treturn '';\n\t}\n\tif (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') {\n\t\treturn String(v);\n\t}\n\treturn Object.prototype.toString.call(v);\n}\n\nfunction escHtml(s: unknown): string {\n\treturn toStr(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\n}\n\nfunction parseSizeGb(s: unknown): number | null {\n\tif (s == null) {\n\t\treturn null;\n\t}\n\tconst m = toStr(s)\n\t\t.trim()\n\t\t.match(/([\\d.,]+)\\s*([KMGT]?B)?/i);\n\tif (!m) {\n\t\treturn null;\n\t}\n\tlet v = parseFloat(m[1].replace(',', '.'));\n\tif (isNaN(v)) {\n\t\treturn null;\n\t}\n\tconst u = (m[2] || 'GB').toUpperCase();\n\tif (u === 'TB') {\n\t\tv *= 1024;\n\t} else if (u === 'MB') {\n\t\tv /= 1024;\n\t} else if (u === 'KB') {\n\t\tv /= 1048576;\n\t} else if (u === 'B') {\n\t\tv /= 1073741824;\n\t}\n\treturn Math.round(v * 100) / 100;\n}\n\nfunction findWH(obj: unknown, depth: number): [number, number] | null {\n\tif (!obj || typeof obj !== 'object' || depth > 5) {\n\t\treturn null;\n\t}\n\tconst o = obj as Record<string, unknown>;\n\tfor (const [kw, kh] of [\n\t\t['width', 'height'],\n\t\t['w', 'h'],\n\t\t['sourceWidth', 'sourceHeight'],\n\t\t['videoWidth', 'videoHeight'],\n\t\t['imageWidth', 'imageHeight'],\n\t]) {\n\t\tconst w = o[kw],\n\t\t\th = o[kh];\n\t\tif (typeof w === 'number' && typeof h === 'number' && w >= 100 && h >= 100 && w <= 12000 && h <= 12000) {\n\t\t\tconst r = w / h;\n\t\t\tif (r >= 0.3 && r <= 4) {\n\t\t\t\treturn [w, h];\n\t\t\t}\n\t\t}\n\t}\n\tfor (const k of Object.keys(o)) {\n\t\tconst g = findWH(o[k], depth + 1);\n\t\tif (g) {\n\t\t\treturn g;\n\t\t}\n\t}\n\treturn null;\n}\n\nfunction detectOt(elem: Record<string, unknown>): number {\n\tconst v = elem.typeID ?? elem.ot ?? elem.objectTypeID ?? elem.type;\n\treturn v === 1 || v === 2 ? v : 2;\n}\n\nfunction findDevices(json: Record<string, unknown>): Device[] {\n\tconst list = Array.isArray(json.objectList) ? json.objectList : [];\n\tconst out: Device[] = [];\n\tfor (const e of list) {\n\t\tif (!e || typeof e !== 'object') {\n\t\t\tcontinue;\n\t\t}\n\t\tconst el = e as Record<string, unknown>;\n\t\tconst rawId = el.id ?? el.oid;\n\t\tif (rawId === undefined || rawId === null) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst id = rawId as number | string;\n\t\tconst name = typeof el.name === 'string' ? el.name : `obj_${id}`;\n\t\tout.push({ oid: id, ot: detectOt(el), name, raw: el });\n\t}\n\treturn out;\n}\n\n// ---- HTML/CSS gallery helpers ----\n\nfunction galleryCss(minCol: number, maxW: number): string {\n\treturn (\n\t\t`.advgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(${minCol}px,1fr));gap:12px;max-width:100%}` +\n\t\t`.advgrid .advlb{display:none}` +\n\t\t`.advcell{display:flex;flex-direction:column;cursor:pointer;text-decoration:none;color:inherit;font-family:inherit}` +\n\t\t`.advimg{position:relative;display:block;border-radius:8px;overflow:hidden;background:rgba(127,127,127,.15)}` +\n\t\t`.advimg img{width:100%;height:auto;display:block}` +\n\t\t`.advimgfix img{height:100%;object-fit:cover}` +\n\t\t`.advtag{position:absolute;background:rgba(0,0,0,.55);color:#fff;font-size:.6rem;padding:2px 4px;border-radius:3px;line-height:1;max-width:85%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}` +\n\t\t`.advplay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .15s;background:rgba(0,0,0,.2)}` +\n\t\t`.advplay::before{content:\"\";border-style:solid;border-width:11px 0 11px 18px;border-color:transparent transparent transparent #fff;filter:drop-shadow(0 1px 2px rgba(0,0,0,.6))}` +\n\t\t`.advcell:hover .advplay{opacity:1}` +\n\t\t`.advcap{font-size:.72rem;margin-top:4px;line-height:1.25;opacity:.85}` +\n\t\t`.advmodal{display:none;position:fixed;inset:0;z-index:99999;align-items:center;justify-content:center}` +\n\t\t`.advgrid .advlb:checked + .advthumb + .advmodal{display:flex}` +\n\t\t`.advbackdrop{position:absolute;inset:0;background:transparent;cursor:pointer}` +\n\t\t`.advbox{position:relative;z-index:1;display:flex;flex-direction:column;gap:8px;max-width:min(92vw,${maxW}px);background:#1c1c1e;padding:12px;border-radius:14px;box-shadow:0 16px 48px rgba(0,0,0,.55),0 0 0 1px rgba(255,255,255,.08)}` +\n\t\t`.advvideo{width:100%;max-height:80vh;border-radius:8px;background:#000;display:block}` +\n\t\t`.advclose{position:absolute;top:18px;right:18px;width:32px;height:32px;border-radius:50%;background:rgba(0,0,0,.65);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:15px;z-index:2;border:2px solid rgba(255,255,255,.35)}` +\n\t\t`.advinfo{color:#fff;font-size:.8rem;text-align:center}` +\n\t\t`.advinfo a{color:#8ab4f8}` +\n\t\t`.advempty{padding:24px;text-align:center;opacity:.7;font-family:inherit}`\n\t);\n}\n\nfunction galleryCssJs(minCol: number): string {\n\treturn (\n\t\t`.advroot{font-family:inherit;color:inherit}` +\n\t\t`.advbar{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-bottom:10px}` +\n\t\t`.advsearchjs{flex:1 1 160px;min-width:120px;padding:6px 10px;border-radius:8px;border:1px solid rgba(127,127,127,.4);background:rgba(127,127,127,.08);color:inherit;font:inherit}` +\n\t\t`.advtagsjs{display:flex;flex-wrap:wrap;gap:6px}` +\n\t\t`.advtagbtn{padding:4px 10px;border-radius:999px;border:1px solid rgba(127,127,127,.4);background:transparent;color:inherit;cursor:pointer;font:inherit;font-size:.8rem}` +\n\t\t`.advtagbtn.on{background:#2d6cdf;color:#fff;border-color:#2d6cdf}` +\n\t\t`.advgridjs{display:grid;grid-template-columns:repeat(auto-fill,minmax(${minCol}px,1fr));gap:12px}` +\n\t\t`.advcelljs{display:flex;flex-direction:column;cursor:pointer}` +\n\t\t`.advimgjs{position:relative;display:block;border-radius:8px;overflow:hidden;background:rgba(127,127,127,.15)}` +\n\t\t`.advimgjs img{width:100%;height:auto;display:block}` +\n\t\t`.advimgjs.advimgfix img{height:100%;object-fit:cover}` +\n\t\t`.advtagjs{position:absolute;background:rgba(0,0,0,.55);color:#fff;font-size:.6rem;padding:2px 4px;border-radius:3px;line-height:1;max-width:85%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}` +\n\t\t`.advplayjs{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .15s;background:rgba(0,0,0,.2)}` +\n\t\t`.advplayjs::before{content:\"\";border-style:solid;border-width:11px 0 11px 18px;border-color:transparent transparent transparent #fff;filter:drop-shadow(0 1px 2px rgba(0,0,0,.6))}` +\n\t\t`.advcelljs:hover .advplayjs{opacity:1}` +\n\t\t`.advcapjs{font-size:.72rem;margin-top:4px;line-height:1.25;opacity:.85}` +\n\t\t`.advemptyjs{padding:24px;text-align:center;opacity:.7}`\n\t);\n}\n\n// Client-side browser code for the JS widget variant.\n// Stored as a string: TypeScript never type-checks browser code running in DOM context.\nconst ADV_CLIENT_CODE = `\n(function(){\nfunction esc(s){return String(s==null?'':s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/\"/g,'"');}\nvar MODAL_CSS='.advmodaljs{display:none;position:fixed;inset:0;z-index:99999;align-items:center;justify-content:center}.advbackdropjs{position:absolute;inset:0;background:transparent;cursor:pointer}.advboxjs{position:relative;z-index:1;display:flex;flex-direction:column;gap:8px;max-width:min(92vw,900px);background:#1c1c1e;padding:12px;border-radius:14px;box-shadow:0 16px 48px rgba(0,0,0,.55),0 0 0 1px rgba(255,255,255,.08)}.advvideojs{width:100%;max-height:80vh;border-radius:8px;background:#000;display:block}.advclosejs{position:absolute;top:18px;right:18px;width:32px;height:32px;border-radius:50%;background:rgba(0,0,0,.65);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:15px;z-index:2;border:2px solid rgba(255,255,255,.35)}.advinfojs{color:#fff;font-size:.8rem;text-align:center}';\nvar modalEl=null,modalVideo=null,modalInfo=null,modalBox=null;\nfunction getModal(){\n if(modalEl&&document.body.contains(modalEl))return modalEl;\n if(!document.getElementById('advmodalcss')){var st=document.createElement('style');st.id='advmodalcss';st.textContent=MODAL_CSS;document.head.appendChild(st);}\n modalEl=document.createElement('div');modalEl.className='advmodaljs';\n var bd=document.createElement('div');bd.className='advbackdropjs';\n modalBox=document.createElement('div');modalBox.className='advboxjs';\n var cl=document.createElement('span');cl.className='advclosejs';cl.textContent='\u2715';\n modalVideo=document.createElement('video');modalVideo.className='advvideojs';\n modalVideo.setAttribute('controls','');modalVideo.setAttribute('preload','none');modalVideo.setAttribute('playsinline','');\n modalInfo=document.createElement('div');modalInfo.className='advinfojs';\n modalBox.appendChild(cl);modalBox.appendChild(modalVideo);modalBox.appendChild(modalInfo);\n modalEl.appendChild(bd);modalEl.appendChild(modalBox);\n document.body.appendChild(modalEl);\n bd.addEventListener('click',closeModal);cl.addEventListener('click',closeModal);\n return modalEl;\n}\nfunction openSrc(src,label,maxW){\n var m=getModal();\n if(maxW)modalBox.style.maxWidth='min(92vw,'+maxW+'px)';\n modalVideo.setAttribute('src',src);modalInfo.textContent=label;m.style.display='flex';\n}\nfunction closeModal(){\n if(!modalEl)return;\n try{modalVideo.pause();}catch(e){}\n modalVideo.removeAttribute('src');try{modalVideo.load();}catch(e){}\n modalEl.style.display='none';\n}\ndocument.addEventListener('keydown',function(e){if(e.key==='Escape'||e.keyCode===27)closeModal();});\nfunction initRoot(root){\n var dataEl=root.querySelector('script.advdata');\n if(!dataEl)return;\n var D;try{D=JSON.parse(dataEl.textContent);}catch(e){return;}\n var cfg=D.cfg||{},items=D.items||[],live=D.live||null;\n var grid=root.querySelector('.advgridjs'),search=root.querySelector('.advsearchjs'),tagsBox=root.querySelector('.advtagsjs');\n var state={q:'',tag:''};\n var tagset={};\n items.forEach(function(it){String(it.tag||'').split(/[,\\\\s]+/).forEach(function(t){if(t)tagset[t]=1;});});\n var tags=Object.keys(tagset);\n if(cfg.show_tags&&tags.length){\n var mk=function(label,val){\n var b=document.createElement('button');b.className='advtagbtn';b.textContent=label;b.setAttribute('data-tag',val);\n b.addEventListener('click',function(){state.tag=state.tag===val?'':val;updateTagBtns();render();});\n tagsBox.appendChild(b);\n };\n mk(cfg.labels&&cfg.labels.all||'All','');tags.forEach(function(t){mk(t,t);});\n }\n function updateTagBtns(){var bs=tagsBox.querySelectorAll('.advtagbtn');for(var i=0;i<bs.length;i++)bs[i].classList.toggle('on',bs[i].getAttribute('data-tag')===state.tag);}\n updateTagBtns();\n if(search)search.addEventListener('input',function(){state.q=this.value.toLowerCase();render();});\n function badge(it){if(!(cfg.show_tags&&it.tag))return '';var pos=String(cfg.tag_position||'bottom-left').split('-');return '<span class=\"advtagjs\" style=\"'+pos[0]+':5px;'+pos[1]+':5px\">'+esc(it.tag)+'</span>';}\n function render(){\n var html='';\n if(live){var ar=cfg.live_aspect||'',arStyle=ar?' style=\"aspect-ratio:'+ar.replace('/',' / ')+'\"':'',arCls=ar?' advimgfix':'';\n html+='<div class=\"advcelljs\" data-live=\"1\"><span class=\"advimgjs'+arCls+'\"'+arStyle+'><img loading=\"lazy\" src=\"'+esc(live.thumb)+'\"><span class=\"advtagjs\" style=\"top:5px;left:5px\">● '+(cfg.labels&&cfg.labels.live||'LIVE')+'</span><span class=\"advplayjs\"></span></span><span class=\"advcapjs\">'+esc(live.name)+'</span></div>';}\n items.forEach(function(it,i){\n if(state.tag&&String(it.tag||'').toLowerCase().indexOf(state.tag.toLowerCase())<0)return;\n if(state.q){var hay=((it.date||'')+' '+(it.time||'')+' '+(it.tag||'')).toLowerCase();if(hay.indexOf(state.q)<0)return;}\n html+='<div class=\"advcelljs\" data-i=\"'+i+'\"><span class=\"advimgjs\"><img loading=\"lazy\" src=\"'+esc(it.thumb)+'\">'+badge(it)+'<span class=\"advplayjs\"></span></span><span class=\"advcapjs\">'+esc(it.date)+' '+esc(it.time)+'<br>'+esc(it.dur)+'s · '+esc(it.size)+' MB</span></div>';\n });\n grid.innerHTML=html||'<div class=\"advemptyjs\">'+(cfg.labels&&cfg.labels.noRecordings||'No recordings')+'</div>';\n var cells=grid.querySelectorAll('.advcelljs');\n for(var k=0;k<cells.length;k++)(function(cell){\n cell.addEventListener('click',function(){\n if(cell.getAttribute('data-live')==='1'){openSrc(live.video,(live.name||'')+' \u00B7 Live',cfg.max_modal_width);return;}\n var it=items[parseInt(cell.getAttribute('data-i'),10)],maxW=Number(cfg.max_modal_width)||900;\n if(cfg.player_url){var u=cfg.player_url.replace(/{video}/g,encodeURIComponent(it.video)).replace(/{videoRaw}/g,it.video).replace(/{fn}/g,encodeURIComponent(it.fn||'')).replace(/{date}/g,encodeURIComponent(it.date)).replace(/{time}/g,encodeURIComponent(it.time)).replace(/{duration}/g,encodeURIComponent(it.dur)).replace(/{size}/g,encodeURIComponent(it.size)).replace(/{tags}/g,encodeURIComponent(it.tag));window.open(u,'_blank');}\n else{openSrc(it.video,esc(it.date)+' '+esc(it.time)+(it.dur!=null?' \u00B7 '+it.dur+'s':'')+(it.size!=null?' \u00B7 '+it.size+' MB':'')+(it.tag?' \u00B7 '+it.tag:''),maxW);}\n });\n })(cells[k]);\n }\n render();\n}\nfunction scan(){var roots=document.querySelectorAll('.advroot');for(var i=0;i<roots.length;i++)if(!roots[i].__adv){roots[i].__adv=1;initRoot(roots[i]);}}\nwindow.ADVscan=scan;scan();\n})();\n`.trim();\n\n// ---- Adapter class ----\n\nclass AgentDvr extends utils.Adapter {\n\tprivate pollTimer: ioBroker.Interval | undefined = undefined;\n\tprivate refreshTimer: ioBroker.Timeout | undefined = undefined;\n\tprivate authHeader: string | null = null;\n\tprivate baseUrl = '';\n\tprivate wt: WidgetLabels = getWidgetLabels('en');\n\n\tprivate readonly ensuredFolders = new Set<string>();\n\tprivate readonly registry = new Map<string, RegistryEntry>();\n\tprivate readonly ptzActive = new Map<string | number, string>();\n\tprivate readonly widgetSig: Record<string, string> = {};\n\tprivate profileSig = '';\n\tprivate readonly lastEventFn: Record<string | number, string> = {};\n\tprivate readonly camAspect: Record<string | number, string> = {};\n\tprivate readonly devById = new Map<string | number, Device>();\n\tprivate readonly eventBusy: Record<string | number, boolean> = {};\n\n\tpublic constructor(options: Partial<utils.AdapterOptions> = {}) {\n\t\tsuper({ ...options, name: 'agent-dvr' });\n\t\tthis.on('ready', this.onReady.bind(this));\n\t\tthis.on('stateChange', this.onStateChange.bind(this));\n\t\tthis.on('unload', this.onUnload.bind(this));\n\t}\n\n\t// ---- lifecycle ----\n\n\tprivate async onReady(): Promise<void> {\n\t\tvoid this.setState('info.connection', false, true);\n\n\t\tconst sysConfig = await this.getForeignObjectAsync('system.config');\n\t\tconst rawLang = (sysConfig?.common as unknown as { language?: string } | undefined)?.language;\n\t\tthis.wt = getWidgetLabels(rawLang ?? 'en');\n\n\t\tconst pollSeconds = Math.max(5, Math.min(3600, this.config.pollSeconds || 30));\n\t\tthis.baseUrl = `http://${this.config.ip}:${this.config.port || 8090}`;\n\n\t\tif (this.config.user || this.config.pass) {\n\t\t\tthis.authHeader = `Basic ${Buffer.from(`${this.config.user}:${this.config.pass}`).toString('base64')}`;\n\t\t}\n\n\t\tawait this.buildSystem();\n\t\ttry {\n\t\t\tawait this.poll();\n\t\t} catch (e) {\n\t\t\tthis.log.warn(`First poll error: ${(e as Error).message}`);\n\t\t}\n\n\t\tthis.pollTimer = this.setInterval(\n\t\t\t() => this.poll().catch(e => this.log.warn(`Poll error: ${(e as Error).message}`)),\n\t\t\tpollSeconds * 1000,\n\t\t);\n\n\t\tthis.subscribeStates('*');\n\t\tthis.log.info(`Agent DVR adapter ready. Polling ${this.baseUrl} every ${pollSeconds}s.`);\n\t}\n\n\tprivate onUnload(callback: () => void): void {\n\t\ttry {\n\t\t\tif (this.pollTimer !== undefined) {\n\t\t\t\tthis.clearInterval(this.pollTimer);\n\t\t\t\tthis.pollTimer = undefined;\n\t\t\t}\n\t\t\tif (this.refreshTimer !== undefined) {\n\t\t\t\tthis.clearTimeout(this.refreshTimer);\n\t\t\t\tthis.refreshTimer = undefined;\n\t\t\t}\n\t\t} catch {\n\t\t\t/* ignore */\n\t\t}\n\t\tcallback();\n\t}\n\n\tprivate onStateChange(id: string, state: ioBroker.State | null | undefined): void {\n\t\tif (!state || state.ack !== false) {\n\t\t\treturn;\n\t\t}\n\t\tconst relId = this.getRelId(id);\n\t\tconst entry = this.registry.get(relId);\n\t\tif (entry) {\n\t\t\tthis.runCommand(relId, entry, state.val).catch(e =>\n\t\t\t\tthis.log.warn(`Command error: ${(e as Error).message}`),\n\t\t\t);\n\t\t}\n\t}\n\n\t// ---- helpers ----\n\n\tprivate getRelId(fullId: string): string {\n\t\treturn fullId.startsWith(`${this.namespace}.`) ? fullId.slice(this.namespace.length + 1) : fullId;\n\t}\n\n\tprivate apiGet(path: string): Promise<ApiResult> {\n\t\treturn new Promise(resolve => {\n\t\t\tconst timeout = Math.max(1000, Math.min(30000, this.config.httpTimeoutMs || 8000));\n\t\t\tconst opts: http.RequestOptions = {\n\t\t\t\thostname: this.config.ip,\n\t\t\t\tport: this.config.port || 8090,\n\t\t\t\tpath,\n\t\t\t\tmethod: 'GET',\n\t\t\t\ttimeout,\n\t\t\t};\n\t\t\tif (this.authHeader) {\n\t\t\t\topts.headers = { Authorization: this.authHeader };\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst req = http.request(opts, res => {\n\t\t\t\t\tlet data = '';\n\t\t\t\t\tres.on('data', (c: Buffer) => {\n\t\t\t\t\t\tdata += c.toString();\n\t\t\t\t\t});\n\t\t\t\t\tres.on('end', () => {\n\t\t\t\t\t\tif (res.statusCode && res.statusCode >= 400) {\n\t\t\t\t\t\t\tresolve({ ok: false, error: `HTTP ${res.statusCode}` });\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresolve({ ok: true, data });\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tres.on('error', (e: Error) => resolve({ ok: false, error: e.message }));\n\t\t\t\t});\n\t\t\t\treq.on('error', (e: Error) => resolve({ ok: false, error: e.message }));\n\t\t\t\treq.on('timeout', () => {\n\t\t\t\t\treq.destroy();\n\t\t\t\t\tresolve({ ok: false, error: 'timeout' });\n\t\t\t\t});\n\t\t\t\treq.end();\n\t\t\t} catch (e) {\n\t\t\t\tresolve({ ok: false, error: String((e as Error).message || e) });\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate apiGetBuffer(path: string): Promise<{ ok: boolean; data?: Buffer; error?: string }> {\n\t\treturn new Promise(resolve => {\n\t\t\tconst timeout = Math.max(1000, Math.min(30000, this.config.httpTimeoutMs || 8000));\n\t\t\tconst opts: http.RequestOptions = {\n\t\t\t\thostname: this.config.ip,\n\t\t\t\tport: this.config.port || 8090,\n\t\t\t\tpath,\n\t\t\t\tmethod: 'GET',\n\t\t\t\ttimeout,\n\t\t\t};\n\t\t\tif (this.authHeader) {\n\t\t\t\topts.headers = { Authorization: this.authHeader };\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst req = http.request(opts, res => {\n\t\t\t\t\tconst chunks: Buffer[] = [];\n\t\t\t\t\tres.on('data', (c: Buffer) => chunks.push(c));\n\t\t\t\t\tres.on('end', () => {\n\t\t\t\t\t\tif (res.statusCode && res.statusCode >= 400) {\n\t\t\t\t\t\t\tresolve({ ok: false, error: `HTTP ${res.statusCode}` });\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresolve({ ok: true, data: Buffer.concat(chunks) });\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tres.on('error', (e: Error) => resolve({ ok: false, error: e.message }));\n\t\t\t\t});\n\t\t\t\treq.on('error', (e: Error) => resolve({ ok: false, error: e.message }));\n\t\t\t\treq.on('timeout', () => {\n\t\t\t\t\treq.destroy();\n\t\t\t\t\tresolve({ ok: false, error: 'timeout' });\n\t\t\t\t});\n\t\t\t\treq.end();\n\t\t\t} catch (e) {\n\t\t\t\tresolve({ ok: false, error: String((e as Error).message || e) });\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate async ensureFolder(id: string, name: string, type: 'device' | 'channel' = 'channel'): Promise<void> {\n\t\tif (this.ensuredFolders.has(id)) {\n\t\t\treturn;\n\t\t}\n\t\tawait this.setObjectNotExistsAsync(id, { type, common: { name }, native: {} });\n\t\tthis.ensuredFolders.add(id);\n\t}\n\n\tprivate async ensurePath(fullId: string): Promise<void> {\n\t\tconst parts = fullId.split('.');\n\t\tlet acc = parts[0];\n\t\tfor (let i = 1; i < parts.length - 1; i++) {\n\t\t\tacc += `.${parts[i]}`;\n\t\t\tif (!this.ensuredFolders.has(acc)) {\n\t\t\t\tawait this.setObjectNotExistsAsync(acc, { type: 'channel', common: { name: parts[i] }, native: {} });\n\t\t\t\tthis.ensuredFolders.add(acc);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate guessRole(id: string, val: unknown): ioBroker.CommonType {\n\t\tif (typeof val === 'string' && /^https?:\\/\\//i.test(val)) {\n\t\t\treturn 'string';\n\t\t}\n\t\treturn 'string';\n\t}\n\n\tprivate async writeLeaf(id: string, val: unknown): Promise<void> {\n\t\tconst t = typeof val;\n\t\tlet common: ioBroker.StateCommon;\n\t\tif (t === 'boolean') {\n\t\t\tcommon = { name: id.split('.').pop() || id, type: 'boolean', role: 'indicator', read: true, write: false };\n\t\t} else if (t === 'number') {\n\t\t\tcommon = { name: id.split('.').pop() || id, type: 'number', role: 'value', read: true, write: false };\n\t\t} else {\n\t\t\tconst role = typeof val === 'string' && /^https?:\\/\\//i.test(val) ? 'text.url' : 'text';\n\t\t\tcommon = { name: id.split('.').pop() || id, type: 'string', role, read: true, write: false };\n\t\t}\n\t\tif (!this.ensuredFolders.has(id)) {\n\t\t\tawait this.ensurePath(id);\n\t\t\tawait this.setObjectNotExistsAsync(id, { type: 'state', common, native: {} });\n\t\t\tthis.ensuredFolders.add(id);\n\t\t}\n\t\tawait this.setStateAsync(id, { val: (val === undefined ? null : val) as ioBroker.StateValue, ack: true });\n\t}\n\n\tprivate async flattenWrite(obj: unknown, prefix: string, depth: number): Promise<void> {\n\t\tconst maxDepth = Math.max(1, Math.min(10, this.config.maxDepth || 6));\n\t\tconst maxArray = Math.max(1, Math.min(500, this.config.maxArray || 30));\n\t\tif (depth > maxDepth) {\n\t\t\treturn;\n\t\t}\n\t\tif (obj === null || obj === undefined) {\n\t\t\tawait this.writeLeaf(prefix, obj);\n\t\t\treturn;\n\t\t}\n\t\tif (typeof obj === 'object') {\n\t\t\tif (Array.isArray(obj)) {\n\t\t\t\tif (obj.length > maxArray) {\n\t\t\t\t\tawait this.writeLeaf(`${prefix}.count`, obj.length);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tfor (let i = 0; i < obj.length; i++) {\n\t\t\t\t\tawait this.flattenWrite(obj[i], `${prefix}.${i}`, depth + 1);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor (const k of Object.keys(obj)) {\n\t\t\t\t\tif (SKIP_KEYS.includes(k)) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tawait this.flattenWrite((obj as Record<string, unknown>)[k], `${prefix}.${sanitize(k)}`, depth + 1);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tawait this.writeLeaf(prefix, obj);\n\t\t}\n\t}\n\n\tprivate async ensureControl(\n\t\tid: string,\n\t\tname: string,\n\t\tentry: RegistryEntry,\n\t\trole: 'button' | 'switch',\n\t): Promise<void> {\n\t\tif (!this.ensuredFolders.has(id)) {\n\t\t\tawait this.ensurePath(id);\n\t\t\tawait this.setObjectNotExistsAsync(id, {\n\t\t\t\ttype: 'state',\n\t\t\t\tcommon: { name, type: 'boolean', role, read: role === 'switch', write: true, def: false },\n\t\t\t\tnative: {},\n\t\t\t});\n\t\t\tawait this.setStateAsync(id, { val: false, ack: true });\n\t\t\tthis.ensuredFolders.add(id);\n\t\t}\n\t\tthis.registry.set(id, entry);\n\t}\n\n\tprivate ensureButton(id: string, name: string, entry: RegistryEntry): Promise<void> {\n\t\treturn this.ensureControl(id, name, entry, 'button');\n\t}\n\n\tprivate async ensureSelector(\n\t\tid: string,\n\t\tname: string,\n\t\tentry: RegistryEntry,\n\t\tstates: Record<number, string>,\n\t): Promise<void> {\n\t\tif (!this.ensuredFolders.has(id)) {\n\t\t\tawait this.ensurePath(id);\n\t\t\tawait this.setObjectNotExistsAsync(id, {\n\t\t\t\ttype: 'state',\n\t\t\t\tcommon: { name, type: 'number', role: 'value', read: true, write: true, states },\n\t\t\t\tnative: {},\n\t\t\t});\n\t\t\tawait this.setStateAsync(id, { val: 0, ack: true });\n\t\t\tthis.ensuredFolders.add(id);\n\t\t}\n\t\tthis.registry.set(id, entry);\n\t}\n\n\tprivate async fetchSnapshotB64(oid: number | string, snapId: string): Promise<void> {\n\t\tconst imgRes = await this.apiGetBuffer(`/photo.jpg?oid=${oid}`);\n\t\tif (imgRes.ok && imgRes.data) {\n\t\t\tawait this.setStateAsync(snapId, {\n\t\t\t\tval: `data:image/jpeg;base64,${imgRes.data.toString('base64')}`,\n\t\t\t\tack: true,\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate async ensureFlag(id: string, name: string): Promise<void> {\n\t\tif (this.ensuredFolders.has(id)) {\n\t\t\treturn;\n\t\t}\n\t\tawait this.ensurePath(id);\n\t\tawait this.setObjectNotExistsAsync(id, {\n\t\t\ttype: 'state',\n\t\t\tcommon: { name, type: 'boolean', role: 'indicator', read: true, write: false, def: false },\n\t\t\tnative: {},\n\t\t});\n\t\tthis.ensuredFolders.add(id);\n\t\tawait this.setStateAsync(id, { val: false, ack: true });\n\t}\n\n\tprivate async pulse(id: string): Promise<void> {\n\t\tawait this.setStateAsync(id, { val: true, ack: true });\n\t\tthis.setTimeout(() => this.setStateAsync(id, { val: false, ack: true }).catch(() => {}), 1500);\n\t}\n\n\t// ---- system structure ----\n\n\tprivate async buildSystem(): Promise<void> {\n\t\tawait this.ensureFolder('system', 'System', 'channel');\n\t\tawait this.writeLeaf('system.online', false);\n\t\tawait this.writeLeaf('system.lastUpdate', '');\n\t\tawait this.writeLeaf('system.lastPoll', 0);\n\t\tawait this.writeLeaf('system.cameraCount', 0);\n\n\t\tif (this.config.enableSystemControls) {\n\t\t\tawait this.ensureFolder('system.control', 'Control', 'channel');\n\t\t\tfor (const c of SYS_COMMANDS) {\n\t\t\t\tawait this.ensureButton(`system.control.${c.id}`, c.name, { kind: 'sys', path: c.path });\n\t\t\t}\n\t\t\tawait this.ensureFolder('system.profile', 'Profile', 'channel');\n\t\t\tawait this.ensureSelector(\n\t\t\t\t'system.profile.selector',\n\t\t\t\t'Active profile',\n\t\t\t\t{ kind: 'setProfile' },\n\t\t\t\t{ 0: 'Home', 1: 'Away', 2: 'Night' },\n\t\t\t);\n\t\t\tif (!this.ensuredFolders.has('system.profile.list')) {\n\t\t\t\tawait this.setObjectNotExistsAsync('system.profile.list', {\n\t\t\t\t\ttype: 'state',\n\t\t\t\t\tcommon: {\n\t\t\t\t\t\tname: 'Available profiles (JSON)',\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\trole: 'json',\n\t\t\t\t\t\tread: true,\n\t\t\t\t\t\twrite: false,\n\t\t\t\t\t},\n\t\t\t\t\tnative: {},\n\t\t\t\t});\n\t\t\t\tthis.ensuredFolders.add('system.profile.list');\n\t\t\t\tawait this.setStateAsync('system.profile.list', { val: '[]', ack: true });\n\t\t\t}\n\t\t}\n\t}\n\n\t// ---- camera aspect detection ----\n\n\tprivate async detectAspect(d: Device): Promise<void> {\n\t\tif (this.camAspect[d.oid] !== undefined) {\n\t\t\treturn;\n\t\t}\n\t\tlet wh = findWH(d.raw, 0);\n\t\tif (!wh) {\n\t\t\tconst res = await this.apiGet(`/command/getObject?oid=${d.oid}&ot=2`);\n\t\t\tconst j = asJson(res.data);\n\t\t\tif (j) {\n\t\t\t\twh = findWH(j, 0);\n\t\t\t}\n\t\t}\n\t\tthis.camAspect[d.oid] = wh ? `${wh[0]}/${wh[1]}` : '';\n\t\tif (wh) {\n\t\t\tthis.log.debug(`cam ${d.oid} resolution ${wh[0]}x${wh[1]} -> AR ${this.camAspect[d.oid]}`);\n\t\t}\n\t}\n\n\t// ---- device folder name ----\n\n\tprivate deviceFolder(d: Device): string {\n\t\treturn `${d.ot === 1 ? 'mic' : 'cam'}_${sanitize(d.oid)}_${sanitize(d.name)}`;\n\t}\n\n\t// ---- build device data points ----\n\n\tprivate async buildDevice(d: Device): Promise<void> {\n\t\tconst fid = this.deviceFolder(d);\n\t\tthis.devById.set(d.oid, d);\n\n\t\tawait this.ensureFolder(fid, d.name, 'device');\n\t\tif (d.ot === 2) {\n\t\t\tawait this.detectAspect(d);\n\t\t}\n\t\tawait this.flattenWrite(d.raw, fid, 0);\n\n\t\tawait this.ensureFolder(`${fid}.control`, 'Control', 'channel');\n\t\tfor (const c of CAM_COMMANDS) {\n\t\t\tif (c.id === 'snapshot' && d.ot !== 2) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tawait this.ensureButton(`${fid}.control.${c.id}`, c.name, {\n\t\t\t\tkind: 'cam',\n\t\t\t\tpath: c.path,\n\t\t\t\tparams: [...c.params],\n\t\t\t\toid: d.oid,\n\t\t\t\tot: d.ot,\n\t\t\t});\n\t\t}\n\n\t\tif (this.config.enablePtz && d.ot === 2) {\n\t\t\tawait this.ensureFolder(`${fid}.control.ptz`, 'PTZ', 'channel');\n\t\t\tfor (const p of PTZ_DIRS) {\n\t\t\t\tconst cid = `${fid}.control.ptz.${p.id}`;\n\t\t\t\tif (p.hold) {\n\t\t\t\t\tawait this.ensureControl(\n\t\t\t\t\t\tcid,\n\t\t\t\t\t\t`PTZ ${p.id} (hold)`,\n\t\t\t\t\t\t{ kind: 'ptzHold', oid: d.oid, dir: p.dir },\n\t\t\t\t\t\t'switch',\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tawait this.ensureButton(cid, `PTZ ${p.id}`, { kind: 'ptz', oid: d.oid, dir: p.dir });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (this.config.enableUrls && d.ot === 2) {\n\t\t\tawait this.ensureFolder(`${fid}.urls`, 'URLs', 'channel');\n\t\t\tawait this.writeLeaf(`${fid}.urls.snapshot`, `${this.baseUrl}/grab.jpg?oid=${d.oid}`);\n\t\t\tawait this.writeLeaf(`${fid}.urls.photo`, `${this.baseUrl}/photo.jpg?oid=${d.oid}`);\n\t\t\tawait this.writeLeaf(`${fid}.urls.mjpeg`, `${this.baseUrl}/video.mjpg?oids=${d.oid}`);\n\t\t\tawait this.writeLeaf(`${fid}.urls.mp4`, `${this.baseUrl}/video.mp4?oids=${d.oid}`);\n\t\t}\n\n\t\tif (d.ot === 2) {\n\t\t\tconst snapId = `${fid}.snapshot_b64`;\n\t\t\tif (!this.ensuredFolders.has(snapId)) {\n\t\t\t\tawait this.ensurePath(snapId);\n\t\t\t\tawait this.setObjectNotExistsAsync(snapId, {\n\t\t\t\t\ttype: 'state',\n\t\t\t\t\tcommon: {\n\t\t\t\t\t\tname: 'Snapshot (Base64)',\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\trole: 'media.picture',\n\t\t\t\t\t\tread: true,\n\t\t\t\t\t\twrite: false,\n\t\t\t\t\t},\n\t\t\t\t\tnative: {},\n\t\t\t\t});\n\t\t\t\tawait this.setStateAsync(snapId, { val: '', ack: true });\n\t\t\t\tthis.ensuredFolders.add(snapId);\n\t\t\t}\n\t\t\tawait this.ensureButton(`${fid}.control.refreshSnapshotB64`, 'Refresh snapshot (Base64)', {\n\t\t\t\tkind: 'snapshotB64',\n\t\t\t\toid: d.oid,\n\t\t\t\tfid,\n\t\t\t});\n\t\t\tif (this.config.enableSnapshotB64) {\n\t\t\t\tawait this.fetchSnapshotB64(d.oid, snapId);\n\t\t\t}\n\t\t}\n\n\t\tawait this.updateCameraEvents(d, fid);\n\t}\n\n\t// ---- event formatting ----\n\n\tprivate fmtEvent(\n\t\tev: Record<string, unknown>,\n\t\toid: number | string,\n\t): {\n\t\tfn: string;\n\t\tsizeMB: number;\n\t\tdur: string;\n\t\ttag: string;\n\t\tdate: string;\n\t\ttime: string;\n\t\tthumb: string;\n\t\tvideo: string;\n\t} {\n\t\tconst e = ev as AgentDvrEvent;\n\t\tconst fn = e.fn ?? '';\n\t\tconst dot = fn.lastIndexOf('.');\n\t\tconst jpg = `${dot > 0 ? fn.slice(0, dot) : fn}_large.jpg`;\n\t\tconst sizeMB = e.sb ? Math.round((Number(e.sb) / 1048576) * 100) / 100 : 0;\n\t\tconst dur = e.d != null ? String(e.d) : '';\n\t\tconst tag = e.tg ?? '';\n\t\tconst ms = (parseFloat(String(e.c ?? 0)) - 621355968000000000) / 10000;\n\t\tconst dt = new Date(ms);\n\t\tconst two = (n: number): string => String(n).padStart(2, '0');\n\t\tconst date = `${two(dt.getDate())}.${two(dt.getMonth() + 1)}.`;\n\t\tconst time = `${two(dt.getHours())}:${two(dt.getMinutes())}`;\n\t\tconst thumb = `${this.baseUrl}/fileThumb.jpg?oid=${oid}&fn=${encodeURIComponent(jpg)}`;\n\t\tconst video = `${this.baseUrl}/streamFile.cgi?oid=${oid}&ot=2&fn=${encodeURIComponent(fn)}`;\n\t\treturn { fn, sizeMB, dur, tag, date, time, thumb, video };\n\t}\n\n\t// ---- gallery HTML builders ----\n\n\tprivate buildGalleryHtml(d: Device, events: Record<string, unknown>[]): string {\n\t\treturn (this.config.widgetMode || 'nojs') === 'js'\n\t\t\t? this.buildGalleryHtmlJs(d, events)\n\t\t\t: this.buildGalleryHtmlNojs(d, events);\n\t}\n\n\tprivate buildGalleryHtmlNojs(d: Device, events: Record<string, unknown>[]): string {\n\t\tconst oid = d.oid;\n\t\tconst minCol = this.config.widgetMinCol || 150;\n\t\tconst maxW = this.config.widgetMaxModalWidth || 900;\n\t\tconst showTags = this.config.widgetShowTags;\n\t\tconst pos = (this.config.widgetTagPosition || 'bottom-left').split('-');\n\t\tconst tagStyle = `${pos[0]}:5px;${pos[1]}:5px`;\n\t\tconst playerUrl = (this.config.widgetPlayerUrl || '').trim();\n\t\tconst PAUSE_ATTR = ` onchange=\"if(!this.checked){var m=this.nextElementSibling.nextElementSibling,v=m&&m.querySelector('video');if(v){v.pause();}}\"`;\n\n\t\tconst items = events\n\t\t\t.map((ev, i) => {\n\t\t\t\tconst p = this.fmtEvent(ev, oid);\n\t\t\t\tconst id = `adv${sanitize(oid)}_${i}`;\n\t\t\t\tconst badge =\n\t\t\t\t\tshowTags && p.tag ? `<span class=\"advtag\" style=\"${tagStyle}\">${escHtml(p.tag)}</span>` : '';\n\t\t\t\tconst inner =\n\t\t\t\t\t`<span class=\"advimg\"><img src=\"${p.thumb}\" loading=\"lazy\" alt=\"\">${badge}<span class=\"advplay\"></span></span>` +\n\t\t\t\t\t`<span class=\"advcap\">${escHtml(p.date)} ${escHtml(p.time)}<br>${escHtml(p.dur)}s · ${escHtml(p.sizeMB)} MB</span>`;\n\t\t\t\tif (playerUrl) {\n\t\t\t\t\tconst href = playerUrl\n\t\t\t\t\t\t.replace(/{video}/g, encodeURIComponent(p.video))\n\t\t\t\t\t\t.replace(/{videoRaw}/g, p.video)\n\t\t\t\t\t\t.replace(/{fn}/g, encodeURIComponent(p.fn))\n\t\t\t\t\t\t.replace(/{date}/g, encodeURIComponent(p.date))\n\t\t\t\t\t\t.replace(/{time}/g, encodeURIComponent(p.time))\n\t\t\t\t\t\t.replace(/{duration}/g, encodeURIComponent(p.dur))\n\t\t\t\t\t\t.replace(/{size}/g, encodeURIComponent(String(p.sizeMB)))\n\t\t\t\t\t\t.replace(/{tags}/g, encodeURIComponent(p.tag));\n\t\t\t\t\treturn `<a class=\"advcell\" href=\"${escHtml(href)}\" target=\"_blank\" rel=\"noopener\">${inner}</a>`;\n\t\t\t\t}\n\t\t\t\treturn (\n\t\t\t\t\t`<input class=\"advlb\" type=\"checkbox\" id=\"${id}\"${PAUSE_ATTR}>` +\n\t\t\t\t\t`<label class=\"advcell advthumb\" for=\"${id}\">${inner}</label>` +\n\t\t\t\t\t`<div class=\"advmodal\"><label class=\"advbackdrop\" for=\"${id}\"></label>` +\n\t\t\t\t\t`<div class=\"advbox\"><label class=\"advclose\" for=\"${id}\">✕</label>` +\n\t\t\t\t\t`<video class=\"advvideo\" controls preload=\"none\" playsinline src=\"${p.video}\"></video>` +\n\t\t\t\t\t`<div class=\"advinfo\">${escHtml(p.date)} ${escHtml(p.time)} · ${escHtml(p.dur)}s · ${escHtml(p.sizeMB)} MB${\n\t\t\t\t\t\tp.tag ? ` · ${escHtml(p.tag)}` : ''\n\t\t\t\t\t} · <a href=\"${p.video}\" download target=\"_blank\">${this.wt.download}</a></div></div></div>`\n\t\t\t\t);\n\t\t\t})\n\t\t\t.join('');\n\n\t\tconst grid = items\n\t\t\t? `<div class=\"advgrid\">${items}</div>`\n\t\t\t: `<div class=\"advempty\">${this.wt.noRecordings}</div>`;\n\t\treturn `<style>${galleryCss(minCol, maxW)}</style>${grid}`;\n\t}\n\n\tprivate buildGalleryHtmlJs(d: Device, events: Record<string, unknown>[]): string {\n\t\tconst oid = d.oid;\n\t\tconst minCol = this.config.widgetMinCol || 150;\n\t\tconst maxW = this.config.widgetMaxModalWidth || 900;\n\t\tconst items = events.map(ev => {\n\t\t\tconst p = this.fmtEvent(ev, oid);\n\t\t\treturn {\n\t\t\t\tfn: p.fn,\n\t\t\t\tdate: p.date,\n\t\t\t\ttime: p.time,\n\t\t\t\tdur: p.dur,\n\t\t\t\tsize: p.sizeMB,\n\t\t\t\ttag: p.tag,\n\t\t\t\tthumb: p.thumb,\n\t\t\t\tvideo: p.video,\n\t\t\t};\n\t\t});\n\t\tconst cfg = {\n\t\t\tmin_col: minCol,\n\t\t\tshow_tags: !!this.config.widgetShowTags,\n\t\t\tmax_modal_width: maxW,\n\t\t\tlive_aspect: this.camAspect[oid] || this.config.widgetLiveAspect || '',\n\t\t\tplayer_url: (this.config.widgetPlayerUrl || '').trim(),\n\t\t\ttag_position: this.config.widgetTagPosition || 'bottom-left',\n\t\t\tlabels: { noRecordings: this.wt.noRecordings, all: this.wt.all, live: this.wt.live },\n\t\t};\n\t\tconst data = JSON.stringify({ items, live: null, cfg }).replace(/</g, '\\\\u003c');\n\t\tconst boot = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';\n\t\treturn (\n\t\t\t`<style>${galleryCssJs(minCol)}</style>` +\n\t\t\t`<div class=\"advroot\"><script type=\"application/json\" class=\"advdata\">${data}</script>` +\n\t\t\t`<div class=\"advbar\"><input class=\"advsearchjs\" type=\"text\" placeholder=\"${escHtml(this.wt.search)}\"><div class=\"advtagsjs\"></div></div>` +\n\t\t\t`<div class=\"advgridjs\"></div></div>` +\n\t\t\t`<script type=\"text/plain\" class=\"advcode\">${ADV_CLIENT_CODE}</script>` +\n\t\t\t`<img alt=\"\" src=\"${boot}\" style=\"display:none\" onload=\"(function(){if(window.ADVscan){window.ADVscan();return;}var c=document.querySelector('script.advcode');if(!c)return;var s=document.createElement('script');s.textContent=c.textContent;document.body.appendChild(s);})()\">`\n\t\t);\n\t}\n\n\tprivate buildOverviewHtml(cams: Device[]): string {\n\t\tconst ts = Date.now();\n\t\tconst minCol = this.config.widgetMinCol || 150;\n\t\tconst maxW = this.config.widgetMaxModalWidth || 900;\n\t\tconst PAUSE_ATTR = ` onchange=\"if(!this.checked){var m=this.nextElementSibling.nextElementSibling,v=m&&m.querySelector('video');if(v){v.pause();}}\"`;\n\t\tconst tiles = cams\n\t\t\t.map(d => {\n\t\t\t\tconst oid = d.oid;\n\t\t\t\tconst id = `advlive${sanitize(oid)}`;\n\t\t\t\tconst grab = `${this.baseUrl}/grab.jpg?oid=${oid}&ot=2&maintainAR=1&ts=${ts}`;\n\t\t\t\tconst webm = `${this.baseUrl}/video.webm?oid=${oid}&ot=2`;\n\t\t\t\tconst name = escHtml(d.name || `Camera ${oid}`);\n\t\t\t\tconst arRaw = this.camAspect[oid] || this.config.widgetLiveAspect || '';\n\t\t\t\tconst ar = arRaw ? String(arRaw).replace('/', ' / ') : '';\n\t\t\t\tconst fix = ar ? ' advimgfix' : '';\n\t\t\t\tconst arStyle = ar ? ` style=\"aspect-ratio:${ar}\"` : '';\n\t\t\t\tconst inner =\n\t\t\t\t\t`<span class=\"advimg${fix}\"${arStyle}><img src=\"${grab}\" loading=\"lazy\" alt=\"\">` +\n\t\t\t\t\t`<span class=\"advtag\" style=\"top:5px;left:5px\">● ${escHtml(this.wt.live)}</span><span class=\"advplay\"></span></span>` +\n\t\t\t\t\t`<span class=\"advcap\">${name}</span>`;\n\t\t\t\treturn (\n\t\t\t\t\t`<input class=\"advlb\" type=\"checkbox\" id=\"${id}\"${PAUSE_ATTR}>` +\n\t\t\t\t\t`<label class=\"advcell advthumb\" for=\"${id}\">${inner}</label>` +\n\t\t\t\t\t`<div class=\"advmodal\"><label class=\"advbackdrop\" for=\"${id}\"></label>` +\n\t\t\t\t\t`<div class=\"advbox\"><label class=\"advclose\" for=\"${id}\">✕</label>` +\n\t\t\t\t\t`<video class=\"advvideo\" controls preload=\"none\" playsinline src=\"${webm}\"></video>` +\n\t\t\t\t\t`<div class=\"advinfo\">${name} · Live</div></div></div>`\n\t\t\t\t);\n\t\t\t})\n\t\t\t.join('');\n\t\tconst grid = tiles ? `<div class=\"advgrid\">${tiles}</div>` : `<div class=\"advempty\">No cameras</div>`;\n\t\treturn `<style>${galleryCss(minCol, maxW)}</style>${grid}`;\n\t}\n\n\t// ---- event data points ----\n\n\tprivate async writeEventDps(d: Device, fid: string, events: Record<string, unknown>[]): Promise<void> {\n\t\tconst oid = d.oid;\n\t\tawait this.ensureFolder(`${fid}.events`, 'Events', 'channel');\n\t\tawait this.ensureFolder(`${fid}.events.last`, 'Last event', 'channel');\n\n\t\tconst two = (n: number): string => String(n).padStart(2, '0');\n\t\tconst now = new Date();\n\t\tconst td = `${two(now.getDate())}.${two(now.getMonth() + 1)}.`;\n\t\tconst todayCount = events.filter(ev => this.fmtEvent(ev, oid).date === td).length;\n\n\t\tawait this.writeLeaf(`${fid}.events.count`, events.length);\n\t\tawait this.writeLeaf(`${fid}.events.today`, todayCount);\n\n\t\tif (events.length) {\n\t\t\tconst p = this.fmtEvent(events[0], oid);\n\t\t\tconst tsMs = (parseFloat(String((events[0] as AgentDvrEvent).c ?? 0)) - 621355968000000000) / 10000;\n\t\t\tawait this.writeLeaf(`${fid}.events.last.time`, `${p.date} ${p.time}`);\n\t\t\tawait this.writeLeaf(`${fid}.events.last.timestamp`, Math.round(tsMs));\n\t\t\tawait this.writeLeaf(`${fid}.events.last.tag`, p.tag);\n\t\t\tawait this.writeLeaf(`${fid}.events.last.duration`, p.dur ? parseFloat(p.dur) : 0);\n\t\t\tawait this.writeLeaf(`${fid}.events.last.size_mb`, p.sizeMB);\n\t\t\tawait this.writeLeaf(`${fid}.events.last.thumb`, p.thumb);\n\t\t\tawait this.writeLeaf(`${fid}.events.last.video`, p.video);\n\t\t}\n\n\t\tawait this.ensureFlag(`${fid}.events.new`, 'New event');\n\n\t\tif (this.config.enablePush) {\n\t\t\tconst pid = `${fid}.events.pushTrigger`;\n\t\t\tif (!this.ensuredFolders.has(pid)) {\n\t\t\t\tawait this.ensurePath(pid);\n\t\t\t\tawait this.setObjectNotExistsAsync(pid, {\n\t\t\t\t\ttype: 'state',\n\t\t\t\t\tcommon: {\n\t\t\t\t\t\tname: 'Push trigger (AgentDVR action)',\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\trole: 'text',\n\t\t\t\t\t\tread: true,\n\t\t\t\t\t\twrite: true,\n\t\t\t\t\t\tdef: '',\n\t\t\t\t\t},\n\t\t\t\t\tnative: {},\n\t\t\t\t});\n\t\t\t\tawait this.setStateAsync(pid, { val: '', ack: true });\n\t\t\t\tthis.ensuredFolders.add(pid);\n\t\t\t}\n\t\t\tthis.registry.set(pid, { kind: 'push', oid, fid });\n\t\t}\n\n\t\tconst ignore = (this.config.eventTagsIgnore || 'detected')\n\t\t\t.split(',')\n\t\t\t.map(s => s.trim().toLowerCase())\n\t\t\t.filter(Boolean);\n\t\tconst tokensOf = (s: string): string[] =>\n\t\t\ts\n\t\t\t\t.toLowerCase()\n\t\t\t\t.split(/[,\\s]+/)\n\t\t\t\t.filter(Boolean);\n\n\t\tfor (const kw of (this.config.eventTags || '')\n\t\t\t.split(',')\n\t\t\t.map(s => s.trim())\n\t\t\t.filter(Boolean)) {\n\t\t\tawait this.ensureFlag(`${fid}.events.detected_${sanitize(kw)}`, `Detected: ${kw}`);\n\t\t}\n\n\t\tif (this.config.eventTagsDynamic) {\n\t\t\tconst seen = new Set<string>();\n\t\t\tfor (const ev of events) {\n\t\t\t\ttokensOf((ev as AgentDvrEvent).tg ?? '').forEach(t => seen.add(t));\n\t\t\t}\n\t\t\tfor (const t of seen) {\n\t\t\t\tif (ignore.includes(t)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tawait this.ensureFlag(`${fid}.events.detected_${sanitize(t)}`, `Detected: ${t}`);\n\t\t\t}\n\t\t}\n\n\t\tlet newCount = 0;\n\t\tif (events.length) {\n\t\t\tconst prev = this.lastEventFn[oid];\n\t\t\tif (prev !== undefined) {\n\t\t\t\tconst i = events.findIndex(e => (e as AgentDvrEvent).fn === prev);\n\t\t\t\tnewCount = i === -1 ? events.length : i;\n\t\t\t}\n\t\t\tthis.lastEventFn[oid] = (events[0] as AgentDvrEvent).fn ?? '';\n\t\t}\n\n\t\tif (newCount > 0) {\n\t\t\tawait this.pulse(`${fid}.events.new`);\n\t\t\tconst tags = new Set<string>();\n\t\t\tfor (let i = 0; i < newCount && i < events.length; i++) {\n\t\t\t\ttokensOf((events[i] as AgentDvrEvent).tg ?? '').forEach(t => tags.add(t));\n\t\t\t}\n\t\t\tfor (const t of tags) {\n\t\t\t\tif (ignore.includes(t)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst tid = `${fid}.events.detected_${sanitize(t)}`;\n\t\t\t\tawait this.ensureFlag(tid, `Detected: ${t}`);\n\t\t\t\tawait this.pulse(tid);\n\t\t\t}\n\t\t\tthis.log.debug(`${newCount} new event(s) cam ${oid} [${[...tags].join(',')}]`);\n\t\t}\n\t}\n\n\t// ---- camera events + widget ----\n\n\tprivate async updateCameraEvents(d: Device, fid: string): Promise<void> {\n\t\tif (d.ot !== 2) {\n\t\t\treturn;\n\t\t}\n\t\tif (!this.config.enableWidget && !this.config.enableEventDps) {\n\t\t\treturn;\n\t\t}\n\t\tif (this.eventBusy[d.oid]) {\n\t\t\treturn;\n\t\t}\n\t\tthis.eventBusy[d.oid] = true;\n\t\ttry {\n\t\t\tconst evRes = await this.apiGet(`/q/getEvents?oid=${d.oid}&ot=2`);\n\t\t\tconst evJson = asJson(evRes.data);\n\t\t\tif (!evJson) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst events = (\n\t\t\t\tArray.isArray(evJson.events) ? evJson.events : Array.isArray(evJson) ? evJson : []\n\t\t\t) as Record<string, unknown>[];\n\n\t\t\tif (this.config.enableEventDps) {\n\t\t\t\tawait this.writeEventDps(d, fid, events);\n\t\t\t}\n\n\t\t\tif (this.config.enableWidget) {\n\t\t\t\tconst wId = `${fid}.widget`;\n\t\t\t\tif (!this.ensuredFolders.has(wId)) {\n\t\t\t\t\tawait this.ensurePath(wId);\n\t\t\t\t\tawait this.setObjectNotExistsAsync(wId, {\n\t\t\t\t\t\ttype: 'state',\n\t\t\t\t\t\tcommon: {\n\t\t\t\t\t\t\tname: 'HTML gallery widget',\n\t\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\t\trole: 'html',\n\t\t\t\t\t\t\tread: true,\n\t\t\t\t\t\t\twrite: false,\n\t\t\t\t\t\t\tdef: '',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tnative: {},\n\t\t\t\t\t});\n\t\t\t\t\tthis.ensuredFolders.add(wId);\n\t\t\t\t}\n\t\t\t\tconst ev = events.slice(0, Math.max(1, this.config.widgetAnzahl || 50));\n\t\t\t\tconst f = (ev[0] ?? {}) as AgentDvrEvent;\n\t\t\t\tconst l = (ev[ev.length - 1] ?? {}) as AgentDvrEvent;\n\t\t\t\tconst sig = `${ev.length}|${f.fn ?? ''}|${l.fn ?? ''}`;\n\t\t\t\tif (this.widgetSig[wId] !== sig) {\n\t\t\t\t\tawait this.setStateAsync(wId, { val: this.buildGalleryHtml(d, ev), ack: true });\n\t\t\t\t\tthis.widgetSig[wId] = sig;\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.eventBusy[d.oid] = false;\n\t\t}\n\t}\n\n\tprivate pushRefresh(oid: number | string): void {\n\t\tconst d = this.devById.get(oid);\n\t\tif (!d) {\n\t\t\tthis.log.warn(`Push: camera ${oid} unknown (available after first poll)`);\n\t\t\treturn;\n\t\t}\n\t\tconst fid = this.deviceFolder(d);\n\t\tfor (const ms of [0, 1500, 3500, 6000]) {\n\t\t\tthis.setTimeout(\n\t\t\t\t() =>\n\t\t\t\t\tthis.updateCameraEvents(d, fid).catch(e =>\n\t\t\t\t\t\tthis.log.warn(`Push refresh error: ${(e as Error).message}`),\n\t\t\t\t\t),\n\t\t\t\tms,\n\t\t\t);\n\t\t}\n\t}\n\n\t// ---- main poll ----\n\n\tprivate async poll(): Promise<void> {\n\t\tconst res = await this.apiGet('/command/getObjects');\n\t\tconst json = asJson(res.data);\n\n\t\tif (!res.ok || !json) {\n\t\t\tawait this.setStateAsync('info.connection', false, true);\n\t\t\tawait this.setStateAsync('system.online', { val: false, ack: true });\n\t\t\tthis.log.warn(`AgentDVR unreachable: ${res.error || 'invalid response'}`);\n\t\t\treturn;\n\t\t}\n\n\t\tawait this.setStateAsync('info.connection', true, true);\n\t\tawait this.setStateAsync('system.online', { val: true, ack: true });\n\n\t\tif (json.settings) {\n\t\t\tawait this.flattenWrite(json.settings, 'system.settings', 0);\n\t\t}\n\n\t\tif (this.config.storeRawJson) {\n\t\t\tconst clean: Record<string, unknown> = { ...json };\n\t\t\tdelete clean.icons;\n\t\t\tawait this.writeLeaf('system.raw_getObjects', JSON.stringify(clean).slice(0, 60000));\n\t\t}\n\n\t\tconst devices = findDevices(json);\n\t\tawait this.setStateAsync('system.cameraCount', { val: devices.filter(d => d.ot === 2).length, ack: true });\n\n\t\tfor (const d of devices) {\n\t\t\tawait this.buildDevice(d);\n\t\t}\n\n\t\tconst stats = asJson((await this.apiGet('/command/getSystemStats')).data);\n\t\tif (stats) {\n\t\t\tawait this.flattenWrite(stats, 'system.stats', 0);\n\t\t\tconst gb = parseSizeGb(stats.disk_free);\n\t\t\tif (gb !== null) {\n\t\t\t\tawait this.writeLeaf('system.disk_free_gb', gb);\n\t\t\t}\n\t\t}\n\n\t\tconst status = asJson((await this.apiGet('/command/getStatus')).data);\n\t\tif (status) {\n\t\t\tawait this.flattenWrite(status, 'system.status', 0);\n\t\t}\n\n\t\tif (this.config.enableSystemControls && Array.isArray(json.profiles)) {\n\t\t\tconst states: Record<number, string> = {};\n\t\t\tlet activeInd: number | null = null;\n\t\t\tfor (const p of json.profiles) {\n\t\t\t\tif (p && typeof p === 'object') {\n\t\t\t\t\tconst po = p as Record<string, unknown>;\n\t\t\t\t\tconst ind = po.id ?? po.ind ?? po.index;\n\t\t\t\t\tconst pname = po.name;\n\t\t\t\t\tif (typeof ind === 'number' && (typeof pname === 'string' || typeof pname === 'number')) {\n\t\t\t\t\t\tstates[ind] = String(pname);\n\t\t\t\t\t\tif (po.active === true) {\n\t\t\t\t\t\t\tactiveInd = ind;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (Object.keys(states).length > 0) {\n\t\t\t\tconst sig = JSON.stringify(states);\n\t\t\t\tif (sig !== this.profileSig) {\n\t\t\t\t\tthis.profileSig = sig;\n\t\t\t\t\tawait this.extendObjectAsync('system.profile.selector', { common: { states } });\n\t\t\t\t\tawait this.setStateAsync('system.profile.list', { val: sig, ack: true });\n\t\t\t\t}\n\t\t\t\tif (activeInd !== null) {\n\t\t\t\t\tawait this.setStateAsync('system.profile.selector', { val: activeInd, ack: true });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (this.config.enableOverview) {\n\t\t\tconst cams = devices.filter(d => d.ot === 2);\n\t\t\tconst ovId = 'overview';\n\t\t\tif (!this.ensuredFolders.has(ovId)) {\n\t\t\t\tawait this.setObjectNotExistsAsync(ovId, {\n\t\t\t\t\ttype: 'state',\n\t\t\t\t\tcommon: {\n\t\t\t\t\t\tname: 'Overview (all cameras)',\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\trole: 'html',\n\t\t\t\t\t\tread: true,\n\t\t\t\t\t\twrite: false,\n\t\t\t\t\t\tdef: '',\n\t\t\t\t\t},\n\t\t\t\t\tnative: {},\n\t\t\t\t});\n\t\t\t\tthis.ensuredFolders.add(ovId);\n\t\t\t}\n\t\t\tawait this.setStateAsync(ovId, { val: this.buildOverviewHtml(cams), ack: true });\n\t\t}\n\n\t\tawait this.setStateAsync('system.lastUpdate', { val: new Date().toISOString(), ack: true });\n\t\tawait this.setStateAsync('system.lastPoll', { val: Date.now(), ack: true });\n\t}\n\n\tprivate scheduleRefresh(): void {\n\t\tif (this.refreshTimer !== undefined) {\n\t\t\tthis.clearTimeout(this.refreshTimer);\n\t\t\tthis.refreshTimer = undefined;\n\t\t}\n\t\tthis.refreshTimer = this.setTimeout(\n\t\t\t() => this.poll().catch(e => this.log.warn(`Refresh error: ${(e as Error).message}`)),\n\t\t\t1500,\n\t\t);\n\t}\n\n\t// ---- command execution ----\n\n\tprivate ptzUrl(oid: number | string, dir: number): string {\n\t\treturn `/ptz.cgi?oid=${encodeURIComponent(String(oid))}&dir=${dir}`;\n\t}\n\n\tprivate buildCommandUrl(entry: RegistryEntry): string {\n\t\tif (entry.kind === 'ptz') {\n\t\t\treturn this.ptzUrl(entry.oid!, entry.dir!);\n\t\t}\n\t\tif (entry.kind === 'sys') {\n\t\t\treturn entry.path as string;\n\t\t}\n\t\tconst qs: string[] = [];\n\t\tfor (const p of entry.params || []) {\n\t\t\tif (p === 'oid') {\n\t\t\t\tqs.push(`oid=${encodeURIComponent(String(entry.oid))}`);\n\t\t\t} else if (p === 'ot') {\n\t\t\t\tqs.push(`ot=${encodeURIComponent(String(entry.ot))}`);\n\t\t\t}\n\t\t}\n\t\treturn (entry.path || '') + (qs.length ? `?${qs.join('&')}` : '');\n\t}\n\n\tprivate async clearPtzSiblings(stateId: string): Promise<void> {\n\t\tconst prefix = stateId.slice(0, stateId.lastIndexOf('.') + 1);\n\t\tfor (const p of PTZ_DIRS) {\n\t\t\tif (!p.hold) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst sid = prefix + p.id;\n\t\t\tif (sid !== stateId) {\n\t\t\t\tawait this.setStateAsync(sid, { val: false, ack: true });\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async runCommand(relId: string, entry: RegistryEntry, val: ioBroker.StateValue): Promise<void> {\n\t\tif (entry.kind === 'setProfile') {\n\t\t\tconst ind = typeof val === 'number' ? val : parseInt(String(val ?? ''), 10);\n\t\t\tif (!isNaN(ind)) {\n\t\t\t\tconst url = `/command/setProfile?ind=${ind}`;\n\t\t\t\tconst cmdRes = await this.apiGet(url);\n\t\t\t\tif (cmdRes.ok) {\n\t\t\t\t\tthis.log.debug(`OK: ${url}`);\n\t\t\t\t\tawait this.setStateAsync(relId, { val: ind, ack: true });\n\t\t\t\t} else {\n\t\t\t\t\tthis.log.warn(`Command failed (${url}): ${cmdRes.error}`);\n\t\t\t\t}\n\t\t\t\tthis.scheduleRefresh();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (entry.kind === 'snapshotB64') {\n\t\t\tconst snapId = `${entry.fid}.snapshot_b64`;\n\t\t\tawait this.fetchSnapshotB64(entry.oid!, snapId);\n\t\t\tawait this.setStateAsync(relId, { val: false, ack: true });\n\t\t\treturn;\n\t\t}\n\n\t\tif (entry.kind === 'push') {\n\t\t\tthis.log.info(`Push trigger cam ${entry.oid}`);\n\t\t\tawait this.setStateAsync(relId, { val: '', ack: true });\n\t\t\tthis.pushRefresh(entry.oid!);\n\t\t\treturn;\n\t\t}\n\n\t\tif (entry.kind === 'ptzHold') {\n\t\t\tconst oid = entry.oid!;\n\t\t\tconst startMove = async (): Promise<void> => {\n\t\t\t\tawait this.clearPtzSiblings(relId);\n\t\t\t\tawait this.apiGet(this.ptzUrl(oid, entry.dir!));\n\t\t\t\tthis.ptzActive.set(oid, relId);\n\t\t\t\tawait this.setStateAsync(relId, { val: true, ack: true });\n\t\t\t};\n\t\t\tconst stopMove = async (): Promise<void> => {\n\t\t\t\tawait this.apiGet(this.ptzUrl(oid, 11));\n\t\t\t\tif (this.ptzActive.get(oid) === relId) {\n\t\t\t\t\tthis.ptzActive.delete(oid);\n\t\t\t\t}\n\t\t\t\tawait this.setStateAsync(relId, { val: false, ack: true });\n\t\t\t};\n\t\t\tif (val) {\n\t\t\t\tif (this.ptzActive.get(oid) === relId) {\n\t\t\t\t\tawait stopMove();\n\t\t\t\t} else {\n\t\t\t\t\tawait startMove();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tawait stopMove();\n\t\t\t}\n\t\t\tthis.scheduleRefresh();\n\t\t\treturn;\n\t\t}\n\n\t\tif (!val) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (entry.kind === 'sys' && entry.path === null) {\n\t\t\tawait this.poll().catch(e => this.log.warn(`Manual refresh error: ${(e as Error).message}`));\n\t\t\tawait this.setStateAsync(relId, { val: false, ack: true });\n\t\t\treturn;\n\t\t}\n\n\t\tconst url = this.buildCommandUrl(entry);\n\t\tconst cmdRes = await this.apiGet(url);\n\t\tif (cmdRes.ok) {\n\t\t\tthis.log.debug(`OK: ${url}`);\n\t\t} else {\n\t\t\tthis.log.warn(`Command failed (${url}): ${cmdRes.error}`);\n\t\t}\n\n\t\tawait this.setStateAsync(relId, { val: false, ack: true });\n\t\tthis.scheduleRefresh();\n\t}\n}\n\nif (require.main !== module) {\n\tmodule.exports = (options: Partial<utils.AdapterOptions> | undefined) => new AgentDvr(options);\n} else {\n\t(() => new AgentDvr())();\n}\n"],
|
|
4
|
+
"sourcesContent": ["import * as utils from '@iobroker/adapter-core';\nimport * as http from 'node:http';\nimport { getWidgetLabels, type WidgetLabels } from './lib/widget-i18n';\n\n// =====================================================================\n// Agent DVR -> ioBroker Adapter\n// API reference: https://ispysoftware.github.io/Agent_API/ (v7.5)\n// =====================================================================\n\n// ---- Types ----\n\ninterface ApiResult {\n\tok: boolean;\n\tdata?: string;\n\terror?: string;\n}\n\ninterface AgentDvrEvent {\n\tfn?: string;\n\ttg?: string;\n\tc?: number;\n\td?: number | string;\n\tsb?: number;\n\t[key: string]: unknown;\n}\n\ninterface Device {\n\toid: number | string;\n\tot: number;\n\tname: string;\n\traw: Record<string, unknown>;\n}\n\ntype RegistryKind = 'cam' | 'sys' | 'ptz' | 'ptzHold' | 'push' | 'setProfile' | 'snapshotB64';\n\ninterface RegistryEntry {\n\tkind: RegistryKind;\n\tpath?: string | null;\n\tparams?: string[];\n\toid?: number | string;\n\tot?: number;\n\tdir?: number;\n\tfid?: string;\n}\n\n// ---- Commands ----\n\nconst CAM_COMMANDS = [\n\t{ id: 'record', path: '/command/record', params: ['oid', 'ot'], name: 'Start recording' },\n\t{ id: 'recordStop', path: '/command/recordStop', params: ['oid', 'ot'], name: 'Stop recording' },\n\t{ id: 'recordRestart', path: '/command/recordRestart', params: ['oid', 'ot'], name: 'Restart recording' },\n\t{ id: 'triggerRecord', path: '/command/triggerRecord', params: ['oid', 'ot'], name: 'Trigger recording (timeout)' },\n\t{ id: 'snapshot', path: '/command/snapshot', params: ['oid'], name: 'Take snapshot' },\n\t{ id: 'detect', path: '/command/detect', params: ['oid', 'ot'], name: 'Trigger motion detection' },\n\t{ id: 'alertOn', path: '/command/alertOn', params: ['oid', 'ot'], name: 'Arm alerts' },\n\t{ id: 'alertOff', path: '/command/alertOff', params: ['oid', 'ot'], name: 'Disarm alerts' },\n\t{ id: 'switchOn', path: '/command/switchOn', params: ['oid', 'ot'], name: 'Switch device on' },\n\t{ id: 'switchOff', path: '/command/switchOff', params: ['oid', 'ot'], name: 'Switch device off' },\n\t{ id: 'objectDetectOn', path: '/command/objectdetecton', params: ['oid', 'ot'], name: 'Enable object detection' },\n\t{\n\t\tid: 'objectDetectOff',\n\t\tpath: '/command/objectdetectoff',\n\t\tparams: ['oid', 'ot'],\n\t\tname: 'Disable object detection',\n\t},\n\t{ id: 'recOnAlert', path: '/command/recordOnAlertOn', params: ['oid', 'ot'], name: 'Record on alert: on' },\n\t{ id: 'recOnDetect', path: '/command/recordOnDetectOn', params: ['oid', 'ot'], name: 'Record on detect: on' },\n\t{ id: 'purge', path: '/command/purge', params: ['oid', 'ot'], name: 'Purge folder (caution!)' },\n] as const;\n\nconst SYS_COMMANDS = [\n\t{ id: 'arm', path: '/command/arm', name: 'Arm system' },\n\t{ id: 'disarm', path: '/command/disarm', name: 'Disarm system' },\n\t{ id: 'allOn', path: '/command/allOn', name: 'All devices on' },\n\t{ id: 'allOff', path: '/command/allOff', name: 'All devices off' },\n\t{ id: 'reloadConfig', path: '/command/reloadConfig', name: 'Reload config' },\n\t{ id: 'reloadObjects', path: '/command/reloadObjects', name: 'Reload objects' },\n\t{ id: 'runStorageMgmt', path: '/command/runStorageMgmt', name: 'Run storage management' },\n\t{ id: 'blockExternal', path: '/command/blockExternal', name: 'Block external access' },\n\t{ id: 'unblockExternal', path: '/command/unblockExternal', name: 'Unblock external access' },\n\t{ id: 'restart', path: '/command/restart', name: 'Restart Agent DVR' },\n\t{ id: 'refresh', path: null, name: 'Refresh data now' },\n] as const;\n\nconst PTZ_DIRS = [\n\t{ id: 'left', dir: 1, hold: true },\n\t{ id: 'upLeft', dir: 2, hold: true },\n\t{ id: 'up', dir: 3, hold: true },\n\t{ id: 'upRight', dir: 4, hold: true },\n\t{ id: 'right', dir: 5, hold: true },\n\t{ id: 'downRight', dir: 6, hold: true },\n\t{ id: 'down', dir: 7, hold: true },\n\t{ id: 'downLeft', dir: 8, hold: true },\n\t{ id: 'zoomIn', dir: 9, hold: true },\n\t{ id: 'zoomOut', dir: 10, hold: true },\n\t{ id: 'stop', dir: 11, hold: false },\n\t{ id: 'center', dir: 0, hold: false },\n] as const;\n\nconst SKIP_KEYS = ['icons', 'image', 'thumbnail', 'base64'];\n\n// ---- Module-level helpers ----\n\nfunction sanitize(s: unknown): string {\n\treturn (\n\t\tString(s)\n\t\t\t.replace(/[\\s.[\\]*?\"'`,;:/\\\\]+/g, '_')\n\t\t\t.replace(/[^A-Za-z0-9_-]/g, '_')\n\t\t\t.replace(/_+/g, '_')\n\t\t\t.replace(/^_+|_+$/g, '') || 'x'\n\t);\n}\n\nfunction asJson(data: unknown): Record<string, unknown> | null {\n\tif (data == null) {\n\t\treturn null;\n\t}\n\tif (typeof data === 'object') {\n\t\treturn data as Record<string, unknown>;\n\t}\n\tif (typeof data === 'string') {\n\t\ttry {\n\t\t\treturn JSON.parse(data) as Record<string, unknown>;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\treturn null;\n}\n\nfunction toStr(v: unknown): string {\n\tif (v == null) {\n\t\treturn '';\n\t}\n\tif (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') {\n\t\treturn String(v);\n\t}\n\treturn Object.prototype.toString.call(v);\n}\n\nfunction escHtml(s: unknown): string {\n\treturn toStr(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\n}\n\nfunction parseSizeGb(s: unknown): number | null {\n\tif (s == null) {\n\t\treturn null;\n\t}\n\tconst m = toStr(s)\n\t\t.trim()\n\t\t.match(/([\\d.,]+)\\s*([KMGT]?B)?/i);\n\tif (!m) {\n\t\treturn null;\n\t}\n\tlet v = parseFloat(m[1].replace(',', '.'));\n\tif (isNaN(v)) {\n\t\treturn null;\n\t}\n\tconst u = (m[2] || 'GB').toUpperCase();\n\tif (u === 'TB') {\n\t\tv *= 1024;\n\t} else if (u === 'MB') {\n\t\tv /= 1024;\n\t} else if (u === 'KB') {\n\t\tv /= 1048576;\n\t} else if (u === 'B') {\n\t\tv /= 1073741824;\n\t}\n\treturn Math.round(v * 100) / 100;\n}\n\nfunction findWH(obj: unknown, depth: number): [number, number] | null {\n\tif (!obj || typeof obj !== 'object' || depth > 5) {\n\t\treturn null;\n\t}\n\tconst o = obj as Record<string, unknown>;\n\tfor (const [kw, kh] of [\n\t\t['width', 'height'],\n\t\t['w', 'h'],\n\t\t['sourceWidth', 'sourceHeight'],\n\t\t['videoWidth', 'videoHeight'],\n\t\t['imageWidth', 'imageHeight'],\n\t]) {\n\t\tconst w = o[kw],\n\t\t\th = o[kh];\n\t\tif (typeof w === 'number' && typeof h === 'number' && w >= 100 && h >= 100 && w <= 12000 && h <= 12000) {\n\t\t\tconst r = w / h;\n\t\t\tif (r >= 0.3 && r <= 4) {\n\t\t\t\treturn [w, h];\n\t\t\t}\n\t\t}\n\t}\n\tfor (const k of Object.keys(o)) {\n\t\tconst g = findWH(o[k], depth + 1);\n\t\tif (g) {\n\t\t\treturn g;\n\t\t}\n\t}\n\treturn null;\n}\n\nfunction detectOt(elem: Record<string, unknown>): number {\n\tconst v = elem.typeID ?? elem.ot ?? elem.objectTypeID ?? elem.type;\n\treturn v === 1 || v === 2 ? v : 2;\n}\n\nfunction findDevices(json: Record<string, unknown>): Device[] {\n\tconst list = Array.isArray(json.objectList) ? json.objectList : [];\n\tconst out: Device[] = [];\n\tfor (const e of list) {\n\t\tif (!e || typeof e !== 'object') {\n\t\t\tcontinue;\n\t\t}\n\t\tconst el = e as Record<string, unknown>;\n\t\tconst rawId = el.id ?? el.oid;\n\t\tif (rawId === undefined || rawId === null) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst id = rawId as number | string;\n\t\tconst name = typeof el.name === 'string' ? el.name : `obj_${id}`;\n\t\tout.push({ oid: id, ot: detectOt(el), name, raw: el });\n\t}\n\treturn out;\n}\n\n// ---- HTML/CSS gallery helpers ----\n\nfunction galleryCss(minCol: number, maxW: number): string {\n\treturn (\n\t\t`.advgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(${minCol}px,1fr));gap:12px;max-width:100%}` +\n\t\t`.advgrid .advlb{display:none}` +\n\t\t`.advcell{display:flex;flex-direction:column;cursor:pointer;text-decoration:none;color:inherit;font-family:inherit}` +\n\t\t`.advimg{position:relative;display:block;border-radius:8px;overflow:hidden;background:rgba(127,127,127,.15)}` +\n\t\t`.advimg img{width:100%;height:auto;display:block}` +\n\t\t`.advimgfix img{height:100%;object-fit:cover}` +\n\t\t`.advtag{position:absolute;background:rgba(0,0,0,.55);color:#fff;font-size:.6rem;padding:2px 4px;border-radius:3px;line-height:1;max-width:85%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}` +\n\t\t`.advplay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .15s;background:rgba(0,0,0,.2)}` +\n\t\t`.advplay::before{content:\"\";border-style:solid;border-width:11px 0 11px 18px;border-color:transparent transparent transparent #fff;filter:drop-shadow(0 1px 2px rgba(0,0,0,.6))}` +\n\t\t`.advcell:hover .advplay{opacity:1}` +\n\t\t`.advcap{font-size:.72rem;margin-top:4px;line-height:1.25;opacity:.85}` +\n\t\t`.advmodal{display:none;position:fixed;inset:0;z-index:99999;align-items:center;justify-content:center}` +\n\t\t`.advgrid .advlb:checked + .advthumb + .advmodal{display:flex}` +\n\t\t`.advbackdrop{position:absolute;inset:0;background:transparent;cursor:pointer}` +\n\t\t`.advbox{position:relative;z-index:1;display:flex;flex-direction:column;gap:8px;max-width:min(92vw,${maxW}px);background:#1c1c1e;padding:12px;border-radius:14px;box-shadow:0 16px 48px rgba(0,0,0,.55),0 0 0 1px rgba(255,255,255,.08)}` +\n\t\t`.advvideo{width:100%;max-height:80vh;border-radius:8px;background:#000;display:block}` +\n\t\t`.advclose{position:absolute;top:18px;right:18px;width:32px;height:32px;border-radius:50%;background:rgba(0,0,0,.65);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:15px;z-index:2;border:2px solid rgba(255,255,255,.35)}` +\n\t\t`.advinfo{color:#fff;font-size:.8rem;text-align:center}` +\n\t\t`.advinfo a{color:#8ab4f8}` +\n\t\t`.advempty{padding:24px;text-align:center;opacity:.7;font-family:inherit}`\n\t);\n}\n\nfunction galleryCssJs(minCol: number): string {\n\treturn (\n\t\t`.advroot{font-family:inherit;color:inherit}` +\n\t\t`.advbar{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-bottom:10px}` +\n\t\t`.advsearchjs{flex:1 1 160px;min-width:120px;padding:6px 10px;border-radius:8px;border:1px solid rgba(127,127,127,.4);background:rgba(127,127,127,.08);color:inherit;font:inherit}` +\n\t\t`.advtagsjs{display:flex;flex-wrap:wrap;gap:6px}` +\n\t\t`.advtagbtn{padding:4px 10px;border-radius:999px;border:1px solid rgba(127,127,127,.4);background:transparent;color:inherit;cursor:pointer;font:inherit;font-size:.8rem}` +\n\t\t`.advtagbtn.on{background:#2d6cdf;color:#fff;border-color:#2d6cdf}` +\n\t\t`.advgridjs{display:grid;grid-template-columns:repeat(auto-fill,minmax(${minCol}px,1fr));gap:12px}` +\n\t\t`.advcelljs{display:flex;flex-direction:column;cursor:pointer}` +\n\t\t`.advimgjs{position:relative;display:block;border-radius:8px;overflow:hidden;background:rgba(127,127,127,.15)}` +\n\t\t`.advimgjs img{width:100%;height:auto;display:block}` +\n\t\t`.advimgjs.advimgfix img{height:100%;object-fit:cover}` +\n\t\t`.advtagjs{position:absolute;background:rgba(0,0,0,.55);color:#fff;font-size:.6rem;padding:2px 4px;border-radius:3px;line-height:1;max-width:85%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}` +\n\t\t`.advplayjs{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .15s;background:rgba(0,0,0,.2)}` +\n\t\t`.advplayjs::before{content:\"\";border-style:solid;border-width:11px 0 11px 18px;border-color:transparent transparent transparent #fff;filter:drop-shadow(0 1px 2px rgba(0,0,0,.6))}` +\n\t\t`.advcelljs:hover .advplayjs{opacity:1}` +\n\t\t`.advcapjs{font-size:.72rem;margin-top:4px;line-height:1.25;opacity:.85}` +\n\t\t`.advemptyjs{padding:24px;text-align:center;opacity:.7}`\n\t);\n}\n\n// Client-side browser code for the JS widget variant.\n// Stored as a string: TypeScript never type-checks browser code running in DOM context.\nconst ADV_CLIENT_CODE = `\n(function(){\nfunction esc(s){return String(s==null?'':s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/\"/g,'"');}\nvar MODAL_CSS='.advmodaljs{display:none;position:fixed;inset:0;z-index:99999;align-items:center;justify-content:center}.advbackdropjs{position:absolute;inset:0;background:transparent;cursor:pointer}.advboxjs{position:relative;z-index:1;display:flex;flex-direction:column;gap:8px;max-width:min(92vw,900px);background:#1c1c1e;padding:12px;border-radius:14px;box-shadow:0 16px 48px rgba(0,0,0,.55),0 0 0 1px rgba(255,255,255,.08)}.advvideojs{width:100%;max-height:80vh;border-radius:8px;background:#000;display:block}.advclosejs{position:absolute;top:18px;right:18px;width:32px;height:32px;border-radius:50%;background:rgba(0,0,0,.65);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:15px;z-index:2;border:2px solid rgba(255,255,255,.35)}.advinfojs{color:#fff;font-size:.8rem;text-align:center}';\nvar modalEl=null,modalVideo=null,modalInfo=null,modalBox=null;\nfunction getModal(){\n if(modalEl&&document.body.contains(modalEl))return modalEl;\n if(!document.getElementById('advmodalcss')){var st=document.createElement('style');st.id='advmodalcss';st.textContent=MODAL_CSS;document.head.appendChild(st);}\n modalEl=document.createElement('div');modalEl.className='advmodaljs';\n var bd=document.createElement('div');bd.className='advbackdropjs';\n modalBox=document.createElement('div');modalBox.className='advboxjs';\n var cl=document.createElement('span');cl.className='advclosejs';cl.textContent='\u2715';\n modalVideo=document.createElement('video');modalVideo.className='advvideojs';\n modalVideo.setAttribute('controls','');modalVideo.setAttribute('preload','none');modalVideo.setAttribute('playsinline','');\n modalInfo=document.createElement('div');modalInfo.className='advinfojs';\n modalBox.appendChild(cl);modalBox.appendChild(modalVideo);modalBox.appendChild(modalInfo);\n modalEl.appendChild(bd);modalEl.appendChild(modalBox);\n document.body.appendChild(modalEl);\n bd.addEventListener('click',closeModal);cl.addEventListener('click',closeModal);\n return modalEl;\n}\nfunction openSrc(src,label,maxW){\n var m=getModal();\n if(maxW)modalBox.style.maxWidth='min(92vw,'+maxW+'px)';\n modalVideo.setAttribute('src',src);modalInfo.textContent=label;m.style.display='flex';\n}\nfunction closeModal(){\n if(!modalEl)return;\n try{modalVideo.pause();}catch(e){}\n modalVideo.removeAttribute('src');try{modalVideo.load();}catch(e){}\n modalEl.style.display='none';\n}\ndocument.addEventListener('keydown',function(e){if(e.key==='Escape'||e.keyCode===27)closeModal();});\nfunction initRoot(root){\n var dataEl=root.querySelector('script.advdata');\n if(!dataEl)return;\n var D;try{D=JSON.parse(dataEl.textContent);}catch(e){return;}\n var cfg=D.cfg||{},items=D.items||[],live=D.live||null;\n var grid=root.querySelector('.advgridjs'),search=root.querySelector('.advsearchjs'),tagsBox=root.querySelector('.advtagsjs');\n var state={q:'',tag:''};\n var tagset={};\n items.forEach(function(it){String(it.tag||'').split(/[,\\\\s]+/).forEach(function(t){if(t)tagset[t]=1;});});\n var tags=Object.keys(tagset);\n if(cfg.show_tags&&tags.length){\n var mk=function(label,val){\n var b=document.createElement('button');b.className='advtagbtn';b.textContent=label;b.setAttribute('data-tag',val);\n b.addEventListener('click',function(){state.tag=state.tag===val?'':val;updateTagBtns();render();});\n tagsBox.appendChild(b);\n };\n mk(cfg.labels&&cfg.labels.all||'All','');tags.forEach(function(t){mk(t,t);});\n }\n function updateTagBtns(){var bs=tagsBox.querySelectorAll('.advtagbtn');for(var i=0;i<bs.length;i++)bs[i].classList.toggle('on',bs[i].getAttribute('data-tag')===state.tag);}\n updateTagBtns();\n if(search)search.addEventListener('input',function(){state.q=this.value.toLowerCase();render();});\n function badge(it){if(!(cfg.show_tags&&it.tag))return '';var pos=String(cfg.tag_position||'bottom-left').split('-');return '<span class=\"advtagjs\" style=\"'+pos[0]+':5px;'+pos[1]+':5px\">'+esc(it.tag)+'</span>';}\n function render(){\n var html='';\n if(live){var ar=cfg.live_aspect||'',arStyle=ar?' style=\"aspect-ratio:'+ar.replace('/',' / ')+'\"':'',arCls=ar?' advimgfix':'';\n html+='<div class=\"advcelljs\" data-live=\"1\"><span class=\"advimgjs'+arCls+'\"'+arStyle+'><img loading=\"lazy\" src=\"'+esc(live.thumb)+'\"><span class=\"advtagjs\" style=\"top:5px;left:5px\">● '+(cfg.labels&&cfg.labels.live||'LIVE')+'</span><span class=\"advplayjs\"></span></span><span class=\"advcapjs\">'+esc(live.name)+'</span></div>';}\n items.forEach(function(it,i){\n if(state.tag&&String(it.tag||'').toLowerCase().indexOf(state.tag.toLowerCase())<0)return;\n if(state.q){var hay=((it.date||'')+' '+(it.time||'')+' '+(it.tag||'')).toLowerCase();if(hay.indexOf(state.q)<0)return;}\n html+='<div class=\"advcelljs\" data-i=\"'+i+'\"><span class=\"advimgjs\"><img loading=\"lazy\" src=\"'+esc(it.thumb)+'\">'+badge(it)+'<span class=\"advplayjs\"></span></span><span class=\"advcapjs\">'+esc(it.date)+' '+esc(it.time)+'<br>'+esc(it.dur)+'s · '+esc(it.size)+' MB</span></div>';\n });\n grid.innerHTML=html||'<div class=\"advemptyjs\">'+(cfg.labels&&cfg.labels.noRecordings||'No recordings')+'</div>';\n var cells=grid.querySelectorAll('.advcelljs');\n for(var k=0;k<cells.length;k++)(function(cell){\n cell.addEventListener('click',function(){\n if(cell.getAttribute('data-live')==='1'){openSrc(live.video,(live.name||'')+' \u00B7 Live',cfg.max_modal_width);return;}\n var it=items[parseInt(cell.getAttribute('data-i'),10)],maxW=Number(cfg.max_modal_width)||900;\n if(cfg.player_url){var u=cfg.player_url.replace(/{video}/g,encodeURIComponent(it.video)).replace(/{videoRaw}/g,it.video).replace(/{fn}/g,encodeURIComponent(it.fn||'')).replace(/{date}/g,encodeURIComponent(it.date)).replace(/{time}/g,encodeURIComponent(it.time)).replace(/{duration}/g,encodeURIComponent(it.dur)).replace(/{size}/g,encodeURIComponent(it.size)).replace(/{tags}/g,encodeURIComponent(it.tag));window.open(u,'_blank');}\n else{openSrc(it.video,esc(it.date)+' '+esc(it.time)+(it.dur!=null?' \u00B7 '+it.dur+'s':'')+(it.size!=null?' \u00B7 '+it.size+' MB':'')+(it.tag?' \u00B7 '+it.tag:''),maxW);}\n });\n })(cells[k]);\n }\n render();\n}\nfunction scan(){var roots=document.querySelectorAll('.advroot');for(var i=0;i<roots.length;i++)if(!roots[i].__adv){roots[i].__adv=1;initRoot(roots[i]);}}\nwindow.ADVscan=scan;scan();\n})();\n`.trim();\n\n// ---- Adapter class ----\n\nclass AgentDvr extends utils.Adapter {\n\tprivate pollTimer: ioBroker.Interval | undefined = undefined;\n\tprivate refreshTimer: ioBroker.Timeout | undefined = undefined;\n\tprivate authHeader: string | null = null;\n\tprivate baseUrl = '';\n\tprivate wt: WidgetLabels = getWidgetLabels('en');\n\n\tprivate readonly ensuredFolders = new Set<string>();\n\tprivate readonly registry = new Map<string, RegistryEntry>();\n\tprivate readonly ptzActive = new Map<string | number, string>();\n\tprivate readonly widgetSig: Record<string, string> = {};\n\tprivate profileSig = '';\n\tprivate readonly lastEventFn: Record<string | number, string> = {};\n\tprivate readonly camAspect: Record<string | number, string> = {};\n\tprivate readonly devById = new Map<string | number, Device>();\n\tprivate readonly eventBusy: Record<string | number, boolean> = {};\n\n\tpublic constructor(options: Partial<utils.AdapterOptions> = {}) {\n\t\tsuper({ ...options, name: 'agent-dvr' });\n\t\tthis.on('ready', this.onReady.bind(this));\n\t\tthis.on('stateChange', this.onStateChange.bind(this));\n\t\tthis.on('unload', this.onUnload.bind(this));\n\t}\n\n\t// ---- lifecycle ----\n\n\tprivate async onReady(): Promise<void> {\n\t\tvoid this.setState('info.connection', false, true);\n\n\t\tconst sysConfig = await this.getForeignObjectAsync('system.config');\n\t\tconst rawLang = (sysConfig?.common as unknown as { language?: string } | undefined)?.language;\n\t\tthis.wt = getWidgetLabels(rawLang ?? 'en');\n\n\t\tconst pollSeconds = Math.max(5, Math.min(3600, this.config.pollSeconds || 30));\n\t\tthis.baseUrl = `http://${this.config.ip}:${this.config.port || 8090}`;\n\n\t\tif (this.config.user || this.config.pass) {\n\t\t\tthis.authHeader = `Basic ${Buffer.from(`${this.config.user}:${this.config.pass}`).toString('base64')}`;\n\t\t}\n\n\t\tawait this.buildSystem();\n\t\ttry {\n\t\t\tawait this.poll();\n\t\t} catch (e) {\n\t\t\tthis.log.warn(`First poll error: ${(e as Error).message}`);\n\t\t}\n\n\t\tthis.pollTimer = this.setInterval(\n\t\t\t() => this.poll().catch(e => this.log.warn(`Poll error: ${(e as Error).message}`)),\n\t\t\tpollSeconds * 1000,\n\t\t);\n\n\t\tthis.subscribeStates('*');\n\t\tthis.log.info(`Agent DVR adapter ready. Polling ${this.baseUrl} every ${pollSeconds}s.`);\n\t}\n\n\tprivate onUnload(callback: () => void): void {\n\t\ttry {\n\t\t\tif (this.pollTimer !== undefined) {\n\t\t\t\tthis.clearInterval(this.pollTimer);\n\t\t\t\tthis.pollTimer = undefined;\n\t\t\t}\n\t\t\tif (this.refreshTimer !== undefined) {\n\t\t\t\tthis.clearTimeout(this.refreshTimer);\n\t\t\t\tthis.refreshTimer = undefined;\n\t\t\t}\n\t\t} catch {\n\t\t\t/* ignore */\n\t\t}\n\t\tcallback();\n\t}\n\n\tprivate onStateChange(id: string, state: ioBroker.State | null | undefined): void {\n\t\tif (!state || state.ack !== false) {\n\t\t\treturn;\n\t\t}\n\t\tconst relId = this.getRelId(id);\n\t\tconst entry = this.registry.get(relId);\n\t\tif (entry) {\n\t\t\tthis.runCommand(relId, entry, state.val).catch(e =>\n\t\t\t\tthis.log.warn(`Command error: ${(e as Error).message}`),\n\t\t\t);\n\t\t}\n\t}\n\n\t// ---- helpers ----\n\n\tprivate getRelId(fullId: string): string {\n\t\treturn fullId.startsWith(`${this.namespace}.`) ? fullId.slice(this.namespace.length + 1) : fullId;\n\t}\n\n\tprivate apiGet(path: string): Promise<ApiResult> {\n\t\treturn new Promise(resolve => {\n\t\t\tconst timeout = Math.max(1000, Math.min(30000, this.config.httpTimeoutMs || 8000));\n\t\t\tconst opts: http.RequestOptions = {\n\t\t\t\thostname: this.config.ip,\n\t\t\t\tport: this.config.port || 8090,\n\t\t\t\tpath,\n\t\t\t\tmethod: 'GET',\n\t\t\t\ttimeout,\n\t\t\t};\n\t\t\tif (this.authHeader) {\n\t\t\t\topts.headers = { Authorization: this.authHeader };\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst req = http.request(opts, res => {\n\t\t\t\t\tlet data = '';\n\t\t\t\t\tres.on('data', (c: Buffer) => {\n\t\t\t\t\t\tdata += c.toString();\n\t\t\t\t\t});\n\t\t\t\t\tres.on('end', () => {\n\t\t\t\t\t\tif (res.statusCode && res.statusCode >= 400) {\n\t\t\t\t\t\t\tresolve({ ok: false, error: `HTTP ${res.statusCode}` });\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresolve({ ok: true, data });\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tres.on('error', (e: Error) => resolve({ ok: false, error: e.message }));\n\t\t\t\t});\n\t\t\t\treq.on('error', (e: Error) => resolve({ ok: false, error: e.message }));\n\t\t\t\treq.on('timeout', () => {\n\t\t\t\t\treq.destroy();\n\t\t\t\t\tresolve({ ok: false, error: 'timeout' });\n\t\t\t\t});\n\t\t\t\treq.end();\n\t\t\t} catch (e) {\n\t\t\t\tresolve({ ok: false, error: String((e as Error).message || e) });\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate apiGetBuffer(path: string): Promise<{ ok: boolean; data?: Buffer; error?: string }> {\n\t\treturn new Promise(resolve => {\n\t\t\tconst timeout = Math.max(1000, Math.min(30000, this.config.httpTimeoutMs || 8000));\n\t\t\tconst opts: http.RequestOptions = {\n\t\t\t\thostname: this.config.ip,\n\t\t\t\tport: this.config.port || 8090,\n\t\t\t\tpath,\n\t\t\t\tmethod: 'GET',\n\t\t\t\ttimeout,\n\t\t\t};\n\t\t\tif (this.authHeader) {\n\t\t\t\topts.headers = { Authorization: this.authHeader };\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst req = http.request(opts, res => {\n\t\t\t\t\tconst chunks: Buffer[] = [];\n\t\t\t\t\tres.on('data', (c: Buffer) => chunks.push(c));\n\t\t\t\t\tres.on('end', () => {\n\t\t\t\t\t\tif (res.statusCode && res.statusCode >= 400) {\n\t\t\t\t\t\t\tresolve({ ok: false, error: `HTTP ${res.statusCode}` });\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresolve({ ok: true, data: Buffer.concat(chunks) });\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tres.on('error', (e: Error) => resolve({ ok: false, error: e.message }));\n\t\t\t\t});\n\t\t\t\treq.on('error', (e: Error) => resolve({ ok: false, error: e.message }));\n\t\t\t\treq.on('timeout', () => {\n\t\t\t\t\treq.destroy();\n\t\t\t\t\tresolve({ ok: false, error: 'timeout' });\n\t\t\t\t});\n\t\t\t\treq.end();\n\t\t\t} catch (e) {\n\t\t\t\tresolve({ ok: false, error: String((e as Error).message || e) });\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate async ensureFolder(id: string, name: string, type: 'device' | 'channel' = 'channel'): Promise<void> {\n\t\tif (this.ensuredFolders.has(id)) {\n\t\t\treturn;\n\t\t}\n\t\tawait this.setObjectNotExistsAsync(id, { type, common: { name }, native: {} });\n\t\tthis.ensuredFolders.add(id);\n\t}\n\n\tprivate async ensurePath(fullId: string): Promise<void> {\n\t\tconst parts = fullId.split('.');\n\t\tlet acc = parts[0];\n\t\tfor (let i = 1; i < parts.length - 1; i++) {\n\t\t\tacc += `.${parts[i]}`;\n\t\t\tif (!this.ensuredFolders.has(acc)) {\n\t\t\t\tawait this.setObjectNotExistsAsync(acc, { type: 'channel', common: { name: parts[i] }, native: {} });\n\t\t\t\tthis.ensuredFolders.add(acc);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate guessRole(id: string, val: unknown): ioBroker.CommonType {\n\t\tif (typeof val === 'string' && /^https?:\\/\\//i.test(val)) {\n\t\t\treturn 'string';\n\t\t}\n\t\treturn 'string';\n\t}\n\n\tprivate async writeLeaf(id: string, val: unknown): Promise<void> {\n\t\tconst t = typeof val;\n\t\tlet common: ioBroker.StateCommon;\n\t\tif (t === 'boolean') {\n\t\t\tcommon = { name: id.split('.').pop() || id, type: 'boolean', role: 'indicator', read: true, write: false };\n\t\t} else if (t === 'number') {\n\t\t\tcommon = { name: id.split('.').pop() || id, type: 'number', role: 'value', read: true, write: false };\n\t\t} else {\n\t\t\tconst role = typeof val === 'string' && /^https?:\\/\\//i.test(val) ? 'text.url' : 'text';\n\t\t\tcommon = { name: id.split('.').pop() || id, type: 'string', role, read: true, write: false };\n\t\t}\n\t\tif (!this.ensuredFolders.has(id)) {\n\t\t\tawait this.ensurePath(id);\n\t\t\tawait this.setObjectNotExistsAsync(id, { type: 'state', common, native: {} });\n\t\t\tthis.ensuredFolders.add(id);\n\t\t}\n\t\tawait this.setStateAsync(id, { val: (val === undefined ? null : val) as ioBroker.StateValue, ack: true });\n\t}\n\n\tprivate async flattenWrite(obj: unknown, prefix: string, depth: number): Promise<void> {\n\t\tconst maxDepth = Math.max(1, Math.min(10, this.config.maxDepth || 6));\n\t\tconst maxArray = Math.max(1, Math.min(500, this.config.maxArray || 30));\n\t\tif (depth > maxDepth) {\n\t\t\treturn;\n\t\t}\n\t\tif (obj === null || obj === undefined) {\n\t\t\tawait this.writeLeaf(prefix, obj);\n\t\t\treturn;\n\t\t}\n\t\tif (typeof obj === 'object') {\n\t\t\tif (Array.isArray(obj)) {\n\t\t\t\tif (obj.length > maxArray) {\n\t\t\t\t\tawait this.writeLeaf(`${prefix}.count`, obj.length);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tfor (let i = 0; i < obj.length; i++) {\n\t\t\t\t\tawait this.flattenWrite(obj[i], `${prefix}.${i}`, depth + 1);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor (const k of Object.keys(obj)) {\n\t\t\t\t\tif (SKIP_KEYS.includes(k)) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tawait this.flattenWrite((obj as Record<string, unknown>)[k], `${prefix}.${sanitize(k)}`, depth + 1);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tawait this.writeLeaf(prefix, obj);\n\t\t}\n\t}\n\n\tprivate async ensureControl(\n\t\tid: string,\n\t\tname: string,\n\t\tentry: RegistryEntry,\n\t\trole: 'button' | 'switch',\n\t): Promise<void> {\n\t\tif (!this.ensuredFolders.has(id)) {\n\t\t\tawait this.ensurePath(id);\n\t\t\tawait this.setObjectNotExistsAsync(id, {\n\t\t\t\ttype: 'state',\n\t\t\t\tcommon: { name, type: 'boolean', role, read: role === 'switch', write: true, def: false },\n\t\t\t\tnative: {},\n\t\t\t});\n\t\t\tawait this.setStateAsync(id, { val: false, ack: true });\n\t\t\tthis.ensuredFolders.add(id);\n\t\t}\n\t\tthis.registry.set(id, entry);\n\t}\n\n\tprivate ensureButton(id: string, name: string, entry: RegistryEntry): Promise<void> {\n\t\treturn this.ensureControl(id, name, entry, 'button');\n\t}\n\n\tprivate async ensureSelector(\n\t\tid: string,\n\t\tname: string,\n\t\tentry: RegistryEntry,\n\t\tstates: Record<number, string>,\n\t): Promise<void> {\n\t\tif (!this.ensuredFolders.has(id)) {\n\t\t\tawait this.ensurePath(id);\n\t\t\tawait this.setObjectNotExistsAsync(id, {\n\t\t\t\ttype: 'state',\n\t\t\t\tcommon: { name, type: 'number', role: 'level', read: true, write: true, states },\n\t\t\t\tnative: {},\n\t\t\t});\n\t\t\tawait this.setStateAsync(id, { val: 0, ack: true });\n\t\t\tthis.ensuredFolders.add(id);\n\t\t}\n\t\tthis.registry.set(id, entry);\n\t}\n\n\tprivate async fetchSnapshotB64(oid: number | string, snapId: string): Promise<void> {\n\t\tconst imgRes = await this.apiGetBuffer(`/photo.jpg?oid=${oid}`);\n\t\tif (imgRes.ok && imgRes.data) {\n\t\t\tawait this.setStateAsync(snapId, {\n\t\t\t\tval: `data:image/jpeg;base64,${imgRes.data.toString('base64')}`,\n\t\t\t\tack: true,\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate async ensureFlag(id: string, name: string): Promise<void> {\n\t\tif (this.ensuredFolders.has(id)) {\n\t\t\treturn;\n\t\t}\n\t\tawait this.ensurePath(id);\n\t\tawait this.setObjectNotExistsAsync(id, {\n\t\t\ttype: 'state',\n\t\t\tcommon: { name, type: 'boolean', role: 'indicator', read: true, write: false, def: false },\n\t\t\tnative: {},\n\t\t});\n\t\tthis.ensuredFolders.add(id);\n\t\tawait this.setStateAsync(id, { val: false, ack: true });\n\t}\n\n\tprivate async pulse(id: string): Promise<void> {\n\t\tawait this.setStateAsync(id, { val: true, ack: true });\n\t\tthis.setTimeout(() => this.setStateAsync(id, { val: false, ack: true }).catch(() => {}), 1500);\n\t}\n\n\t// ---- system structure ----\n\n\tprivate async buildSystem(): Promise<void> {\n\t\tawait this.ensureFolder('system', 'System', 'channel');\n\t\tawait this.writeLeaf('system.online', false);\n\t\tawait this.writeLeaf('system.lastUpdate', '');\n\t\tawait this.writeLeaf('system.lastPoll', 0);\n\t\tawait this.writeLeaf('system.cameraCount', 0);\n\n\t\tif (this.config.enableSystemControls) {\n\t\t\tawait this.ensureFolder('system.control', 'Control', 'channel');\n\t\t\tfor (const c of SYS_COMMANDS) {\n\t\t\t\tawait this.ensureButton(`system.control.${c.id}`, c.name, { kind: 'sys', path: c.path });\n\t\t\t}\n\t\t\tawait this.ensureFolder('system.profile', 'Profile', 'channel');\n\t\t\tawait this.ensureSelector(\n\t\t\t\t'system.profile.selector',\n\t\t\t\t'Active profile',\n\t\t\t\t{ kind: 'setProfile' },\n\t\t\t\t{ 0: 'Home', 1: 'Away', 2: 'Night' },\n\t\t\t);\n\t\t\tif (!this.ensuredFolders.has('system.profile.list')) {\n\t\t\t\tawait this.setObjectNotExistsAsync('system.profile.list', {\n\t\t\t\t\ttype: 'state',\n\t\t\t\t\tcommon: {\n\t\t\t\t\t\tname: 'Available profiles (JSON)',\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\trole: 'json',\n\t\t\t\t\t\tread: true,\n\t\t\t\t\t\twrite: false,\n\t\t\t\t\t},\n\t\t\t\t\tnative: {},\n\t\t\t\t});\n\t\t\t\tthis.ensuredFolders.add('system.profile.list');\n\t\t\t\tawait this.setStateAsync('system.profile.list', { val: '[]', ack: true });\n\t\t\t}\n\t\t}\n\t}\n\n\t// ---- camera aspect detection ----\n\n\tprivate async detectAspect(d: Device): Promise<void> {\n\t\tif (this.camAspect[d.oid] !== undefined) {\n\t\t\treturn;\n\t\t}\n\t\tlet wh = findWH(d.raw, 0);\n\t\tif (!wh) {\n\t\t\tconst res = await this.apiGet(`/command/getObject?oid=${d.oid}&ot=2`);\n\t\t\tconst j = asJson(res.data);\n\t\t\tif (j) {\n\t\t\t\twh = findWH(j, 0);\n\t\t\t}\n\t\t}\n\t\tthis.camAspect[d.oid] = wh ? `${wh[0]}/${wh[1]}` : '';\n\t\tif (wh) {\n\t\t\tthis.log.debug(`cam ${d.oid} resolution ${wh[0]}x${wh[1]} -> AR ${this.camAspect[d.oid]}`);\n\t\t}\n\t}\n\n\t// ---- device folder name ----\n\n\tprivate deviceFolder(d: Device): string {\n\t\treturn `${d.ot === 1 ? 'mic' : 'cam'}_${sanitize(d.oid)}_${sanitize(d.name)}`;\n\t}\n\n\t// ---- build device data points ----\n\n\tprivate async buildDevice(d: Device): Promise<void> {\n\t\tconst fid = this.deviceFolder(d);\n\t\tthis.devById.set(d.oid, d);\n\n\t\tawait this.ensureFolder(fid, d.name, 'device');\n\t\tif (d.ot === 2) {\n\t\t\tawait this.detectAspect(d);\n\t\t}\n\t\tawait this.flattenWrite(d.raw, fid, 0);\n\n\t\tawait this.ensureFolder(`${fid}.control`, 'Control', 'channel');\n\t\tfor (const c of CAM_COMMANDS) {\n\t\t\tif (c.id === 'snapshot' && d.ot !== 2) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tawait this.ensureButton(`${fid}.control.${c.id}`, c.name, {\n\t\t\t\tkind: 'cam',\n\t\t\t\tpath: c.path,\n\t\t\t\tparams: [...c.params],\n\t\t\t\toid: d.oid,\n\t\t\t\tot: d.ot,\n\t\t\t});\n\t\t}\n\n\t\tif (this.config.enablePtz && d.ot === 2) {\n\t\t\tawait this.ensureFolder(`${fid}.control.ptz`, 'PTZ', 'channel');\n\t\t\tfor (const p of PTZ_DIRS) {\n\t\t\t\tconst cid = `${fid}.control.ptz.${p.id}`;\n\t\t\t\tif (p.hold) {\n\t\t\t\t\tawait this.ensureControl(\n\t\t\t\t\t\tcid,\n\t\t\t\t\t\t`PTZ ${p.id} (hold)`,\n\t\t\t\t\t\t{ kind: 'ptzHold', oid: d.oid, dir: p.dir },\n\t\t\t\t\t\t'switch',\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tawait this.ensureButton(cid, `PTZ ${p.id}`, { kind: 'ptz', oid: d.oid, dir: p.dir });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (this.config.enableUrls && d.ot === 2) {\n\t\t\tawait this.ensureFolder(`${fid}.urls`, 'URLs', 'channel');\n\t\t\tawait this.writeLeaf(`${fid}.urls.snapshot`, `${this.baseUrl}/grab.jpg?oid=${d.oid}`);\n\t\t\tawait this.writeLeaf(`${fid}.urls.photo`, `${this.baseUrl}/photo.jpg?oid=${d.oid}`);\n\t\t\tawait this.writeLeaf(`${fid}.urls.mjpeg`, `${this.baseUrl}/video.mjpg?oids=${d.oid}`);\n\t\t\tawait this.writeLeaf(`${fid}.urls.mp4`, `${this.baseUrl}/video.mp4?oids=${d.oid}`);\n\t\t}\n\n\t\tif (d.ot === 2) {\n\t\t\tconst snapId = `${fid}.snapshot_b64`;\n\t\t\tif (!this.ensuredFolders.has(snapId)) {\n\t\t\t\tawait this.ensurePath(snapId);\n\t\t\t\tawait this.setObjectNotExistsAsync(snapId, {\n\t\t\t\t\ttype: 'state',\n\t\t\t\t\tcommon: {\n\t\t\t\t\t\tname: 'Snapshot (Base64)',\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\trole: 'state',\n\t\t\t\t\t\tread: true,\n\t\t\t\t\t\twrite: false,\n\t\t\t\t\t},\n\t\t\t\t\tnative: {},\n\t\t\t\t});\n\t\t\t\tawait this.setStateAsync(snapId, { val: '', ack: true });\n\t\t\t\tthis.ensuredFolders.add(snapId);\n\t\t\t}\n\t\t\tawait this.ensureButton(`${fid}.control.refreshSnapshotB64`, 'Refresh snapshot (Base64)', {\n\t\t\t\tkind: 'snapshotB64',\n\t\t\t\toid: d.oid,\n\t\t\t\tfid,\n\t\t\t});\n\t\t\tif (this.config.enableSnapshotB64) {\n\t\t\t\tawait this.fetchSnapshotB64(d.oid, snapId);\n\t\t\t}\n\t\t}\n\n\t\tawait this.updateCameraEvents(d, fid);\n\t}\n\n\t// ---- event formatting ----\n\n\tprivate fmtEvent(\n\t\tev: Record<string, unknown>,\n\t\toid: number | string,\n\t): {\n\t\tfn: string;\n\t\tsizeMB: number;\n\t\tdur: string;\n\t\ttag: string;\n\t\tdate: string;\n\t\ttime: string;\n\t\tthumb: string;\n\t\tvideo: string;\n\t} {\n\t\tconst e = ev as AgentDvrEvent;\n\t\tconst fn = e.fn ?? '';\n\t\tconst dot = fn.lastIndexOf('.');\n\t\tconst jpg = `${dot > 0 ? fn.slice(0, dot) : fn}_large.jpg`;\n\t\tconst sizeMB = e.sb ? Math.round((Number(e.sb) / 1048576) * 100) / 100 : 0;\n\t\tconst dur = e.d != null ? String(e.d) : '';\n\t\tconst tag = e.tg ?? '';\n\t\tconst ms = (parseFloat(String(e.c ?? 0)) - 621355968000000000) / 10000;\n\t\tconst dt = new Date(ms);\n\t\tconst two = (n: number): string => String(n).padStart(2, '0');\n\t\tconst date = `${two(dt.getDate())}.${two(dt.getMonth() + 1)}.`;\n\t\tconst time = `${two(dt.getHours())}:${two(dt.getMinutes())}`;\n\t\tconst thumb = `${this.baseUrl}/fileThumb.jpg?oid=${oid}&fn=${encodeURIComponent(jpg)}`;\n\t\tconst video = `${this.baseUrl}/streamFile.cgi?oid=${oid}&ot=2&fn=${encodeURIComponent(fn)}`;\n\t\treturn { fn, sizeMB, dur, tag, date, time, thumb, video };\n\t}\n\n\t// ---- gallery HTML builders ----\n\n\tprivate buildGalleryHtml(d: Device, events: Record<string, unknown>[]): string {\n\t\treturn (this.config.widgetMode || 'nojs') === 'js'\n\t\t\t? this.buildGalleryHtmlJs(d, events)\n\t\t\t: this.buildGalleryHtmlNojs(d, events);\n\t}\n\n\tprivate buildGalleryHtmlNojs(d: Device, events: Record<string, unknown>[]): string {\n\t\tconst oid = d.oid;\n\t\tconst minCol = this.config.widgetMinCol || 150;\n\t\tconst maxW = this.config.widgetMaxModalWidth || 900;\n\t\tconst showTags = this.config.widgetShowTags;\n\t\tconst pos = (this.config.widgetTagPosition || 'bottom-left').split('-');\n\t\tconst tagStyle = `${pos[0]}:5px;${pos[1]}:5px`;\n\t\tconst playerUrl = (this.config.widgetPlayerUrl || '').trim();\n\t\tconst PAUSE_ATTR = ` onchange=\"if(!this.checked){var m=this.nextElementSibling.nextElementSibling,v=m&&m.querySelector('video');if(v){v.pause();}}\"`;\n\n\t\tconst items = events\n\t\t\t.map((ev, i) => {\n\t\t\t\tconst p = this.fmtEvent(ev, oid);\n\t\t\t\tconst id = `adv${sanitize(oid)}_${i}`;\n\t\t\t\tconst badge =\n\t\t\t\t\tshowTags && p.tag ? `<span class=\"advtag\" style=\"${tagStyle}\">${escHtml(p.tag)}</span>` : '';\n\t\t\t\tconst inner =\n\t\t\t\t\t`<span class=\"advimg\"><img src=\"${p.thumb}\" loading=\"lazy\" alt=\"\">${badge}<span class=\"advplay\"></span></span>` +\n\t\t\t\t\t`<span class=\"advcap\">${escHtml(p.date)} ${escHtml(p.time)}<br>${escHtml(p.dur)}s · ${escHtml(p.sizeMB)} MB</span>`;\n\t\t\t\tif (playerUrl) {\n\t\t\t\t\tconst href = playerUrl\n\t\t\t\t\t\t.replace(/{video}/g, encodeURIComponent(p.video))\n\t\t\t\t\t\t.replace(/{videoRaw}/g, p.video)\n\t\t\t\t\t\t.replace(/{fn}/g, encodeURIComponent(p.fn))\n\t\t\t\t\t\t.replace(/{date}/g, encodeURIComponent(p.date))\n\t\t\t\t\t\t.replace(/{time}/g, encodeURIComponent(p.time))\n\t\t\t\t\t\t.replace(/{duration}/g, encodeURIComponent(p.dur))\n\t\t\t\t\t\t.replace(/{size}/g, encodeURIComponent(String(p.sizeMB)))\n\t\t\t\t\t\t.replace(/{tags}/g, encodeURIComponent(p.tag));\n\t\t\t\t\treturn `<a class=\"advcell\" href=\"${escHtml(href)}\" target=\"_blank\" rel=\"noopener\">${inner}</a>`;\n\t\t\t\t}\n\t\t\t\treturn (\n\t\t\t\t\t`<input class=\"advlb\" type=\"checkbox\" id=\"${id}\"${PAUSE_ATTR}>` +\n\t\t\t\t\t`<label class=\"advcell advthumb\" for=\"${id}\">${inner}</label>` +\n\t\t\t\t\t`<div class=\"advmodal\"><label class=\"advbackdrop\" for=\"${id}\"></label>` +\n\t\t\t\t\t`<div class=\"advbox\"><label class=\"advclose\" for=\"${id}\">✕</label>` +\n\t\t\t\t\t`<video class=\"advvideo\" controls preload=\"none\" playsinline src=\"${p.video}\"></video>` +\n\t\t\t\t\t`<div class=\"advinfo\">${escHtml(p.date)} ${escHtml(p.time)} · ${escHtml(p.dur)}s · ${escHtml(p.sizeMB)} MB${\n\t\t\t\t\t\tp.tag ? ` · ${escHtml(p.tag)}` : ''\n\t\t\t\t\t} · <a href=\"${p.video}\" download target=\"_blank\">${this.wt.download}</a></div></div></div>`\n\t\t\t\t);\n\t\t\t})\n\t\t\t.join('');\n\n\t\tconst grid = items\n\t\t\t? `<div class=\"advgrid\">${items}</div>`\n\t\t\t: `<div class=\"advempty\">${this.wt.noRecordings}</div>`;\n\t\treturn `<style>${galleryCss(minCol, maxW)}</style>${grid}`;\n\t}\n\n\tprivate buildGalleryHtmlJs(d: Device, events: Record<string, unknown>[]): string {\n\t\tconst oid = d.oid;\n\t\tconst minCol = this.config.widgetMinCol || 150;\n\t\tconst maxW = this.config.widgetMaxModalWidth || 900;\n\t\tconst items = events.map(ev => {\n\t\t\tconst p = this.fmtEvent(ev, oid);\n\t\t\treturn {\n\t\t\t\tfn: p.fn,\n\t\t\t\tdate: p.date,\n\t\t\t\ttime: p.time,\n\t\t\t\tdur: p.dur,\n\t\t\t\tsize: p.sizeMB,\n\t\t\t\ttag: p.tag,\n\t\t\t\tthumb: p.thumb,\n\t\t\t\tvideo: p.video,\n\t\t\t};\n\t\t});\n\t\tconst cfg = {\n\t\t\tmin_col: minCol,\n\t\t\tshow_tags: !!this.config.widgetShowTags,\n\t\t\tmax_modal_width: maxW,\n\t\t\tlive_aspect: this.camAspect[oid] || this.config.widgetLiveAspect || '',\n\t\t\tplayer_url: (this.config.widgetPlayerUrl || '').trim(),\n\t\t\ttag_position: this.config.widgetTagPosition || 'bottom-left',\n\t\t\tlabels: { noRecordings: this.wt.noRecordings, all: this.wt.all, live: this.wt.live },\n\t\t};\n\t\tconst data = JSON.stringify({ items, live: null, cfg }).replace(/</g, '\\\\u003c');\n\t\tconst boot = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';\n\t\treturn (\n\t\t\t`<style>${galleryCssJs(minCol)}</style>` +\n\t\t\t`<div class=\"advroot\"><script type=\"application/json\" class=\"advdata\">${data}</script>` +\n\t\t\t`<div class=\"advbar\"><input class=\"advsearchjs\" type=\"text\" placeholder=\"${escHtml(this.wt.search)}\"><div class=\"advtagsjs\"></div></div>` +\n\t\t\t`<div class=\"advgridjs\"></div></div>` +\n\t\t\t`<script type=\"text/plain\" class=\"advcode\">${ADV_CLIENT_CODE}</script>` +\n\t\t\t`<img alt=\"\" src=\"${boot}\" style=\"display:none\" onload=\"(function(){if(window.ADVscan){window.ADVscan();return;}var c=document.querySelector('script.advcode');if(!c)return;var s=document.createElement('script');s.textContent=c.textContent;document.body.appendChild(s);})()\">`\n\t\t);\n\t}\n\n\tprivate buildOverviewHtml(cams: Device[]): string {\n\t\tconst ts = Date.now();\n\t\tconst minCol = this.config.widgetMinCol || 150;\n\t\tconst maxW = this.config.widgetMaxModalWidth || 900;\n\t\tconst PAUSE_ATTR = ` onchange=\"if(!this.checked){var m=this.nextElementSibling.nextElementSibling,v=m&&m.querySelector('video');if(v){v.pause();}}\"`;\n\t\tconst tiles = cams\n\t\t\t.map(d => {\n\t\t\t\tconst oid = d.oid;\n\t\t\t\tconst id = `advlive${sanitize(oid)}`;\n\t\t\t\tconst grab = `${this.baseUrl}/grab.jpg?oid=${oid}&ot=2&maintainAR=1&ts=${ts}`;\n\t\t\t\tconst webm = `${this.baseUrl}/video.webm?oid=${oid}&ot=2`;\n\t\t\t\tconst name = escHtml(d.name || `Camera ${oid}`);\n\t\t\t\tconst arRaw = this.camAspect[oid] || this.config.widgetLiveAspect || '';\n\t\t\t\tconst ar = arRaw ? String(arRaw).replace('/', ' / ') : '';\n\t\t\t\tconst fix = ar ? ' advimgfix' : '';\n\t\t\t\tconst arStyle = ar ? ` style=\"aspect-ratio:${ar}\"` : '';\n\t\t\t\tconst inner =\n\t\t\t\t\t`<span class=\"advimg${fix}\"${arStyle}><img src=\"${grab}\" loading=\"lazy\" alt=\"\">` +\n\t\t\t\t\t`<span class=\"advtag\" style=\"top:5px;left:5px\">● ${escHtml(this.wt.live)}</span><span class=\"advplay\"></span></span>` +\n\t\t\t\t\t`<span class=\"advcap\">${name}</span>`;\n\t\t\t\treturn (\n\t\t\t\t\t`<input class=\"advlb\" type=\"checkbox\" id=\"${id}\"${PAUSE_ATTR}>` +\n\t\t\t\t\t`<label class=\"advcell advthumb\" for=\"${id}\">${inner}</label>` +\n\t\t\t\t\t`<div class=\"advmodal\"><label class=\"advbackdrop\" for=\"${id}\"></label>` +\n\t\t\t\t\t`<div class=\"advbox\"><label class=\"advclose\" for=\"${id}\">✕</label>` +\n\t\t\t\t\t`<video class=\"advvideo\" controls preload=\"none\" playsinline src=\"${webm}\"></video>` +\n\t\t\t\t\t`<div class=\"advinfo\">${name} · Live</div></div></div>`\n\t\t\t\t);\n\t\t\t})\n\t\t\t.join('');\n\t\tconst grid = tiles ? `<div class=\"advgrid\">${tiles}</div>` : `<div class=\"advempty\">No cameras</div>`;\n\t\treturn `<style>${galleryCss(minCol, maxW)}</style>${grid}`;\n\t}\n\n\t// ---- event data points ----\n\n\tprivate async writeEventDps(d: Device, fid: string, events: Record<string, unknown>[]): Promise<void> {\n\t\tconst oid = d.oid;\n\t\tawait this.ensureFolder(`${fid}.events`, 'Events', 'channel');\n\t\tawait this.ensureFolder(`${fid}.events.last`, 'Last event', 'channel');\n\n\t\tconst two = (n: number): string => String(n).padStart(2, '0');\n\t\tconst now = new Date();\n\t\tconst td = `${two(now.getDate())}.${two(now.getMonth() + 1)}.`;\n\t\tconst todayCount = events.filter(ev => this.fmtEvent(ev, oid).date === td).length;\n\n\t\tawait this.writeLeaf(`${fid}.events.count`, events.length);\n\t\tawait this.writeLeaf(`${fid}.events.today`, todayCount);\n\n\t\tif (events.length) {\n\t\t\tconst p = this.fmtEvent(events[0], oid);\n\t\t\tconst tsMs = (parseFloat(String((events[0] as AgentDvrEvent).c ?? 0)) - 621355968000000000) / 10000;\n\t\t\tawait this.writeLeaf(`${fid}.events.last.time`, `${p.date} ${p.time}`);\n\t\t\tawait this.writeLeaf(`${fid}.events.last.timestamp`, Math.round(tsMs));\n\t\t\tawait this.writeLeaf(`${fid}.events.last.tag`, p.tag);\n\t\t\tawait this.writeLeaf(`${fid}.events.last.duration`, p.dur ? parseFloat(p.dur) : 0);\n\t\t\tawait this.writeLeaf(`${fid}.events.last.size_mb`, p.sizeMB);\n\t\t\tawait this.writeLeaf(`${fid}.events.last.thumb`, p.thumb);\n\t\t\tawait this.writeLeaf(`${fid}.events.last.video`, p.video);\n\t\t}\n\n\t\tawait this.ensureFlag(`${fid}.events.new`, 'New event');\n\n\t\tif (this.config.enablePush) {\n\t\t\tconst pid = `${fid}.events.pushTrigger`;\n\t\t\tif (!this.ensuredFolders.has(pid)) {\n\t\t\t\tawait this.ensurePath(pid);\n\t\t\t\tawait this.setObjectNotExistsAsync(pid, {\n\t\t\t\t\ttype: 'state',\n\t\t\t\t\tcommon: {\n\t\t\t\t\t\tname: 'Push trigger (AgentDVR action)',\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\trole: 'text',\n\t\t\t\t\t\tread: true,\n\t\t\t\t\t\twrite: true,\n\t\t\t\t\t\tdef: '',\n\t\t\t\t\t},\n\t\t\t\t\tnative: {},\n\t\t\t\t});\n\t\t\t\tawait this.setStateAsync(pid, { val: '', ack: true });\n\t\t\t\tthis.ensuredFolders.add(pid);\n\t\t\t}\n\t\t\tthis.registry.set(pid, { kind: 'push', oid, fid });\n\t\t}\n\n\t\tconst ignore = (this.config.eventTagsIgnore || 'detected')\n\t\t\t.split(',')\n\t\t\t.map(s => s.trim().toLowerCase())\n\t\t\t.filter(Boolean);\n\t\tconst tokensOf = (s: string): string[] =>\n\t\t\ts\n\t\t\t\t.toLowerCase()\n\t\t\t\t.split(/[,\\s]+/)\n\t\t\t\t.filter(Boolean);\n\n\t\tfor (const kw of (this.config.eventTags || '')\n\t\t\t.split(',')\n\t\t\t.map(s => s.trim())\n\t\t\t.filter(Boolean)) {\n\t\t\tawait this.ensureFlag(`${fid}.events.detected_${sanitize(kw)}`, `Detected: ${kw}`);\n\t\t}\n\n\t\tif (this.config.eventTagsDynamic) {\n\t\t\tconst seen = new Set<string>();\n\t\t\tfor (const ev of events) {\n\t\t\t\ttokensOf((ev as AgentDvrEvent).tg ?? '').forEach(t => seen.add(t));\n\t\t\t}\n\t\t\tfor (const t of seen) {\n\t\t\t\tif (ignore.includes(t)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tawait this.ensureFlag(`${fid}.events.detected_${sanitize(t)}`, `Detected: ${t}`);\n\t\t\t}\n\t\t}\n\n\t\tlet newCount = 0;\n\t\tif (events.length) {\n\t\t\tconst prev = this.lastEventFn[oid];\n\t\t\tif (prev !== undefined) {\n\t\t\t\tconst i = events.findIndex(e => (e as AgentDvrEvent).fn === prev);\n\t\t\t\tnewCount = i === -1 ? events.length : i;\n\t\t\t}\n\t\t\tthis.lastEventFn[oid] = (events[0] as AgentDvrEvent).fn ?? '';\n\t\t}\n\n\t\tif (newCount > 0) {\n\t\t\tawait this.pulse(`${fid}.events.new`);\n\t\t\tconst tags = new Set<string>();\n\t\t\tfor (let i = 0; i < newCount && i < events.length; i++) {\n\t\t\t\ttokensOf((events[i] as AgentDvrEvent).tg ?? '').forEach(t => tags.add(t));\n\t\t\t}\n\t\t\tfor (const t of tags) {\n\t\t\t\tif (ignore.includes(t)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst tid = `${fid}.events.detected_${sanitize(t)}`;\n\t\t\t\tawait this.ensureFlag(tid, `Detected: ${t}`);\n\t\t\t\tawait this.pulse(tid);\n\t\t\t}\n\t\t\tthis.log.debug(`${newCount} new event(s) cam ${oid} [${[...tags].join(',')}]`);\n\t\t}\n\t}\n\n\t// ---- camera events + widget ----\n\n\tprivate async updateCameraEvents(d: Device, fid: string): Promise<void> {\n\t\tif (d.ot !== 2) {\n\t\t\treturn;\n\t\t}\n\t\tif (!this.config.enableWidget && !this.config.enableEventDps) {\n\t\t\treturn;\n\t\t}\n\t\tif (this.eventBusy[d.oid]) {\n\t\t\treturn;\n\t\t}\n\t\tthis.eventBusy[d.oid] = true;\n\t\ttry {\n\t\t\tconst evRes = await this.apiGet(`/q/getEvents?oid=${d.oid}&ot=2`);\n\t\t\tconst evJson = asJson(evRes.data);\n\t\t\tif (!evJson) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst events = (\n\t\t\t\tArray.isArray(evJson.events) ? evJson.events : Array.isArray(evJson) ? evJson : []\n\t\t\t) as Record<string, unknown>[];\n\n\t\t\tif (this.config.enableEventDps) {\n\t\t\t\tawait this.writeEventDps(d, fid, events);\n\t\t\t}\n\n\t\t\tif (this.config.enableWidget) {\n\t\t\t\tconst wId = `${fid}.widget`;\n\t\t\t\tif (!this.ensuredFolders.has(wId)) {\n\t\t\t\t\tawait this.ensurePath(wId);\n\t\t\t\t\tawait this.setObjectNotExistsAsync(wId, {\n\t\t\t\t\t\ttype: 'state',\n\t\t\t\t\t\tcommon: {\n\t\t\t\t\t\t\tname: 'HTML gallery widget',\n\t\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\t\trole: 'html',\n\t\t\t\t\t\t\tread: true,\n\t\t\t\t\t\t\twrite: false,\n\t\t\t\t\t\t\tdef: '',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tnative: {},\n\t\t\t\t\t});\n\t\t\t\t\tthis.ensuredFolders.add(wId);\n\t\t\t\t}\n\t\t\t\tconst ev = events.slice(0, Math.max(1, this.config.widgetAnzahl || 50));\n\t\t\t\tconst f = (ev[0] ?? {}) as AgentDvrEvent;\n\t\t\t\tconst l = (ev[ev.length - 1] ?? {}) as AgentDvrEvent;\n\t\t\t\tconst sig = `${ev.length}|${f.fn ?? ''}|${l.fn ?? ''}`;\n\t\t\t\tif (this.widgetSig[wId] !== sig) {\n\t\t\t\t\tawait this.setStateAsync(wId, { val: this.buildGalleryHtml(d, ev), ack: true });\n\t\t\t\t\tthis.widgetSig[wId] = sig;\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.eventBusy[d.oid] = false;\n\t\t}\n\t}\n\n\tprivate pushRefresh(oid: number | string): void {\n\t\tconst d = this.devById.get(oid);\n\t\tif (!d) {\n\t\t\tthis.log.warn(`Push: camera ${oid} unknown (available after first poll)`);\n\t\t\treturn;\n\t\t}\n\t\tconst fid = this.deviceFolder(d);\n\t\tfor (const ms of [0, 1500, 3500, 6000]) {\n\t\t\tthis.setTimeout(\n\t\t\t\t() =>\n\t\t\t\t\tthis.updateCameraEvents(d, fid).catch(e =>\n\t\t\t\t\t\tthis.log.warn(`Push refresh error: ${(e as Error).message}`),\n\t\t\t\t\t),\n\t\t\t\tms,\n\t\t\t);\n\t\t}\n\t}\n\n\t// ---- main poll ----\n\n\tprivate async poll(): Promise<void> {\n\t\tconst res = await this.apiGet('/command/getObjects');\n\t\tconst json = asJson(res.data);\n\n\t\tif (!res.ok || !json) {\n\t\t\tawait this.setStateAsync('info.connection', false, true);\n\t\t\tawait this.setStateAsync('system.online', { val: false, ack: true });\n\t\t\tthis.log.warn(`AgentDVR unreachable: ${res.error || 'invalid response'}`);\n\t\t\treturn;\n\t\t}\n\n\t\tawait this.setStateAsync('info.connection', true, true);\n\t\tawait this.setStateAsync('system.online', { val: true, ack: true });\n\n\t\tif (json.settings) {\n\t\t\tawait this.flattenWrite(json.settings, 'system.settings', 0);\n\t\t}\n\n\t\tif (this.config.storeRawJson) {\n\t\t\tconst clean: Record<string, unknown> = { ...json };\n\t\t\tdelete clean.icons;\n\t\t\tawait this.writeLeaf('system.raw_getObjects', JSON.stringify(clean).slice(0, 60000));\n\t\t}\n\n\t\tconst devices = findDevices(json);\n\t\tawait this.setStateAsync('system.cameraCount', { val: devices.filter(d => d.ot === 2).length, ack: true });\n\n\t\tfor (const d of devices) {\n\t\t\tawait this.buildDevice(d);\n\t\t}\n\n\t\tconst stats = asJson((await this.apiGet('/command/getSystemStats')).data);\n\t\tif (stats) {\n\t\t\tawait this.flattenWrite(stats, 'system.stats', 0);\n\t\t\tconst gb = parseSizeGb(stats.disk_free);\n\t\t\tif (gb !== null) {\n\t\t\t\tawait this.writeLeaf('system.disk_free_gb', gb);\n\t\t\t}\n\t\t}\n\n\t\tconst status = asJson((await this.apiGet('/command/getStatus')).data);\n\t\tif (status) {\n\t\t\tawait this.flattenWrite(status, 'system.status', 0);\n\t\t}\n\n\t\tif (this.config.enableSystemControls && Array.isArray(json.profiles)) {\n\t\t\tconst states: Record<number, string> = {};\n\t\t\tlet activeInd: number | null = null;\n\t\t\tfor (const p of json.profiles) {\n\t\t\t\tif (p && typeof p === 'object') {\n\t\t\t\t\tconst po = p as Record<string, unknown>;\n\t\t\t\t\tconst ind = po.id ?? po.ind ?? po.index;\n\t\t\t\t\tconst pname = po.name;\n\t\t\t\t\tif (typeof ind === 'number' && (typeof pname === 'string' || typeof pname === 'number')) {\n\t\t\t\t\t\tstates[ind] = String(pname);\n\t\t\t\t\t\tif (po.active === true) {\n\t\t\t\t\t\t\tactiveInd = ind;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (Object.keys(states).length > 0) {\n\t\t\t\tconst sig = JSON.stringify(states);\n\t\t\t\tif (sig !== this.profileSig) {\n\t\t\t\t\tthis.profileSig = sig;\n\t\t\t\t\tawait this.extendObjectAsync('system.profile.selector', { common: { states } });\n\t\t\t\t\tawait this.setStateAsync('system.profile.list', { val: sig, ack: true });\n\t\t\t\t}\n\t\t\t\tif (activeInd !== null) {\n\t\t\t\t\tawait this.setStateAsync('system.profile.selector', { val: activeInd, ack: true });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (this.config.enableOverview) {\n\t\t\tconst cams = devices.filter(d => d.ot === 2);\n\t\t\tconst ovId = 'overview';\n\t\t\tif (!this.ensuredFolders.has(ovId)) {\n\t\t\t\tawait this.setObjectNotExistsAsync(ovId, {\n\t\t\t\t\ttype: 'state',\n\t\t\t\t\tcommon: {\n\t\t\t\t\t\tname: 'Overview (all cameras)',\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\trole: 'html',\n\t\t\t\t\t\tread: true,\n\t\t\t\t\t\twrite: false,\n\t\t\t\t\t\tdef: '',\n\t\t\t\t\t},\n\t\t\t\t\tnative: {},\n\t\t\t\t});\n\t\t\t\tthis.ensuredFolders.add(ovId);\n\t\t\t}\n\t\t\tawait this.setStateAsync(ovId, { val: this.buildOverviewHtml(cams), ack: true });\n\t\t}\n\n\t\tawait this.setStateAsync('system.lastUpdate', { val: new Date().toISOString(), ack: true });\n\t\tawait this.setStateAsync('system.lastPoll', { val: Date.now(), ack: true });\n\t}\n\n\tprivate scheduleRefresh(): void {\n\t\tif (this.refreshTimer !== undefined) {\n\t\t\tthis.clearTimeout(this.refreshTimer);\n\t\t\tthis.refreshTimer = undefined;\n\t\t}\n\t\tthis.refreshTimer = this.setTimeout(\n\t\t\t() => this.poll().catch(e => this.log.warn(`Refresh error: ${(e as Error).message}`)),\n\t\t\t1500,\n\t\t);\n\t}\n\n\t// ---- command execution ----\n\n\tprivate ptzUrl(oid: number | string, dir: number): string {\n\t\treturn `/ptz.cgi?oid=${encodeURIComponent(String(oid))}&dir=${dir}`;\n\t}\n\n\tprivate buildCommandUrl(entry: RegistryEntry): string {\n\t\tif (entry.kind === 'ptz') {\n\t\t\treturn this.ptzUrl(entry.oid!, entry.dir!);\n\t\t}\n\t\tif (entry.kind === 'sys') {\n\t\t\treturn entry.path as string;\n\t\t}\n\t\tconst qs: string[] = [];\n\t\tfor (const p of entry.params || []) {\n\t\t\tif (p === 'oid') {\n\t\t\t\tqs.push(`oid=${encodeURIComponent(String(entry.oid))}`);\n\t\t\t} else if (p === 'ot') {\n\t\t\t\tqs.push(`ot=${encodeURIComponent(String(entry.ot))}`);\n\t\t\t}\n\t\t}\n\t\treturn (entry.path || '') + (qs.length ? `?${qs.join('&')}` : '');\n\t}\n\n\tprivate async clearPtzSiblings(stateId: string): Promise<void> {\n\t\tconst prefix = stateId.slice(0, stateId.lastIndexOf('.') + 1);\n\t\tfor (const p of PTZ_DIRS) {\n\t\t\tif (!p.hold) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst sid = prefix + p.id;\n\t\t\tif (sid !== stateId) {\n\t\t\t\tawait this.setStateAsync(sid, { val: false, ack: true });\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async runCommand(relId: string, entry: RegistryEntry, val: ioBroker.StateValue): Promise<void> {\n\t\tif (entry.kind === 'setProfile') {\n\t\t\tconst ind = typeof val === 'number' ? val : parseInt(String(val ?? ''), 10);\n\t\t\tif (!isNaN(ind)) {\n\t\t\t\tconst url = `/command/setProfile?ind=${ind}`;\n\t\t\t\tconst cmdRes = await this.apiGet(url);\n\t\t\t\tif (cmdRes.ok) {\n\t\t\t\t\tthis.log.debug(`OK: ${url}`);\n\t\t\t\t\tawait this.setStateAsync(relId, { val: ind, ack: true });\n\t\t\t\t} else {\n\t\t\t\t\tthis.log.warn(`Command failed (${url}): ${cmdRes.error}`);\n\t\t\t\t}\n\t\t\t\tthis.scheduleRefresh();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (entry.kind === 'snapshotB64') {\n\t\t\tconst snapId = `${entry.fid}.snapshot_b64`;\n\t\t\tawait this.fetchSnapshotB64(entry.oid!, snapId);\n\t\t\tawait this.setStateAsync(relId, { val: false, ack: true });\n\t\t\treturn;\n\t\t}\n\n\t\tif (entry.kind === 'push') {\n\t\t\tthis.log.info(`Push trigger cam ${entry.oid}`);\n\t\t\tawait this.setStateAsync(relId, { val: '', ack: true });\n\t\t\tthis.pushRefresh(entry.oid!);\n\t\t\treturn;\n\t\t}\n\n\t\tif (entry.kind === 'ptzHold') {\n\t\t\tconst oid = entry.oid!;\n\t\t\tconst startMove = async (): Promise<void> => {\n\t\t\t\tawait this.clearPtzSiblings(relId);\n\t\t\t\tawait this.apiGet(this.ptzUrl(oid, entry.dir!));\n\t\t\t\tthis.ptzActive.set(oid, relId);\n\t\t\t\tawait this.setStateAsync(relId, { val: true, ack: true });\n\t\t\t};\n\t\t\tconst stopMove = async (): Promise<void> => {\n\t\t\t\tawait this.apiGet(this.ptzUrl(oid, 11));\n\t\t\t\tif (this.ptzActive.get(oid) === relId) {\n\t\t\t\t\tthis.ptzActive.delete(oid);\n\t\t\t\t}\n\t\t\t\tawait this.setStateAsync(relId, { val: false, ack: true });\n\t\t\t};\n\t\t\tif (val) {\n\t\t\t\tif (this.ptzActive.get(oid) === relId) {\n\t\t\t\t\tawait stopMove();\n\t\t\t\t} else {\n\t\t\t\t\tawait startMove();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tawait stopMove();\n\t\t\t}\n\t\t\tthis.scheduleRefresh();\n\t\t\treturn;\n\t\t}\n\n\t\tif (!val) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (entry.kind === 'sys' && entry.path === null) {\n\t\t\tawait this.poll().catch(e => this.log.warn(`Manual refresh error: ${(e as Error).message}`));\n\t\t\tawait this.setStateAsync(relId, { val: false, ack: true });\n\t\t\treturn;\n\t\t}\n\n\t\tconst url = this.buildCommandUrl(entry);\n\t\tconst cmdRes = await this.apiGet(url);\n\t\tif (cmdRes.ok) {\n\t\t\tthis.log.debug(`OK: ${url}`);\n\t\t} else {\n\t\t\tthis.log.warn(`Command failed (${url}): ${cmdRes.error}`);\n\t\t}\n\n\t\tawait this.setStateAsync(relId, { val: false, ack: true });\n\t\tthis.scheduleRefresh();\n\t}\n}\n\nif (require.main !== module) {\n\tmodule.exports = (options: Partial<utils.AdapterOptions> | undefined) => new AgentDvr(options);\n} else {\n\t(() => new AgentDvr())();\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["url", "cmdRes"]
|
|
7
7
|
}
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "agent-dvr",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.4",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.0.4": {
|
|
7
|
+
"en": "fix DP roles: snapshot_b64 role state, profile selector role level",
|
|
8
|
+
"de": "DP-Rollen korrigiert: snapshot_b64 role state, Profil-Selektor role level",
|
|
9
|
+
"ru": "исправлены роли DP: snapshot_b64 role state, селектор профиля role level",
|
|
10
|
+
"pt": "corrigir roles DP: snapshot_b64 role state, seletor de perfil role level",
|
|
11
|
+
"nl": "DP-rollen gecorrigeerd: snapshot_b64 role state, profielkiezer role level",
|
|
12
|
+
"fr": "correction des rôles DP: snapshot_b64 role state, sélecteur de profil role level",
|
|
13
|
+
"it": "corretto ruoli DP: snapshot_b64 role state, selettore profilo role level",
|
|
14
|
+
"es": "corregir roles DP: snapshot_b64 role state, selector de perfil role level",
|
|
15
|
+
"pl": "poprawiono role DP: snapshot_b64 role state, selektor profilu role level",
|
|
16
|
+
"uk": "виправлено ролі DP: snapshot_b64 role state, вибір профілю role level",
|
|
17
|
+
"zh-cn": "修正 DP 角色:snapshot_b64 角色 state,配置文件选择器角色 level"
|
|
18
|
+
},
|
|
6
19
|
"0.0.3": {
|
|
7
20
|
"en": "profile selector: fetch available profiles via getProfiles, writable dropdown",
|
|
8
21
|
"de": "Profil-Selektor: verfügbare Profile via getProfiles laden, beschreibbares Dropdown",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.agent-dvr",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Connects ioBroker to AgentDVR: auto-discovers cameras, mirrors all device values as data points, delivers real-time triggers on new recordings, and generates a responsive HTML gallery with optional search and tag filtering.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "ipod86",
|