FHEM Forum

FHEM - Hausautomations-Systeme => Sonstige Systeme => Thema gestartet von: Prof. Dr. Peter Henning am 18 März 2026, 11:39:03

Titel: FHEM-Integration von Tapo Kameras
Beitrag von: Prof. Dr. Peter Henning am 18 März 2026, 11:39:03
Die Kameras des Herstellers Tapo sind recht gut und preiswert. Zwar normalerweise auch an eine chinesische Cloud angebunden, können aber mit relativ geringem Aufwand auch ohne diese betrieben werden.

Mit der FHEM-Integration bin ich schon relativ weit: Stream, Snapshots, Bewegung etc. funktionieren schon sehr gut. Derzeit arbeitet das mit ein paar Python-Skripten, prinzipiell sollte es aber auch ohne diesen Umweg komplett mit HTTPMOD gehen.

Ob ich das nur für mich mache, oder ein allgemein nutzbares System zusammenbaue, hängt vom Interesse hier im Forum ab.

Ich habe das Modell hier: https://www.amazon.de/dp/B0DY94MCLN?ref=fed_asin_title
und bin sehr zufrieden.

LG

pah
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: DeeSPe am 18 März 2026, 12:30:56
Ich bekunde mal Interesse, habe zwei C222 im Einsatz.

Gruß
Dan
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: Gisbert am 18 März 2026, 13:14:32
Hallo pah,

dito hier. Ich überlege, mit welchen Kameras ich aufrüsten möchte. Das Problem bei meinen jetzigen Kameras (Foscam) sind entweder Spinnennetze vor der Linse (nachts) und/oder zu wenig (d.h. ausbleibende Alarmierungen) oder zu viele Fehlalarme.

Viele Grüße Gisbert
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: CQuadrat am 18 März 2026, 13:29:51
Ebenfalls dito.
Kameraüberwachung ist das nächste Thema, was ich angehen wollte.
Bisher schwankte ich noch zwischen Eufy und Reolink.
Cloud-Anbindung ist für mich ein No-Go.
Tapo hatte ich bisher nicht auf dem Schirm. Sieht aber echt preiswert aus.
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: Prof. Dr. Peter Henning am 18 März 2026, 15:11:07
Die Cloud ist nötig, um das Ding zu installieren. Dabei wird u.a. das Cloud-Passwort auf die Kamera übertragen, das braucht sie hinterher für Zugriffe aus dem Netz.

Ich mache mal ein Wiki dazu auf: https://wiki.fhem.de/wiki/Tapo_Kameras

@DeeSPee: Würde mich interessieren, was die Ausgabe des tapo_testmethod bei der C222 ergibt.
Bei mir zeigt die Kamera zwar an, dass sie die Methoden zum manuellen Auslösen des Alarms und zum Schalten des Lichts akzeptiert - steigt dann aber bei der Anwendung aus.

LG

pah
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: enno am 19 März 2026, 08:28:40
Moin pah,

ich klinke mich hier mal mit ein. Bei mir liegt eine Tapo C510W in der Schublade und wartet auf ihren Einsatz in der Ferienwohnung. Zum Testen, kann ich sie aber hier zu Hause schon mal anschließen.

Gruss
  Enno
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: CQuadrat am 19 März 2026, 08:41:42
Zitat von: Prof. Dr. Peter Henning am 18 März 2026, 15:11:07Die Cloud ist nötig, um das Ding zu installieren. Dabei wird u.a. das Cloud-Passwort auf die Kamera übertragen, das braucht sie hinterher für Zugriffe aus dem Netz.
(...)
Das würde ich lieber mit meinem eigenen VPN realisieren.

Erstmalige Installation per Cloud: okay. Aber dann darf die Kamera nur noch im eigenen Netz bleiben.
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: Prof. Dr. Peter Henning am 19 März 2026, 12:16:36
Die Kamera gefällt mir immer besser. Ich hab damit wirklich einen ganzen Tag lang herumgespielt, jetzt kann ich alles Mögliche setzen.
Wie man die entsprechende Python-Umgebung einrichtet, steht schon im Wiki. Anbei eine Zahl von Python-Skripten, um Detektion, Motorsteuerung, Licht und Privatmodus zu steuern. Wichtig ist tapo_credentials.py, dort muss natürlich die eigene IP-Adresse der Kamera und das Cloud-Passwort hinein.

Das bash-Skript für die Snapshots steht komplett im Wiki.

Die Integration in FHEM ist eigentlich ganz easy, ich muss nur noch überlegen, wie ich die Readings sinnvoll benenne.

Ein Skript für Alarm etc. kommt noch

Na, und wenn dann alles läuft und ausgetestet ist, kann ich aus dem Python-Zeug die passenden Methoden abgreifen und direkt über HTTPMOD gehen. Das sind nämlich alles nur REST-Calls. Dafür muss nur jeweils spätestens all 10 Minuten eine neue login-Prozedur auf der Kamera ausgeführt werden, um ein aktuelles Token zu holen, das bei den REST-Calls mit übergeben wird.

LG

pah

Zitat von: CQuadrat am 19 März 2026, 08:41:42Aber dann darf die Kamera nur noch im eigenen Netz bleiben.
Das "darf" kannst Du ja mit den chinesischen Herstellern besprechen...
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: Gisbert am 19 März 2026, 14:37:26
Hallo pah,

kann man Bilder und ggf. Videos bei Alarmierung/Personen-/Bewegungserkennung im lokalen Netzwerk speichern?

