|
@@ -1,6 +1,6 @@
|
|
|
/**
|
|
/**
|
|
|
* PTZ 云台控制 API
|
|
* PTZ 云台控制 API
|
|
|
- * 支持海康威视 ISAPI 协议
|
|
|
|
|
|
|
+ * 通过独立的 PTZ 后端服务调用海康威视 ISAPI 协议
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
export interface PTZConfig {
|
|
export interface PTZConfig {
|
|
@@ -15,6 +15,9 @@ export interface PTZDirection {
|
|
|
tilt: number // -100 ~ 100, 负下正上
|
|
tilt: number // -100 ~ 100, 负下正上
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// PTZ 后端服务地址
|
|
|
|
|
+const PTZ_API_BASE = 'http://localhost:3002'
|
|
|
|
|
+
|
|
|
// 方向预设值
|
|
// 方向预设值
|
|
|
export const PTZ_DIRECTIONS = {
|
|
export const PTZ_DIRECTIONS = {
|
|
|
UP: { pan: 0, tilt: 50 },
|
|
UP: { pan: 0, tilt: 50 },
|
|
@@ -28,52 +31,69 @@ export const PTZ_DIRECTIONS = {
|
|
|
STOP: { pan: 0, tilt: 0 }
|
|
STOP: { pan: 0, tilt: 0 }
|
|
|
} as const
|
|
} as const
|
|
|
|
|
|
|
|
-/**
|
|
|
|
|
- * 生成 Basic Auth Header
|
|
|
|
|
- */
|
|
|
|
|
-function createBasicAuth(username: string, password: string): string {
|
|
|
|
|
- const credentials = btoa(`${username}:${password}`)
|
|
|
|
|
- return `Basic ${credentials}`
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-/**
|
|
|
|
|
- * 生成 PTZ 控制 XML
|
|
|
|
|
- */
|
|
|
|
|
-function createPTZXml(direction: PTZDirection): string {
|
|
|
|
|
- return `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
-<PTZData>
|
|
|
|
|
- <pan>${direction.pan}</pan>
|
|
|
|
|
- <tilt>${direction.tilt}</tilt>
|
|
|
|
|
-</PTZData>`
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
/**
|
|
/**
|
|
|
* 发送 PTZ 控制命令
|
|
* 发送 PTZ 控制命令
|
|
|
- * 通过 Vite 代理转发请求,避免 CORS 问题
|
|
|
|
|
|
|
+ * 通过独立的 PTZ 后端服务,支持 Digest Auth
|
|
|
*/
|
|
*/
|
|
|
export async function sendPTZCommand(
|
|
export async function sendPTZCommand(
|
|
|
config: PTZConfig,
|
|
config: PTZConfig,
|
|
|
direction: PTZDirection
|
|
direction: PTZDirection
|
|
|
): Promise<{ success: boolean; error?: string }> {
|
|
): Promise<{ success: boolean; error?: string }> {
|
|
|
- const channel = config.channel || 1
|
|
|
|
|
- // 开发环境: 使用 /camera-proxy 代理 (vite.config.ts 配置固定 IP)
|
|
|
|
|
- // 生产环境: 需要通过后端 API 代理
|
|
|
|
|
- const url = `/camera-proxy/ISAPI/PTZCtrl/channels/${channel}/continuous`
|
|
|
|
|
-
|
|
|
|
|
try {
|
|
try {
|
|
|
- const response = await fetch(url, {
|
|
|
|
|
- method: 'PUT',
|
|
|
|
|
|
|
+ const response = await fetch(`${PTZ_API_BASE}/ptz/control`, {
|
|
|
|
|
+ method: 'POST',
|
|
|
headers: {
|
|
headers: {
|
|
|
- 'Content-Type': 'application/xml',
|
|
|
|
|
- Authorization: createBasicAuth(config.username, config.password)
|
|
|
|
|
|
|
+ 'Content-Type': 'application/json'
|
|
|
},
|
|
},
|
|
|
- body: createPTZXml(direction)
|
|
|
|
|
|
|
+ body: JSON.stringify({
|
|
|
|
|
+ host: config.host,
|
|
|
|
|
+ username: config.username,
|
|
|
|
|
+ password: config.password,
|
|
|
|
|
+ channel: config.channel || 1,
|
|
|
|
|
+ pan: direction.pan,
|
|
|
|
|
+ tilt: direction.tilt,
|
|
|
|
|
+ zoom: 0
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- if (response.ok) {
|
|
|
|
|
|
|
+ const data = await response.json()
|
|
|
|
|
+
|
|
|
|
|
+ if (data.code === 200) {
|
|
|
return { success: true }
|
|
return { success: true }
|
|
|
} else {
|
|
} else {
|
|
|
- return { success: false, error: `HTTP ${response.status}` }
|
|
|
|
|
|
|
+ return { success: false, error: data.msg || 'Unknown error' }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ return { success: false, error: String(error) }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 获取 PTZ 状态
|
|
|
|
|
+ */
|
|
|
|
|
+export async function getPTZStatus(
|
|
|
|
|
+ config: PTZConfig
|
|
|
|
|
+): Promise<{ success: boolean; data?: string; error?: string }> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await fetch(`${PTZ_API_BASE}/ptz/status`, {
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ 'Content-Type': 'application/json'
|
|
|
|
|
+ },
|
|
|
|
|
+ body: JSON.stringify({
|
|
|
|
|
+ host: config.host,
|
|
|
|
|
+ username: config.username,
|
|
|
|
|
+ password: config.password,
|
|
|
|
|
+ channel: config.channel || 1
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const data = await response.json()
|
|
|
|
|
+
|
|
|
|
|
+ if (data.code === 200) {
|
|
|
|
|
+ return { success: true, data: data.data?.raw }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return { success: false, error: data.msg || 'Unknown error' }
|
|
|
}
|
|
}
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
return { success: false, error: String(error) }
|
|
return { success: false, error: String(error) }
|