 fc62a966db
			
		
	
	fc62a966db
	
	
	
		
			
			2. 增加 NET_DVR_FindNextFile_V30 函数 3. 增加 NET_DVR_FindFile_V30 函数 4. 增加 新的 Go 数据模型 5. 规范化 Go 数据模型 6. 调整部分的 C 头文件中的结构体,以修复 CGo 无法识别函数参数类型的问题(https://stackoverflow.com/questions/59353668/get-the-struct-from-c-to-golang) 7. 简化球机退出登陆 8. 实现 NVR 相关接口 9. 编写 NVR 接口测试用例
		
			
				
	
	
		
			435 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			435 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package HikSDK
 | |
| 
 | |
| /*
 | |
| #cgo LDFLAGS: -Wl,--allow-multiple-definition
 | |
| #include <stdio.h>
 | |
| #include <string.h>
 | |
| */
 | |
| import "C"
 | |
| import (
 | |
| 	"encoding/binary"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"gitea.com/kunmeng/HikNetSDKPkg/Core"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 	"unsafe"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	Base      = 0
 | |
| 	BuKongQiu = 1
 | |
| )
 | |
| 
 | |
| type PTZEnumObj struct {
 | |
| 	PTZ_LEFT       int
 | |
| 	PTZ_RIGHT      int
 | |
| 	PTZ_UP         int
 | |
| 	PTZ_DOWN       int
 | |
| 	PTZ_UP_LEFT    int
 | |
| 	PTZ_UP_RIGHT   int
 | |
| 	PTZ_DOWN_LEFT  int
 | |
| 	PTZ_DOWN_RIGHT int
 | |
| 	PTZ_ZOOM_IN    int
 | |
| 	PTZ_ZOOM_OUT   int
 | |
| 	PTZ_Focus_Far  int
 | |
| 	PTZ_Focus_Near int
 | |
| }
 | |
| 
 | |
| func (receiver *PTZEnumObj) toHikPTZEnum(v int) int {
 | |
| 	switch v {
 | |
| 	case PTZEnum.PTZ_LEFT:
 | |
| 		return HikPTZEnum.PAN_LEFT
 | |
| 	case PTZEnum.PTZ_RIGHT:
 | |
| 		return HikPTZEnum.PAN_RIGHT
 | |
| 	case PTZEnum.PTZ_UP:
 | |
| 		return HikPTZEnum.TILT_UP
 | |
| 	case PTZEnum.PTZ_DOWN:
 | |
| 		return HikPTZEnum.TILT_DOWN
 | |
| 	case PTZEnum.PTZ_ZOOM_IN:
 | |
| 		return HikPTZEnum.ZOOM_IN
 | |
| 	case PTZEnum.PTZ_ZOOM_OUT:
 | |
| 		return HikPTZEnum.ZOOM_OUT
 | |
| 	case PTZEnum.PTZ_Focus_Far:
 | |
| 		return HikPTZEnum.FOCUS_FAR
 | |
| 	case PTZEnum.PTZ_Focus_Near:
 | |
| 		return HikPTZEnum.FOCUS_NEAR
 | |
| 	case PTZEnum.PTZ_UP_LEFT:
 | |
| 		return HikPTZEnum.UP_LEFT
 | |
| 	case PTZEnum.PTZ_UP_RIGHT:
 | |
| 		return HikPTZEnum.UP_RIGHT
 | |
| 	case PTZEnum.PTZ_DOWN_LEFT:
 | |
| 		return HikPTZEnum.DOWN_LEFT
 | |
| 	case PTZEnum.PTZ_DOWN_RIGHT:
 | |
| 		return HikPTZEnum.DOWN_RIGHT
 | |
| 
 | |
| 	default:
 | |
| 		return -1
 | |
| 	}
 | |
| }
 | |
| 
 | |
| var PTZEnum = PTZEnumObj{
 | |
| 	PTZ_LEFT:       1,
 | |
| 	PTZ_RIGHT:      2,
 | |
| 	PTZ_UP:         3,
 | |
| 	PTZ_DOWN:       4,
 | |
| 	PTZ_UP_LEFT:    5,
 | |
| 	PTZ_UP_RIGHT:   6,
 | |
| 	PTZ_DOWN_LEFT:  7,
 | |
| 	PTZ_DOWN_RIGHT: 8,
 | |
| 	PTZ_ZOOM_IN:    9,
 | |
| 	PTZ_ZOOM_OUT:   10,
 | |
| 	PTZ_Focus_Far:  11,
 | |
| 	PTZ_Focus_Near: 12,
 | |
| }
 | |
| 
 | |
| var HikPTZEnum = struct {
 | |
| 	LIGHT_PWRON         int //接通灯光电源
 | |
| 	WIPER_PWRON         int //接通雨刷开关
 | |
| 	FAN_PWRON           int //接通风扇开关
 | |
| 	HEATER_PWRON        int //接通加热器开关
 | |
| 	AUX_PWRON1          int //接通辅助设备开关
 | |
| 	AUX_PWRON2          int //接通辅助设备开关
 | |
| 	ZOOM_IN             int //焦距变大(倍率变大)
 | |
| 	ZOOM_OUT            int //焦距变小(倍率变小)
 | |
| 	FOCUS_NEAR          int //焦点前调
 | |
| 	FOCUS_FAR           int //焦点后调
 | |
| 	IRIS_OPEN           int //光圈扩大
 | |
| 	IRIS_CLOSE          int //光圈缩小
 | |
| 	TILT_UP             int //云台上仰
 | |
| 	TILT_DOWN           int //云台下俯
 | |
| 	PAN_LEFT            int //云台左转
 | |
| 	PAN_RIGHT           int //云台右转
 | |
| 	UP_LEFT             int //云台上仰和左转
 | |
| 	UP_RIGHT            int //云台上仰和右转
 | |
| 	DOWN_LEFT           int //云台下俯和左转
 | |
| 	DOWN_RIGHT          int //云台下俯和右转
 | |
| 	PAN_AUTO            int //云台左右自动扫描
 | |
| 	TILT_DOWN_ZOOM_IN   int //云台下俯和焦距变大(倍率变大)
 | |
| 	TILT_DOWN_ZOOM_OUT  int //云台下俯和焦距变小(倍率变小)
 | |
| 	PAN_LEFT_ZOOM_IN    int //云台左转和焦距变大(倍率变大)
 | |
| 	PAN_LEFT_ZOOM_OUT   int //云台左转和焦距变小(倍率变小)
 | |
| 	PAN_RIGHT_ZOOM_IN   int //云台右转和焦距变大(倍率变大)
 | |
| 	PAN_RIGHT_ZOOM_OUT  int //云台右转和焦距变小(倍率变小)
 | |
| 	UP_LEFT_ZOOM_IN     int //云台上仰和左转和焦距变大(倍率变大)
 | |
| 	UP_LEFT_ZOOM_OUT    int //云台上仰和左转和焦距变小(倍率变小)
 | |
| 	UP_RIGHT_ZOOM_IN    int //云台上仰和右转和焦距变大(倍率变大)
 | |
| 	UP_RIGHT_ZOOM_OUT   int //云台上仰和右转和焦距变小(倍率变小)
 | |
| 	DOWN_LEFT_ZOOM_IN   int //云台下俯和左转和焦距变大(倍率变大)
 | |
| 	DOWN_LEFT_ZOOM_OUT  int //云台下俯和左转和焦距变小(倍率变小)
 | |
| 	DOWN_RIGHT_ZOOM_IN  int //云台下俯和右转和焦距变大(倍率变大)
 | |
| 	DOWN_RIGHT_ZOOM_OUT int //云台下俯和右转和焦距变小(倍率变小)
 | |
| 	TILT_UP_ZOOM_IN     int //云台上仰和焦距变大(倍率变大)
 | |
| 	TILT_UP_ZOOM_OUT    int //云台上仰和焦距变小(倍率变小)
 | |
| }{LIGHT_PWRON: 2,
 | |
| 	WIPER_PWRON:         3,
 | |
| 	FAN_PWRON:           4,
 | |
| 	HEATER_PWRON:        5,
 | |
| 	AUX_PWRON1:          6,
 | |
| 	AUX_PWRON2:          7,
 | |
| 	ZOOM_IN:             11,
 | |
| 	ZOOM_OUT:            12,
 | |
| 	FOCUS_NEAR:          13,
 | |
| 	FOCUS_FAR:           14,
 | |
| 	IRIS_OPEN:           15,
 | |
| 	IRIS_CLOSE:          16,
 | |
| 	TILT_UP:             21,
 | |
| 	TILT_DOWN:           22,
 | |
| 	PAN_LEFT:            23,
 | |
| 	PAN_RIGHT:           24,
 | |
| 	UP_LEFT:             25,
 | |
| 	UP_RIGHT:            26,
 | |
| 	DOWN_LEFT:           27,
 | |
| 	DOWN_RIGHT:          28,
 | |
| 	PAN_AUTO:            29,
 | |
| 	TILT_DOWN_ZOOM_IN:   58,
 | |
| 	TILT_DOWN_ZOOM_OUT:  59,
 | |
| 	PAN_LEFT_ZOOM_IN:    60,
 | |
| 	PAN_LEFT_ZOOM_OUT:   61,
 | |
| 	PAN_RIGHT_ZOOM_IN:   62,
 | |
| 	PAN_RIGHT_ZOOM_OUT:  63,
 | |
| 	UP_LEFT_ZOOM_IN:     64,
 | |
| 	UP_LEFT_ZOOM_OUT:    65,
 | |
| 	UP_RIGHT_ZOOM_IN:    66,
 | |
| 	UP_RIGHT_ZOOM_OUT:   67,
 | |
| 	DOWN_LEFT_ZOOM_IN:   68,
 | |
| 	DOWN_LEFT_ZOOM_OUT:  69,
 | |
| 	DOWN_RIGHT_ZOOM_IN:  70,
 | |
| 	DOWN_RIGHT_ZOOM_OUT: 71,
 | |
| 	TILT_UP_ZOOM_IN:     72,
 | |
| 	TILT_UP_ZOOM_OUT:    73,
 | |
| }
 | |
| 
 | |
| type BallCamera struct {
 | |
| 	userId       Core.LONG
 | |
| 	_type        uint8
 | |
| 	deviceInfo   Core.NET_DVR_DEVICEINFO_V30
 | |
| 	mu           sync.Mutex
 | |
| 	expectedType byte
 | |
| }
 | |
| 
 | |
| func NewBallCamera(Ip string, Port int, Username, Password string, Type uint8) (*BallCamera, error) {
 | |
| 	UserId, DeviceInfo, err := Core.Login(Ip, Port, Username, Password)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return &BallCamera{
 | |
| 		userId:     UserId,
 | |
| 		_type:      Type,
 | |
| 		deviceInfo: DeviceInfo,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| type PTZ struct {
 | |
| 	P float32
 | |
| 	T float32
 | |
| 	Z float32
 | |
| }
 | |
| 
 | |
| func (this *BallCamera) GetPTZ() (PTZ, error) {
 | |
| 	if this._type == BuKongQiu {
 | |
| 		var data PTZ
 | |
| 
 | |
| 		ch := make(chan bool)
 | |
| 		SerialStartHandle, err := Core.SerialStart(this.userId, func(lSerialHandle Core.LONG, lChannel Core.LONG, pRecvDataBuffer []byte, dwBufSize Core.DWORD, pUser unsafe.Pointer) {
 | |
| 			if dwBufSize != 7 {
 | |
| 				ch <- false
 | |
| 				return
 | |
| 			}
 | |
| 			Type := pRecvDataBuffer[3]
 | |
| 			this.mu.Lock()
 | |
| 			expected := this.expectedType
 | |
| 			this.mu.Unlock()
 | |
| 			if Type != expected {
 | |
| 				ch <- false
 | |
| 				return
 | |
| 			}
 | |
| 			switch Type {
 | |
| 			case 0x59:
 | |
| 				data.P = float32(binary.BigEndian.Uint16(pRecvDataBuffer[4:6])) / 100.
 | |
| 			case 0x5B:
 | |
| 				data.T = float32(binary.BigEndian.Uint16(pRecvDataBuffer[4:6])) / 100.
 | |
| 			case 0x5D:
 | |
| 				data.Z = float32(binary.BigEndian.Uint16(pRecvDataBuffer[4:6])) / 100.
 | |
| 			default:
 | |
| 				ch <- false
 | |
| 			}
 | |
| 			ch <- true
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			return data, err
 | |
| 		}
 | |
| 		defer func() {
 | |
| 			err = Core.SerialStop(SerialStartHandle)
 | |
| 			if err != nil {
 | |
| 				println(err.Error())
 | |
| 			}
 | |
| 			time.Sleep(1 * time.Second)
 | |
| 		}()
 | |
| 
 | |
| 		// 获取P值
 | |
| 		this.mu.Lock()
 | |
| 		this.expectedType = 0x59
 | |
| 		this.mu.Unlock()
 | |
| 		if err := this.retrySend(SerialStartHandle, []byte{0xff, 0x01, 0x00, 0x51, 0x00, 0x00, 0x52}, 5, ch); err != nil {
 | |
| 			return data, fmt.Errorf("获取P值失败: %w", err)
 | |
| 		}
 | |
| 
 | |
| 		this.mu.Lock()
 | |
| 		this.expectedType = 0x5B
 | |
| 		this.mu.Unlock()
 | |
| 		if err := this.retrySend(SerialStartHandle, []byte{0xff, 0x01, 0x00, 0x53, 0x00, 0x00, 0x54}, 5, ch); err != nil {
 | |
| 			return data, fmt.Errorf("获取T值失败: %w", err)
 | |
| 		}
 | |
| 
 | |
| 		this.mu.Lock()
 | |
| 		this.expectedType = 0x5D
 | |
| 		this.mu.Unlock()
 | |
| 		if err := this.retrySend(SerialStartHandle, []byte{0xff, 0x01, 0x00, 0x55, 0x00, 0x00, 0x56}, 5, ch); err != nil {
 | |
| 			return data, fmt.Errorf("获取Z值失败: %w", err)
 | |
| 		}
 | |
| 		return data, nil
 | |
| 	}
 | |
| 	var data Core.CDVR_PTZPOS
 | |
| 	var dataPtr = unsafe.Pointer(&data)
 | |
| 	err := Core.GetDVRConfig(this.userId, 293, 1, dataPtr, Core.DWORD(unsafe.Sizeof(data)))
 | |
| 	if err != nil {
 | |
| 		return PTZ{}, err
 | |
| 	}
 | |
| 	res := data.Go()
 | |
| 	return PTZ{
 | |
| 		P: float32(res.WPanPos),
 | |
| 		T: float32(res.WTiltPos),
 | |
| 		Z: float32(res.WZoomPos),
 | |
| 	}, nil
 | |
| 
 | |
| }
 | |
| 
 | |
| func padding(n int) ([]byte, error) {
 | |
| 	if n < 0 || n > 65535 {
 | |
| 		return []byte{0x00, 0x00}, errors.New("n must be in the range 0-65535")
 | |
| 	}
 | |
| 	return []byte{byte(n >> 8), byte(n & 0xFF)}, nil
 | |
| }
 | |
| 
 | |
| func (this *BallCamera) PtzGotoPut(Action int, P, T, Z float64) error {
 | |
| 	if this._type == BuKongQiu {
 | |
| 		SerialStartHandle, err := Core.SerialStart(this.userId, func(lSerialHandle Core.LONG, lChannel Core.LONG, pRecvDataBuffer []byte, dwBufSize Core.DWORD, pUser unsafe.Pointer) {
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		defer func() {
 | |
| 			err = Core.SerialStop(SerialStartHandle)
 | |
| 			if err != nil {
 | |
| 				println(err.Error())
 | |
| 			}
 | |
| 			time.Sleep(1 * time.Second)
 | |
| 		}()
 | |
| 
 | |
| 		PByte, err := padding(int(P * 100))
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		TByte, err := padding(int(T * 100))
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		ZByte, err := padding(int(Z * 100))
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		pBuf := append([]byte{0xff, 0x01, 0x00, 0x4b}, PByte...)
 | |
| 		tBuf := append([]byte{0xff, 0x01, 0x00, 0x4d}, TByte...)
 | |
| 		zBuf := append([]byte{0xff, 0x01, 0x00, 0x4f}, ZByte...)
 | |
| 
 | |
| 		pBufv, err := verify(pBuf)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		tBufv, err := verify(tBuf)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		zBufv, err := verify(zBuf)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		pBuf = append(pBuf, pBufv)
 | |
| 		tBuf = append(tBuf, tBufv)
 | |
| 		zBuf = append(zBuf, zBufv)
 | |
| 		switch Action {
 | |
| 		case 1:
 | |
| 			err = Core.SerialSend(SerialStartHandle, pBuf)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			err = Core.SerialSend(SerialStartHandle, tBuf)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			err = Core.SerialSend(SerialStartHandle, zBuf)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			break
 | |
| 		case 2:
 | |
| 			err = Core.SerialSend(SerialStartHandle, pBuf)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			break
 | |
| 		case 3:
 | |
| 			err = Core.SerialSend(SerialStartHandle, tBuf)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			break
 | |
| 		case 4:
 | |
| 			err = Core.SerialSend(SerialStartHandle, zBuf)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			break
 | |
| 		case 5:
 | |
| 			err = Core.SerialSend(SerialStartHandle, pBuf)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			err = Core.SerialSend(SerialStartHandle, tBuf)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			break
 | |
| 		default:
 | |
| 			return errors.New("action error")
 | |
| 		}
 | |
| 		return nil
 | |
| 	}
 | |
| 	var data Core.CDVR_PTZPOS
 | |
| 	data.Set(float64(Action), P, T, Z)
 | |
| 	var dataPtr = unsafe.Pointer(&data)
 | |
| 	err := Core.SetDVRConfig(this.userId, 292, 1, dataPtr, Core.DWORD(unsafe.Sizeof(data)))
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (this *BallCamera) retrySend(handle Core.LONG, cmd []byte, maxRetries int, ch <-chan bool) error {
 | |
| 	for retry := 0; retry < maxRetries; retry++ {
 | |
| 		if err := Core.SerialSend(handle, cmd); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		select {
 | |
| 		case success := <-ch:
 | |
| 			if success {
 | |
| 				return nil
 | |
| 			}
 | |
| 			if retry == maxRetries-1 {
 | |
| 				return fmt.Errorf("达到最大重试次数 %d", maxRetries)
 | |
| 			}
 | |
| 		case <-time.After(2 * time.Second): // 添加超时机制
 | |
| 			if retry == maxRetries-1 {
 | |
| 				return fmt.Errorf("响应超时,重试 %d 次后失败", maxRetries)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (receiver *BallCamera) StartBus(direction int, speed int) error {
 | |
| 
 | |
| 	err := Core.PTZControlWithSpeed_Other(receiver.userId, Core.LONG(receiver.deviceInfo.ByStartChan), Core.DWORD(PTZEnum.toHikPTZEnum(direction)), Core.DWORD(0), Core.DWORD(speed))
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| func (receiver *BallCamera) StopBus(direction int, speed int) error {
 | |
| 	err := Core.PTZControlWithSpeed_Other(receiver.userId, Core.LONG(receiver.deviceInfo.ByStartChan), Core.DWORD(PTZEnum.toHikPTZEnum(direction)), Core.DWORD(1), Core.DWORD(speed))
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func verify(data []byte) (byte, error) {
 | |
| 	if len(data) < 6 {
 | |
| 		return 0, fmt.Errorf("data too short")
 | |
| 	}
 | |
| 	sum := 0
 | |
| 	for i := 1; i < 6; i++ {
 | |
| 		sum += int(data[i])
 | |
| 	}
 | |
| 
 | |
| 	// 取模并转换为16进制
 | |
| 	checksum := sum % 0x100
 | |
| 	return byte(checksum), nil
 | |
| }
 | |
| 
 | |
| func (this *BallCamera) Logout() error {
 | |
| 	return Core.Logout(this.userId)
 | |
| }
 |