STM32F407-LWIP-Onvif协议控制海康相机

STM32F407-Onvif协议控制海康相机

0.说在前面

使用STM32F4实现Onvif协议需要板子本身集成了以太网网口并且要有TCP/IP协议栈才行。这里使用的是基于正点原子的探索者开发板实现的,网络的库函数为lWIP。

1.原理介绍

要想使用STM32实现Onvif协议,则有关HTTP的许多的底层部分的工作都得自己来实现。下面介绍使用STM32F407实现一次完整的控制海康相机预置位转动的流程:
1. STM32给海康摄像头发送HTTP请求:

xml 复制代码
POST /onvif/PTZ HTTP/1.1
Host: 192.168.1.60:80
Content-Type: application/soap+xml; charset=utf-8
Content-Length: 361
 
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
xmlns:tptz="http://www.onvif.org/ver20/ptz/wsdl"
xmlns:sch="http://www.onvif.org/ver10/schema">
<soap:Body><tptz:GotoPreset>
<tptz:ProfileToken>Profile_1</tptz:ProfileToken>
<tptz:PresetToken>2</tptz:PresetToken>
</tptz:GotoPreset></soap:Body></soap:Envelope>

上部分为HTTP请求的请求头信息,下部分为所需要发送给海康的SOAP信息(即控制信息)。

2. 海康摄像头回复STM32:未登录,请进行登录,随后附上登录的相关加密信息:

xml 复制代码
HTTP/1.1 401 Unauthorized
Date: Fri, 02 Jan 1970 01:50:21 GMT
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Content-Length: 234
Content-Type: text/html
Connection: close
WWW-Authenticate: Digest qop="auth", realm="IP Camera(J0361)", nonce="4d7a566c5a5449334d574d364e5468694e6a4e695a6a6f784f5449754d5459344c6a45754d773d3d", stale="FALSE"
 
<!DOCTYPE html>
<html><head><title>Document Error: Unauthorized</title></head>
<body><h2>Access Error: 401 -- Unauthorized</h2>
<p>
Authentication Error: This onvif request requires authentication information
</p>
</body>
</html>

下面是回复的相关信息解释:

401:表示未登录

WWW-Authentication:用来定义使用何种方式(Basic、Digest、Bearer等)去进行认证以获取受保护的资源

realm:表示Web服务器中受保护文档的安全域(比如公司财务信息域和公司员工信息域),用来指示需要哪个域的用户名和密码

qop:保护质量,包含auth(默认的)和auth-int(增加了报文完整性检测)两种策略,(可以为空,但是)不推荐为空值

nonce:服务端向客户端发送质询时附带的一个随机数,这个数会经常发生变化。客户端计算密码摘要时将其附加上去,使得多次生成同一用户的密码摘要各不相同,用来防止重放攻击

nc:nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量。例如,在响应的第一个请求中,客户端将发送"nc=00000001"。这个指示值的目的是让服务器保持这个计数器的一个副本,以便检测重复的请求

cnonce:客户端随机数,这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护

response:这是由用户代理软件计算出的一个字符串,以证明用户知道口令

stale:当密码摘要使用的随机数过期时,服务器可以返回一个附带有新随机数的401响应,并指定stale=true,表示服务器在告知客户端用新的随机数来重试,而不再要求用户重新输入用户名和密码了

3. STM32将数据进行处理,并将处理后的结果回复给海康摄像头

xml 复制代码
POST /onvif/PTZ HTTP/1.1
Host: 192.168.1.60:80
Content-Type: application/soap+xml; charset=utf-8
Content-Length: 361
Authorization: Digest username="admin", realm="IP Camera(J0361)", 
nonce="4d7a566c5a5449334d574d364e5468694e6a4e695a6a6f784f5449754d5459344c6a45754d773d3d", 
uri="/onvif/PTZ", qop=auth, 
response="3918e51f2ddde47fa1d9a7f0c367330b", cnonce="f0afda84", nc=00000001
 
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
xmlns:tptz="http://www.onvif.org/ver20/ptz/wsdl"
xmlns:sch="http://www.onvif.org/ver10/schema">
<soap:Body><tptz:GotoPreset>
<tptz:ProfileToken>Profile_1</tptz:ProfileToken>
<tptz:PresetToken>3</tptz:PresetToken>
</tptz:GotoPreset></soap:Body></soap:Envelope>

