Use pan/tilt function for TP-Link Tapo C200 from Home assistant?

Can you post here your your request formated as code?

I have a feeling that you somehow malformed the request.

I have seen that error message before when I played with the camera, but I cannot remember what triggers it.

forget it, i managed to use the wrong password, sorry for any confusion

1 Like

thanks for all your help!

1 Like

Hi, got this up and running with my c200 :slight_smile:
However I don’t think I can hear any sound on live view. I checked on Enable sound threshold detection but so far no sound in HA. Working fine in tp app.
Something I have forgotten to do?

Supported audio codecs in Home Assistant are “aac”, “ac3” and “mp3”.

Tapo Cameras use PCM ALAW (alaw) which is not supported.

More details here.

1 Like

Thanks, thats why. :slight_smile:

Is it possible to still use the app with vpn somehow? Like a local connection so that its not completely exposed on the internet. I guess the app needs some open ports even when I’m on my wifi.
Edit: I want to use it as a baby monitor so sound is essential :slight_smile:

HA sound and lag was a bummer :confused:

Just a hint, WebRTC allows you to stream the Tapo feed with sound support.

1 Like

Hi, I found this thread about missing PTZ functionality for Tapo 200 with 3rd-party software. I tried using iSpy on Windows 10 and PTZ works fine. But now i am searching for a solution to use 3rd party software on iPhone to use Tapo200 with PTZ (without the app) - any idea?

Thomas

Does anyone know how to get motion notifications out of a Tapo?

GitHub - JurajNyiri/HomeAssistant-Tapo-Control: Control for Tapo cameras as a Home Assistant component exposes binary sensor for motion. Alternatively, native onvif integration does too.

So I got rhasspy recognizing wake word and intents from Tapo C200, hence notifying homeassistant (I can share details if interested).
BTW it would also be great if we could send audio (TTS) back to the cam.
Just wondering if someone reverse engineered also the api calls related to the audio backchannel.
@JurajNyiri any clue?

1 Like

I have the same problem. How did you solve it?

Why don’t you try out this code?
Might be useful to have smaller buttons…

type: picture-glance
title: Ingresso
camera_image: camera.tapo_camera_sd
camera_view: live
entities:
  - entity: camera.tapo_camera_sd
    icon: mdi:arrow-left-drop-circle-outline
    tap_action:
      action: call-service
      service: tapo_control.ptz
      service_data:
        entity_id: camera.tapo_camera_sd
        pan: LEFT
  - entity: camera.tapo_camera_sd
    icon: mdi:arrow-up-drop-circle-outline
    tap_action:
      action: call-service
      service: tapo_control.ptz
      service_data:
        entity_id: camera.tapo_camera_sd
        tilt: UP
  - entity: camera.tapo_camera_sd
    icon: mdi:arrow-down-drop-circle-outline
    tap_action:
      action: call-service
      service: tapo_control.ptz
      service_data:
        entity_id: camera.tapo_camera_sd
        tilt: DOWN
  - entity: camera.tapo_camera_sd
    icon: mdi:arrow-right-drop-circle-outline
    tap_action:
      action: call-service
      service: tapo_control.ptz
      service_data:
        entity_id: camera.tapo_camera_sd
        pan: RIGHT
  - entity: camera.tapo_camera_sd
    icon: mdi:eye-outline
    tap_action:
      action: call-service
      service: tapo_control.set_privacy_mode
      service_data:
        entity_id: camera.tapo_camera_sd
        privacy_mode: 'off'
  - entity: camera.tapo_camera_sd
    icon: mdi:eye-off-outline
    tap_action:
      action: call-service
      service: tapo_control.set_privacy_mode
      service_data:
        entity_id: camera.tapo_camera_sd
        privacy_mode: 'on'
  - entity: camera.tapo_camera_sd
    icon: mdi:power
    tap_action:
      action: call-service
      service: tapo_control.reboot
      service_data:
        entity_id: camera.tapo_camera_sd

image

5 Likes

Hey,

I just intruduced a couple of Tapo C200 cameras and this code came in handy. My only issue is that I have a good 8-10 second latency between pressing the button and the camera turning… Any idea how to fix that? I’m using the Tapo: Cameras Control integration.

Yes there is a solution. See faq in readme.

2 Likes

Can u share details?

Hello, I found this thread and I have trouble on controlling tapo C200 camera using python

I can still using normal opencv of python to capture image from tapo C200 but I can’t use pytapo, in which I enter exact same username, password and ID. It always give me “Invalid authentication data”.
I have read the FAQ and try to change ‘admin’ as user and password as “password”, the tapo app don’t let me have user less than 6 character, so I only change the user in python code to “admin” and password to “passwork”, nothing work, it still “Invalid authentication data”

Do you have any other way to try get authentication? Sorry if my English is wrong and thank you for your help.

Thank you for sharing all you extensive research.

Between your work and that of @JuraNyiri , I have been able to convert step 1 of your reearch into a perl script to access a Tapo C520WS camera and I have the stok response you showed and can run queries to interact with the Tapo camera