Viele Grüße Gisbert
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: DeeSPe am 19 März 2026, 15:20:17
Zitat von: Prof. Dr. Peter Henning am 18 März 2026, 15:11:07@DeeSPee: Würde mich interessieren, was die Ausgabe des tapo_testmethod bei der C222 ergibt.
Bei mir kommt leider ein Authentifikationsproblem:
root@fhem-test:/opt/fhem/tapo# ./tapo_testmethod.py
Traceback (most recent call last):
  File "/opt/fhem/tapo/./tapo_testmethod.py", line 4, in <module>
    tapo = Tapo("192.168.107.78", "name", "password")
  File "/opt/fhem/tapo/.venv/lib/python3.13/site-packages/pytapo/__init__.py", line 108, in __init__
    self.basicInfo = self.getBasicInfo()
                     ~~~~~~~~~~~~~~~~~^^
  File "/opt/fhem/tapo/.venv/lib/python3.13/site-packages/pytapo/__init__.py", line 1161, in getBasicInfo
    return self.executeFunction(
           ~~~~~~~~~~~~~~~~~~~~^
        "getDeviceInfo", {"device_info": {"name": ["basic_info"]}}
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/opt/fhem/tapo/.venv/lib/python3.13/site-packages/pytapo/__init__.py", line 171, in executeFunction
    data = self.performRequest(
           ~~~~~~~~~~~~~~~~~~~^
        {
        ^
    ...<2 lines>...
        }
        ^
    )["result"]["responses"][0]
    ^
  File "/opt/fhem/tapo/.venv/lib/python3.13/site-packages/pytapo/__init__.py", line 214, in performRequest
    self.asyncHandler.executeAsyncExecutorJob(self.transport.authenticate)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/fhem/tapo/.venv/lib/python3.13/site-packages/pytapo/asyncHandler.py", line 21, in executeAsyncExecutorJob
    return self._loop.run_until_complete(job(*args))
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
  File "/usr/lib/python3.13/asyncio/base_events.py", line 725, in run_until_complete
    return future.result()
           ~~~~~~~~~~~~~^^
  File "/opt/fhem/tapo/.venv/lib/python3.13/site-packages/pytapo/transport/transport.py", line 43, in authenticate
    return await self.transport.authenticate(self, retry)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/fhem/tapo/.venv/lib/python3.13/site-packages/pytapo/transport/pytapo/pytapo.py", line 185, in authenticate
    await self._run_blocking(self._refreshStok)
  File "/opt/fhem/tapo/.venv/lib/python3.13/site-packages/pytapo/transport/pytapo/pytapo.py", line 176, in _run_blocking
    return func(*args, **kwargs)
  File "/opt/fhem/tapo/.venv/lib/python3.13/site-packages/pytapo/transport/pytapo/pytapo.py", line 710, in _refreshStok
    return self._refreshStok(loginRetryCount)
           ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "/opt/fhem/tapo/.venv/lib/python3.13/site-packages/pytapo/transport/pytapo/pytapo.py", line 715, in _refreshStok
    raise Exception("Invalid authentication data")
Exception: Invalid authentication data

Ansonsten klappt es aber mit takePhoto bereits.

Gruß
Dan
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: Prof. Dr. Peter Henning am 19 März 2026, 15:29:45
Zitat von: Gisbert am 19 März 2026, 14:37:26Hallo pah,

kann man Bilder und ggf. Videos bei Alarmierung/Personen-/Bewegungserkennung im lokalen Netzwerk speichern?

Viele Grüße Gisbert

Bilder und Videos kannst Du natürlich im lokalen Netz abspeichern. "Erkennung" ist noch offen, weil ich noch nicht ganz durchblicke, wie ich die AKTIVE Signalisierung der Kamera an FHEM hinbekomme. Es sollte aber möglich sein, bei Bewegungserkennung eine Aufnahme zu starten, diese auf der SD-Karte in der Kamera abzulegen und später herunterzuladen.

Zitat von: DeeSPe am 19 März 2026, 15:20:17Bei mir kommt leider ein Authentifikationsproblem:
Ich hoffe mal, dass Du als Username "admin" und als Passwort Dein Cloudpasswort angegeben hast. Es könnte sein, dass Du eine ältere Firmware hast. In dem Fall musst Du als Username den angeben, den Du bei der Einrichtung des Kamerakontos auf der Kamera verwendet hast, sowie das zugehörige Passwort.

LG

pah
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: DeeSPe am 19 März 2026, 16:03:35
Zitat von: Prof. Dr. Peter Henning am 19 März 2026, 15:29:45Ich hoffe mal, dass Du als Username "admin" und als Passwort Dein Cloudpasswort angegeben hast. Es könnte sein, dass Du eine ältere Firmware hast. In dem Fall musst Du als Username den angeben, den Du bei der Einrichtung des Kamerakontos auf der Kamera verwendet hast, sowie das zugehörige Passwort.

Ich habe es mit "admin" und jetzt auch mit meiner Mailadresse, die zur Anmeldung verwendet wurde, versucht. Es klappt mit keinem von beiden.
Jetzt kommt erst einmal:
Exception: Temporary Suspension: Try again in 1800 seconds
Gruß
Dan

P.S. Firmware is the latest.
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: Prof. Dr. Peter Henning am 19 März 2026, 17:06:22
Zitat von: DeeSPe am 19 März 2026, 16:03:35Exception: Temporary Suspension: Try again in 1800 seconds
Tja, hatte ich auch schon mal ...

Nochmal, es gibt m.e. 2 Möglichkeiten

a.) Username und passwort des _in der kamera_ eingerichteten kontos
b.) admin und cloud-passwort für das Tapo-System

LG

pah
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: mi.ke am 19 März 2026, 17:09:51
Hi,

ich bestell mir auf mal eine, weil der Arlo Dinger zwar Sack teuer waren, aber schon seit ewig bei mir nicht mehr unter FHEM funktionieren.