其中,上部分为Digest登录认证信息,下部分为SOAP格式的数据。

认证信息的计算如下:

python 复制代码
response=MD5(MD5(username:realm:password):nonce:nc:cnonce:qop:MD5(<request-method>:url))

可以看到,response 算法里面有个 password 参数,浏览器通过弹框用户输入密码的值得到,服务器是通过用户名从数据库查到的。摘要验证主要就是通过上面的HASH比较的步骤避免掉了基本验证中的安全性问题。

而下面部分的SOAP数据大多为固定格式,其中:

(1). ProfileToken

xml 复制代码
<tptz:ProfileToken>Profile_1</tptz:ProfileToken>

海康摄像头所设置的ProfileToken,可在海康的配置页或使用Onvif Device Manager查到。

(2). PresetToken

xml 复制代码
<tptz:PresetToken>3</tptz:PresetToken>

设置海康跳转预置位至预置位3。

4. 海康收到数据,校验登录密文,若成功则返回信息并作出对应动作(即预置位跳转)

xml 复制代码
HTTP/1.1 200 OK
Date: Fri, 02 Jan 1970 01:50:23 GMT
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Content-Length: 2105
Connection: close
Content-Type: application/soap+xml; charset=utf-8
 
<?xml version="1.0" encoding="UTF-8"?>
<--此处忽略一些无关的onvif附带的版本链接信息等-->
<env:Body><tptz:GotoPresetResponse/>
</env:Body>
</env:Envelope>

其中:

http 复制代码
HTTP/1.1 200 OK

则表示登录成功并且已作出对应动作。

至此,一次控制的流程完毕。

2.Onvif驱动代码

这里直接给出驱动代码,注意:驱动代码包含了:

随机数生成

FLASH存取字符串

MD5码计算

如果有能力推荐自己修改,可省略的部分:FLASH存取字符串、随机数生成

onvif.c

c 复制代码
#include "onvif.h"

#include "lwip/tcp.h"
#include "lwip/ip_addr.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>
#include "myrandom.h"



/*
onvif.c

用于通过onvif协议控制海康摄像头

*/


//全局变量,用于存取摄像头的参数
uint8_t cam_ipa_out;
uint8_t cam_ipb_out;
uint8_t cam_ipc_out;
uint8_t cam_ipd_out;
uint16_t cam_port_out;
char cam_user_out[20];
char cam_pwd_out[20];
char ip_str[16];

//调试指令所需参数
int preset_num = 0;
float pan = 0;
float tilt = 0;
float zoom = 0;
float pan_speed = 0;
float tilt_speed = 0;
float zoom_speed = 0;

// 全局变量,用于存储接收到的数据
static char full_response[MAX_RESPONSE_LENGTH];
static int total_received = 0;
static int expected_length = 0;
static char realm[128] = {0};
static char nonce[128] = {0};
static char qop[16] = {0};
static char digest_response[33];
static char cnonce_nc_info[128];
uint8_t onvif_connect_flag = 0;
char nc[] = "00000000";



// 初始化函数实现
void camera_config_init(void)
{
	//将FLASH中存储的ip信息合并为字符串来使用
	snprintf(ip_str, sizeof(ip_str), "%d.%d.%d.%d", 
			 cam_ipa_out, cam_ipb_out, cam_ipc_out, cam_ipd_out);
}


//请求次数nc自增函数
void nc_addself(void) 
{
    int carry = 1;  // 初始进位为1(表示要加1)
    
    // 从右到左逐位处理
    for (int i = 7; i >= 0; i--) {
        if (carry == 0) break;  // 无进位,提前结束
        
        if (nc[i] == '9') {
            // 当前位是9,加1后变为0,继续进位
            nc[i] = '0';
            carry = 1;
        } else {
            // 当前位不是9,直接加1,进位清零
            nc[i]++;
            carry = 0;
        }
    }
    
    // 如果循环结束后仍有进位(例如原值为"99999999"),重置为"00000000"
    // 根据需求,也可以选择不处理,保留全0
	

	
}



