[TUTO] piloter une caméra Tp-link Tapo

N’hésitez pas à mettre en commentaire les résultats de vos essais.
Ce tuto est un peu une mise en forme de ce que j’ai trouvé sur le forum. Par exemple ici :

Et de mes propres essais.
Le code est inspiré de celui de @Nostromo42

Ce tuto nécessite l’installation du plugin Script.
Comment piloter une caméra Tapo ?
Il existe une bibliothèque python qui permet de piloter la caméra. Très complète, elle permet de faire la plupart des actions que vous pouvez faire avec l’appli Tapo.

Installation de pip :
Si vous ne l’avez pas encore installez pip.
sudo apt install python3-pip
EDIT : Attention, voir dans les derniers messages celui de PyToon 24 novembre 2023:

  • installer pytapo dernière version aujourd’hui 3.3.18*
  • installer python récent j’ai installé python 3.9*

Chargement de la bibliothèque :
Via ssh, par exemple via putty tapez la ligne suivante :

sudo python3 -m pip install pytapo

A vérifier, mais j’ai l’impression que si on ne met pas le sudo, la bibliothèque n’est disponible que pour l’utilisateur en cours lors de l’installation et donc pas pour jeedom.
Dans jeedom, dans le plugin script
Créez une nouvelle action de type script :
Cliquez sur « nouveau »
image
Donnez un nom à votre script
image

ATTENTION l’extension PY doit être en majuscule.

Puis, copiez ce code dans la fenêtre d’édition qui s’ouvre :

Attention, erreur classique : pas de blanc derrière #

Remplacez les XXXXX par vos valeurs en laissant les guillemets
utilisateur : mdp que vous aurez ajouté via l’application Tapo (via votre smartphone).
Pour les C100, C200, il faut bien utiliser le « login du compte local » ou « admin » ainsi que le « mot de pass local » de l’application tapo. Utilisateur et mdp que vous aurez donc ajouté via l’application Tapo (via votre smartphone) en faisant mdp/utiisateur que l’on donne dans l’ap Tapo :
roue dentée / Réglage avancé / compte de la caméra
Pour la C310, il faut utiliser le login « admin » et le « mot de passe local » du compte application tapo
Pour la C210, il faut utiliser le login « admin » et le "mot de passe du compte TPLINK " (le compte principal)

Host : l’IP de votre caméra.

#!/usr/bin/env python3

from pytapo import Tapo
import sys
  
user="xxxxxxx" # user you set in Advanced Settings -> Camera Account
password="xxxxxxxxxxxx" # password you set in Advanced Settings -> Camera Account
host="xxxxxxxxxxxx" # ip of the camera, example: 192.168.1.52

def  moveX(x) :
    tapo2=Tapo(host, user, password)
    res ={}
    try :
        res = tapo2.moveMotor(x,0)
    except Exception :
        print("Caméra en butée (X)")
        res["error_code"] =-1
    if(res.get("error_code")==0) :
        return 1
    else :
        return -1

moveX(sys.argv[1])

Donnez un nom à votre commande

image

Donnez une valeur de déplacement en paramètre (ici 10 )

image

image

Vous devriez voir votre caméra tourner si elle n’est pas déjà en limite de rotation.
Le paramètre peut être une commande info :

D’autres fonctions que vous pouvez utiliser :

moveY (inclinaison de la caméra)

#!/usr/bin/env python3

from pytapo import Tapo
import sys
  
user="xxxxxxx"
password="xxxxxxxx"
host="xxxxxxxxxxxx"

def  moveY(y) :

    tapo2=Tapo(host, user, password)
    res ={}
    try :
        res = tapo2.moveMotor(0,y)
    except Exception :
        print("Caméra en butée (Y)")
        res["error_code"] =-1

    if(res.get("error_code")==0) :
        return 1
    else :
        return -1

moveY(sys.argv[1])

moveXY (déplacement dans les deux sens à la fois)

#!/usr/bin/env python3