Danke für den Tipp und die schon geleistete Arbeit
cheers
mi.ke
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: Prof. Dr. Peter Henning am 19 März 2026, 18:03:11
So, die tapo_control_light.py habe ich noch um die beleuchtung für nachtaufnahmen ergänzt - ir, white, oder auto.

Und neu dazu tapo_control_alarm.py.

Was ich noch nicht ohne die App hinbekomme ist, den Alarmmodus auf tagsüber, nachtsüber oder ein Zeitintervall zu setzen.

Als nächstes kommt jetzt die FHEM-Anbindung dran, teilweise habe ich die ja schon im Wiki.

LG

pah
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: DeeSPe am 19 März 2026, 18:53:15
Zitat von: Prof. Dr. Peter Henning am 19 März 2026, 17:06:22Nochmal, es gibt m.e. 2 Möglichkeiten

a.) Username und passwort des _in der kamera_ eingerichteten kontos
b.) admin und cloud-passwort für das Tapo-System

Beide jetzt mehrfach probiert! Computer sagt: nein!

Beim Aufrufen der tapo_control.py bekomme ich folgenden Fehler:
root@fhem-test:/opt/fhem/tapo# ./tapo_control.py
  File "/opt/fhem/tapo/./tapo_control.py", line 27
    try:def safe_call(label, func):
        ^^^
SyntaxError: invalid syntax

Gruß
Dan
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: Prof. Dr. Peter Henning am 19 März 2026, 19:41:39
Zitat von: DeeSPe am 19 März 2026, 18:53:15Beim Aufrufen der tapo_control.py bekomme ich folgenden Fehler:
Vlt. ein Tippfehler beim Übertragen ins Wiki. Mal die angehängte Datei nehmen. Nutzt aber nichts, wenn die credentials zurückgewiesen werden.

Edit: Wichtig: In der Tapo-App unter "Ich" unbedingt "Dienste von Drittanbietern" aufrufen und "Kompatibilität mit Drittanbietern" auf EIN stellen !
Ich habe kurzerhand vergessen, das ins Wiki zu schreiben.

LG

pah
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: DeeSPe am 19 März 2026, 21:51:54
Zitat von: Prof. Dr. Peter Henning am 19 März 2026, 19:41:39Edit: Wichtig: In der Tapo-App unter "Ich" unbedingt "Dienste von Drittanbietern" aufrufen und "Kompatibilität mit Drittanbietern" auf EIN stellen !

Habe ich jetzt aktiviert, bringt aber leider nicht den erhofften Erfolg.
Folgenden Kombinationen aus Logindaten habe ich NICHT erfolgreich getestet:
admin:<CLOUD-PW>
<CLOUD-ANMELDENAME>:<CLOUD-PW>
<KAMERAKONTO-NAME>:<KAMERAKONTO-PW>

Aktuelle C222 Firmware v1.4.1 und keine neuere verfügbar.

Gruß
Dan

P.S. Hab mal ein paar kleine Berichtigungen im Wiki gemacht.
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: DeeSPe am 19 März 2026, 22:14:29
Keine Ahnung warum, aber jetzt, nach Ablauf der Anmeldesperre, hat es endlich geklappt mit admin:<CLOUD-PW>

root@fhem-test:/opt/fhem/tapo# ./tapo_testmethod.py
getAlarm
getAlarmConfig
getAudioConfig
getAudioSpec
getChimeAlarmConfigure
getChimeRingPlan
getFloodlightCapability
getFloodlightConfig
getFloodlightStatus
getHubSirenConfig
getHubSirenStatus
getHubSirenTypeList
getLightFrequencyMode
getPrivacyMode
getRingStatus
getSupportAlarmTypeList
manualFloodlightOp
playAlarm
setAlarm
setChimeAlarmConfigure
setChimeRingPlan
setFloodlightConfig
setHubSirenConfig
setHubSirenStatus
setLightFrequencyMode
setMicrophone
setPrivacyMode
setRecordAudio
setRingStatus
setSirenStatus
setSpeakerVolume
startManualAlarm
stopManualAlarm
testUsrDefAudio

Gruß
Dan

EDIT: tapo_control.py funktioniert auch, ist nur sehr träge.
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: Prof. Dr. Peter Henning am 20 März 2026, 17:29:01
So, ich habe das jetzt auf einem guten Stand. Mit einem FHEM Dummy
defmod TapoCam dummy
attr TapoCam readingList snapshot motor_action motor_result motor_presets error
attr TapoCam setList takePhoto:noArg privacy:on,off light:on,off light_intensity light_duration light_night:ir,white,auto led:on,off left right up down calibrate:noArg preset_goto:1,2,3,4,5,6,7,8 preset_save preset_delete presets_get detection_motion detection_person detection_pet detection_tamper detection_vehicle detection_linecrossing alarm:on,off alarm_light:on,off alarm_sound:on,off alarm_duration alarm_volume:low,medium,high
attr TapoCam webCmd takePhoto:left:right:up:down
und einem DOIF
defmod TapoCam.N DOIF ([TapoCam:state] ne "ready") \
({TapoCamHandler("$DEVICE","$EVENT")})\
(setreading TapoCam state ready)\
\
DOELSEIF\
([+00:05:00])\
({TapoCamHandler("TapoCam","status_update")})\
\