void variable_Init(void)
{
	memset(full_response, 0, MAX_RESPONSE_LENGTH);
	total_received = 0;
	expected_length = 0;
	memset(realm, 0, 128);
	memset(nonce, 0, 128);
	memset(qop, 0, 16);
	memset(digest_response, 0, 33);
	memset(cnonce_nc_info, 0, 128);
	onvif_connect_flag = 0;
	
	//随机数初始化
	RNG_Init();
}


// 计算 MD5 哈希值
void md5_hash(const char *input, char *output) {
    MD5_CTX md5c;
    unsigned char decrypt[16];  // 存放 MD5 计算结果
    char temp[8] = {0};

    // 初始化 MD5 上下文
    MD5Init(&md5c);
    
    // 更新 MD5 上下文,将输入数据加入计算
    int read_len = strlen(input);
    MD5Update(&md5c, (unsigned char *)input, read_len);
    
    // 完成 MD5 计算,得到最终结果
    MD5Final(&md5c, decrypt);
    
    // 将二进制的 MD5 结果转换为十六进制字符串
    output[0] = '\0';  // 清空输出字符串
    for (int i = 0; i < 16; i++) {
        sprintf(temp, "%02x", decrypt[i]);
        strcat(output, temp);
    }
}


// 生成8位十六进制小写的cnonce
void generate_cnonce(char *cnonce) {
    const char hex_chars[] = "0123456789abcdef"; // 十六进制字符集
    generate_random_string(cnonce, 8, hex_chars);
}


//计算MD5码
void calculate_digest_response(const char *username, const char *password, const char *realm, const char *nonce, const char *qop, const char *method, const char *uri, char *response, char *cnonce_nc_info) {
    char ha1[33];
    char ha2[33];
    char final_hash[33];
	
	memset(ha1, 0, 33);
	memset(ha2, 0, 33);
	memset(final_hash, 0, 33);

    // 计算 HA1 = MD5(username:realm:password)
    char ha1_input[256];
    snprintf(ha1_input, sizeof(ha1_input), "%s:%s:%s", username, realm, password);
    md5_hash(ha1_input, ha1);
    //printf("HA1: %s\r\n", ha1);  // 调试信息
    
    // 计算 HA2 = MD5(method:uri)
    char ha2_input[256];
    snprintf(ha2_input, sizeof(ha2_input), "%s:%s", method, uri);
    md5_hash(ha2_input, ha2);
    //printf("HA2: %s\r\n", ha2);  // 调试信息
    
    char cnonce[15];
    generate_cnonce(cnonce);
    

    char final_input[512];
	
	nc_addself();
    snprintf(final_input, sizeof(final_input), "%s:%s:%s:%s:%s:%s", ha1, nonce, nc, cnonce, qop, ha2);
    md5_hash(final_input, final_hash);
    sprintf(cnonce_nc_info, ", cnonce=\"%s\", nc=%s", cnonce, nc);

    //printf("Final Hash: %s\r\n", final_hash);  // 调试信息
    //printf("cnonce_nc_info: %s\r\n", cnonce_nc_info);  // 打印 cnonce_nc_info 用于调试

    strcpy(response, final_hash);
}

// 解析 WWW-Authenticate 头部字段
uint8_t parse_www_authenticate(const char *header, char *realm, char *nonce, char *qop) {
    const char *www_auth_start = strstr(header, "WWW-Authenticate: ");
    if (www_auth_start == NULL) {
        printf("Error: WWW-Authenticate header not found.\n");
        return 0;
    }
    www_auth_start += 18; // 跳过 "WWW-Authenticate: "

    const char *realm_start = strstr(www_auth_start, "realm=\"");
    const char *nonce_start = strstr(www_auth_start, "nonce=\"");
    const char *qop_start = strstr(www_auth_start, "qop=\"");
    
    if (realm_start != NULL) {
        realm_start += 7; // 跳过 "realm=\""
        const char *realm_end = strchr(realm_start, '"');
        if (realm_end != NULL) {
            strncpy(realm, realm_start, realm_end - realm_start);
            realm[realm_end - realm_start] = '\0';
        }
    }
    
    if (nonce_start != NULL) {
        nonce_start += 7; // 跳过 "nonce=\""
        const char *nonce_end = strchr(nonce_start, '"');
        if (nonce_end != NULL) {
            strncpy(nonce, nonce_start, nonce_end - nonce_start);
            nonce[nonce_end - nonce_start] = '\0';
        }
    }
    
    if (qop_start != NULL) {
        qop_start += 5; // 跳过 "qop=\""
        const char *qop_end = strchr(qop_start, '"');
        if (qop_end != NULL) {
            strncpy(qop, qop_start, qop_end - qop_start);
            qop[qop_end - qop_start] = '\0';
        }
    }
	
	return 1;
}