from pytapo import Tapo
import sys
  
user="xxxxxx"
password="xxxxxxxxxx"
host="xxxxxxxxx"

def  moveY(x,y) :

    tapo2=Tapo(host, user, password)
    res ={}
    try :
        res = tapo2.moveMotor(x,y)
    except Exception :
        print("Caméra en butée (Y)")
        res["error_code"] =-1

    if(res.get("error_code")==0) :
        return 1
    else :
        return -1

moveY(sys.argv[1],sys.argv[2])

exemple d’appel :

ou en utilisant des virtuels donnant le déplacement à réaliser :

setPreset (aller à une position présélectionnée)
A vous de voir si vous ajoutez les try exept. Là, je ne les ai pas mis
Passez en paramètre le n° de la présélection
image

#!/usr/bin/env python3

from pytapo import Tapo
import sys
  
user="xxxxxxx"
password="xxxxxxxx"
host="xxxxxxxxx"

def  preselect(s) :
    tapo2=Tapo(host, user, password)
    res=tapo2.setPreset(s) # ID de la préposition voulue
    
preselect(str(sys.argv[1]))

Création d’un présélection
Passez en paramètre le nom que vous voulez donner à votre présélection

#!/usr/bin/env python3
from pytapo import Tapo
import sys

def savePreset(s) :
    user="xxxxx"
    password="xxxxx"
    host="xxxxxxx"
    tapo2=Tapo(host, user, password)
    res=tapo2.savePreset(s) # ID de la préposition voulue
    
savePreset(str(sys.argv[1]))

reboot`Pour le reboot :

#!/usr/bin/env python3

from pytapo import Tapo
import sys
  
user="XXXX"
password="XXXX"
host="XXXXXX"

def reboot() :
    tapo2=Tapo(host, user, password)
    res=tapo2.reboot() 
    return res

    
result=reboot()
print (result) 

Attention, un reboot met du temps avant de se faire. Etre patient après l’avoir déclenché.

`

4 « J'aime »

hello merci c’est super @mic78000

comment on peut lancer des autres commandes plus basiques genre reboot ou stopper la détection, rallumer la détection? j’ai lu ici GitHub - JurajNyiri/pytapo: Python library for communication with Tapo Cameras mais je ne comprends pas grand chose tant qu’il n’y a pas un code à copier-coller comme tu l’as si gentiment proposé plus haut :smiley: jai la C100 uniquement… merci.

Je vais regarder cela, mais pas avant disons 10j. Là, pas chez moi…

top merci bcp, pas d’urgence :sweat_smile:

J’avais un code mais non
Je l’ai donc effacé

Pour le reboot :

#!/usr/bin/env python3

from pytapo import Tapo
import sys
  
user="XXXX"
password="XXXX"
host="XXXXXX"

def reboot() :
    tapo2=Tapo(host, user, password)
    res=tapo2.reboot() 
    return res

    
result=reboot()
print (result) 

Attention, un reboot met du temps avant de se faire. Etre patient après l’avoir déclenché.

1 « J'aime »

:ok_hand: reboot et l’info sur LED activé ou pas fonctionnent bien, je te remercie.

tu sais si il y a moyen de remonter la détection de mouvement? je vois un fichier de test de l’auteur:

def test_getMotionDetection():
    tapo = Tapo(host, user, password)
    result = tapo.getMotionDetection()
    assert result[".name"] == "motion_det"
    assert result[".type"] == "on_off"
    assert "enabled" in result
    assert "enhanced" in result
    assert "sensitivity" in result
    assert "digital_sensitivity" in result

:thinking: remarque j’ai limpression que c’est uniquement pour savoir si c’est activé ou pas … et pas s’il y a une alarme mouvement en cours…

EDIT : je viens de tester, ça a l’air de renvoyer la config et non un déclenchement…