attr TapoCam.N do always
attr TapoCam.N group Control
attr TapoCam.N room Kontrollraum
attr TapoCam.N wait 0,3
sowie zwei verschiedenen Perl-Funktionen
sub TapoCamHandler($$){
  my ($name,$event) = @_;
 
  my $hash = $defs{$name};
  return if(!$hash);
  my $res;
  my $val;
  my $cmd;
 
  #--
  if( $event =~ /^takePhoto$/){
    $res = qx(/opt/fhem/tapo/tapo_snapshot.sh);
    chomp($res);
    fhem("setreading $name snapshot $res");
 
  #-- status update
  }elsif( $event eq "status_update" ){
    my $cmd = '/opt/fhem/tapo/.venv/bin/python3 /opt/fhem/tapo/tapo_control_privacy.py; '
          . '/opt/fhem/tapo/.venv/bin/python3 /opt/fhem/tapo/tapo_control_light.py status; '
          . '/opt/fhem/tapo/.venv/bin/python3 /opt/fhem/tapo/tapo_control_alarm.py status; '
          . '/opt/fhem/tapo/.venv/bin/python3 /opt/fhem/tapo/tapo_control_detection.py status; '
          . '/opt/fhem/tapo/.venv/bin/python3 /opt/fhem/tapo/tapo_control_events.py events 300; ';
    system('/bin/sh', '-c', "($cmd) >/dev/null 2>&1 &");
  
  #-- privacy
  }elsif( $event =~ /^privacy\s+(on|off)$/){
    $event = $1;
    system("/opt/fhem/tapo/.venv/bin/python3 /opt/fhem/tapo/tapo_control_privacy.py $event >/dev/null 2>&1 &");
   
  #-- light
  }elsif($event =~ /^(led|light)\s+(on|off)$/){
    $event = $1;
    $val = $2;
    system("/opt/fhem/tapo/.venv/bin/python3 /opt/fhem/tapo/tapo_control_light.py $event $val >/dev/null 2>&1 &");
  }elsif($event =~ /^light_(intensity|duration)\s+(\d+)$/){
    $event = $1;
    $val = $2;
    $event =~ s/duration/time/;
    system("/opt/fhem/tapo/.venv/bin/python3 /opt/fhem/tapo/tapo_control_light.py $event $val >/dev/null 2>&1 &");
  }elsif($event =~ /^light_night\s+(ir|white|auto)$/){
    $event ="night";
    $val = $1;
    system("/opt/fhem/tapo/.venv/bin/python3 /opt/fhem/tapo/tapo_control_light.py $event $val >/dev/null 2>&1 &");
   
  #-- motor
  }elsif( $event =~ /^(left|right|up|down|calibrate|presets_get)$/){
    $event =~ s/_get//;
    system("/opt/fhem/tapo/.venv/bin/python3 /opt/fhem/tapo/tapo_control_motor.py $event >/dev/null 2>&1 &");
  }elsif( $event =~ /^(left|right|up|down|preset_goto|preset_delete)\s+(\d+)$/){
    $event = $1;
    $val = $2;
    $event =~ s/preset_//;
    system("/opt/fhem/tapo/.venv/bin/python3 /opt/fhem/tapo/tapo_control_motor.py $event $val >/dev/null 2>&1 &");
  }elsif( $event =~ /^preset_save\s+(\d+)\s+(.+)$/){
    $event = "save";
    $val = $1;
    $cmd = $2;
    system("/opt/fhem/tapo/.venv/bin/python3 /opt/fhem/tapo/tapo_control_motor.py $event $val $cmd >/dev/null 2>&1 &");
 
  #-- detection
  }elsif( $event =~ /^detection_(motion|person|pet|tamper|vehicle|linecrossing)\s+(\d+)$/){
    $event = $1;
    $val = $2;
    system("/opt/fhem/tapo/.venv/bin/python3 /opt/fhem/tapo/tapo_control_detection.py $event $val >/dev/null 2>&1 &");
 
  #-- alarm
   }elsif($event =~ /^alarm\s+(on|off)$/){
    $val = $1;
    system("/opt/fhem/tapo/.venv/bin/python3 /opt/fhem/tapo/tapo_control_alarm.py $val >/dev/null 2>&1 &");
  }elsif( $event =~ /^alarm_(light|sound)\s+(on|off)$/){
    $event = $1;
    $val = $2;
    system("/opt/fhem/tapo/.venv/bin/python3 /opt/fhem/tapo/tapo_control_alarm.py $event $val >/dev/null 2>&1 &");
  }elsif( $event =~ /^alarm_(volume|duration)\s+(.+)$/){
    $event = $1;
    $val = $2;
    system("/opt/fhem/tapo/.venv/bin/python3 /opt/fhem/tapo/tapo_control_alarm.py $event $val >/dev/null 2>&1 &");
  }
}
zum Setzen und
sub TapoReturnHandler($$$){
  my ($name,$group,$json) = @_;
 
  my $hash = $defs{$name};
  return if(!$hash);
 
  #Log 1,"obtained return to $name from Tapo group $group. json-result = $json";
 
  #-- JSON-String nach Perl-Hash wandeln
  if(!defined($json) || $json eq "") {
    readingsSingleUpdate($hash, "error", "empty json", 1) if $hash;
    return;
  }
  my $data;
  eval {
    $data = decode_json($json);
  };
  if($@ || ref($data) ne "HASH") {
    readingsSingleUpdate($hash, "error", "invalid json for group $group", 1);
    return;
  }
 
  #-- privacy
  if( $group eq "privacy" ){
    readingsBeginUpdate($hash);
    #-- Fehler-Rückgabe aus Python
    if(defined($data->{result})
      && $data->{result} eq "error"){
      my $msg = $data->{message} // "unknown error";
      readingsBulkUpdate($hash, "error", $msg);
      readingsBulkUpdate($hash, "state", "$group error: $msg");
    }else {
      my $state = $data->{privacy} // "";
      readingsBulkUpdate($hash,"privacy",$state);
      readingsBulkUpdate($hash, "error", "");
      readingsBulkUpdate($hash, "state", "privacy $state");
    }
    readingsEndUpdate($hash,1);
   
  #-- light
  }elsif($group eq "light") {
    readingsBeginUpdate($hash);
    #-- Fehler-Rückgabe aus Python
    if(defined($data->{result})
      && $data->{result} eq "error"){
      my $msg = $data->{message} // "unknown error";
      readingsBulkUpdate($hash, "error", $msg);
      readingsBulkUpdate($hash, "state", "$group error: $msg");
    }else {
      readingsBulkUpdate($hash, "light",           $data->{status})      if defined $data->{status};
      readingsBulkUpdate($hash, "light_intensity", $data->{intensity})   if defined $data->{intensity};
      readingsBulkUpdate($hash, "light_duration",  $data->{time})        if defined $data->{time};
      readingsBulkUpdate($hash, "light_remain",    $data->{time_remain}) if defined $data->{time_remain};
      readingsBulkUpdate($hash, "light_night",     $data->{night})       if defined $data->{night};
      readingsBulkUpdate($hash, "led",             $data->{led})         if defined $data->{led};
      readingsBulkUpdate($hash, "error", "");
      readingsBulkUpdate($hash, "state", "light ok");
    }
    readingsEndUpdate($hash, 1);
    
  #-- motor
  }elsif( $group eq "motor" ){
    readingsBeginUpdate($hash);
    #-- Fehler-Rückgabe aus Python
    if(defined($data->{result}) && $data->{result} eq "error") {
      my $msg = $data->{message} // "unknown error";
      readingsBulkUpdate($hash, "error", $msg);
      readingsBulkUpdate($hash, "state", "$group error: $msg");
    }else{
      my $action = $data->{action} // "";
      my $result = $data->{result} // "";
      my $motor_action = "";
      my $state = "";

      #-- motor_action zusammensetzen
      if($action =~ /^(left|right|up|down)$/) {
        my $value = $data->{value} // 0;
        $motor_action = "$action $value";
      }elsif($action =~ /^(goto|save|delete)$/) {
        my $preset = $data->{preset} // "";
        $motor_action = "$action $preset";
      }
      else {
        $motor_action = $action;
      }
      readingsBulkUpdate($hash, "motor_action", $motor_action);
      readingsBulkUpdate($hash, "motor_result", $result);
      $state = $motor_action || $result;

      #-- presets-Liste
      if($action eq "presets") {
        my $presets = $data->{presets};
        if(ref($presets) eq "HASH") {
          my @names = map { $presets->{$_} } sort { $a <=> $b } keys %{$presets};
          my $preset_list = join(",", @names);
          readingsBulkUpdate($hash, "motor_presets", $preset_list);
          $state = "presets: $preset_list";
        } else {
          readingsBulkUpdate($hash, "motor_presets", "");
          $state = "presets";
        }
      }
      readingsBulkUpdate($hash, "error", "");
      readingsBulkUpdate($hash, "state", $state);
    }
    readingsEndUpdate($hash, 1);
 
  #-- detection
  }elsif($group eq "detection") {
    readingsBeginUpdate($hash);
    #-- Fehler-Rückgabe aus Python
    if(defined($data->{result}) && $data->{result} eq "error") {
      my $msg = $data->{message} // "unknown error";
      readingsBulkUpdate($hash, "error", $msg);
      readingsBulkUpdate($hash, "state", "$group error: $msg");
    }else{
      #-- Fall 1: kompletter Status
      if(ref($data->{motion})       eq "HASH"
        || ref($data->{person})       eq "HASH"
        || ref($data->{vehicle})      eq "HASH"
        || ref($data->{pet})          eq "HASH"
        || ref($data->{tamper})       eq "HASH"
        || ref($data->{linecrossing}) eq "HASH") {
        foreach my $type (qw(motion person vehicle pet tamper linecrossing)) {
          next if ref($data->{$type}) ne "HASH";

          my $enabled = $data->{$type}{enabled};
          next if !defined $enabled;

          my $reading = "detection_$type";
          my $value;

          if($type eq "linecrossing") {
            $value = $enabled;
          } else {
            if($enabled eq "off") {
              $value = "off";
            } else {
              my $sens = $data->{$type}{sensitivity};
              $value = defined($sens) ? "on $sens" : "on";
            }
          }
          readingsBulkUpdate($hash, $reading, $value);
        }
        readingsBulkUpdate($hash, "error", "");
        readingsBulkUpdate($hash, "state", "detection status");

      #-- Fall 2: Rückgabe eines set-Befehls
      }elsif(defined $data->{action}) {
        my $action  = $data->{action} // "";
        my $enabled = $data->{enabled};
        my $value   = $data->{value};
        my $result  = $data->{result};
        my $reading = "detection_$action";
        my $onoff = defined($enabled) ? ($enabled ? "on" : "off") : "";

        if($action eq "linecrossing") {
          readingsBulkUpdate($hash, $reading, $onoff) if $onoff ne "";
        } else {
          my $reading_value;
          if($onoff eq "off") {
            $reading_value = "off";
          } else {
            $reading_value = "on";
            $reading_value .= " $value" if defined $value;
          }
          readingsBulkUpdate($hash, $reading, $reading_value) if $reading_value ne "";
        }
        my $state = $action;
        $state .= " $onoff" if $onoff ne "";
        $state .= " $value" if defined $value && $action ne "linecrossing" && $onoff eq "on";
        readingsBulkUpdate($hash, "error", "");
        readingsBulkUpdate($hash, "state", $state);
      }
    }
    readingsEndUpdate($hash, 1);
   
  #-- alarm
  }elsif($group eq "alarm") {
    readingsBeginUpdate($hash);
    #-- Fehler-Rückgabe aus Python
    if(defined($data->{result})
      && $data->{result} eq "error"){
      my $msg = $data->{message} // "unknown error";
      readingsBulkUpdate($hash, "error", $msg);
      readingsBulkUpdate($hash, "state", "alarm error: $msg");
    }else {
      readingsBulkUpdate($hash, "alarm",          $data->{status})   if defined $data->{status};
      readingsBulkUpdate($hash, "alarm_sound",    $data->{sound})    if defined $data->{sound};
      readingsBulkUpdate($hash, "alarm_light",    $data->{light})    if defined $data->{light};
      readingsBulkUpdate($hash, "alarm_duration", $data->{duration}) if defined $data->{duration};
      readingsBulkUpdate($hash, "alarm_volume",   $data->{volume})   if defined $data->{volume};
     readingsBulkUpdate($hash, "error", "");

      my $state = "";
      $state .= "alarm:$data->{status} " if defined $data->{status};
      $state .= "sound:$data->{sound} " if defined $data->{sound};
      $state .= "light:$data->{light} " if defined $data->{light};
      $state .= "vol:$data->{volume} " if defined $data->{volume};
      $state .= "dur:$data->{duration}" if defined $data->{duration};
      $state =~ s/\s+$//;
      readingsBulkUpdate($hash, "state", $state) if $state ne "";
    }
   readingsEndUpdate($hash, 1);

   #-- events
   }elsif( $group eq "events" ){
    readingsBeginUpdate($hash);
    #-- Fehler-Rückgabe aus Python
    if(defined($data->{result})
      && $data->{result} eq "error"){
      my $msg = $data->{message} // "unknown error";
      readingsBulkUpdate($hash, "error", $msg);
      readingsBulkUpdate($hash, "state", "$group error: $msg");
    }else {
      my $window = $data->{events_window} // "";
      my $start  = $data->{events_start}  // "";
      my $list   = $data->{events_list}   // "";

      readingsBulkUpdate($hash, "events_window", $window);
      readingsBulkUpdate($hash, "events_start",  $start);
      readingsBulkUpdate($hash, "events_list",   $list);
      readingsBulkUpdate($hash, "error", "");
      readingsBulkUpdate($hash, "state", "events updated");
    }
    readingsEndUpdate($hash,1);
  }
}
zum Auswerten der Returns kann ich die Kamera jetzt wunderbar steuern.
Die Python-Programme habe ich alle noch einmal grundlegend überarbeitet. Weil die relativ langsam laufen, erfolgt eine asychrone Rückmeldung an FHEM über einen REST-Call, FHEM wird also nicht blockiert. Alarm etc. lässt sich prima setzen, bei den Events muss man allerdings pollen - die werden über ONVIF nicht ausgegeben. Derzeit werden in FHEM jeweils die Events der letzten 5 Minuten angezeigt.

