fix(beacon): 修复RSSI滑动窗口检测逻辑 - 窗口大小改用max(enter/exit.windowSize) - 进入/退出检测分别使用各自的windowSize子窗口 - 到岗确认时清理RSSI窗口避免历史脏数据
This commit is contained in:
@@ -57,10 +57,10 @@ public class RssiSlidingWindowDetector {
|
||||
/**
|
||||
* 执行检测(强进弱出算法)
|
||||
*
|
||||
* @param window RSSI 滑动窗口样本(从旧到新)
|
||||
* @param enterConfig 进入(到岗)判定配置 - 强阈值
|
||||
* @param exitConfig 退出(离岗)判定配置 - 弱阈值
|
||||
* @param currentState 当前状态
|
||||
* @param window RSSI 滑动窗口样本(从旧到新)
|
||||
* @param enterConfig 进入(到岗)判定配置 - 强阈值
|
||||
* @param exitConfig 退出(离岗)判定配置 - 弱阈值
|
||||
* @param currentState 当前状态
|
||||
* @return 检测结果
|
||||
*/
|
||||
public DetectionResult detect(
|
||||
@@ -74,24 +74,26 @@ public class RssiSlidingWindowDetector {
|
||||
return DetectionResult.NO_CHANGE;
|
||||
}
|
||||
|
||||
// 1. 统计满足强阈值的样本数(用于进入判定)
|
||||
long enterHits = window.stream()
|
||||
.filter(rssi -> rssi >= enterConfig.getRssiThreshold())
|
||||
.count();
|
||||
|
||||
// 2. 统计满足弱阈值的样本数(用于退出判定)
|
||||
// 条件:RSSI < 弱阈值 或 为缺失值(-999)
|
||||
long exitHits = window.stream()
|
||||
.filter(rssi -> rssi < exitConfig.getWeakRssiThreshold() || rssi == -999)
|
||||
.count();
|
||||
|
||||
log.debug("[RssiDetector] 检测统计:currentState={}, windowSize={}, enterHits={}/{}, exitHits={}/{}",
|
||||
currentState, window.size(), enterHits, enterConfig.getHitCount(),
|
||||
exitHits, exitConfig.getHitCount());
|
||||
|
||||
// 3. 状态转换判定
|
||||
// 3. 状态转换判定(使用各自的窗口大小)
|
||||
if (currentState == AreaState.OUT_AREA) {
|
||||
// 当前在区域外,检测是否进入
|
||||
// 使用 enter.windowSize 取最近的样本子窗口
|
||||
int enterWindowSize = enterConfig.getWindowSize();
|
||||
List<Integer> enterWindow = getSubWindow(window, enterWindowSize);
|
||||
|
||||
if (enterWindow.size() < enterWindowSize) {
|
||||
log.debug("[RssiDetector] 进入窗口样本不足:current={}, required={}",
|
||||
enterWindow.size(), enterWindowSize);
|
||||
return DetectionResult.NO_CHANGE;
|
||||
}
|
||||
|
||||
long enterHits = enterWindow.stream()
|
||||
.filter(rssi -> rssi >= enterConfig.getRssiThreshold())
|
||||
.count();
|
||||
|
||||
log.debug("[RssiDetector] 进入检测:windowSize={}, enterHits={}/{}",
|
||||
enterWindow.size(), enterHits, enterConfig.getHitCount());
|
||||
|
||||
if (enterHits >= enterConfig.getHitCount()) {
|
||||
log.info("[RssiDetector] 进入条件满足:enterHits={}, required={}",
|
||||
enterHits, enterConfig.getHitCount());
|
||||
@@ -99,6 +101,24 @@ public class RssiSlidingWindowDetector {
|
||||
}
|
||||
} else {
|
||||
// 当前在区域内,检测是否离开
|
||||
// 使用 exit.windowSize 取最近的样本子窗口
|
||||
int exitWindowSize = exitConfig.getWindowSize();
|
||||
List<Integer> exitWindow = getSubWindow(window, exitWindowSize);
|
||||
|
||||
if (exitWindow.size() < exitWindowSize) {
|
||||
log.debug("[RssiDetector] 退出窗口样本不足:current={}, required={}",
|
||||
exitWindow.size(), exitWindowSize);
|
||||
return DetectionResult.NO_CHANGE;
|
||||
}
|
||||
|
||||
// 条件:RSSI < 弱阈值 或 为缺失值(-999)
|
||||
long exitHits = exitWindow.stream()
|
||||
.filter(rssi -> rssi < exitConfig.getWeakRssiThreshold() || rssi == -999)
|
||||
.count();
|
||||
|
||||
log.debug("[RssiDetector] 退出检测:windowSize={}, exitHits={}/{}",
|
||||
exitWindow.size(), exitHits, exitConfig.getHitCount());
|
||||
|
||||
if (exitHits >= exitConfig.getHitCount()) {
|
||||
log.info("[RssiDetector] 离开条件满足:exitHits={}, required={}",
|
||||
exitHits, exitConfig.getHitCount());
|
||||
@@ -109,12 +129,26 @@ public class RssiSlidingWindowDetector {
|
||||
return DetectionResult.NO_CHANGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取窗口的子窗口(最近 N 个样本)
|
||||
*
|
||||
* @param window 完整窗口
|
||||
* @param size 子窗口大小
|
||||
* @return 子窗口(最近的样本)
|
||||
*/
|
||||
private List<Integer> getSubWindow(List<Integer> window, int size) {
|
||||
if (window.size() <= size) {
|
||||
return window;
|
||||
}
|
||||
return window.subList(window.size() - size, window.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从蓝牙设备列表中提取目标信标的 RSSI
|
||||
*
|
||||
* @param bluetoothDevices 蓝牙设备列表
|
||||
* 格式:[{"rssi":-52,"type":243,"mac":"F0:C8:60:1D:10:BB"},...]
|
||||
* @param targetMac 目标信标 MAC 地址
|
||||
* 格式:[{"rssi":-52,"type":243,"mac":"F0:C8:60:1D:10:BB"},...]
|
||||
* @param targetMac 目标信标 MAC 地址
|
||||
* @return RSSI 值,如果未找到返回 -999(缺失值)
|
||||
*/
|
||||
public Integer extractTargetRssi(Object bluetoothDevices, String targetMac) {
|
||||
@@ -124,8 +158,7 @@ public class RssiSlidingWindowDetector {
|
||||
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<java.util.Map<String, Object>> deviceList =
|
||||
(List<java.util.Map<String, Object>>) bluetoothDevices;
|
||||
List<java.util.Map<String, Object>> deviceList = (List<java.util.Map<String, Object>>) bluetoothDevices;
|
||||
|
||||
return deviceList.stream()
|
||||
.filter(device -> targetMac.equals(device.get("mac")))
|
||||
@@ -143,8 +176,8 @@ public class RssiSlidingWindowDetector {
|
||||
* <p>
|
||||
* 窗口大小必须 >= 配置的窗口大小
|
||||
*
|
||||
* @param window 窗口样本
|
||||
* @param windowSize 配置的窗口大小
|
||||
* @param window 窗口样本
|
||||
* @param windowSize 配置的窗口大小
|
||||
* @return true - 有效,false - 无效
|
||||
*/
|
||||
public boolean isWindowValid(List<Integer> window, int windowSize) {
|
||||
|
||||
@@ -102,9 +102,11 @@ public class BeaconDetectionRuleProcessor {
|
||||
log.debug("[BeaconDetection] 提取RSSI:deviceId={}, beaconMac={}, rssi={}",
|
||||
deviceId, beaconConfig.getBeaconMac(), targetRssi);
|
||||
|
||||
// 4. 更新滑动窗口
|
||||
BeaconPresenceConfig.WindowConfig windowConfig = beaconConfig.getWindow();
|
||||
windowRedisDAO.updateWindow(deviceId, areaId, targetRssi, windowConfig.getSampleTtlSeconds());
|
||||
// 4. 更新滑动窗口(使用 enter 和 exit 中较大的窗口大小)
|
||||
int maxWindowSize = Math.max(
|
||||
beaconConfig.getEnter().getWindowSize(),
|
||||
beaconConfig.getExit().getWindowSize());
|
||||
windowRedisDAO.updateWindow(deviceId, areaId, targetRssi, maxWindowSize);
|
||||
|
||||
// 5. 获取当前窗口样本
|
||||
List<Integer> window = windowRedisDAO.getWindow(deviceId, areaId);
|
||||
@@ -151,7 +153,11 @@ public class BeaconDetectionRuleProcessor {
|
||||
// 2. 清除离岗记录(如果存在)
|
||||
signalLossRedisDAO.clearLossRecord(deviceId, areaId);
|
||||
|
||||
// 3. 获取当前最新的 RSSI 值
|
||||
// 3. 清理 RSSI 窗口(避免历史脏数据影响新的在岗周期)
|
||||
windowRedisDAO.clearWindow(deviceId, areaId);
|
||||
log.debug("[BeaconDetection] 到岗时清理RSSI窗口:deviceId={}, areaId={}", deviceId, areaId);
|
||||
|
||||
// 4. 获取当前最新的 RSSI 值(使用原窗口快照,因为已清理)
|
||||
Integer currentRssi = window.isEmpty() ? -999 : window.get(window.size() - 1);
|
||||
|
||||
// 4. 构建触发数据
|
||||
|
||||
Reference in New Issue
Block a user