// 构造 SOAP 请求
void build_absolute_move_soap_request(char *request, float pan, float tilt, float zoom, float pan_speed, float tilt_speed, float zoom_speed) {
    snprintf(request, MAX_SOAP_REQUEST_LENGTH, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
                                               "<soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" "
                                               "xmlns:tptz=\"http://www.onvif.org/ver20/ptz/wsdl\" "
                                               "xmlns:sch=\"http://www.onvif.org/ver10/schema\">"
                                               "<soap:Body>"
                                               "<tptz:AbsoluteMove>"
                                               "<tptz:ProfileToken>%s</tptz:ProfileToken>"
                                               "<tptz:Position>"
                                               "<sch:PanTilt x=\"%.2f\" y=\"%.2f\"/>"
                                               "<sch:Zoom>%.2f</sch:Zoom>"
                                               "</tptz:Position>"
                                               "<tptz:Speed>"
                                               "<sch:PanTilt x=\"%.2f\" y=\"%.2f\"/>"
                                               "<sch:Zoom>%.2f</sch:Zoom>"
                                               "</tptz:Speed>"
                                               "</tptz:AbsoluteMove>"
                                               "</soap:Body>"
                                               "</soap:Envelope>",
               PROFILE_TOKEN, pan, tilt, zoom, pan_speed, tilt_speed, zoom_speed);
}


// 构造预置位跳转的SOAP请求
void build_goto_preset_soap_request(char *request, uint8_t preset_token) {
    snprintf(request, MAX_SOAP_REQUEST_LENGTH, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
                                               "<soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" "
                                               "xmlns:tptz=\"http://www.onvif.org/ver20/ptz/wsdl\" "
                                               "xmlns:sch=\"http://www.onvif.org/ver10/schema\">"
                                               "<soap:Body>"
                                               "<tptz:GotoPreset>"
                                               "<tptz:ProfileToken>%s</tptz:ProfileToken>"
                                               "<tptz:PresetToken>%d</tptz:PresetToken>"
                                               "</tptz:GotoPreset>"
                                               "</soap:Body>"
                                               "</soap:Envelope>",
               PROFILE_TOKEN, preset_token);
}



// 新增用于处理认证请求的连接回调函数
static err_t connected_login_callback(void *arg, struct tcp_pcb *tpcb, err_t err) {
    if (err != ERR_OK) {
        printf("Login connection failed: %d\n", err);
        return err;
    }

    char *soap_request = (char *)arg;
    char http_request[MAX_SOAP_REQUEST_LENGTH + 512]; // 增加缓冲区大小
    int request_length = strlen(soap_request);

    // 计算Digest认证参数
    calculate_digest_response(USERNAME, CAMPASSWORD, realm, nonce, qop, "POST", ONVIF_PATH, digest_response, cnonce_nc_info);

    // 构造带认证头的HTTP请求
    snprintf(http_request, sizeof(http_request),
        "POST %s HTTP/1.1\r\n"
        "Host: %s:%d\r\n"
        "Content-Type: application/soap+xml; charset=utf-8\r\n"
        "Content-Length: %d\r\n"
        "Authorization: Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", qop=%s, response=\"%s\"%s\r\n"
        "Connection: close\r\n" // 明确关闭连接
        "\r\n"
        "%s",
        ONVIF_PATH, CAMERA_IP_ADDR, CAMERA_PORT, request_length, 
        USERNAME, realm, nonce, ONVIF_PATH, qop, digest_response, cnonce_nc_info, 
        soap_request);

    // 发送请求
	//printf("reply_data:\r\n%s\r\n",http_request);
    err = tcp_write(tpcb, http_request, strlen(http_request), TCP_WRITE_FLAG_COPY);
	//延时100毫秒
	OS_ERR err_os;
	OSTimeDlyHMSM(0,0,0,100,OS_OPT_TIME_HMSM_STRICT,&err_os);
	onvif_connect_flag = 1;
	//发送完成后,关闭TCP连接,释放资源
	tcp_close(tpcb);
	
    if (err != ERR_OK) {
        printf("Failed to send login request: %d\n", err);
        tcp_close(tpcb);
        return err;
    }
    tcp_output(tpcb);
    return ERR_OK;
}


