Connected to video source rtsp://user:pass@192.168.179.237:554/stream2.
Traceback (most recent call last):
File "C:\Users\futopparagg\env\lib\site-packages\onvif\client.py", line 23, in wrapped
return func(*args, **kwargs)
File "C:\Users\futopparagg\env\lib\site-packages\onvif\client.py", line 153, in wrapped
return call(params, callback)
File "C:\Users\futopparagg\env\lib\site-packages\onvif\client.py", line 140, in call
ret = func(**params)
File "C:\Users\futopparagg\env\lib\site-packages\zeep\proxy.py", line 46, in call
return self._proxy._binding.send(
File "C:\Users\futopparagg\env\lib\site-packages\zeep\wsdl\bindings\soap.py", line 135, in send
return self.process_reply(client, operation_obj, response)
File "C:\Users\futopparagg\env\lib\site-packages\zeep\wsdl\bindings\soap.py", line 229, in process_reply
return self.process_error(doc, operation)
File "C:\Users\futopparagg\env\lib\site-packages\zeep\wsdl\bindings\soap.py", line 391, in process_error
raise Fault(
zeep.exceptions.Fault
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Users\futopparagg\ptz_viewer.py", line 62, in <module>
cam_ptz.setup_ptz()
File "C:\Users\futopparagg\camera.py", line 65, in setup_ptz
self.moverequest.Velocity = self.ptz.GetStatus({'ProfileToken': media_profile.token}).Position
File "C:\Users\futopparagg\venv\lib\site-packages\onvif\client.py", line 26, in wrapped
raise ONVIFError(err)
onvif.exceptions.ONVIFError: Unknown error:
try:
with urllib.request.urlopen(req) as response:
body = response.read()
headers = response.getheaders()
status = response.getcode()
print(headers)
print(status)
print(body)
except urllib.error.URLError as e:
print(e.reason)
Discussion
kotaprojさん、こんばんわ!
この記事、すごく参考になりました。ありがとうございました。
ただ、記事の手順どおりに進めると、以下のエラーで手詰まりましたw
zeepかどこかのバグですかねぇ?
何か対処方法をご存じだったら、教えていただけるとありがたいです。。。
Connected to video source rtsp://user:pass@192.168.179.237:554/stream2.
Traceback (most recent call last):
File "C:\Users\futopparagg\env\lib\site-packages\onvif\client.py", line 23, in wrapped
return func(*args, **kwargs)
File "C:\Users\futopparagg\env\lib\site-packages\onvif\client.py", line 153, in wrapped
return call(params, callback)
File "C:\Users\futopparagg\env\lib\site-packages\onvif\client.py", line 140, in call
ret = func(**params)
File "C:\Users\futopparagg\env\lib\site-packages\zeep\proxy.py", line 46, in call
return self._proxy._binding.send(
File "C:\Users\futopparagg\env\lib\site-packages\zeep\wsdl\bindings\soap.py", line 135, in send
return self.process_reply(client, operation_obj, response)
File "C:\Users\futopparagg\env\lib\site-packages\zeep\wsdl\bindings\soap.py", line 229, in process_reply
return self.process_error(doc, operation)
File "C:\Users\futopparagg\env\lib\site-packages\zeep\wsdl\bindings\soap.py", line 391, in process_error
raise Fault(
zeep.exceptions.Fault
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Users\futopparagg\ptz_viewer.py", line 62, in <module>
cam_ptz.setup_ptz()
File "C:\Users\futopparagg\camera.py", line 65, in setup_ptz
self.moverequest.Velocity = self.ptz.GetStatus({'ProfileToken': media_profile.token}).Position
File "C:\Users\futopparagg\venv\lib\site-packages\onvif\client.py", line 26, in wrapped
raise ONVIFError(err)
onvif.exceptions.ONVIFError: Unknown error:
私の方で、さきほど手順を最初から確認してみましたが、問題なく実施することができました。
機材 or 各モジュールのバージョンの差異なのかもしれません。
特に、機材が異なる場合、onvifといっても方言があるので、その辺のエラーかもです。
参考になるかわかりませんが、細かく条件を記載しました。
機材
環境
kotoprojさん、こんばんわ!
早速返信いただいたのに、応答できず、恐縮です。。。
検証して情報までご提示いただけて、本当にありがとうございます。
いま、細かい確認ができない場所にいるので申し訳ないですが、いただいた情報と突き合わせしてみます。
ちなみに、当方のカメラは、Tapo C210/Aなんですが、ONVIF Device ManagerからだとちゃんとPTが制御できたので、カメラ側の機種の問題ではないかも?とは思っていますが、1つ1つ切り分けてみますね。
まずは、御礼まで。
kotoprojさん、pip以外のバージョンを合わせて実行してみましたが、やはりconnect時に同じエラーでプログラムが停止してしまいました。時間が作れたら、エラーのソースを追いかけてみます。
以下、ご参考まで。
・機材
・Tapo C210/A(Tapoアプリの表示では、型番:C210)
・ハードウェアバージョン:1.0
・ファームウェアバージョン:1.1.12 Build 211028 Rel.19131n(4A50)
→Tapoアプリでアップデートの確認を押しても「最新のファームウェア」と表示されます。
・環境
・Python 3.10.2(python -Vの結果)
・各モジュールのバージョン↓
attrs 21.4.0
cached-property 1.5.2
certifi 2021.10.8
charset-normalizer 2.0.11
idna 3.3
isodate 0.6.1
lxml 4.7.1
MouseInfo 0.1.3
numpy 1.22.2
onvif-zeep 0.2.12
opencv-python 4.5.5.62
Pillow 9.0.1
pip 22.0.3
platformdirs 2.4.1
PyAutoGUI 0.9.53
PyGetWindow 0.0.9
PyMsgBox 1.0.9
pyperclip 1.8.2
PyRect 0.1.4
PyScreeze 0.1.28
PySimpleGUI 4.56.0
pytweening 1.0.4
pytz 2021.3
requests 2.27.1
requests-file 1.5.1
requests-toolbelt 0.9.1
rtsp 1.1.12
setuptools 56.0.0
six 1.16.0
urllib3 1.26.8
zeep 4.1.0
参考まで。
Python3.10.2だったため、こちらの環境でも3.10.2で実施しました。
→問題なく動作することができました。
C200とC210Aの差かもしれませんね。
エラーを見ると、onvif clientのunkownになっているため、
media_profileのところに差異があるのかもしれません。
こちらの環境で、camera.pyのsetup_ptz()にprint文を追加しました。
正常時(C200)の結果が以下です。
'PTZConfiguration'あたりに差異があるのかもです。
あと、onvifのポートが2020でない場合、同様のエラーになる可能性があります。
kotaprojさん、こんばんわ!
返信&情報提供いただいてありがとうございます。
また、お返事に時間がかかってしまって大変恐縮です。
こちらも切り分けに進展がありました。
kotaprojさんに調べてばかりいただいて申し訳なかったので、なんとかC200を入手して、同じプログラムを実行したところ、こちらの環境でも例外が発生せずに接続できて、パン・チルトまで動作することが確認できました。
やはり、ご指摘いただいたように、C200とC210の何らかの差異による例外のようです。
画質向上して上位互換だろうと勝手に思い込んでいたのが裏目にでました。。。
ともあれ両方のハードがあるので、kotaprojさんの情報も参考にして、少し実験を進めてみます。
もし何かしら、差異がわかったら、共有させていただきますね。
本当にいろいろとありがとうございました。(- -)(_ _)ペコリ
kotaprojさん、参考までにC210でmedia_profileをprintした結果を共有させていただきます。
最後のほう(ZoomLimitsの直下のExtension)に単純でない差分が少し見受けられましたが、理由については調査できていません。
media_profile: {
'Name': 'mainStream',
'VideoSourceConfiguration': {
'Name': 'VideoSourceConfig',
'UseCount': 2,
'SourceToken': 'raw_vs1',
'Bounds': {
'x': 0,
'y': 0,
'width': 2304,
'height': 1296
},
'_value_1': None,
'Extension': None,
'token': 'vsconf',
'_attr_1': {
}
},
'AudioSourceConfiguration': {
'Name': 'AudioSourceConfig',
'UseCount': 2,
'SourceToken': 'raw_as1',
'_value_1': None,
'token': 'asconf',
'_attr_1': {
}
},
'VideoEncoderConfiguration': {
'Name': 'VideoEncoder_1',
'UseCount': 1,
'Encoding': 'H264',
'Resolution': {
'Width': 1280,
'Height': 720
},
'Quality': 5.0,
'RateControl': {
'FrameRateLimit': 15,
'EncodingInterval': 1,
'BitrateLimit': 2048
},
'MPEG4': None,
'H264': {
'GovLength': 25,
'H264Profile': 'Main'
},
'Multicast': {
'Address': {
'Type': 'IPv4',
'IPv4Address': '0.0.0.0',
'IPv6Address': None
},
'Port': 0,
'TTL': 0,
'AutoStart': False,
'_value_1': None,
'_attr_1': None
},
'SessionTimeout': datetime.timedelta(seconds=65),
'_value_1': None,
'token': 'main',
'_attr_1': {
}
},
'AudioEncoderConfiguration': {
'Name': 'AudioEncoder_1',
'UseCount': 2,
'Encoding': 'G711',
'Bitrate': 128000,
'SampleRate': 8000,
'Multicast': {
'Address': {
'Type': 'IPv4',
'IPv4Address': '0.0.0.0',
'IPv6Address': None
},
'Port': 0,
'TTL': 0,
'AutoStart': False,
'_value_1': None,
'_attr_1': None
},
'SessionTimeout': datetime.timedelta(seconds=65),
'_value_1': None,
'token': 'microphone',
'_attr_1': {
}
},
'VideoAnalyticsConfiguration': {
'Name': 'VideoAnalyticsName',
'UseCount': 2,
'AnalyticsEngineConfiguration': {
'AnalyticsModule': [
{
'Parameters': {
'SimpleItem': [
{
'Name': 'Sensitivity',
'Value': 'medium'
},
{
'Name': 'Enabled',
'Value': 'on'
}
],
'ElementItem': [
{
'_value_1': <Element {http://www.onvif.org/ver10/schema}CellLayout at 0x1fe51b358c0>,
'Name': 'Layout'
}
],
'Extension': None,
'_attr_1': None
},
'Name': 'MyCellMotionModule',
'Type': 'tt:CellMotionEngine'
},
{
'Parameters': {
'SimpleItem': [
{
'Name': 'Sensitivity',
'Value': 'medium'
},
{
'Name': 'Enabled',
'Value': 'off'
}
],
'ElementItem': [],
'Extension': None,
'_attr_1': None
},
'Name': 'MyTamperDetecModule',
'Type': 'tt:TamperEngine'
}
],
'Extension': None,
'_attr_1': None
},
'RuleEngineConfiguration': {
'Rule': [
{
'Parameters': {
'SimpleItem': [
{
'Name': 'ActiveCells',
'Value': '0P8A8A=='
},
{
'Name': 'MinCount',
'Value': '5'
},
{
'Name': 'AlarmOnDelay',
'Value': '1000'
},
{
'Name': 'AlarmOffDelay',
'Value': '1000'
}
],
'ElementItem': [],
'Extension': None,
'_attr_1': None
},
'Name': 'MyMotionDetectorRule',
'Type': 'tt:CellMotionDetector'
},
{
'Parameters': None,
'Name': 'MyTamperDetectorRule',
'Type': 'tt:TamperDetector'
}
],
'Extension': None,
'_attr_1': None
},
'_value_1': None,
'token': 'VideoAnalyticsToken',
'_attr_1': {
}
},
'PTZConfiguration': {
'Name': 'PTZ',
'UseCount': 2,
'NodeToken': 'PTZNODETOKEN',
'DefaultAbsolutePantTiltPositionSpace': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/PositionGenericSpace',
'DefaultAbsoluteZoomPositionSpace': None,
'DefaultRelativePanTiltTranslationSpace': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/TranslationGenericSpace',
'DefaultRelativeZoomTranslationSpace': None,
'DefaultContinuousPanTiltVelocitySpace': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/VelocityGenericSpace',
'DefaultContinuousZoomVelocitySpace': None,
'DefaultPTZSpeed': {
'PanTilt': {
'x': 0.349999994,
'y': 0.349999994,
'space': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/GenericSpeedSpace'
},
'Zoom': None
},
'DefaultPTZTimeout': datetime.timedelta(seconds=180),
'PanTiltLimits': {
'Range': {
'URI': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/PositionGenericSpace',
'XRange': {
'Min': -1.0,
'Max': 1.0
},
'YRange': {
'Min': -1.0,
'Max': 1.0
}
}
},
'ZoomLimits': None,
'Extension': {
'_value_1': [
<Element {http://www.onvif.org/ver10/schema}PTControlDirection at 0x1fe519d7f40>
],
'PTControlDirection': None,
'Extension': None
},
'token': 'PTZTOKEN',
'_attr_1': {
}
},
'MetadataConfiguration': None,
'Extension': None,
'token': 'profile_1',
'fixed': True,
'_attr_1': {
}
}
onvif自体の規格をよくわかっていないのですが、エラーは↓で上がっているようなので、print文をみると何かわかるかもしれませんね。
kotaprojさん、こんばんわ!
だいぶ間が空いてしまい大変恐縮ですが、C210でパンチルトのエラーが発生する件について、以下、わかったことを共有します。
C210の場合、どうしてもonvifパッケージでうまく動かせなかったため、知り合いの方に相談して、以下のようなソースを書くことで、AbsoluteMoveであれば、動かせることがわかりました。
残念ながら、自分の理解が追い付いておらず、どうすれば、ContinuousMoveで動かせるかまでには、至っていません。
ポイントは、PositionとSpeedのところのPatilt内に、xmlnsプロパティを明記することだそうです。
この指定がないと、lang=en は知らんというようなエラーが発生して、失敗するようです。ここも知り合いの方の受け売りなので、自分でもよく咀嚼できておらず、恐縮です。
ちなみに、このソースであれば、C200もC210もパンチルトすることは可能でした。
OnvifRequest(username = 'xxxxx', password = 'xxxxx')のxxxxxの部分と
url = 'http://xxxxxxxxx:2020/'のxxxxxxxxxの部分と
absolute_move(0, 1)のx、y(0と1)の部分を
環境や任意の値に合わせてもらうと、動きます。
同じようにハマってしまった方の参考になれば幸いです。
#!/usr/bin/env python3
import hashlib
import os
import base64
from datetime import datetime
class OnvifRequest:
def init(self, username, password):
self.username = username
self.password = password
################################################################
import urllib.request
url = 'http://xxxxxxxxx:2020/'
headers = {'Content-Type': 'text/xml; charset=utf-8'}
onvif_request = OnvifRequest(username = 'xxxxx', password = 'xxxxx').absolute_move(0, 1)
req = urllib.request.Request(url, data=onvif_request.encode(), method='POST', headers=headers)
try:
with urllib.request.urlopen(req) as response:
body = response.read()
headers = response.getheaders()
status = response.getcode()
print(headers)
print(status)
print(body)
except urllib.error.URLError as e:
print(e.reason)
以上、取り急ぎ、お知らせまで。
AbsoluteMoveの説明、ありがとうございます。
自身での動作確認がてらに、記事を更新させていただきました。
こちらでも正しく制御できました。
詳しい知り合いの方と相談できるのが、うらやましいです。
初めまして。
こちらの記事を参考に、PythonでTapo C210のカメラビューアを開発しています。
とても有益な記事を公開していただき、まずはありがとうございました。
私も記事前半のContinuousMoveを使う方法では太っ腹じじぃさんと同じ症状が出ました。
Tapo C210
ハードウェアバージョン:2.0
ファームウェアバージョン:1.3.6 (2023/08/17時点の最新版)
環境
Windows11
Python 3.8.9(64bit)
onvif-zeep==0.2.12
また、AbsoluteMove版に変更しても、with urllib.request.urlopen(req) as response:部分で、
Bad Requestが返ってしまいます。
確認したところ、onvifreq.pyのabsolute_move()関数に渡すx, yの値が-1~1の間であれば動きました。
その範囲外になるとBad Requestを返すようです。
冒頭のパンチルトパラメータを以下のように書き換えるとPAN/TILTできました。
また、absolute_move()に渡す際に-を付けないと、上下左右が逆転しました。
さらに、RelativeMove、ContinuousMoveも試しました。
AbsoluteMove版のonvifreq.pyに下記関数を追加します。
relative_move()もx, yを-1~0の範囲で指定すると、良い感じに動きました。
こちらは、渡す際の-は不要でした。
relative_move()のx, yの値が範囲外(10や-5)などの場合、一度限界まで移動し、
さらに同じ方向に移動させようとするとBad Requestが返ります。
範囲外の数字は、Tapoアプリにある「垂直パトロール」「水平パトロール」の実現に使えるかもしれません。
私は猫を監視する目的でカメラを天井に設置しており、
Tapoアプリで「動作トラッキング」をONにしています。
猫を追ってカメラが動くので、RelativeMoveの方を採用しました。
その場合、X_MIN, X_MAX, Y_MIN, Y_MAXを使わずにPAN/TILT操作をして、
Bad Requestが返ったら「操作範囲外になったというメッセージを出力する」のがベストでした。
ContinuousMoveは私の状況では利用を想定しておらず、
relative_move()用のx, y値を渡してカメラが動く事だけテストしました。
こちらも、渡す際の-は不要でした。
absolute_move()に渡すx, y値に-が必要と書き込んでしまった件で訂正します。
私は、カメラを天井に逆さに取り付けているため、「画像を反転」をONにしています。
そのため-がないと、移動方向が逆になっていました。
absolute_move()にrelative_move()渡すx, yに-を付けるかは、
以下のようにするのが良いようです。
Tapoアプリで「画像を反転」をONにしている場合、
absolute_move()には-を付ける。
relative_move()には-を付けない。
continuous_move()には-を付けない。
Tapoアプリで「画像を反転」をOFFにしている場合、
absolute_move()には-を付けない。
relative_move()には-を付ける。
continuous_move()には-を付ける。
また、今回の件で私が調べたことなどをこちらにまとめました。 もし記事を修正される予定があれば、必要に応じて引用等してください。
tapoC210は製品の仕様上、公式ではiOSもしくはアンドロイドスマフォのみ対応しており、パソコンモニター上で、なんとかライブストリーミングできないか、しかも、既製品に頼ることなくブラウザを自作できないかと思いネットサーフィンをしていたところ、この記事を見つけました。
真似をしてみたいのですが、そもそも前提知識や経験がなく、環境構築から作業が必要です。可能な範囲で謝礼をお支払いしますので、記事の内容のトレースできるところまで、サポートを頂けないか、ご検討をお願いしたいです。
記事をトレースするには、以下の手順になると思われます。
上記でわかるものはどれになりますか
お返事ありがとうございます。
ググりながら見様見真似で、tapoという名前の仮想環境venvの作成し、
サンプルコードの実行まで行いました。
実行したところ、
(tapo) C:\Users\user\tapo>python view.py
[ WARN:0@30.065] global cap_ffmpeg_impl.hpp:453 _opencv_ffmpeg_interrupt_callback Stream timeout triggered after 30059.696000 ms
Connected to video source rtsp://ユーザー名:パスワード@192.168.68.53:554/stream2.
none
none
none
none
none
none
none
none 以下、続く
というエラーが発生し、躓いているところです。
環境構築には問題ないように見えます
下記のいずれかだと思われます
カメラ側の設定 → "高度な設定" → "カメラのアカウント"から実施できます
また、実施後、再起動しないと反映されない現象に遭遇したことがあるので、
アプリから再起動させてみてください。
それで表示されるのではないでしょうか(Python3.12で問題ないことは確認済み)
もし表示されない場合は、vlc media playerを使って、
メニューバー "メディア" -> "ネットワークストリームを開く" にて、
で表示されるか確認してください
表示されないようであれば、やはり認証の問題と思われます。
vlc(サードパーティでの表示)は公式も対応しているとのことなので。
無事に
tapoC210 ハードウェアバージョン2.0 ファームウェアバージョン1.3.11
で上記のサンプルコードを動作することができました。
先日まで実家に帰省しており、スマフォのテザリングで実行していたので、ネットワーク環境(ポートとかですかね?よく分かりませんが)の問題で、カメラを見つけることができなかったようです。
無事にサンプルコードを動作できたので、これをいろいろカスタマイズしていきたいです。
まず、2つやりたいことがあります。
1つ目は、複数のカメラを同時にライブビューしたい。一つのアプリ画面内で複数表示してもよいし、アプリを複数同時起動する、どちらでも構いません。
2つ目は、パソコンでライブビューしている動画を、パソコンに保存する機能をつけたいです。こちらは、最悪、パソコン画面のスクリーンショットを定期的に撮影することでまずは疑似的に、パソコンへの録画でも良いです。
何かしら、アドバイスやご提案頂けないでしょうか。全部、無料で教えてほしいという意図はないです。ご検討よろしくお願いします。
それはよかったです。
先にお伝えしておきますが、本記事の主旨はRTSPクライアントの使い方を理解し、簡単なビューアを作成することにあります。
したがって、カスタマイズやアプリケーションの拡張に関しては、ご自身で調査を進めていただくのが良いかと思います。
アプリケーションの拡張に関する返信はこれを最後にしてください。
以下で、上記の仕様は満足していると思います。
カメラ情報の管理
マルチビューアコード