Sharing the code here in case anyone else wants to use it / evolve it. Just replace:

  • Free Text Name of Camera
  • CameraIP
  • TapoAccountPassword

with the specifics of your installation and this should work for you.

While debugging, set $script_debug_flag to 1 but for regular use, to save screen real estate, set it to 0.

I am using perl since my system is Zoneminder and it uses all perl modules.

The code as it sits so far (it is a test script to minimal effort to create user interface)

#!/usr/bin/perl5.28-x86_64-linux-gnu

use strict;
use warnings;
use LWP::UserAgent;
use LWP::ConnCache;
use URI;
use URI::Escape;
use JSON;
use Data::Dumper;
use Encode qw(decode encode);
use Digest::MD5 qw(md5 md5_hex md5_base64);
use IO::Socket::SSL qw();

# Create a debug flag so the script prints out progress as it goes
my $script_debug_flag = 0;

# Define the target URL - the one to which the data is POSTed
# In this case it is the camera being identified
my $camera_name = "Free Text Name of Camera";
print "Fetching from " , $camera_name , "\n";
my $camera_ip = "CameraIP";
my $camera_uri = "https:\/\/".$camera_ip.":443";
my $destination_url_object = URI->new( $camera_uri );

# Define the data to be POSTed
# Password is the Tapo App Password
# encoded for UTF-8, with md5_hex encoding, converted to All CAPS
my $text_pw = "TapoAccountPassword";
if ( $script_debug_flag == 1 ) {
    print("The text password is: ",$text_pw,"\n");
}
my $utf8_pw = encode( 'UTF-8' , $text_pw );
if ( $script_debug_flag == 1 ) {
    print("The UTF-8 encoded password is: ",$utf8_pw,"\n");
}
my $md5_pw = md5_hex( $utf8_pw );
if ( $script_debug_flag == 1 ) {
    print("The md5_hex password is: ",$md5_pw,"\n");
}
my $caps_md5_pw = uc( $md5_pw );
if ( $script_debug_flag == 1 ) {
    print("The ALL CAPS password is: ",$caps_md5_pw,"\n");
}
# Put the encoded password in with the rest of the data to be POSTed
my %post_data_array = (
    "method" => "login",
    "params" => {
        "hashed" => "true",
        "password" => $caps_md5_pw,
        "username" => "admin",
        }
);
if ( $script_debug_flag == 1 ) {
    print("The post data in array format is: \n");
    print Dumper( \%post_data_array );
}
# Encode the data as a json-formatted string
my $post_data_json = encode_json(\%post_data_array);
if ( $script_debug_flag == 1 ) {
    print("The json version of the post data array is: \n");
    print( $post_data_json , "\n" );
}

# Define the header parameters for the POST
my %post_header_array = (
    'Content-Type'      => 'application/json;charset=UTF-8',
    'User-Agent'        => 'Tapo CameraClient Android',
    'Accept'            => 'application/json',
    'Accept-Encoding'   => 'gzip, deflate',
    'Connection'        => 'close',
    'requestByApp'      => "true",
    'Referer'           => $camera_uri,
    'Host'              => $camera_uri,
);
if ( $script_debug_flag == 1 ) {
    print("The post header array contains: \n");
    print Dumper( \%post_header_array );
}

# Initiate the browser the Perl POST will emulate
my $browser_object = LWP::UserAgent->new;
# Inspects proxy settings
# If you don't use a proxy, the call to env_proxy( ) has no effect
$browser_object->env_proxy;
# Establish Keep-Alive setup
my $cache_object = $browser_object->conn_cache(LWP::ConnCache->new());
# Allow multiple (unlimited) cached items
$browser_object->conn_cache->total_capacity(undef);
# Enable POST to follow redirects
push @{ $browser_object->requests_redirectable }, 'POST';
# Disable need for proper SSL certificates
$browser_object->ssl_opts(verify_hostname => 0 , SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE,  );

# POST the data to the URL and capture the response (action) at the URL
my $response_object = $browser_object->post( $destination_url_object , %post_header_array , Content => $post_data_json );

if ( $script_debug_flag == 1 ) {
    print("The response object is: \n");
    print Dumper( $response_object );
}

# Determine what to do with the response
print "\nThe response from the camera was:\n";
if ($response_object->is_success) {
    print "The camera acknowledged the POST and replied with:\n";
    print "HTTP Reponse MIME Type: ", $response_object->content_type, "\n";
    print "Received reply: ", $response_object->decoded_content, "\n";
}
else {
    print "The POST generated an error message:\n";
    print "HTTP POST error code: ", $response_object->code, "\n";
    print "HTTP POST error message: ", $response_object->message, "\n";
    print "HTTP Realm (to authenticate): ", $response_object->header('WWW-Authenticate') , "\n";
}

# Parse out the stok value to use with additional camera enquiries
my $response_reference = decode_json( $response_object->decoded_content );
if ( $script_debug_flag == 1 ) {
    print "The content returned by the camera was: \n";
    print Dumper( $response_reference );
}
my $stok_value = $response_reference->{result}->{stok};
if ( $script_debug_flag == 1 ) {
    print "The returned stok value was \n";
    print $stok_value , "\n";
}