Jetzt könntet ihr mit dem Zeug erst einmal spielen. Die Doku im Wiki werde ich noch nachziehen, für heute habe ich allerdings genug  :P

LG

pah
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: Prof. Dr. Peter Henning am 21 März 2026, 17:41:48
Damit ich die Videos auch mit einem Browser ansehen kann, habe ich basierend auf einem älteren Thread
https://forum.fhem.de/index.php?topic=64081.0
auch noch eine Umsetzung auf andere Formate gebaut. Astrein, das Dummy-device kriegt jetzt auch noch ein stateFormat, das bei laufender Kamera einen direkten Link anbietet.

LG

pah
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: Prof. Dr. Peter Henning am 22 März 2026, 13:47:39
Nächste Ergänzung: Ich kann mir (jedenfalls so lange Platz auf der SD-Karte ist) in meinem FHEM-Device die Events des jeweiligen Tages anschauen. Die werden durchnummeriert, z.B.
Zitat28 10:21:49 2026-03-22 person
29 10:19:52 2026-03-22 person
30 09:39:25 2026-03-22 person
31 09:09:59 2026-03-22 pet
32 09:07:39 2026-03-22 person
33 09:04:51 2026-03-22 pet
34 08:47:17 2026-03-22 pet
35 08:43:59 2026-03-22 pet