// 接收回调函数
static err_t recv_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) {
    if (p != NULL) {
        int recv_len = p->tot_len;

        // 检查是否有足够的空间存储接收到的数据
        if (total_received + recv_len > MAX_RESPONSE_LENGTH) {
            printf("Received data exceeds the maximum buffer size.\r\r\n");
            // 释放 pbuf
            pbuf_free(p);
            return ERR_MEM;
        }

        // 将接收到的数据复制到 full_response 中
        memcpy(full_response + total_received, p->payload, recv_len);
        total_received += recv_len;

        // 释放 pbuf
        pbuf_free(p);

        if (expected_length == 0) {
            // 解析 HTTP 头部的 Content-Length 字段
            char *content_length_str = strstr(full_response, "Content-Length: ");
            if (content_length_str != NULL) {
                content_length_str += strlen("Content-Length: ");
                expected_length = atoi(content_length_str);
            }
        }

        // 判断数据是否接收完成
        if (expected_length > 0 && total_received >= expected_length) {
            // 数据接收完成,显示接收到的数据
            full_response[total_received] = '\0'; // 添加字符串结束符
            //printf("Received HTTP_data:\r\n%s\r\r\n", full_response);
			
			// 重置全局变量
			total_received = 0;
			expected_length = 0;

            // 判断及接收回复的数据,计划进行再次请求
            // 判断是否为401
			if (strstr(full_response, "HTTP/1.1 401 Unauthorized") != NULL) {
				parse_www_authenticate(full_response, realm, nonce, qop);
				// 解析服务器参数
				if(parse_www_authenticate(full_response, realm, nonce, qop) == 1)
				{
					// 关闭当前连接
					tcp_close(tpcb);
					

					// 创建新连接并重新发送认证请求
					struct tcp_pcb *new_pcb = tcp_new();
					tcp_arg(new_pcb, arg);
					tcp_recv(new_pcb, recv_callback);
					tcp_err(new_pcb, err_callback);

					ip_addr_t camera_ip;
					ipaddr_aton(CAMERA_IP_ADDR, &camera_ip);
					if (tcp_connect(new_pcb, &camera_ip, CAMERA_PORT, connected_login_callback) != ERR_OK) {
						printf("Failed to reconnect for authentication\n");
						tcp_abort(new_pcb);
					}
				}else
				{
					// 关闭当前连接
					tcp_close(tpcb);
					printf("close_connect");
					return err;
				}
			}

        }

        return ERR_OK;
    } else if (err == ERR_OK) {
        // 对方关闭连接
        if (total_received > 0) {
            // 显示接收到的数据
            full_response[total_received] = '\0'; // 添加字符串结束符
            printf("Received data\r\n");

            // 重置全局变量
            total_received = 0;
            expected_length = 0;
        }
        // 关闭 TCP 连接
        tcp_close(tpcb);
		// 重置全局变量
		total_received = 0;
		expected_length = 0;
        return ERR_OK;
    }
	// 重置全局变量
	total_received = 0;
	expected_length = 0;
    return err;
}

// 错误回调函数
static void err_callback(void *arg, err_t err) {
    printf("TCP error: %d\n", err);
}

// 连接成功回调函数
static err_t connected_callback(void *arg, struct tcp_pcb *tpcb, err_t err) {
    if (err == ERR_OK) {
        char *soap_request = (char *)arg;
        char http_request[MAX_SOAP_REQUEST_LENGTH + 256];
        int request_length = strlen(soap_request);

        // 构造 HTTP 请求
        snprintf(http_request, sizeof(http_request), "POST %s HTTP/1.1\r\n"
                                                    "Host: %s:%d\r\n"
                                                    "Content-Type: application/soap+xml; charset=utf-8\r\n"
                                                    "Content-Length: %d\r\n"
                                                    "\r\n"
                                                    "%s",
                 ONVIF_PATH, CAMERA_IP_ADDR, CAMERA_PORT, request_length, soap_request);

        // 发送请求
        err = tcp_write(tpcb, http_request, strlen(http_request), TCP_WRITE_FLAG_COPY);
        //printf("Send_HTTP_request:\r%s\r\r\n",http_request);
        if (err != ERR_OK) {
            printf("Failed to write data: %d\n", err);
            tcp_close(tpcb);
            return err;
        }
        err = tcp_output(tpcb);
        if (err != ERR_OK) {
            printf("Failed to output data: %d\n", err);
            tcp_close(tpcb);
			// 使用完后释放内存
            return err;
        }
    }
    return err;
}

