Zum Inhalt springen

Tapo Kameras

Aus FHEMWiki

Verifiziert für Tapo C530WS

Cloud-Installation

Die Kamera zunächst mit der von Tapo angegebenen App installieren und testen. Dafür muss ein Konto angelegt werden, mit Mailadresse und einem Passwort, das im Folgenden als <PWCLOUD> abgekürzt wird.

Die Kamera erhält im internen Netz eine IP-Adresse, die im Folgenden als <IPKAMERA> abgekürzt wird.

Stream und Snapshot

Hierfür muss zunächst ein so genanntes Kamera-Konto auf der Kamera angelegt werden, unter Einstellungen->Erweiterte Einstellungen. Dabei werden ein Username und ein Passwort festgelegt, die im Folgenden als <USERKAMERA> und <PWKAMERA> abgekürzt werden.

Auf dem FHEM-Server (oder einem anderen System zum Testen) muss dann die ffmpeg-Suite installiert werden.

Wenn die Kamera eingeschaltet ist, lässt sich dann der Stream der Kamera abgreifen mit

ffplay rtsp://<USERKAMERA>:<PWKAMERA>@<IPKAMERA>:554/stream1

in voller Auflösung, sowie mit reduzierter Auflösung als

ffplay rtsp://<USERKAMERA>:<PWKAMERA>@<IPKAMERA>:554/stream2

Die Erstellung eines Snapshots wird über ein Shellskript gesteuert, mit folgendem Inhalt

#!/bin/bash
OUT="/opt/fhem/www/images/Tapo.jpg"
URL="/fhem/images/Tapo.jpg"
ffmpeg -rtsp_transport tcp -y \
  -i "rtsp://<USERKAMERA>:<PWKAMERA>@<IPKAMERA>:554/stream1" \
  -frames:v 1 "$OUT" >/dev/null 2>&1
if [ $? -eq 0 ] && [ -s "$OUT" ]; then
  echo "$URL"
else
  echo "error creating image"
fi

Für das Weitere gehen wir davon aus, dass dieses Shellskript unter /opt/fhem/tapo liegt und durch den FHEM-Prozess ausführbar ist. Wir definieren ein Dummy-Device mit zunächst minimalen Eigenschaften

defmod TapoCam dummy
attr TapoCam readingList snapshot
attr TapoCam setList takePhoto:noArg 

sowie ein DOIF

defmod TapoCam.move.N DOIF ([TapoCam:state] =~ /^takePhoto$/) \
({my $res = qx(/opt/fhem/tapo/tapo_snapshot.sh);;\
 chomp($res);;\
 fhem("setreading TapoCam snapshot $res");;\
 fhem("setreading TapoCam state ready")\
}\
)

mit Attribut

attr TapoCam.N do always

Dann sorgt der FHEM-Befehl

set TapoCam takePhoto

dafür, dass ein Snapshot unter /opt/fhem/www/images gespeichert wird. Im FHEMWEB Frontend ist er dann unter /fhem/images/Tapo.jpg ansehbar

TODO: Komfortabler machen

Kamerasteuerung mit Python

Zur Vorbereitung folgende Schritte ggf. mit root-Rechten unternehmen:

mkdir -p /opt/fhem/tapo
cd /opt/fhem/tapo
python3 -m venv .venv
/opt/fhem/tapo/.venv/bin/pip install --upgrade pip
/opt/fhem/tapo/.venv/bin/pip install pytapo
chown -R fhem:dialout /opt/fhem/tapo
chmod -R u+rwX /opt/fhem/tapo

Dadurch wird im Verzeichnis /opt/fhem/tapo eine virtuelle Umgebung für die TapoCam angelegt. Danach eine Datei /opt/fhem/tapo/tapo_testmethod.py anlegen, mit dem Inhalt

#!/opt/fhem/tapo/.venv/bin/python3
from pytapo import Tapo
tapo = Tapo("<IPKAMERA>", "admin", "<PWCLOUD>")
for m in dir(tapo):
   ml = m.lower()
   if any(x in ml for x in [
       "light", "spot", "privacy", "mask", "alarm", "siren",
       "speaker", "micro", "audio", "talk", "voice", "ring"
   ]):
       print(m)

Wichtig: Für die Kamerasteuerung verlangt Tapo inzwischen das Cloud-Passwort und den Usernamen admin. Dieses Python-Skript ausführbar machen und ausführen:

chmod +x tapo_testmethod.py
./tapo_testmethod.py

Man erhält auf diese Weise eine unstrukturierte Liste alle Methode, die von der Kamera akzeptiert werden.

Kontrollprogramm

Das zentrale Python-Skript zur Kamerasteuerung liegt in der Datei /opt/fhem7tapo/tapo_controlpy und hat den Inhalt

#!/opt/fhem/tapo/.venv/bin/python3

import sys 
import json
from pytapo import Tapo
HOST = "<IPKAMERA>"
USER = "admin"
PASSWORD = "<PWCLOUD>"

STEP = 10

def out(obj):
    if isinstance(obj, (dict, list)):
        print(json.dumps(obj, ensure_ascii=False))
    else:
        print(str(obj))

def ok(msg):
    print(msg)
    sys.exit(0)

def err(msg):
    print(msg)
    sys.exit(1)

def safe_call(label, func):
    try:def safe_call(label, func):
    try:
        return func()
    except Exception as e:
        return f"error: {e}"

try:
    tapo = Tapo(HOST, USER, PASSWORD)
except Exception as e:
    err(f"login error: {e}")

cmd = sys.argv[1] if len(sys.argv) > 1 else ""

try:
    if cmd == "left":
        tapo.moveMotor(-STEP, 0)
        ok("ok left")

    elif cmd == "right":
        tapo.moveMotor(STEP, 0)
        ok("ok right")

    elif cmd == "up":
        tapo.moveMotor(0, STEP)
        ok("ok up")

    elif cmd == "down":
        tapo.moveMotor(0, -STEP)
        ok("ok down")

    elif cmd == "privacy_on":
        res = tapo.setPrivacyMode(True)
        out(res)

    elif cmd == "privacy_off":
        res = tapo.setPrivacyMode(False)
        out(res)

    elif cmd == "alarm_light_enable":
        res = tapo.setAlarm(True, soundEnabled=False, lightEnabled=True)
        out(res)

    elif cmd == "alarm_sound_enable":
        res = tapo.setAlarm(True, soundEnabled=True, lightEnabled=False)
        out(res)

    elif cmd == "alarm_both_enable":
        res = tapo.setAlarm(True, soundEnabled=True, lightEnabled=True)
        out(res)

    elif cmd == "alarm_off":
        res = tapo.setAlarm(False)
        out(res)

    elif cmd == "status":
        status = {
            "privacy": safe_call("privacy", lambda: tapo.getPrivacyMode()),
            "alarm": safe_call("alarm", lambda: tapo.getAlarm()),
            "audio": safe_call("audio", lambda: tapo.getAudioConfig()),
            "floodlight_status": safe_call("floodlight_status", lambda: tapo.getFloodlightStatus()),
            "floodlight_config": safe_call("floodlight_config", lambda: tapo.getFloodlightConfig()),
            "floodlight_capability": safe_call("floodlight_capability", lambda: tapo.getFloodlightCapability()),
        }
        out(status)

    else:
        err("unknown command")

except Exception as e:
    err(f"command error: {e}")