Update auf Skills
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import json, os, ssl, sys
|
||||
from urllib.request import Request, urlopen
|
||||
from urllib.error import HTTPError, URLError
|
||||
PROTO='jarvis.skill.invoke.v1'
|
||||
def truthy(v): return str(v or '').strip().lower() in ('1','true','yes','on')
|
||||
def fail(c,m): return {'protocol':PROTO,'success':False,'error':{'code':c,'message':m}}
|
||||
def ok(d,m='OK',mut=False): return {'protocol':PROTO,'success':True,'data':d,'message':m,'mutated':mut}
|
||||
def cfg():
|
||||
b=os.getenv('UNIFI_PROTECT_URL','').strip().rstrip('/'); k=os.getenv('UNIFI_API_KEY','').strip()
|
||||
if not b or not k: raise RuntimeError('UNIFI_PROTECT_URL und UNIFI_API_KEY müssen gesetzt sein')
|
||||
if not b.startswith(('http://','https://')): b='https://'+b
|
||||
return b,k,truthy(os.getenv('UNIFI_VERIFY_TLS','false'))
|
||||
def request(method,path,body=None):
|
||||
b,k,verify=cfg(); data=None; h={'Accept':'application/json','X-API-Key':k}
|
||||
if body is not None: data=json.dumps(body).encode(); h['Content-Type']='application/json'
|
||||
ctx=ssl._create_unverified_context() if b.startswith('https://') and not verify else None
|
||||
try:
|
||||
with urlopen(Request(b+path,data=data,headers=h,method=method),timeout=8,context=ctx) as r:
|
||||
raw=r.read(); return json.loads(raw.decode()) if raw else {}
|
||||
except HTTPError as e: raise RuntimeError(f'UniFi Protect HTTP {e.code}: {e.read().decode(errors="replace")[:1000]}')
|
||||
except URLError as e: raise RuntimeError(f'UniFi Protect nicht erreichbar: {e.reason}')
|
||||
def name(v):
|
||||
n=v.get('name','') if isinstance(v,dict) else ''
|
||||
if isinstance(n,dict): return n.get('name') or n.get('value') or str(n)
|
||||
return n or ''
|
||||
def list_norm(path,kind):
|
||||
raw=request('GET',path); vals=raw if isinstance(raw,list) else raw.get('data',[]) if isinstance(raw,dict) else []
|
||||
out=[]
|
||||
for v in vals:
|
||||
base={'id':v.get('id',''),'name':name(v),'model':v.get('modelKey',''),'state':v.get('state',''),'mac':v.get('mac','')}
|
||||
if kind=='camera':
|
||||
f=v.get('featureFlags') or {}; base.update({'video_mode':v.get('videoMode',''),'hdr':v.get('hdrType',''),'has_mic':f.get('hasMic'),'has_speaker':f.get('hasSpeaker'),'smart_detect_types':f.get('smartDetectTypes') or []})
|
||||
out.append(base)
|
||||
return out
|
||||
def main(inv):
|
||||
a=inv.get('action'); x=inv.get('input') or {}
|
||||
if a=='info': return ok(request('GET','/v1/meta/info'),'Protect API erreichbar.')
|
||||
if a in ('list_cameras','list_sensors','list_lights'):
|
||||
typ={'list_cameras':('cameras','camera'),'list_sensors':('sensors','sensor'),'list_lights':('lights','light')}[a]
|
||||
vals=list_norm('/v1/'+typ[0],typ[1]); return ok({typ[0]:vals,'count':len(vals)},'Protect-Geräte geladen.')
|
||||
if a=='get_camera':
|
||||
raw=request('GET',f"/v1/cameras/{x['camera_id']}"); return ok(raw if isinstance(raw,dict) else {'camera':raw},'Kamera geladen.')
|
||||
if a=='ptz_goto': request('POST',f"/v1/cameras/{x['camera_id']}/ptz/goto/{int(x['slot'])}"); return ok({'accepted':True},'PTZ-Preset angefahren.',True)
|
||||
if a=='ptz_patrol_start': request('POST',f"/v1/cameras/{x['camera_id']}/ptz/patrol/start/{int(x['slot'])}"); return ok({'accepted':True},'PTZ-Patrouille gestartet.',True)
|
||||
if a=='ptz_patrol_stop': request('POST',f"/v1/cameras/{x['camera_id']}/ptz/patrol/stop"); return ok({'accepted':True},'PTZ-Patrouille gestoppt.',True)
|
||||
if a=='trigger_alarm_webhook': request('POST',f"/v1/alarm-manager/webhook/{x['trigger_id']}"); return ok({'accepted':True},'Protect-Alarm-Webhook ausgelöst.',True)
|
||||
return fail('UNKNOWN_ACTION',f'Unbekannte Aktion: {a}')
|
||||
try: res=main(json.load(sys.stdin))
|
||||
except Exception as e: res=fail('UNIFI_PROTECT_ERROR',str(e))
|
||||
json.dump(res,sys.stdout,ensure_ascii=False)
|
||||
@@ -0,0 +1,266 @@
|
||||
{
|
||||
"protocol": "jarvis.skill.v1",
|
||||
"id": "unifi.protect",
|
||||
"name": "UniFi Protect",
|
||||
"version": "1.0.0",
|
||||
"description": "Lokale UniFi-Protect-Integration über die offizielle Integration API.",
|
||||
"runtime": {
|
||||
"type": "process",
|
||||
"command": "python3",
|
||||
"args": [
|
||||
"main.py"
|
||||
],
|
||||
"timeout_ms": 10000,
|
||||
"env_from": [
|
||||
"UNIFI_PROTECT_URL",
|
||||
"UNIFI_API_KEY",
|
||||
"UNIFI_VERIFY_TLS"
|
||||
]
|
||||
},
|
||||
"permissions": {
|
||||
"network": true,
|
||||
"system_exec": true
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"name": "info",
|
||||
"description": "Prüft die Protect-API und liefert Versionsinformationen.",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_cameras",
|
||||
"description": "Listet Protect-Kameras mit Status und Fähigkeiten.",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cameras": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cameras",
|
||||
"count"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_camera",
|
||||
"description": "Liest Details einer Protect-Kamera.",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"camera_id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"camera_id"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_sensors",
|
||||
"description": "Listet Protect-Sensoren.",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sensors": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sensors",
|
||||
"count"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_lights",
|
||||
"description": "Listet Protect-Lights/Floodlights.",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"lights": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"lights",
|
||||
"count"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ptz_goto",
|
||||
"description": "Fährt eine PTZ-Kamera auf ein vorhandenes Preset.",
|
||||
"mutates": true,
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"camera_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"slot": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"camera_id",
|
||||
"slot"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accepted": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accepted"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ptz_patrol_start",
|
||||
"description": "Startet eine konfigurierte PTZ-Patrouille.",
|
||||
"mutates": true,
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"camera_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"slot": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"camera_id",
|
||||
"slot"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accepted": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accepted"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ptz_patrol_stop",
|
||||
"description": "Stoppt die aktive PTZ-Patrouille.",
|
||||
"mutates": true,
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"camera_id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"camera_id"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accepted": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accepted"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "trigger_alarm_webhook",
|
||||
"description": "Triggert einen in Protect konfigurierten Alarm-Manager-Webhook.",
|
||||
"mutates": true,
|
||||
"requires_confirmation": true,
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"trigger_id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"trigger_id"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accepted": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accepted"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user