//坐标跳转函数
uint8_t onvif_absolute_move(void)
{
	camera_config_init();
    char *soap_request;
	
    // 在合适的位置进行内存分配
    soap_request = (char *)mymalloc(SRAMIN,MAX_SOAP_REQUEST_LENGTH);
    if (soap_request == NULL) {
        // 内存分配失败处理
        printf("soap_request_malloc_fiald\r\n");
    }
	memset(soap_request,0,MAX_SOAP_REQUEST_LENGTH);
	
	//初始化各项参数
	variable_Init();

    struct tcp_pcb *tpcb;
    ip_addr_t camera_ip;

    // 构造 SOAP 请求
    build_absolute_move_soap_request(soap_request, pan, tilt, zoom, pan_speed, tilt_speed, zoom_speed);
    //printf("soap_request_text:%s\r\r\n",soap_request);

    // 创建 TCP 控制块
    tpcb = tcp_new();
    if (tpcb == NULL) 
    {
        printf("Failed to create TCP PCB.\n");
        return 0;
    }

    // 设置回调函数
    tcp_arg(tpcb, soap_request);
    tcp_recv(tpcb, recv_callback);
    tcp_err(tpcb, err_callback);

    // 解析相机 IP 地址
    ipaddr_aton(CAMERA_IP_ADDR, &camera_ip);

    // 连接到相机
    err_t err = tcp_connect(tpcb, &camera_ip, CAMERA_PORT, connected_callback);
    if (err != ERR_OK) {
        printf("Failed to connect to camera: %d\n", err);
        tcp_abort(tpcb);
        return 0;
    }
	OS_ERR err_os;
	while(1)
	{
		if(onvif_connect_flag == 1)
		{
			//关闭所有资源
			pan = 0, tilt = 0, zoom = 0, pan_speed = 0, tilt_speed = 0, zoom_speed = 0;
			variable_Init();
			myfree(SRAMIN,soap_request);
			tcp_close(tpcb);
			break;
		}
		else
			OSTimeDlyHMSM(0,0,0,50,OS_OPT_TIME_HMSM_STRICT,&err_os);
	}
	return 1;
}

//转动到预置位功能
uint8_t onvif_goto_preset(void)
{
	camera_config_init();
    char *soap_request;
	
    // 在合适的位置进行内存分配
    soap_request = (char *)mymalloc(SRAMIN,MAX_SOAP_REQUEST_LENGTH);
    if (soap_request == NULL) {
        // 内存分配失败处理
        printf("soap_request_malloc_fiald\r\n");
    }
	memset(soap_request,0,MAX_SOAP_REQUEST_LENGTH);
	
	//初始化各项全局变量参数,防止二次使用时出错
	variable_Init();

    struct tcp_pcb *tpcb;
    ip_addr_t camera_ip;

    // 构造 SOAP 请求
    build_goto_preset_soap_request(soap_request,preset_num);
    //printf("soap_request_text:%s\r\r\n",soap_request);

    // 创建 TCP 控制块
    tpcb = tcp_new();
    if (tpcb == NULL) 
    {
        printf("Failed to create TCP PCB.\n");
        return 0;
    }

    // 设置回调函数
    tcp_arg(tpcb, soap_request);
    tcp_recv(tpcb, recv_callback);
    tcp_err(tpcb, err_callback);

    // 解析相机 IP 地址
    ipaddr_aton(CAMERA_IP_ADDR, &camera_ip);

    // 连接到相机
    err_t err = tcp_connect(tpcb, &camera_ip, CAMERA_PORT, connected_callback);
    if (err != ERR_OK) {
        printf("Failed to connect to camera: %d\n", err);
        tcp_abort(tpcb);
        return 0;
    }
	OS_ERR err_os;
	while(1)
	{
		if(onvif_connect_flag == 1)
		{
			//关闭所有资源
			preset_num = 0;
			variable_Init();
			myfree(SRAMIN,soap_request);
			tcp_close(tpcb);
			break;
		}
		else
			OSTimeDlyHMSM(0,0,0,50,OS_OPT_TIME_HMSM_STRICT,&err_os);
	}
	return 1;
}




