FHEM Forum

FHEM - Hausautomations-Systeme => Sonstige Systeme => Thema gestartet von: Prof. Dr. Peter Henning am 23 Januar 2026, 15:35:12

Titel: Katzenklo mit Internetanschluss
Beitrag von: Prof. Dr. Peter Henning am 23 Januar 2026, 15:35:12
Nein, das ist keine Frage, und keine Umfrage - und nur im Anfängerbereich, weil hier fast jeder mitliest.

Wir haben jetzt auch die Katze automatisiert, mit einem automatischen Katzenklo https://www.amazon.de/dp/B0FDGGK194

Die Dinger haben seit der Pandemiezeit insbesondere in chinesischen Hochhaussiedlungen extrem an Beliebtheit gewonnen, und sind inzwischen auch in Europa auf dem Vormarsch. Leider, wie oft bei chinesischer Massenware, mit einer Schnittstelle zur Tuya-Cloud.

Das könnte man zwar auf Tasmota umflashen, allerdings habe ich dort kein entsprechendes Device gefunden.

Anscheinend gibt es aber für Home Assistant eine Lösung, die ohne dieses Umflashen auskommt. Hat jemand damit Erfahrung?

LG

pah



Titel: Aw: Katzenklo mit Internetanschluss
Beitrag von: Gisbert am 23 Januar 2026, 20:27:17
Hallo pah,

es gibt in Fhem eine lokale Tuya-Anwendung: https://forum.fhem.de/index.php?topic=127441.0 (https://forum.fhem.de/index.php?topic=127441.0)
Ich hab einen Luftentfeuchter und 2 schaltbare Steckdosen mit Leistungsmessung damit laufen. Beabsichtigt hatte ich die Integration des Luftbefeuchters in Fhem nicht, da ich beim Kauf davon noch nichts wusste.
Es funktioniert alles. Zwischenzeitlich hatte ich noch 2 schaltbare Steckdosen gekauft. Ob ich die Integration von Tuya in Fhem empfehlen kann - eher nicht. Es ist doch arg kompliziert. Jedenfalls plane ich keine weiteren Geräte mit Tuya anzuschaffen.

Viele Grüße Gisbert
Titel: Aw: Katzenklo mit Internetanschluss
Beitrag von: Prof. Dr. Peter Henning am 24 Januar 2026, 04:13:37
Danke für den Hinweis, ich hatte schon damit geliebäugelt, dieses pytuya selbst anzubinden.
Zitat von: Gisbert am 23 Januar 2026, 20:27:17Jedenfalls plane ich keine weiteren Geräte mit Tuya anzuschaffen.
Na ja, wir leben im 21. Jahrhundert. Da gibt es manche Sachen nur aus China mit der Tuya-Cloud...

Und wenn ich das mit der werbeverseuchten Mist-Cloud von BSH (Bosch-Siemens-Hausgeräte) vergleiche, oder mit der neuen ebenfalls total werbeverseuchten integrierten Verkaufs- und Ladeapp von Shell: Da ist Tuya eindeutig besser, die betreiben sogar eigene Data-Center in unterschiedlichen Weltregionen.

Nachdem ich mal in China gearbeitet habe, gibt es da nur so ein kleines rotes Warnlicht in meiner Wahrnehmung, wenn ich Software aus chinesischer Quelle verwende...

LG

pah
Titel: Aw: Katzenklo mit Internetanschluss
Beitrag von: betateilchen am 24 Januar 2026, 08:57:17
Elektronik rausschmeissen und die Steuerung mit ESP nachbauen?

Nur so eine Idee, ich habe die Anforderung nicht.
Titel: Aw: Katzenklo mit Internetanschluss
Beitrag von: Prof. Dr. Peter Henning am 24 Januar 2026, 14:51:13
Zitat von: betateilchen am 24 Januar 2026, 08:57:17Elektronik rausschmeissen und die Steuerung mit ESP nachbauen?
Gewichtssensoren, Zeitsteuerung der Bewegung => sehr hoher Aufwand...

Ich mache den Thread jetzt hier dicht, habe das Ganze mit diesem tuya-fhempy so halb zum Laufen bekommen.

LG

