web-map/src/services/auto-door-simulation.service.ts

389 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 自动门点服务
* 提供自动门点的WebSocket数据处理功能
* 注模拟数据功能已被注释现在主要用于处理真实WebSocket推送数据
*/
import { type MapPen, MapPointType } from '@api/map';
import type { EditorService } from './editor.service';
/**
* 自动门点模拟数据配置
*/
export interface AutoDoorSimulationConfig {
/** 设备ID需要与地图中自动门点的deviceId匹配 */
deviceId: string;
/** 设备标签 */
label?: string;
/** 状态切换间隔毫秒默认3000ms */
interval?: number;
/** 初始状态0=关门1=开门默认0 */
initialStatus?: 0 | 1;
/** 是否启用控制台日志 */
enableLogging?: boolean;
}
/**
* 自动门点状态数据结构模拟WebSocket推送的数据格式
*/
interface AutoDoorStatusData {
gid: string;
id: string;
label: string;
brand: null;
type: 99; // 自动门点类型标识
ip: null;
battery: number;
isConnected: boolean;
state: number;
canOrder: boolean;
canStop: null;
canControl: boolean;
targetPoint: null;
deviceStatus: 0 | 1; // 设备状态0=关门1=开门
x: number;
y: number;
active: boolean;
angle: number;
isWaring: null;
isFault: null;
isLoading: null;
path: [];
}
/**
* WebSocket推送的自动门点数据接口
*/
export interface AutoDoorWebSocketData {
gid: string;
id: string; // 设备ID
label: string;
brand: null;
type: 99; // 自动门点类型标识
ip: null;
battery: number;
isConnected: boolean;
state: number;
canOrder: boolean;
canStop: null;
canControl: boolean;
targetPoint: null;
deviceStatus: 0 | 1; // 设备状态0=关门1=开门
x: number;
y: number;
active: boolean;
angle: number;
isWaring: null;
isFault: null;
isLoading: null;
path: [];
}
/**
* 自动门点服务
* 主要用于WebSocket数据处理模拟功能保留但默认不使用
*/
export class AutoDoorService {
private timers = new Map<string, NodeJS.Timeout>();
private statusMap = new Map<string, 0 | 1>();
private editorService: EditorService | null = null;
private latestAutoDoorData = new Map<string, { deviceId: string; deviceStatus: number; active: boolean }>();
// 设备ID到自动门点ID的映射缓存避免每次都遍历查找
private deviceIdToPointIdMap = new Map<string, string>();
/**
* 设置编辑器服务实例并初始化自动门点映射
* @param editor 编辑器服务实例
*/
setEditorService(editor: EditorService): void {
this.editorService = editor;
// 初始化设备ID到点位ID的映射关系
this.initializeDeviceMapping();
}
/**
* 初始化设备ID到自动门点ID的映射关系
* 在场景加载后调用,建立高效的查找缓存
*/
private initializeDeviceMapping(): void {
if (!this.editorService) {
console.warn('⚠️ 编辑器服务未设置,无法初始化自动门点映射');
return;
}
// 清空现有映射
this.deviceIdToPointIdMap.clear();
// 遍历所有点位,找出自动门点并建立映射
const pens = this.editorService.data().pens;
let autoDoorCount = 0;
pens.forEach((pen) => {
if (pen.name === 'point' && (pen as MapPen).point?.type === MapPointType.) {
const deviceId = (pen as MapPen).point?.deviceId;
if (deviceId && pen.id) {
this.deviceIdToPointIdMap.set(deviceId, pen.id);
autoDoorCount++;
}
}
});
console.log(`🚪 自动门点映射初始化完成: 共找到 ${autoDoorCount} 个自动门点`);
}
/**
* 手动刷新设备映射(当场景发生变化时调用)
*/
refreshDeviceMapping(): void {
this.initializeDeviceMapping();
}
/**
* 启动自动门点模拟(已停用,保留接口用于兼容性)
* 注当前使用真实WebSocket数据此方法仅在测试时启用
* @param config 模拟配置
*/
startSimulation(config: AutoDoorSimulationConfig): void {
const { deviceId, interval = 3000, initialStatus = 0, enableLogging = true } = config;
// 如果已经存在相同设备ID的模拟先停止它
this.stopSimulation(deviceId);
if (enableLogging) {
console.log(`🚪 启动自动门点模拟: ${deviceId}`);
}
// 设置初始状态
this.statusMap.set(deviceId, initialStatus);
// 创建定时器
const timer = setInterval(() => {
const currentStatus = this.statusMap.get(deviceId) ?? 0;
const newStatus = currentStatus === 0 ? 1 : 0;
// 更新状态
this.statusMap.set(deviceId, newStatus);
// 注意:这里不需要创建完整的模拟数据对象,只需要更新状态
if (enableLogging) {
console.log(`🚪 自动门点状态更新: ${newStatus === 0 ? '关门(红色)' : '开门(蓝色)'} (deviceId: ${deviceId})`);
}
// 更新编辑器中的自动门点状态
if (this.editorService) {
this.editorService.updateAutoDoorByDeviceId(deviceId, newStatus, true);
} else {
console.warn('⚠️ 编辑器服务未设置,无法更新自动门点状态');
}
}, interval);
// 保存定时器引用
this.timers.set(deviceId, timer);
}
/**
* 停止指定设备的模拟(已停用,保留接口用于兼容性)
* @param deviceId 设备ID
*/
stopSimulation(deviceId: string): void {
const timer = this.timers.get(deviceId);
if (timer) {
clearInterval(timer);
this.timers.delete(deviceId);
this.statusMap.delete(deviceId);
console.log(`🚪 停止自动门点模拟: ${deviceId}`);
}
}
/**
* 停止所有模拟(已停用,保留接口用于兼容性)
*/
stopAllSimulations(): void {
for (const deviceId of this.timers.keys()) {
this.stopSimulation(deviceId);
}
console.log('🚪 停止所有自动门点模拟');
}
/**
* 获取当前正在模拟的设备列表
*/
getActiveSimulations(): string[] {
return Array.from(this.timers.keys());
}
/**
* 获取指定设备的当前状态
* @param deviceId 设备ID
*/
getCurrentStatus(deviceId: string): 0 | 1 | undefined {
return this.statusMap.get(deviceId);
}
/**
* 手动设置设备状态
* @param deviceId 设备ID
* @param status 设备状态
*/
setDeviceStatus(deviceId: string, status: 0 | 1): void {
this.statusMap.set(deviceId, status);
if (this.editorService) {
this.editorService.updateAutoDoorByDeviceId(deviceId, status, true);
console.log(`🚪 手动设置自动门点状态: ${status === 0 ? '关门(红色)' : '开门(蓝色)'} (deviceId: ${deviceId})`);
}
}
/**
* 启动多个自动门点模拟
* @param configs 多个模拟配置
*/
startMultipleSimulations(configs: AutoDoorSimulationConfig[]): void {
configs.forEach((config) => this.startSimulation(config));
}
/**
* 处理WebSocket推送的自动门点数据
* @param data WebSocket推送的数据
*/
handleWebSocketData(data: AutoDoorWebSocketData): void {
const { label: deviceId, deviceStatus, active = true } = data;
if (!deviceId || deviceStatus === undefined) {
console.warn('⚠️ 自动门点数据格式不正确', data);
return;
}
// 缓存最新数据
this.latestAutoDoorData.set(deviceId, { deviceId, deviceStatus, active });
console.log(
`🚪 收到自动门点WebSocket数据: ${deviceStatus === 0 ? '关门(红色)' : '开门(蓝色)'} (deviceId: ${deviceId})`,
);
}
/**
* 处理缓冲的自动门点数据(在渲染循环中调用)
* @param frameBudget 帧预算(毫秒)
* @param startTime 开始时间
* @returns 是否还有待处理的数据
*/
processBufferedData(frameBudget: number, startTime: number): boolean {
// 在时间预算内,持续处理自动门点数据
while (performance.now() - startTime < frameBudget && this.latestAutoDoorData.size > 0) {
// 获取并移除 Map 中的第一条自动门点数据
const entry = this.latestAutoDoorData.entries().next().value;
if (!entry) break;
const [deviceId, data] = entry;
this.latestAutoDoorData.delete(deviceId);
// 使用映射缓存快速查找点位ID
const pointId = this.deviceIdToPointIdMap.get(data.deviceId);
if (!pointId) {
console.warn(`⚠️ 未找到设备ID ${data.deviceId} 对应的自动门点,可能需要刷新映射`);
continue;
}
// 更新自动门点状态使用pointId直接更新避免查找
if (this.editorService) {
this.editorService.updateAutoDoorByDeviceId(data.deviceId, data.deviceStatus, data.active, pointId);
}
}
// 返回是否还有待处理的数据
return this.latestAutoDoorData.size > 0;
}
/**
* 获取当前缓冲数据数量
*/
getBufferedDataCount(): number {
return this.latestAutoDoorData.size;
}
/**
* 清空缓冲数据
*/
clearBufferedData(): void {
this.latestAutoDoorData.clear();
}
/**
* 获取映射关系数量
*/
getMappingCount(): number {
return this.deviceIdToPointIdMap.size;
}
/**
* 检查设备ID是否有对应的自动门点
*/
hasDeviceMapping(deviceId: string): boolean {
return this.deviceIdToPointIdMap.has(deviceId);
}
/**
* 获取所有已映射的设备ID列表
*/
getMappedDeviceIds(): string[] {
return Array.from(this.deviceIdToPointIdMap.keys());
}
/**
* 创建模拟数据
* @param deviceId 设备ID
* @param label 设备标签
* @param deviceStatus 设备状态
*/
private createMockData(deviceId: string, label: string, deviceStatus: 0 | 1): AutoDoorStatusData {
return {
gid: '',
id: deviceId,
label,
brand: null,
type: 99,
ip: null,
battery: 0,
isConnected: true,
state: 0,
canOrder: false,
canStop: null,
canControl: false,
targetPoint: null,
deviceStatus,
x: 0,
y: 0,
active: true,
angle: 0,
isWaring: null,
isFault: null,
isLoading: null,
path: [],
};
}
/**
* 销毁服务,清理所有资源
*/
destroy(): void {
this.stopAllSimulations();
this.clearBufferedData();
this.deviceIdToPointIdMap.clear();
this.editorService = null;
console.log('🚪 自动门点服务已销毁');
}
}
/**
* 自动门点服务单例
*/
export const autoDoorService = new AutoDoorService();
// 保持向后兼容
export const autoDoorSimulationService = autoDoorService;