在带有 C# 摘要的 IP 摄像机上使用 Onvif PTZ

问题描述 投票:0回答:0

我需要在 C# 模型中为网络摄像机模型 (HNP322-IR/32X) 使用 Onvif PTZ 功能

问题是相机使用带有 MD5 的 Digest WEB 身份验证(我很难找到)

我得出的唯一结论是使用 Onvif 遗传密码,因为没有公共图书馆作品。

我使用了这个类,它是迄今为止最可靠的代码,可能在 Internet 上的许多代码中工作

using ONVIFPTZControl.OnvifMedia10;
using ONVIFPTZControl.OnvifPTZService;
using System;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Security.Tokens;
using System.Text;
using System.Timers;
using System.Windows;

namespace ONVIFPTZControl
{
    public class Controller
    {
        private enum Direction { None, Up, Down, Left, Right };

        ONVIFPTZControl.OnvifMedia10.MediaClient mediaClient;
        PTZClient ptzClient;
        Profile profile;
        OnvifPTZService.PTZSpeed velocity;
        PTZVector vector;
        PTZConfigurationOptions options;
        bool relative = false;
        bool initialised = false;
        Timer timer;
        Direction direction;
        float panDistance;
        float tiltDistance;

        public string ErrorMessage { get; private set; }

        public bool Initialised { get { return initialised; } }

        public int PanIncrements { get; set; } = 20;

        public int TiltIncrements { get; set; } = 20;

        public double TimerInterval { get; set; } = 1500;

        public Controller(bool relative = false)
        {
            this.relative = relative;
        }

        public bool Initialise(string cameraAddress, string userName, string password)
        {
            bool result = false;

            try
            {
                var messageElement = new TextMessageEncodingBindingElement()
                {
                    MessageVersion = MessageVersion.CreateVersion(
                      EnvelopeVersion.Soap12, AddressingVersion.None)
                };
                HttpTransportBindingElement httpBinding = new HttpTransportBindingElement()
                {
                    AuthenticationScheme = AuthenticationSchemes.Digest
                };
                CustomBinding bind = new CustomBinding(messageElement, httpBinding);
                mediaClient = new ONVIFPTZControl.OnvifMedia10.MediaClient(bind,
                  new EndpointAddress($"http://{cameraAddress}/onvif/Media"));
                mediaClient.ClientCredentials.HttpDigest.AllowedImpersonationLevel =
                  System.Security.Principal.TokenImpersonationLevel.Impersonation;
                mediaClient.ClientCredentials.HttpDigest.ClientCredential.UserName = userName;
                mediaClient.ClientCredentials.HttpDigest.ClientCredential.Password = password;
                ptzClient = new PTZClient(bind,
                  new EndpointAddress($"http://{cameraAddress}/onvif/PTZ"));
                ptzClient.ClientCredentials.HttpDigest.AllowedImpersonationLevel =
                  System.Security.Principal.TokenImpersonationLevel.Impersonation;
                ptzClient.ClientCredentials.HttpDigest.ClientCredential.UserName = userName;
                ptzClient.ClientCredentials.HttpDigest.ClientCredential.Password = password;

                var profs = mediaClient.GetProfiles();
                profile = mediaClient.GetProfile(profs[0].token);

                var configs = ptzClient.GetConfigurations();

                options = ptzClient.GetConfigurationOptions(configs[0].token);

                velocity = new OnvifPTZService.PTZSpeed()
                {
                    PanTilt = new OnvifPTZService.Vector2D()
                    {
                        x = 0,
                        y = 0,
                        space = options.Spaces.ContinuousPanTiltVelocitySpace[0].URI,
                    },
                    Zoom = new OnvifPTZService.Vector1D()
                    {
                        x = 0,
                        space = options.Spaces.ContinuousZoomVelocitySpace[0].URI,
                    }
                };
                if (relative)
                {
                    timer = new Timer(TimerInterval);
                    timer.Elapsed += Timer_Elapsed;
                    velocity.PanTilt.space = options.Spaces.RelativePanTiltTranslationSpace[0].URI;
                    panDistance = (options.Spaces.RelativePanTiltTranslationSpace[0].XRange.Max -
                      options.Spaces.RelativePanTiltTranslationSpace[0].XRange.Min) / PanIncrements;
                    tiltDistance = (options.Spaces.RelativePanTiltTranslationSpace[0].YRange.Max -
                      options.Spaces.RelativePanTiltTranslationSpace[0].YRange.Min) / TiltIncrements;
                }

                vector = new PTZVector()
                {
                    PanTilt = new OnvifPTZService.Vector2D()
                    {
                        x = 0,
                        y = 0,
                        space = options.Spaces.RelativePanTiltTranslationSpace[0].URI
                    }
                };

                ErrorMessage = "";
                result = initialised = true;
            }
            catch (Exception ex)
            {
                ErrorMessage = ex.Message;
            }
            return result;
        }

        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            Move();
        }