pah
Titel: Aw: Katzenklo mit Internetanschluss - De-clouding von Tuya-Geräten
Beitrag von: Prof. Dr. Peter Henning am 25 März 2026, 15:12:04
Doch noch einmal geöffnet, denn jetzt habe ich eine gut funktionierende Lösung.

Vorab: Der Zugang mit fhempy hat immer nur halb geklappt. Offenbar wird dieses Projekt nicht mehr richtig gepflegt, und die ganze Idee der schlecht dokumentierten "Plugins" halte ich für höchst fragwürdig.

Also habe ich mit Hilfe eines anderen Github-Projektes https://github.com/jasonacox/tinytuya
ein lokales Python-Kontrollskript geschaffen, das die Katzentoilette nicht nur auslesen, sondern wunderbar steuern kann.

Das Konzept ist dasselbe, wie bei den Tapo Sicherheitskameras: FHEM ruft Python-Skripte auf, diese melden asynchron an FHEM zurück. Das ist auch für diejenigen, die keine Katzentoilette haben, ein gangbarer Weg, neue Tuya-Geräte zu integrieren. Die werden dann eben cloudfrei betrieben.

Gesteuert wird das über ein Dummy-device
defmod TuyaToilet dummy
attr TuyaToilet setList update_status:noArg clean:noArg empty:noArg child_lock:on,off smart_clean:on,off auto_deodorizer:on,off odourless:on,off induction_delay induction_interval
ein DOIF
defmod TuyaToilet.N DOIF ([TuyaToilet:state] ne "ready") ()\
({TuyaToiletHandler("TuyaToilet","$EVENT")})\
(setreading TuyaToilet state ready)\
\
DOELSEIF\
([+00:05:00])\
({TuyaToiletHandler("TuyaToilet","update_status")})
und ein paar Perl-Funktionen
###############################################################################
#
#  TuyaToiletHandler
#
###############################################################################

sub TuyaToiletHandler($$) {
  my ($name, $event) = @_;

  my $hash = $defs{$name};
  return if(!$hash);

  if(!defined($event) || $event eq '') {
    readingsSingleUpdate($hash, 'error', 'missing event', 1);
    return;
  }

  $event =~ s/^\s+|\s+$//g;

  # Schutz: komplette Reading-/Eventlisten ignorieren
  # typische FHEM-Events enthalten ":" oder ","
  if($event =~ /,|:\s/) {
    Log 3, "TuyaToiletHandler($name): ignored non-command event [$event]";
    return;
  }

  my $python = '/opt/fhem/tuya/.venv/bin/python3';
  my $script = '/opt/fhem/tuya/tuya_control.py';
  my $cmd;

  my ($action, $value) = split(/\s+/, $event, 2);
  $action = '' if !defined($action);
  $value  = '' if !defined($value);

  if ($action eq 'update_status') {
    $cmd = qq($python $script status >/dev/null 2>&1 &);

  } elsif ($action eq 'clean') {
    $cmd = qq($python $script clean >/dev/null 2>&1 &);

  } elsif ($action eq 'empty') {
    $cmd = qq($python $script empty >/dev/null 2>&1 &);

  } elsif ($action eq 'child_lock') {
    $value = 'off' if $value eq '';
    $cmd = qq($python $script child_lock $value >/dev/null 2>&1 &);

  } elsif ($action eq 'smart_clean') {
    $value = 'off' if $value eq '';
    $cmd = qq($python $script smart_clean $value >/dev/null 2>&1 &);

  } elsif ($action eq 'auto_deodorizer') {
    $value = 'off' if $value eq '';
    $cmd = qq($python $script auto_deodorizer $value >/dev/null 2>&1 &);

  } elsif ($action eq 'odourless') {
    $value = 'off' if $value eq '';
    $cmd = qq($python $script odourless $value >/dev/null 2>&1 &);

  } elsif ($action eq 'induction_delay') {
    if($value eq '') {
      readingsSingleUpdate($hash, 'error', 'missing value for induction_delay', 1);
      return;
    }
    $cmd = qq($python $script induction_delay $value >/dev/null 2>&1 &);

  } elsif ($action eq 'induction_interval') {
    if($value eq '') {
      readingsSingleUpdate($hash, 'error', 'missing value for induction_interval', 1);
      return;
    }
    $cmd = qq($python $script induction_interval $value >/dev/null 2>&1 &);

  } else {
    readingsSingleUpdate($hash, 'error', "unknown event: $event", 1);
    return;
  }

  system($cmd);
  return;
}