my $camera_uri_w_stok = "https://".$camera_ip.":443/stok=".$stok_value."/ds";
my $camera_url_object_w_stok = URI->new( $camera_uri_w_stok );

my %post_data_array_w_stok = (
    "method"    => "get",
    "device_info"  => {
        "name" => 'basic_info'
    }
);

=begin comment

my %post_data_array_w_stok = (
    "method"    => "get",
    "device_info"  => {
        "name" => 'basic_info'
    }
);

my %post_data_array_w_stok = (
    "method"    => "get",
    "function"  => {
        "name" => 'module_spec'
    }
);

my %post_data_array_w_stok = (
    "method"    => "get",
    "lens_mask"  => {
        "name" => 'lens_mask_info'
    }
);

my %post_data_array_w_stok = (
    "method"    => "get",
    "image"  => {
        "name" => 'switch'
    }
);

my %post_data_array_w_stok = (
    "method"    => "get",
    "led"  => {
        "name" => 'config'
    }
);

# Working
my %post_data_array_w_stok = (
    "method"    => "do",
    "motor"  => {
        "move" => {
            "x_coord" => "0",
            "y_coord" => "-15"
        }
    }
);

# NOT working
my %post_data_array_w_stok = (
    "method"    => "do",
    "motor"  => {
        "movestep" => {
            "direction" => "15"
        }
    }
);

=cut

my $post_data_json_w_stok = encode_json(\%post_data_array_w_stok);
my $response_w_stok = $browser_object->post( $camera_url_object_w_stok , %post_header_array , Content => $post_data_json_w_stok );
# Determine what to do with the response
print "\nThe response w stok from the camera was:\n";
if ($response_object->is_success) {
    print "The camera acknowledged the POST and replied with:\n";
    print "HTTP Reponse MIME Type: ", $response_w_stok->content_type, "\n";
    print "Received reply: ", $response_w_stok->decoded_content, "\n";
}
else {
    print "The POST generated an error message:\n";
    print "HTTP POST error code: ", $response_w_stok->code, "\n";
    print "HTTP POST error message: ", $response_w_stok->message, "\n";
    print "HTTP Realm (to authenticate): ", $response_w_stok->header('WWW-Authenticate') , "\n";
}

1;

Thank you again for sharing

@jonrub11
There is a functional PT (not Zoom for C520WS) script for Tapo at

which works for the TapoC520WS as well - once you install the additional libraries as defined in the insructions on that page.
I am going to see about evolving it, but for now, it works fine

1 Like

THANK YOU !!

I just purchased and installed Tapo C520WS cameras and after spending over a month on email exchanges with TP-Link ?support? personnel, I was told the Tapo engineers do not share APIs.

I am using Zoneminder and wanted to control Pan and Tilt of the cameras. Your pytapo and the above research from @GSZabados hopefully will let me do that (and more) natively.

I need to convert to perl for use with Zoneminder but you and @GSZabados have given me agreat start.

I have already used your experimental script from your pytapo git account to download mp4 files from the MicroCard in the camera - which Tapo documents say you cannot do. Your pytapo lets me do that.

Do you have any other “experimental” scripts?

I would be happy to serve as your tester for the Tapo C520WS camera …

Thanks again; great stuff!

1 Like

I got mine C210 Tapo tilt working with the steps below:

Create a new Card
Select card: Picture Glance

Edit the code with the text below:

type: picture-glance
title: Garagem
camera_image: camera.c210_mainstream
camera_view: live
entities:
  - entity: camera.c210_mainstream
    icon: mdi:arrow-left-drop-circle-outline
    tap_action:
      action: call-service
      service: onvif.ptz
      service_data:
        entity_id: camera.c210_mainstream
        pan: LEFT
  - entity: camera.c210_mainstream
    icon: mdi:arrow-up-drop-circle-outline
    tap_action:
      action: call-service
      service: onvif.ptz
      service_data:
        entity_id: camera.c210_mainstream
        tilt: UP
  - entity: camera.c210_mainstream
    icon: mdi:arrow-down-drop-circle-outline
    tap_action:
      action: call-service
      service: onvif.ptz
      service_data:
        entity_id: camera.c210_mainstream
        tilt: DOWN
  - entity: camera.c210_mainstream
    icon: mdi:arrow-right-drop-circle-outline
    tap_action:
      action: call-service
      service: onvif.ptz
      service_data:
        entity_id: camera.c210_mainstream
        pan: RIGHT
  - entity: camera.c210_mainstream
    icon: mdi:eye-outline
    tap_action:
      action: call-service
      service: onvif.set_privacy_mode
      service_data:
        entity_id: camera.c210_mainstream
        privacy_mode: 'off'
  - entity: camera.c210_mainstream
    icon: mdi:eye-off-outline
    tap_action:
      action: call-service
      service: onvif.set_privacy_mode
      service_data:
        entity_id: camera.c210_mainstream
        privacy_mode: 'on'
  - entity: camera.c210_mainstream
    icon: mdi:power
    tap_action:
      action: call-service
      service: onvif.reboot
      service_data:
        entity_id: camera.c210_mainstream