389 lines
10 KiB
TypeScript
389 lines
10 KiB
TypeScript
/**
|
||
* 自动门点服务
|
||
* 提供自动门点的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;
|