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