Wenn ich dann eingebe "set TapoCam download 34", läuft ein Python-Skript los, prüft die Videos auf der SD-Karte. Lädt dasjenige runter, das automatisch bei diesem Event aufgenommen wurde. Wandelt das mit ffmpeg in eine MP4-Datei um. Und legt mir in das Reading "event_downloadlink" einen Link auf diese MP4-Datei. Diese wird natürlich in den Webspace von FHEMWEB verlinkt, so dass ich sie mit einem Mausklick ansehen kann.

Sieht dann so aus (natürlich habe ich das runterskaliert, die richtig hohe Auflösung wollte ich jetzt nicht posten)...

Scharfe Sache, endlich Katzenüberwachung...

LG

pah



Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: Prof. Dr. Peter Henning am 24 März 2026, 17:47:01
Ich habe jetzt die Dokumentation im FHEM Wiki https://wiki.fhem.de/wiki/Tapo_Kameras komplettiert. Da fehlt zwar noch etwas Prosa, mit der Beschreibung sollte aber auch jetzt schon jeder in der Lage sein, das Teil bei sich zum Laufen zu bekommen.

Die Skripte selber, ebenso wie die Perl-Funktionen zum Handling und das "Snapshot"-Skript habe ich jetzt auf github ausgelagert, Adresse siehe Wiki.

Es fehlt jetzt noch die Doku der Installation des RSTPtoWeb-Servers, mit dem man den Stream der Kamera direkt im Browser ansehen kann.

LG

pah
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: Gisbert am 24 März 2026, 19:29:54
Hallo pah,

vielen Dank für die Entwicklung der Programme und deiner Beschreibung im Wiki. Die Kamera ist ja bei Stiftung Warentest sehr gut, bzw. weit vorne bewertet - und dabei ja auch recht preiswert.