Pour la détection de mouvement,
Quand je regarde le source de la bibliothèque, je vois ces deux fonctions :getMotion et getAlarm. Pas sûr de ce qu’elles font; Le problème est que si ça renvoit l’état de l’alarme, il va falloir tester (la lancer) en permanence pour capturer le moment où l’alarme est déclenchée. Il va falloir encore creuser un peu.

def getMotionDetection(self):
        data = {"method": "get", "motion_detection": {"name": ["motion_det"]}}
        return self.performRequest(data)["motion_detection"]["motion_det"]

    def getAlarm(self):
        data = {"method": "get", "msg_alarm": {"name": ["chn1_msg_alarm_info"]}}
        return self.performRequest(data)["msg_alarm"]["chn1_msg_alarm_info"]

Il faudrait tester. pas trop le temps là mais la fonction devrait être un truc style

#!/usr/bin/env python3

from pytapo import Tapo
import sys
  
user="XXXX"
password="XXXX"
host="XXXXXX"

def getAlarm() :
    tapo2=Tapo(host, user, password)
    res=tapo2.getAlarm() 
    return res

    
result=getAlarm()
print (result) 

ou

#!/usr/bin/env python3

from pytapo import Tapo
import sys
  
user="XXXX"
password="XXXX"
host="XXXXXX"

def getMotionDetection() :
    tapo2=Tapo(host, user, password)
    res=tapo2.getMotionDetection() 
    return res

    
result=getMotionDetection()
print (result) 

okay merci

Bonjour,

J’ai suivi votre tuto mais dès la première exécution du script j’ai un message d’erreur. Avez vous une idée de ce qui cause ce problème?

Erreur sur /var/www/html/plugins/script/data/moveX.PY 10 2>&1 valeur retournée : 1. Détails : Traceback (most recent call last): File « /var/www/html/plugins/script/data/moveX.PY », line 3, in from pytapo import Tapo File « /usr/local/lib/python3.7/dist-packages/pytapo/init.py », line 12, in from .media_stream.session import HttpMediaSession File « /usr/local/lib/python3.7/dist-packages/pytapo/media_stream/session.py », line 13, in from pytapo.media_stream._utils import ( File « /usr/local/lib/python3.7/dist-packages/pytapo/media_stream/_utils.py », line 45 if i := b.find(sep, start_index) != -1: ^ SyntaxError: invalid syntax

Bonjour, j’ai le même problème, a-t-il été résolut ?

Merci

Pour l’instant, problème toujours pas résolu, ne connaissant pas le langage python je n’arrive pas à résoudre l’erreur de syntaxe.

En ssh le problème qui est pointé plus précisément est le := de la ligne 45 dans _utils.py.

Je n’ai pas regardé mais dans tous les cas il y a un problème de parenthèses (comme en langage C&co) donc pour respecter la variable par rapport au test ce serait plutôt :

if (i := b.find(sep, start_index)) != -1:

à la place de :

if i := b.find(sep, start_index) != -1:

Je viens de regarder, et visiblement, je n’ai pas le même fichier utils.py

Le mien contient

import hashlib
import os

from typing import Mapping, Tuple, Optional


def md5digest(to_hash: bytes) -> bytes:
    return hashlib.md5(to_hash).digest().hex().upper().encode()


def generate_nonce(length: int) -> bytes:
    return os.urandom(length).hex().encode()


def parse_http_headers(data: bytes) -> Mapping[str, str]:
    return {
        i[0].strip(): i[1].strip()
        for i in (j.split(":", 1) for j in data.decode().strip().split("\r\n"))
    }


def parse_http_response(res_line: bytes) -> Tuple[bytes, int, Optional[bytes]]:
    http_ver, status_code_and_status = res_line.split(b" ", 1)
    if b" " in status_code_and_status:
        status_code, status = status_code_and_status.split(b" ", 1)
    else:
        status_code = status_code_and_status
        status = None
    return http_ver, int(status_code.decode()), status

Un problème de versions entre la version installée et celle de l’ordi ? lors de la commande d’installation

sudo python3 -m pip install pytapo

Pour info, voici le contenu dui répertoire ::/usr/local/lib/python3.7/dist-packages/pytapo/media_stream
crypto.py error.py __init__.py __pycache__ response.py session.py _utils.py

Je viens de regarder j’ai ajouté les parenthèses mais toujours la même ligne en erreur de syntaxe.

En ce qui concerne les fichiers, j’ai relancer la commande d’installation et voici le log


Requirement already satisfied: pytapo in /usr/local/lib/python3.7/dist-packages (3.2.14)
Requirement already satisfied: requests in /usr/local/lib/python3.7/dist-packages (from pytapo) (2.31.0)
Requirement already satisfied: urllib3 in /usr/local/lib/python3.7/dist-packages (from pytapo) (2.0.6)
Requirement already satisfied: rtp in /usr/local/lib/python3.7/dist-packages (from pytapo) (0.0.3)
Requirement already satisfied: pycryptodome in /usr/local/lib/python3.7/dist-packages (from pytapo) (3.19.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.7/dist-packages (from requests->pytapo) (3.3.0)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests->pytapo) (2023.7.22)
Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests->pytapo) (3.4)
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
[notice] A new release of pip available: 22.3.1 -> 23.3.1
[notice] To update, run: python3 -m pip install --upgrade pip