###############################################################################
#
#  TuyaReturnHandler
#
###############################################################################

sub TuyaReturnHandler($$$) {
  my ($name, $command, $json) = @_;

  my $hash = $defs{$name};
  return if(!$hash);

  if(!defined($json) || $json eq '') {
    readingsSingleUpdate($hash, 'error', 'empty json', 1);
    return;
  }

  my $data;
  eval {
    $data = decode_json($json);
  };
  if($@ || ref($data) ne 'HASH') {
    readingsSingleUpdate($hash, 'error', "invalid json for command $command", 1);
    return;
  }

  # --- Fehler-Rückgabe aus Python
  if(defined($data->{result}) && $data->{result} eq 'error') {
    my $msg = $data->{message} // 'unknown error';
    readingsBeginUpdate($hash);
    readingsBulkUpdate($hash, 'error', $msg);
    readingsBulkUpdate($hash, 'last_command', $command);
    readingsBulkUpdate($hash, 'last_command_result', 'error');
    readingsBulkUpdate($hash, 'state', 'error');
    readingsEndUpdate($hash, 1);
    return;
  }

  my %map = (
    '6'   => 'cat_weight',
    '7'   => 'excretion_times_day',
    '8'   => 'excretion_time_day',
    '17'  => 'deodorization',
    '22'  => 'fault',
    '101' => 'clean',
    '102' => 'empty',
    '103' => 'trash_status',
    '104' => 'monitoring',
    '105' => 'induction_clean',
    '106' => 'clean_time',
    '107' => 'clean_time_switch',
    '108' => 'clean_taste',
    '109' => 'clean_taste_switch',
    '110' => 'not_disturb',
    '111' => 'clean_notice',
    '112' => 'toilet_notice',
    '113' => 'net_notice',
    '114' => 'child_lock',
    '115' => 'calibration',
    '116' => 'unit',
    '117' => 'induction_delay',
    '118' => 'induction_interval',
    '119' => 'store_full_notify',
    '120' => 'odourless',
    '121' => 'smart_clean',
    '122' => 'not_disturb_switch',
    '123' => 'usage_times',
    '124' => 'capacity_calibration',
    '125' => 'sand_surface_calibration',
    '126' => 'auto_deodorizer',
    '127' => 'detection_sensitivity',
    '128' => 'number',
    '129' => 'lbs',
    '130' => 'deport_mode',
  );

  # --------------------------------------------------
  # status
  # --------------------------------------------------
  if($command eq 'status') {
    my $dps = $data->{dps};

    if(ref($dps) ne 'HASH') {
      readingsSingleUpdate($hash, 'error', 'status result without dps', 1);
      return;
    }

    readingsBeginUpdate($hash);
    readingsBulkUpdate($hash, 'error', 'none');
    readingsBulkUpdate($hash, 'last_command', 'status');
    readingsBulkUpdate($hash, 'last_command_result', 'ok');

    # --- rohe DPS als normale Readings schreiben
    foreach my $dp (sort { $a <=> $b } keys %{$dps}) {
      my $reading = exists $map{$dp} ? $map{$dp} : sprintf("dp_%03d", $dp);
      my $orig    = $dps->{$dp};
      my $value   = $orig;

      if(ref($orig) =~ /Boolean/) {
        $value = $orig ? 'on' : 'off';
      } elsif(ref($orig)) {
        eval {
          $value = encode_json($orig);
        };
        $value = "$value";
      }

      readingsBulkUpdate($hash, $reading, $value);
    }

    # --- virtuelle Readings ableiten
    my $cat_weight      = exists $dps->{'6'}   ? $dps->{'6'}   : 0;
    my $excretion_count = exists $dps->{'7'}   ? $dps->{'7'}   : 0;
    my $deodorization   = exists $dps->{'17'}  ? $dps->{'17'}  : 0;
    my $fault           = exists $dps->{'22'}  ? $dps->{'22'}  : 0;
    my $clean           = exists $dps->{'101'} ? $dps->{'101'} : 0;
    my $empty           = exists $dps->{'102'} ? $dps->{'102'} : 0;
    my $trash_status    = exists $dps->{'103'} ? $dps->{'103'} : '';
    my $child_lock      = exists $dps->{'114'} ? $dps->{'114'} : 0;

    my $weight_num = 0;
    $weight_num = $cat_weight if(defined($cat_weight) && $cat_weight =~ /^\d+$/);

    my $excretion_num = 0;
    $excretion_num = $excretion_count if(defined($excretion_count) && $excretion_count =~ /^\d+$/);

    my $fault_num = 0;
    $fault_num = $fault if(defined($fault) && $fault =~ /^\d+$/);

    my $occupied = 0;
    my $cat_size = 'none';

    # Gewichtslogik:
    # < 2000 g => kein Tier / typischer Sackwechselwert etc.
    # 2000..3999 g => leichte Katze
    # >= 4000 g => schwere Katze
    if($weight_num >= 4000) {
      $occupied = 1;
      $cat_size = 'large';
    } elsif($weight_num >= 2000) {
      $occupied = 1;
      $cat_size = 'small';
    }

    my $fault_active       = $fault_num != 0 ? 1 : 0;
    my $cleaning_active    = $clean ? 1 : 0;
    my $emptying_active    = $empty ? 1 : 0;
    my $deodorizing_active = $deodorization ? 1 : 0;
    my $child_locked       = $child_lock ? 1 : 0;
    my $recent_use         = $excretion_num > 0 ? 1 : 0;

    my $weight_kg = '';
    if($weight_num > 0) {
      $weight_kg = sprintf("%.3f", $weight_num / 1000);
    }

    readingsBulkUpdate($hash, 'virtual_occupied',           $occupied ? 'yes' : 'no');
    readingsBulkUpdate($hash, 'virtual_cat_size',           $cat_size);
    readingsBulkUpdate($hash, 'virtual_fault',              $fault_active ? 'yes' : 'no');
    readingsBulkUpdate($hash, 'virtual_fault_code',         $fault_num);
    readingsBulkUpdate($hash, 'virtual_cleaning_active',    $cleaning_active ? 'yes' : 'no');
    readingsBulkUpdate($hash, 'virtual_emptying_active',    $emptying_active ? 'yes' : 'no');
    readingsBulkUpdate($hash, 'virtual_deodorizing_active', $deodorizing_active ? 'yes' : 'no');
    readingsBulkUpdate($hash, 'virtual_child_locked',       $child_locked ? 'yes' : 'no');
    readingsBulkUpdate($hash, 'virtual_recent_use',         $recent_use ? 'yes' : 'no');
    readingsBulkUpdate($hash, 'virtual_usage_today',        $excretion_num);
    readingsBulkUpdate($hash, 'virtual_weight_kg',          $weight_kg);

    # optional hilfreiche Zeitstempel
    if($occupied) {
      readingsBulkUpdate($hash, 'virtual_last_occupied', TimeNow());
    }
    if($recent_use) {
      readingsBulkUpdate($hash, 'virtual_last_usage_day_seen', TimeNow());
    }

    # trash_status vorsichtig behandeln, solange Semantik nicht sicher bekannt ist
    my $bin_full = 'unknown';
    if(defined($trash_status) && $trash_status ne '') {
      if($trash_status =~ /full|voll/i) {
        $bin_full = 'yes';
      } elsif($trash_status =~ /^(0|false|off)$/i) {
        $bin_full = 'no';
      }
    }
    readingsBulkUpdate($hash, 'virtual_bin_full', $bin_full);

    # --- virtuelle Betriebsart + state
    my $virtual_mode = 'ready';
    my $state        = 'ready';

    if($fault_active) {
      $virtual_mode = 'fault';
      $state        = "fault_$fault_num";
    } elsif($occupied) {
      $virtual_mode = 'occupied';
      if($cat_size eq 'large') {
        $state = "occupied_large_${weight_num}g";
      } elsif($cat_size eq 'small') {
        $state = "occupied_small_${weight_num}g";
      } else {
        $state = "occupied_${weight_num}g";
      }
    } elsif($emptying_active) {
      $virtual_mode = 'emptying';
      $state        = 'emptying';
    } elsif($cleaning_active) {
      $virtual_mode = 'cleaning';
      $state        = 'cleaning';
    } elsif($deodorizing_active) {
      $virtual_mode = 'deodorizing';
      $state        = 'deodorizing';
    } elsif($child_locked) {
      $virtual_mode = 'child_lock';
      $state        = 'child_lock';
    } elsif($weight_num > 0 && $weight_num < 2000) {
      # typischer Sackwechsel-/Technikwert sichtbar machen
      $virtual_mode = 'idle';
      $state        = "idle_weight_${weight_num}g";
    } else {
      $virtual_mode = 'ready';
      $state        = 'ready';
    }

    readingsBulkUpdate($hash, 'virtual_mode', $virtual_mode);
    readingsBulkUpdate($hash, 'state', $state);

    readingsEndUpdate($hash, 1);
    return;
  }

  # --------------------------------------------------
  # erfolgreiche Schreibkommandos
  # --------------------------------------------------
  if(
       $command eq 'clean'
    || $command eq 'empty'
    || $command eq 'child_lock'
    || $command eq 'smart_clean'
    || $command eq 'auto_deodorizer'
    || $command eq 'odourless'
    || $command eq 'induction_delay'
    || $command eq 'induction_interval'
  ) {
    readingsBeginUpdate($hash);
    readingsBulkUpdate($hash, 'error', 'none');
    readingsBulkUpdate($hash, 'last_command', $command);
    readingsBulkUpdate($hash, 'last_command_result', 'ok');
    readingsEndUpdate($hash, 1);

    TuyaToiletHandler($name, 'update_status');
    return;
  }

  readingsSingleUpdate($hash, 'error', "unknown command: $command", 1);
}

