package com.ard.work.sdk.hp.service;
|
|
import com.ard.work.api.domian.ArdCamera;
|
import com.ard.work.api.domian.PtzDto;
|
import com.ard.work.sdk.hik.lib.HCNetSDK;
|
import com.ard.work.sdk.hp.cache.CameraPTZCache;
|
import com.sun.jna.Pointer;
|
import lombok.extern.slf4j.Slf4j;
|
|
import java.util.Arrays;
|
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
/**
|
* 实时解析Pelco-D完整帧版
|
*/
|
@Slf4j
|
public class HpSerialDataCallBack implements HCNetSDK.FSerialDataCallBack_V40, AutoCloseable {
|
// 协议常量
|
private static final byte PELCO_D_HEAD = (byte) 0xFF;
|
private static final int PELCO_D_FRAME_LEN = 7;
|
private static final int CACHE_MAX_SIZE = 1024;
|
|
private final ArdCamera camera;
|
private final AtomicBoolean isRunning = new AtomicBoolean(true);
|
private final byte[] frameCache = new byte[CACHE_MAX_SIZE];
|
private int cacheLen = 0;
|
|
// 数据队列
|
private final BlockingQueue<byte[]> queue = new LinkedBlockingQueue<>();
|
|
// 镜头参数
|
private static final float MIN_FOCAL = 3.3f;
|
private static final float MAX_FOCAL = 37f;
|
private static final float COEFFICIENT = 1.63f;
|
private static final int FRONT_MIN = 1;
|
private static final int FRONT_MAX = 60;
|
|
public HpSerialDataCallBack(ArdCamera camera) {
|
this.camera = camera;
|
startConsumer();
|
}
|
|
/**
|
* SDK回调:接收碎片化数据 → 放入队列
|
*/
|
@Override
|
public void invoke(int lSerialHandle, int lChannel, Pointer pRecvDataBuffer, int dwBufSize, Pointer pUser) {
|
if (!isRunning.get() || dwBufSize <= 0 || pRecvDataBuffer == null) return;
|
|
byte[] recvData = new byte[dwBufSize];
|
pRecvDataBuffer.read(0, recvData, 0, dwBufSize);
|
queue.offer(recvData);
|
}
|
|
/**
|
* 单线程消费队列
|
*/
|
private void startConsumer() {
|
Thread t = new Thread(() -> {
|
while (isRunning.get()) {
|
try {
|
byte[] data = queue.take();
|
synchronized (frameCache) {
|
if (cacheLen + data.length > CACHE_MAX_SIZE) cacheLen = 0;
|
System.arraycopy(data, 0, frameCache, cacheLen, data.length);
|
cacheLen += data.length;
|
parseCompleteFrames();
|
}
|
} catch (InterruptedException ignored) {
|
}
|
}
|
}, "Serial-Consumer-" + camera.getIp());
|
t.setDaemon(true);
|
t.start();
|
}
|
|
/**
|
* 解析缓存中的完整帧
|
*/
|
private void parseCompleteFrames() {
|
while (cacheLen >= PELCO_D_FRAME_LEN) {
|
int headIndex = findHeadIndex(frameCache, cacheLen);
|
if (headIndex == -1) {
|
cacheLen = 0;
|
break;
|
}
|
if (cacheLen - headIndex < PELCO_D_FRAME_LEN) {
|
byte[] remain = Arrays.copyOfRange(frameCache, headIndex, cacheLen);
|
System.arraycopy(remain, 0, frameCache, 0, remain.length);
|
cacheLen = remain.length;
|
break;
|
}
|
byte[] completeFrame = Arrays.copyOfRange(frameCache, headIndex, headIndex + PELCO_D_FRAME_LEN);
|
int remainLen = cacheLen - (headIndex + PELCO_D_FRAME_LEN);
|
if (remainLen > 0) {
|
System.arraycopy(frameCache, headIndex + PELCO_D_FRAME_LEN, frameCache, 0, remainLen);
|
}
|
cacheLen = remainLen;
|
|
parsePelcoDFrame(completeFrame);
|
}
|
}
|
|
private int findHeadIndex(byte[] cache, int len) {
|
for (int i = 0; i < len; i++) {
|
if (cache[i] == PELCO_D_HEAD) return i;
|
}
|
return -1;
|
}
|
|
private void parsePelcoDFrame(byte[] frame) {
|
try {
|
if (frame.length != PELCO_D_FRAME_LEN || frame[0] != PELCO_D_HEAD) return;
|
|
int address = frame[1] & 0xFF;
|
if (address != 1) return;
|
|
int command2 = frame[3] & 0xFF;
|
int data1 = frame[4] & 0xFF;
|
int data2 = frame[5] & 0xFF;
|
int completeData = (data1 << 8) | data2;
|
|
PtzDto ptzDto = CameraPTZCache.get(camera.getId());
|
if (ptzDto == null) ptzDto = new PtzDto();
|
|
switch (command2) {
|
case 0x59:
|
ptzDto.setP(completeData / 100f);
|
break;
|
case 0x5B:
|
float pitch = 360 - completeData / 100f;
|
if (Math.abs(pitch - 360f) < 1e-6) pitch = 0f;
|
ptzDto.setT(Math.round(pitch * 100f) / 100f);
|
break;
|
case 0x5D:
|
float zoomValue = completeData / 100f * COEFFICIENT;
|
ptzDto.setZ((float) convertZoomToFrontValue(zoomValue));
|
break;
|
default:
|
break;
|
}
|
CameraPTZCache.put(camera.getId(), ptzDto);
|
//printStatusLog();
|
} catch (Exception e) {
|
log.error("[{}] 解析Pelco-D帧异常 | 帧:{}", camera.getIp(), bytesToHex(frame), e);
|
}
|
}
|
|
private void printStatusLog() {
|
PtzDto ptzDto = CameraPTZCache.get(camera.getId());
|
if (ptzDto != null) {
|
log.info("【{}】实时状态- Azimuth = {}° Pitch = {}° Zoom={}",
|
camera.getIp(), ptzDto.getP(), ptzDto.getT(), ptzDto.getZ());
|
}
|
}
|
|
private int convertZoomToFrontValue(float zoomValue) {
|
float minZoom = MIN_FOCAL * COEFFICIENT;
|
float maxZoom = MAX_FOCAL * COEFFICIENT;
|
zoomValue = Math.max(minZoom, Math.min(maxZoom, zoomValue));
|
float frontFloat = FRONT_MIN + (FRONT_MAX - FRONT_MIN) * (zoomValue - minZoom) / (maxZoom - minZoom);
|
return Math.max(FRONT_MIN, Math.min(FRONT_MAX, Math.round(frontFloat)));
|
}
|
|
private String bytesToHex(byte[] bytes) {
|
if (bytes == null || bytes.length == 0) return "";
|
StringBuilder sb = new StringBuilder();
|
for (byte b : bytes) sb.append(String.format("%02X ", b & 0xFF));
|
return sb.toString().trim();
|
}
|
|
public void stop() {
|
isRunning.set(false);
|
queue.clear();
|
log.info("[{}] 串口回调已关闭", camera.getIp());
|
}
|
|
@Override
|
public void close() {
|
stop();
|
}
|
}
|