        public void PanLeft()
        {
            if (initialised)
            {
                if (relative)
                {
                    direction = Direction.Left;
                    Move();
                }
                else
                {
                    velocity.PanTilt.x = options.Spaces.ContinuousPanTiltVelocitySpace[0].XRange.Min;
                    velocity.PanTilt.y = 0;
                    ptzClient.ContinuousMoveAsync(profile.token, velocity, "PT10S");
                }
            }
        }

        public void PanRight()
        {
            if (initialised)
            {
                if (relative)
                {
                    direction = Direction.Right;
                    Move();
                }
                else
                {
                    velocity.PanTilt.x = options.Spaces.ContinuousPanTiltVelocitySpace[0].XRange.Max;
                    velocity.PanTilt.y = 0;
                    ptzClient.ContinuousMoveAsync(profile.token, velocity, "PT10S");
                }
            }
        }

        public void TiltUp()
        {
            if (initialised)
            {
                if (relative)
                {
                    direction = Direction.Up;
                    Move();
                }
                else
                {
                    velocity.PanTilt.x = 0;
                    velocity.PanTilt.y = options.Spaces.ContinuousPanTiltVelocitySpace[0].YRange.Max;
                    ptzClient.ContinuousMoveAsync(profile.token, velocity, "PT10S");
                }
            }
        }

        public void TiltDown()
        {
            if (initialised)
            {
                if (relative)
                {
                    direction = Direction.Down;
                    Move();
                }
                else
                {
                    velocity.PanTilt.x = 0;
                    velocity.PanTilt.y = options.Spaces.ContinuousPanTiltVelocitySpace[0].YRange.Min;
                    ptzClient.ContinuousMoveAsync(profile.token, velocity, "PT10S");
                }
            }
        }

        public void Stop()
        {
            if (initialised)
            {
                if (relative)
                    timer.Enabled = false;
                direction = Direction.None;
                ptzClient.Stop(profile.token, true, true);
            }
        }

        private void Move()
        {
            bool move = true;

            switch (direction)
            {
                case Direction.Up:
                    velocity.PanTilt.x = 0;
                    velocity.PanTilt.y = options.Spaces.ContinuousPanTiltVelocitySpace[0].YRange.Max;
                    vector.PanTilt.x = 0;
                    vector.PanTilt.y = tiltDistance;
                    break;

                case Direction.Down:
                    velocity.PanTilt.x = 0;
                    velocity.PanTilt.y = options.Spaces.ContinuousPanTiltVelocitySpace[0].YRange.Max;
                    vector.PanTilt.x = 0;
                    vector.PanTilt.y = -tiltDistance;
                    break;

                case Direction.Left:
                    velocity.PanTilt.x = options.Spaces.ContinuousPanTiltVelocitySpace[0].XRange.Max;
                    velocity.PanTilt.y = 0;
                    vector.PanTilt.x = -panDistance;
                    vector.PanTilt.y = 0;
                    break;

                case Direction.Right:
                    velocity.PanTilt.x = options.Spaces.ContinuousPanTiltVelocitySpace[0].XRange.Max;
                    velocity.PanTilt.y = 0;
                    vector.PanTilt.x = panDistance;
                    vector.PanTilt.y = 0;
                    break;

                case Direction.None:
                default:
                    move = false;
                    break;
            }
            if (move)
            {
                ptzClient.RelativeMove(profile.token, vector, velocity);
            }
            timer.Enabled = true;
        }
    }
}

我还添加了两个在线服务:

1- http://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl

2- http://onvif.org/onvif/ver20/ptz/wsdl/ptz.wsdl

现在,在下面的函数中,我已经使用正确的凭据实施了摘要身份验证请求,因此我希望 PTZClient 获得授权和连接