Den Python-Kram habe ich mal hier angehängt, bei Gelegenheit gibt es dazu noch ein Wiki.

LG

pah
Titel: Aw: Katzenklo mit Internetanschluss
Beitrag von: Gisbert am 25 März 2026, 18:18:00
Hallo pah,

das ist ja Klasse, dass du doch noch einen Weg gefunden hast. Ich habe einen Aktobis-Entfeuchter, den ich technisch und von der Bedienbarkeit sehr gut finde. Ich hab dieses Gerät einem Bekannten empfohlen, der sich aber für die initiale Anmeldung partout nicht in der Tuya-(oder wie sie auch immer heißt)-Cloud anmelden will.

Ist bei deiner Lösung eine initiale Anmeldung in der Tuya...-Cloud noch nötig?

Viele Grüße Gisbert
Titel: Aw: Katzenklo mit Internetanschluss
Beitrag von: Prof. Dr. Peter Henning am 25 März 2026, 19:11:18
Zitat von: Gisbert am 25 März 2026, 18:18:00Ist bei deiner Lösung eine initiale Anmeldung in der Tuya...-Cloud noch nötig?
Leider ja. Der Grund ist, dass man nur auf diese Weise an einen lokalen Key kommt, der die Kommunikation von FHEM mit dem Device verschlüsselt.