//预置位任务
#define Onvif_move_TASK_PRIO		5
//任务堆栈大小
#define Onvif_move_TASK_SIZE	256
//任务控制块
OS_TCB	Onvif_move_TASK_TCB;
//任务堆栈
CPU_STK Onvif_move_TASK_STK[Onvif_move_TASK_SIZE];


INT8U onvif_move_task(void)
{
	OS_ERR err;
	CPU_SR_ALLOC();
	
	OS_CRITICAL_ENTER();//进入临界区
	//创建TCP客户端任务
	OSTaskCreate((OS_TCB 	* )&Onvif_move_TASK_TCB,		
				 (CPU_CHAR	* )"onvif_move_task", 		
                 (OS_TASK_PTR )onvif_absolute_move, 			
                 (void		* )0,					
                 (OS_PRIO	  )Onvif_move_TASK_PRIO,     
                 (CPU_STK   * )&Onvif_move_TASK_STK[0],	
                 (CPU_STK_SIZE)Onvif_move_TASK_SIZE/10,	
                 (CPU_STK_SIZE)Onvif_move_TASK_SIZE,		
                 (OS_MSG_QTY  )0,					
                 (OS_TICK	  )0,					
                 (void   	* )0,					
                 (OS_OPT      )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
                 (OS_ERR 	* )&err);
	OS_CRITICAL_EXIT();	//退出临界区
	return err;
}


//坐标点任务
#define Onvif_preset_TASK_PRIO		5
//任务堆栈大小
#define Onvif_preset_TASK_SIZE	256
//任务控制块
OS_TCB	Onvif_preset_TASK_TCB;
//任务堆栈
CPU_STK Onvif_preset_TASK_STK[Onvif_preset_TASK_SIZE];


INT8U onvif_goto_preset_task(void)
{
	OS_ERR err;
	CPU_SR_ALLOC();
	
	OS_CRITICAL_ENTER();//进入临界区
	//创建TCP客户端任务
	OSTaskCreate((OS_TCB 	* )&Onvif_preset_TASK_TCB,		
				 (CPU_CHAR	* )"onvif_goto_preset_task", 		
                 (OS_TASK_PTR )onvif_goto_preset, 			
                 (void		* )0,					
                 (OS_PRIO	  )Onvif_preset_TASK_PRIO,     
                 (CPU_STK   * )&Onvif_preset_TASK_STK[0],	
                 (CPU_STK_SIZE)Onvif_preset_TASK_SIZE/10,	
                 (CPU_STK_SIZE)Onvif_preset_TASK_SIZE,		
                 (OS_MSG_QTY  )0,					
                 (OS_TICK	  )0,					
                 (void   	* )0,					
                 (OS_OPT      )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
                 (OS_ERR 	* )&err);
	OS_CRITICAL_EXIT();	//退出临界区
	return err;
}

onvif.h

c 复制代码
#ifndef __ONVIF_H__
#define __ONVIF_H__

#include "md5.h"
#include "main.h"
#include <lwip/sockets.h>
#include <lwip/inet.h>
#include <stdio.h>
#include <string.h>

// 摄像头信息
//#define CAMERA_IP_ADDR "192.168.1.60"
//#define CAMERA_PORT 80
//#define ONVIF_PATH "/onvif/PTZ"
//#define PROFILE_TOKEN "Profile_1"
//#define USERNAME "admin"
//#define CAMPASSWORD "asdqwe123"

#define CAMERA_IP_ADDR ip_str
#define CAMERA_PORT cam_port_out
#define ONVIF_PATH "/onvif/PTZ"
#define PROFILE_TOKEN "Profile_1"
#define USERNAME cam_user_out
#define CAMPASSWORD cam_pwd_out