Ich hab noch Fragen zu Kamera-Events und deren Speicherung auf dem Fhem-Server. Dazu ist ist eine Speicherkarte (Empfehlung 16 GB) nötig. Können auch einzelne Bilder gespeichert werden und anschließend auf den Fhem-Server übertragen werden? Ist es praktikabel, direkt nach einem Event Bilder oder Videos zu übertragen und lässt sich das mit deinem Programm automatisiert durchführen - ich vermute ja, kann es aber ohne Kamera nicht beurteilen. Falls das geht, in welchem Zeitraum vermutest du die Speicherung, eher schnell im Sekundenbereich oder kann es dauern? Könnte die Kamera mit der Eventerkennung an der Haustür genutzt werden, um eine Meldung aufs Handy zu bekommen und zu sehen, wer da ist, möglichst bevor der- oder diejenige bereits den Rückzug angetreten hat? Das erfordert Push-Dienste, die bei mir schon vorhanden wären.

Viele Grüße Gisbert
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: Prof. Dr. Peter Henning am 24 März 2026, 21:15:41
Nein, so funktioniert das nicht. Die Kamera sendet zwar in Echtzeit Events an die App. Ich habe aber fast einen Tag lang versucht, einen Mechanismus zu finden, das abzugreifen - ohne Erfolg. Die Events, die man abgreifen kann, liegen also alle in der Vergangenheit, mit fertigen Clips auf der Karte. Auch einen Push-Mechanismus für Alarme gibt es nach gegenwärtigen Stand nicht.

Einzelne Bilder holt man aus dem Stream, ganz ohne SD-Karte. Die liegen also schon per App auf dem Smartphone, oder per Shellskript tapo_snapshot.sh  auf dem FHEM-Server, nicht auf der Kamera. Das kann man natürlich durch eine Türklingel triggern und dann das Bild verwenden. So mache ich das seit etlichen Jahren auch im DoorPi-Projekt.

LG

pah
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: andre07 am 05 April 2026, 22:42:08
Motion habe ich realisiert mit hilfe des PSNR Wert verglichen in einer kleinen script, den wert habe ich so lange runtergesetzt bis es gepaßt hat bei mir 18 für diese Kamera.PSNR mißt die Unterschiede zwischen zwei bildern pixel für Pixel.
Im terminal dann testen und anpassen mit /opt/fhem/tapo/.venv/bin/python3 -u /opt/fhem/tapo_motion.py
das script sieht so aus #!/opt/fhem/tapo/.venv/bin/python3
import subprocess
import time
import requests
import os

SNAPSHOT_SCRIPT = "/opt/fhem/tapo_snapshot.sh"
SNAPSHOT_PATH = "/opt/fhem/www/images/Tapo.jpg"
PREV_PATH = "/tmp/Tapo_prev.jpg"
FHEM_IP = "192.168.0.0"
FHEM_PORT = "8083"
COOLDOWN = 60
INTERVAL = 10

last_alert = 0

def take_snapshot():
    subprocess.run(["sudo", SNAPSHOT_SCRIPT], capture_output=True)

def images_differ():
    if not os.path.exists(PREV_PATH):
        return False
    subprocess.run(["convert", SNAPSHOT_PATH, "-resize", "320x180!", "/tmp/curr.jpg"], capture_output=True)
    subprocess.run(["convert", PREV_PATH, "-resize", "320x180!", "/tmp/prev.jpg"], capture_output=True)
    result = subprocess.run([
        "compare", "-metric", "PSNR",
        "/tmp/curr.jpg", "/tmp/prev.jpg",
        "/dev/null"
    ], capture_output=True, text=True)
    raw = result.stderr.strip()
    try:
        psnr = float(raw.split()[0])
        print(f"PSNR: {psnr}", flush=True)
        return psnr < 18
    except:
        print(f"Parse Fehler: '{raw}'", flush=True)
        return False

def send_alert():
    requests.get(
        f"http://{FHEM_IP}:{FHEM_PORT}/fhem",
        params={"cmd": "set telegram_andre sendImage /opt/fhem/www/images/Tapo.jpg Bewegung Tapo C210", "XHR": "1"}
    )

print("Starte Motionerkennung...", flush=True)

# Erstes Bild als Referenzwert
take_snapshot()
time.sleep(2)
subprocess.run(["cp", SNAPSHOT_PATH, PREV_PATH])
print("Referenzbild gespeichert", flush=True)

while True:
    try:
        # dann neues bild holen
        take_snapshot()
        time.sleep(2)

        # Vergleichen mit vorherigem Bild
        now = time.time()
        if images_differ() and (now - last_alert > COOLDOWN):
            last_alert = now
            print(f"Bewegung erkannt! {time.strftime('%H:%M:%S')}", flush=True)
            send_alert()

        # Aktuelles Bild als neues Referenzbild speichern
        subprocess.run(["cp", SNAPSHOT_PATH, PREV_PATH])

    except Exception as e:
        print(f"Fehler: {e}", flush=True)

    time.sleep(INTERVAL)
snapshot.sh ist das hier vorgestellte.
Fehlt mir nur noch wie ich auf der Steuerung der Kamera zugreifen kann die Anleitung hier funktioniert bei mir leider nicht.

Andre
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: Prof. Dr. Peter Henning am 06 April 2026, 06:13:51
Das kann man zwar so machen, habe ich in ähnlicher Weise schon 2016 in den "SmartHome Hacks" beschrieben, siehe Anlage.

Hat allerdings in der Form hier den wesentlichen Nachteil, dass auch jeder vom Wind bewegte Zweig eine Telegram-Nachricht auslöst. In meinem alten Beispiel habe ich mit ImageMagick ein Differenzbild erzeugt, das erspart den pixelweisen Vergleich. Nicht nur kann man da sehr flexibel eine Schwelle setzen, sondern vor allem eine Maske integrieren.