Es ist also nicht nur die Anmeldung des Devices in der Tuya App nötig, sondern zusätzlich ein Tuya Developer Account. Die genaue Beschreibung der Vorgehensweise findet man hier: https://github.com/jasonacox/tinytuya/blob/master/README.md

Also leider nichts, was man einem Laien empfehlen könnte.

Allerdings liefert das im Endeffekt tatsächlich etwas, das ein solches Tuya-Device komplett von der Cloud isolieren kann. Dazu muss man nur die Firewall für dieses Device dicht machen und es eventuell aus der Tuya-App löschen. Der letzte Schritt ist aber gefährlich, bei manchen Geräten wird dann auch der Local Key gelöscht. Muss man ausprobieren und eventuell wieder rückgängig machen. Auch der Wechsel in ein anderes WLAN kann eventuell den Local Key zerstören.


Für andere Tuya-Geräte kann ich nur empfehlen, die Vorgehensweise in dem oben genannten README zu befolgen. Wenn man erstmal den Local Key hat, die Datapoints abgerufen und vielleicht noch mit Hilfe des Tuya-Cloud-Accounts zugeordnet hat, ist das ein stabiles System.

LG

pah
Titel: Aw: Katzenklo mit Internetanschluss
Beitrag von: TheTrumpeter am 30 März 2026, 07:44:44
Zitat von: Prof. Dr. Peter Henning am 25 März 2026, 19:11:18Für andere Tuya-Geräte kann ich nur empfehlen, die Vorgehensweise in dem oben genannten README zu befolgen. Wenn man erstmal den Local Key hat, die Datapoints abgerufen und vielleicht noch mit Hilfe des Tuya-Cloud-Accounts zugeordnet hat, ist das ein stabiles System.
Worin genau liegt der Unterschied zum Betreiben über fhempy_local?
Da wird auch initial der Schlüssel mittels Developer-Acocunt bezogen und danach läuft es im Wesentlichen ohne Cloud.
Titel: Aw: Katzenklo mit Internetanschluss
Beitrag von: Prof. Dr. Peter Henning am 30 März 2026, 08:59:22
Es ist aus meiner Sicht nicht transparent, was fhempy dann überhaupt noch macht, und wieso zum Betrieb lokaler Geräte überhaupt "Plugins" für fhempy nötig sind.