#define MAX_SOAP_REQUEST_LENGTH  1024
#define MAX_RESPONSE_LENGTH		 2048


#include <stdint.h>
#include <stddef.h>

// IP地址拆分存储
extern uint8_t cam_ipa_out;  // 192
extern uint8_t cam_ipb_out;  // 168
extern uint8_t cam_ipc_out;  // 1
extern uint8_t cam_ipd_out;  // 60
extern char ip_str[16];		//合并后的参数

// 端口号(建议改为uint16_t)
extern uint16_t cam_port_out;  // 80

// 登录凭证(带长度限制)
extern char cam_user_out[20];   // "admin"
extern char cam_pwd_out[20];    // "asdqwe123"

//任务传递变量
extern int preset_num;
extern float pan;
extern float tilt;
extern float zoom;
extern float pan_speed;
extern float tilt_speed;
extern float zoom_speed;

extern char full_response[MAX_RESPONSE_LENGTH];
extern int total_received;
extern int expected_length;
extern char realm[128];
extern char nonce[128];
extern char qop[16];
extern char digest_response[33];
extern char cnonce_nc_info[128];
extern uint8_t onvif_connect_flag;
extern char nc[];


// 初始化函数
void camera_config_init(void);

//请求次数nc自增函数
void nc_addself(void);


//初始化变量函数
void variable_Init(void);


// 计算 MD5 哈希值
void md5_hash(const char *input, char *output);

// 生成8位十六进制小写的cnonce
void generate_cnonce(char *cnonce);

//计算MD5码
void calculate_digest_response(const char *username, const char *password, const char *realm, const char *nonce, const char *qop, const char *method, const char *uri, char *response, char *cnonce_nc_info);

// 解析 WWW-Authenticate 头部字段
uint8_t parse_www_authenticate(const char *header, char *realm, char *nonce, char *qop);

// 构造 SOAP 请求
void build_absolute_move_soap_request(char *request, float pan, float tilt, float zoom, float pan_speed, float tilt_speed, float zoom_speed);



// 构造预置位跳转的SOAP请求
void build_goto_preset_soap_request(char *request, uint8_t preset_token);

// 新增用于处理认证请求的连接回调函数
static err_t connected_login_callback(void *arg, struct tcp_pcb *tpcb, err_t err);



// 接收回调函数
static err_t recv_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err);

// 错误回调函数
static void err_callback(void *arg, err_t err);

// 连接成功回调函数
static err_t connected_callback(void *arg, struct tcp_pcb *tpcb, err_t err);


//坐标跳转函数
uint8_t onvif_absolute_move(void);

//转动到预置位功能
uint8_t onvif_goto_preset(void);


//功能测试函数
void onvif_main(void);

//转动到预置位的创建任务函数
INT8U onvif_goto_preset_task(void);

//坐标跳转的创建任务函数
INT8U onvif_move_task(void);

#endif
相关推荐
1+α2 小时前
汽车里的“神经网络”——CAN总线科普
c语言·stm32·嵌入式硬件·信息与通信
Amrzs_hp10 小时前
stm32温度采集
stm32·单片机·嵌入式硬件
鄭郑14 小时前
STM32学习笔记--I2C封装与OLED(2026.2.1)
笔记·stm32·学习
想放学的刺客15 小时前
单片机嵌入式试题(第29期)嵌入式系统的电源完整性设计与去耦电容选型。抗干扰设计与EMC合规性
c语言·stm32·嵌入式硬件·物联网·51单片机
-Springer-16 小时前
STM32 学习 —— 个人学习笔记2-2(新建工程)
笔记·stm32·学习
代码游侠17 小时前
学习笔记——Linux字符设备驱动开发
linux·arm开发·驱动开发·单片机·嵌入式硬件·学习·算法
czhaii17 小时前
STC32G.H中文注释各寄存器特殊功能寄存器作用
单片机·嵌入式硬件
码农三叔17 小时前
(9-3)电源管理与能源系统:充电与扩展能源方案
人工智能·嵌入式硬件·机器人·能源·人形机器人
集芯微电科技有限公司18 小时前
15V/2A同步开关型降压单节/双节锂电池充电管理IC支持输入适配器 DPM 功能
c语言·开发语言·stm32·单片机·嵌入式硬件·电脑