Der bessere Weg wäre jedoch, die integrierten Erkennungsfunktionen der Kamera für Menschen, Tiere, Fahrzeuge etc. zu nutzen und dafür die Kommunikation der Kamera mit der App zu belauschen. Es muss eigentlich möglich sein, die Benachrichtigungsadresse der Kamera umzubiegen. Die ersten Ansätze dafür gibt es in pytapo schon.

Der Link auf snapshot.sh ist tot.

ZitatAnleitung hier funktioniert bei mir leider nicht.
Das ist mir zu vage.

LG

pah

Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: andre07 am 13 April 2026, 22:18:56
Die Kamera läßt sich mit deinen dummy nicht bedienen keine snapshot bewegung nichts geht. In der app habe ich drittanbieter freigeschalten und in der credencial alles eingetragen.
Wenn ich ein script in der konsole aufrufe sudo ./tapo_control_light.py status
bekomme ich error Verbindung zur Kamera fehlgeschlagen: Invalid authentication data.(im dummy)
Internals:
  .FhemMetaInternals 1
  FUUID      69cd5087-f33f-c2a9-978d-b99012f30603bea0
  FVERSION  98_dummy.pm:0.256060/2022-02-01
  NAME      TapoCamP
  NR        704
  STATE      <p style="text-align:left;font-weight:normal">
stream_stopped
<br/><br/><a href="http://192.168.178.78:8084/fhem/images/Tapo.jpg">Tapo.jpg</a> (728)
<br/><a href="">TapoClip.mp4</a> ()
</p>
  TYPE      dummy
  eventCount 60
  .attraggr:
  .attrminint:
  OLDREADINGS:
  READINGS:
    2026-04-13 22:06:47  error          Verbindung zur Kamera fehlgeschlagen: Invalid authentication data
    2026-04-13 21:54:39  snapshot        error creating image
    2026-04-13 22:06:47  state          ready
    2026-04-01 23:55:59  test            ok
Attributes:
  DbLogExclude .*
  devStateIcon stream_running:it_camera@green stream_stopped:it_camera@black
  readingList snapshot motor_action motor_result motor_presets error
  room      Kamera
  setList    getPhoto:noArg getClip getEvents privacy:on,off light:on,off light_intensity light_duration light_night:ir,white,auto led:on,off moveLeft moveRight moveUp moveDown calibrate:noArg preset_goto:1,2,3,4,5,6,7,8 preset_save preset_delete getPresets detection_motion detection_person detection_pet detection_tamper detection_vehicle detection_linecrossing alarm:on,off alarm_light:on,off alarm_sound:on,off alarm_duration alarm_volume:low,medium,high
  stateFormat {my $x=ReadingsAge("TapoCamP","snapshot","");
 my $v=ReadingsVal("TapoCamP","event_dl_link","");
 my $w=ReadingsAge("TapoCamP","event_dl","");
 my $y=(ReadingsVal("TapoCamP","privacy","") eq "off")?"<a href=\"http://192.168.178.78:8084/pages/player/webrtc/stream1/0\">\nstream_running\n</a>":"stream_stopped";
 sprintf("<p style=\"text-align:left;font-weight:normal\">\n%s\n<br/><br/><a href=\"http://192.168.178.78:8084/fhem/images/Tapo.jpg\">Tapo.jpg</a> (%s)\n<br/><a href=\"%s\">TapoClip.mp4</a> (%s)\n</p>",$y,$x,$v,$w)}
  webCmd    getPhoto:moveLeft:moveRight:moveUp:moveDown
Vieleicht liegt es an meinen Modell C210.Kamerakonto wurde auch eingerichtet und dies eingetragen in credencials
Nachtrag bei port 8085 und 8083 bekomme ich diesen Fehler:   
"Verbindung zur Kamera fehlgeschlagen: Temporary Suspension: Try again in 1799 seconds"

Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: Prof. Dr. Peter Henning am 14 April 2026, 08:28:37
Die Snapshots und die Bedienung sind zwei komplett unterschiedliche Zugänge. Für den Zugang zum Stream, aus dem die Snapshots geholt werden, benötigt man das Kamera-Passwort und den Kamera-Account. Für den Zugang zur Bedienung benötigt man das Cloud-Passwort.

Wenn beide nicht funktionieren, besteht ein generelles Rechteproblem.

Das muss man auch alles nicht in FHEM ausprobieren, sondern zuerst sollte das alles durch den direkten Aufruf der Python-Skripte (für die Bedienung) bzw. des Bash-Skriptes (für die Snapshots) getestet werden. Dort gibt es auch entsprechende Fehlermeldungen.

Der Authentifizierungsfehler bei den Python-Skripten ist übrigens ein klarer Hinweis darauf, dass die credentials _eben nicht_ richtig eingetragen wurden. Nach mehr als einem Fehlversuch sperrt die Kamera den Zugang dann für 30 Minuten, das hat _gar nichts_ mit FHEM zu tun. Ganz im Gegenteil ist es vollkommen richtig, dass diese Fehlermeldung der Kamera dann an FHEM weitergeleitet wird.

LG

pah
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: andre07 am 19 April 2026, 20:59:54
Danke für die Antwort. Was kommt nun in die  crendentials .py rein ?

# /opt/fhem/tapo/tapo_credentials.py

CAMERA_IP = "192.168.178.45"
USERNAME = "andre@gamil.com" account bei tapo username
PASSWORD = "passwortvonderkamera"

FHEMIP = "192.168.178.78"
FHEMPORT = "8084"
FHEMCSRF = "static csrf"
FHEMDEVICE = "TapoCamP"

Andre
Titel: Aw: FHEM-Integration von Tapo Kameras
Beitrag von: Prof. Dr. Peter Henning am 19 April 2026, 22:04:06
Zitat von: andre07 am 19 April 2026, 20:59:54PASSWORD = "passwortvonderkamera"
In der Anleitung steht ganz klar CLOUD-PASSWORT

pah