Die Python-Skripte, die ich für Tuya- und Tapo-Geräte geschrieben habe, funktionieren vollkommen autonom. Mit dem (in beiden Fällen identischen) Kommunikationskonzept fungieren sie als Erweiterung von FHEM, um den Begriff "Plugin" hier zu vermeiden.

Wir leben (noch) in einem freien Land, und jeder kann das verwenden, was ihm gefällt. Aber ich werde das weiter in diese Richtung steuern:

- Ggf. "abholen" von Cloud-Credentials irgendwo
- Lokale und autonome Programme, die asynchron mit FHEM kommunizieren.

LG

pah
Titel: Aw: Katzenklo mit Internetanschluss
Beitrag von: TheTrumpeter am 30 März 2026, 10:27:41
Zitat von: Prof. Dr. Peter Henning am 30 März 2026, 08:59:22Es ist aus meiner Sicht nicht transparent, was fhempy dann überhaupt noch macht
Ich gebe Dir insofern Recht, dass fhempy irgendwie "ungepflegt" ausschaut und es - abgesehen von der Selbsthilfe durch die User - keinerlei Support zu geben scheint.

Zitat von: Prof. Dr. Peter Henning am 30 März 2026, 08:59:22Ggf. "abholen" von Cloud-Credentials irgendwo
Das ist mit dem Tuya-Zeug aktuell die größte Schwäche: Die Cloud-Credentials aka "Local Key" gibt's nur mit aktivem Developer-Account. Auch wenn's den nach Kontakt mit dem Support immer wieder gratis gibt, ist es doch mühsam neue Geräte nicht "sofort" einbinden zu können, sondern ggf. erst den Developer-Account (re-) aktivieren zu müssen.

Zitat von: Prof. Dr. Peter Henning am 30 März 2026, 08:59:22Die Python-Skripte, die ich für Tuya- und Tapo-Geräte geschrieben habe, funktionieren vollkommen autonom. Mit dem (in beiden Fällen identischen) Kommunikationskonzept fungieren sie als Erweiterung von FHEM, um den Begriff "Plugin" hier zu vermeiden.
Bisher habe ich immer vermieden externe "Bastellösungen" an FHEM anzubinden sofern es FHEM-integrierte Lösungen gibt... aber mal sehen wie's mit dem Tuya-Zeug weitergeht.
Titel: Aw: Katzenklo mit Internetanschluss
Beitrag von: Prof. Dr. Peter Henning am 30 März 2026, 17:02:20
Zitat von: TheTrumpeter am 30 März 2026, 10:27:41Bisher habe ich immer vermieden externe "Bastellösungen" an FHEM anzubinden
Den Begriff der "Bastellösung" weise ich zurück, das ist polemischer Unsinn  >:(  >:(

Wir sind in FHEM schon immer auf Reverse Engineering angewiesen gewesen, man denke an HomeMatic.

In solchen Paketen Dritter, wie tinytuya, pytapo oder carconnectivity, steckt erhebliche und sehr ernsthafte Entwicklungsarbeit, die mit anderen geteilt wird. Das ist das zentrale Paradigma von FLOSS.

Zitat von: TheTrumpeter am 30 März 2026, 10:27:41Die Cloud-Credentials aka "Local Key"
Nix "aka". Der Local Key ist zwar nur aus der Cloud zu bekommen, aber dann unabhängig davon. Insbesondere könnte man das betreffende Tuya-Gerät auch komplett vom Internetzugang trennen.

pah