et le fichier _utils.py comporte le texte suivant

import hashlib
import os

from typing import Mapping, Tuple, Optional


def md5digest(to_hash: bytes) -> bytes:
    return hashlib.md5(to_hash).digest().hex().upper().encode()


def generate_nonce(length: int) -> bytes:
    return os.urandom(length).hex().encode()


def parse_http_headers(data: bytes) -> Mapping[str, str]:
    return {
        i[0].strip(): i[1].strip()
        for i in (j.split(":", 1) for j in data.decode().strip().split("\r\n"))
    }


def parse_http_response(res_line: bytes) -> Tuple[bytes, int, Optional[bytes]]:
    http_ver, status_code_and_status = res_line.split(b" ", 1)
    if b" " in status_code_and_status:
        status_code, status = status_code_and_status.split(b" ", 1)
    else:
        status_code = status_code_and_status
        status = None
    return http_ver, int(status_code.decode()), status


def parse_time(b: bytes) -> int:
    return (
        ((b[0] & 0x0E) << 29)
        | (b[1] << 22)
        | ((b[2] & 0xFE) << 14)
        | (b[3] << 7)
        | (b[4] >> 1)
    )


def index_from(b: bytes, sep: bytes, start_index: int) -> int:
    if start_index > 0:
        if start_index < len(b):
            if (i := b.find(sep, start_index)) != -1:
                return i
        return -1
    return b.find(sep)


# todo: is this function correct???
def annexB2AVC(b):
    b = bytes(b)
    i = 0
    # f = open("data1.txt", "w")
    # f.write(str(list(b)))
    # f.close()
    while i < len(b):
        if i + 4 >= len(b):
            break

        size = b[i + 4 :].find(b"\x00\x00\x00\x01")

        # f = open("data1.5.txt", "w")
        # f.write(str(list(b[i + 4 :])))
        # f.close()

        if size < 0:
            size = len(b) - (i + 4)

        size_bytes = size.to_bytes(4, "big")

        b = bytearray(b)
        b[i : i + 4] = size_bytes
        b = bytes(b)

        i += size + 4
    # f = open("data2.txt", "w")
    # f.write(str(list(b)))
    # f.close()
    # sys.exit(0)

    return bytearray(b)

On a visiblement pas les mêmes fichiers. Sans doute pas la même version de pytapo.
Là, pas trop le temps, mais je pourrais te passer les fichiers qui sont sur mon ordi. Si tu arrives à en avoir la liste… Mais jamais évident avec les dépendances.

Mon tuto date du 5 février 2022. Mon installation sur mon poste date donc d’avant.
Sur ce site

tu peux trouver les anciennes versions.

logiquement, il faudrait que tu charges la version 1.2.1

ou plus précisement