public bool Initialise(string cameraAddress, string userName, string password)
        {
            bool result = false;

            try
            {
                var messageElement = new TextMessageEncodingBindingElement()
                {
                    MessageVersion = MessageVersion.CreateVersion(
                      EnvelopeVersion.Soap12, AddressingVersion.None)
                };
                HttpTransportBindingElement httpBinding = new HttpTransportBindingElement()
                {
                    AuthenticationScheme = AuthenticationSchemes.Digest
                };
                CustomBinding bind = new CustomBinding(messageElement, httpBinding);
                mediaClient = new ONVIFPTZControl.OnvifMedia10.MediaClient(bind,
                  new EndpointAddress($"http://{cameraAddress}/onvif/Media"));
                mediaClient.ClientCredentials.HttpDigest.AllowedImpersonationLevel =
                  System.Security.Principal.TokenImpersonationLevel.Impersonation;
                mediaClient.ClientCredentials.HttpDigest.ClientCredential.UserName = userName;
                mediaClient.ClientCredentials.HttpDigest.ClientCredential.Password = password;
                ptzClient = new PTZClient(bind,
                  new EndpointAddress($"http://{cameraAddress}/onvif/PTZ"));
                ptzClient.ClientCredentials.HttpDigest.AllowedImpersonationLevel =
                  System.Security.Principal.TokenImpersonationLevel.Impersonation;
                ptzClient.ClientCredentials.HttpDigest.ClientCredential.UserName = userName;
                ptzClient.ClientCredentials.HttpDigest.ClientCredential.Password = password;

                var profs = mediaClient.GetProfiles();
                profile = mediaClient.GetProfile(profs[0].token);

                var configs = ptzClient.GetConfigurations();

                options = ptzClient.GetConfigurationOptions(configs[0].token);

                velocity = new OnvifPTZService.PTZSpeed()
                {
                    PanTilt = new OnvifPTZService.Vector2D()
                    {
                        x = 0,
                        y = 0,
                        space = options.Spaces.ContinuousPanTiltVelocitySpace[0].URI,
                    },
                    Zoom = new OnvifPTZService.Vector1D()
                    {
                        x = 0,
                        space = options.Spaces.ContinuousZoomVelocitySpace[0].URI,
                    }
                };
                if (relative)
                {
                    timer = new Timer(TimerInterval);
                    timer.Elapsed += Timer_Elapsed;
                    velocity.PanTilt.space = options.Spaces.RelativePanTiltTranslationSpace[0].URI;
                    panDistance = (options.Spaces.RelativePanTiltTranslationSpace[0].XRange.Max -
                      options.Spaces.RelativePanTiltTranslationSpace[0].XRange.Min) / PanIncrements;
                    tiltDistance = (options.Spaces.RelativePanTiltTranslationSpace[0].YRange.Max -
                      options.Spaces.RelativePanTiltTranslationSpace[0].YRange.Min) / TiltIncrements;
                }

                vector = new PTZVector()
                {
                    PanTilt = new OnvifPTZService.Vector2D()
                    {
                        x = 0,
                        y = 0,
                        space = options.Spaces.RelativePanTiltTranslationSpace[0].URI
                    }
                };

                ErrorMessage = "";
                result = initialised = true;
            }
            catch (Exception ex)
            {
                ErrorMessage = ex.Message;
            }
            return result;
        }

但相反,我得到了这个回应

The HTTP request is unauthorized with client authentication scheme 'Digest'. The authentication header received from the server was 'Digest qop="auth", realm="IP Camera(G6877)", nonce="393038343a31363637303766363a51cd6c2b3d4f0a0b9942d2dfb810023b", stale="FALSE"'.

我知道 Digest auth 是这样工作的:

1-客户提出要求

2- 客户端从服务器取回随机数和 401 身份验证请求

3-客户端发回以下响应数组(用户名,领域,generate_md5_key(nonce,用户名,领域,URI,password_given_by_user_to_browser))(是的,这非常简单)

4- 服务器获取用户名和领域(加上它知道客户端请求的 URI)并查找该用户名的密码。然后它开始并执行自己版本的 generate_md5_key(nonce, username, realm, URI, password_I_have_for_this_user_in_my_db)

5- 它比较 generate_md5() 的输出和客户端发送的输出,如果它们匹配客户端发送的正确密码。如果它们不匹配,则发送的密码是错误的。

现在,我认为 ptzClient.GetConfigurations()mediaClient.GetProfiles() 只发送一次请求并且没有完成身份验证过程。

我什至尝试使用那个 repository 中的代码进行通用请求,但是我得到了这个错误

System.IO.IOException: The response ended prematurely

所以相机看起来甚至没有响应,因为第一个请求是一个普通的 GET 方法,它似乎只响应摘要身份验证请求,并且在这种情况下响应始终相同

The HTTP request is unauthorized with client authentication scheme 'Digest'. The authentication header received from the server was 'Digest qop="auth", realm="IP Camera(G6877)", nonce="393038343a31363637303766363a51cd6c2b3d4f0a0b9942d2dfb810023b", stale="FALSE"'.

所以,如果相机仅在摘要身份验证请求后才响应它,我应该如何获得具有所需领域和随机数的标头(由于我首先需要标头,因此不会获得授权)

过去两周我一直在想办法,但没有结果。

即使是 Postman 也应该处理这个并从第一个请求中提取信息,然后发送另一个授权请求,但它不会.

hash onvif digest-authentication http-digest
© www.soinside.com 2019 - 2024. All rights reserved.