Les spécialiste linux devront te dire si tu dois désinstaller la version actuelle ou pas avant de la charger.

Merci pour tes réponses, j’ai désinstallé la version la plus récente et forcé l’installation de la version 1.2.1 grâce à la commande python3 -m pip install pytapo==1.2.1
voici le résultat de la commande

Defaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: pytapo==1.2.1 in /var/www/.local/lib/python3.7/site-packages (1.2.1)
Requirement already satisfied: urllib3 in /usr/local/lib/python3.7/dist-packages (from pytapo==1.2.1) (2.0.6)
Requirement already satisfied: requests in /usr/local/lib/python3.7/dist-packages (from pytapo==1.2.1) (2.31.0)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests->pytapo==1.2.1) (2023.7.22)
Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests->pytapo==1.2.1) (3.4)
Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.7/dist-packages (from requests->pytapo==1.2.1) (3.3.0)

[notice] A new release of pip available: 22.3.1 -> 23.3.1
[notice] To update, run: python3 -m pip install --upgrade pip

Mais malheureusement au moment d’exécuter le script j’ai encore un message d’erreur m’indiquant un problème d’authentification, j’ai bien vérifié les informations de connexion. Je penche sur une modification du firmware par le fabricant des cameras modifiant la manière de s’identifier (d’où les nouvelle version de pytapo)… Le firmware actuel est le 1.3.7 build 230920 de ma c210.

Je ne sais plus trop quoi faire.

Merci pour ce super tuto.
J’ai 2 C210 et j’essaie de les configurer via des scripts jeedom grâce à vous.
Mais je bloque sur « Exception: Invalid authentication data ».
J’ai essayé le login password de la caméra, le login password du compte, …
Tu précises dans ton intro : " Pour la C210, il faut utiliser le login « admin » et le « mot de passe du compte TPLINK " (le compte principal) » c’est à dire le mot de passe de l’appli tapo ?
Merci pour votre aide.
Traces au cas où :

[2023-11-23 20:34:41][ERROR] : Erreur exécution de la commande [Intranet][active_cameras_interieur][SetPrivacyModeFalse] : Erreur sur /var/www/html/plugins/script/data/SetPrivacyMode.PY False 2>&1 valeur retournée : 1. Détails : Traceback (most recent call last): File "/var/www/html/plugins/script/data/SetPrivacyMode.PY", line 14, in <module> SetPrivacyMode(sys.argv[1]) File "/var/www/html/plugins/script/data/SetPrivacyMode.PY", line 11, in SetPrivacyMode tapo=Tapo(host,user,password) File "/usr/local/lib/python3.7/dist-packages/pytapo/__init__.py", line 34, in __init__ self.basicInfo = self.getBasicInfo() File "/usr/local/lib/python3.7/dist-packages/pytapo/__init__.py", line 221, in getBasicInfo {"method": "get", "device_info": {"name": ["basic_info"]}} File "/usr/local/lib/python3.7/dist-packages/pytapo/__init__.py", line 85, in performRequest self.ensureAuthenticated() File "/usr/local/lib/python3.7/dist-packages/pytapo/__init__.py", line 51, in ensureAuthenticated return self.refreshStok() File "/usr/local/lib/python3.7/dist-packages/pytapo/__init__.py", line 70, in refreshStok raise Exception("Invalid authentication data") Exception: Invalid authentication data

Avec pytapo 1.2.1 j’ai cette erreur d’authentification line 51, in ensureAuthenticated return self.refreshStok() File "/usr/local/lib/python3.7/dist-packages/pytapo/__init__.py", line 70, in refreshStok raise Exception("Invalid authentication data") Exception: Invalid authentication data

Avec pytapo 3.3.18 j’ai cette erreur : line 13, in from pytapo.media_stream._utils import ( File "/usr/local/lib/python3.7/dist-packages/pytapo/media_stream/_utils.py", line 45 if i:=b.find(sep, start_index) != -1: ^ SyntaxError: invalid syntax