# -*- coding: utf-8 -*- """ 装备模型抽象基类(核心) ───────────────────────────────────────────────────────────────── BaseEquipmentModel 所有装备模型的统一抽象基类 职责: - 生命周期管理(start/stop/pause/resume/reset) - 仿真驱动(step/step_to) - 参数管理(set_params/update_param/get_param) - 参数版本快照(save_snapshot/restore_snapshot) - PyQt5 信号槽(state_updated/lifecycle_changed/ params_changed/error_occurred) ───────────────────────────────────────────────────────────────── """ from __future__ import annotations import asyncio import copy import logging from abc import abstractmethod from typing import Any, List, Optional, Type, Callable from PyQt5.QtCore import QObject, pyqtSignal from uas.comm import CommAdapterFactory from uas.comm.base import CommBaseAdapter from uas.comm.message import Message from uas.model.lifecycle import LifecycleFSM, LifecycleState from uas.model.params import EquipmentParams from uas.model.state import BaseState from uas.task.task_model import Task logger = logging.getLogger(__name__) # ───────────────────────────────────────────────────────────────── # 装备模型抽象基类 # ───────────────────────────────────────────────────────────────── class BaseEquipmentModel(QObject): """ 装备模型统一抽象基类 ── 子类必须实现 ────────────────────────────────────────────── _do_step(dt) 核心仿真推进(更新 _state) _build_initial_state() 构建初始状态快照 params_class 类属性:对应的 EquipmentParams 子类 ── 子类可选覆盖 ────────────────────────────────────────────── _on_start() 启动时的额外初始化 _on_stop() 停止时的清理 _on_pause() 暂停时的处理 _on_resume() 恢复时的处理 _on_reset() 重置时的处理 _on_params_changed(key, value) 参数变更时的响应 _check_constraints() 约束检查(越界告警) """ # ── PyQt5 信号 ──────────────────────────────────────────────── state_updated = pyqtSignal(object) # BaseState 子类实例 lifecycle_changed = pyqtSignal(str, str) # (old_state, new_state) params_changed = pyqtSignal(str, object) # (key, new_value) error_occurred = pyqtSignal(str) # 错误消息 # ── 子类须声明的类属性 ──────────────────────────────────────── params_class: Type[EquipmentParams] = EquipmentParams def __init__( self, params: Optional[EquipmentParams] = None, parent: QObject = None, ): super().__init__(parent) # 参数 self._params: EquipmentParams = ( params if params is not None else self.params_class() ) # 状态 self._state: Optional[BaseState] = None self._initial_state: Optional[BaseState] = None self._sub_models: List[BaseEquipmentModel] = [] # 生命周期 FSM self._fsm = LifecycleFSM( initial = LifecycleState.CREATED, on_changed = self._on_fsm_changed, ) self._comm = None # 仿真时钟 self._sim_time: float = 0.0 # 仿真绝对时刻 self._elapsed: float = 0.0 # 自启动累计时长 # 参数快照栈 self._snapshots: List[EquipmentParams] = [] # 日志 self.logger = logging.getLogger( f"{self.__class__.__name__}[{self._params.equipment_id}]" ) # 初始化状态 → READY self.set_state(self._build_initial_state()) self._fsm.to_ready(0.0) self._actions = {} def action_is_available(self, action: str): return True def register_action(self, object: BaseEquipmentModel, action: str, handler: Callable): if (object.equipment_id, action) not in self._actions: self._actions[(object.equipment_id, action) ].append((object, handler)) def dereigster_action(self, object: BaseEquipmentModel, action): if (object.equipment_id, action) in self._actions: del self._actions[(object.equipment_id, action)] def call_action(self, object_id:str , action: str, **parameters): if (object_id, action) not in self._actions: raise ValueError() object, handler = self._actions[(object_id, action)] assert isinstance(object, BaseState) if object.action_is_available(action): return handler(**parameters) else: raise def set_state(self, state: Optional[BaseState]): self._state = state.copy_with() self._initial_state = state.copy_with() self.state_updated.emit(self._state) def add_sub_model(self, model: BaseEquipmentModel): if model not in self._sub_models: self._sub_models.append(model) def add_sub_models(self, *models): for model in models: self.add_sub_model(model) # ── 属性(只读)────────────────────────────────────────────── @property def actions(self): return self._actions @property def sub_models(self) -> List[BaseEquipmentModel]: return self._sub_models @property def params(self) -> EquipmentParams: return self._params @property def state(self) -> Optional[BaseState]: return self._state @property def lifecycle(self) -> LifecycleState: return self._fsm.state @property def sim_time(self) -> float: return self._sim_time @property def is_running(self) -> bool: return self._fsm.is_running @property def equipment_id(self) -> str: return self._params.equipment_id @property def name(self) -> str: return self._params.name # ── 生命周期控制 ────────────────────────────────────────────── def start(self) -> bool: """ 启动模型(READY → RUNNING) :return: True=成功,False=当前状态不允许启动 """ try: for model in self._sub_models: if not model.start(): raise errors = self.validate_params() if errors: msg = f"参数校验失败,无法启动: {errors}" self.logger.error(msg) self.error_occurred.emit(msg) return False self._fsm.to_running(self._sim_time) self._on_start() self.logger.info(f"模型启动: {self.name}") return True except ValueError as e: self._handle_error(str(e)) return False def stop(self) -> bool: """停止模型(RUNNING/PAUSED → STOPPED)""" try: for model in self._sub_models: if not model.stop(): raise self._fsm.to_stopped(self._sim_time) self._on_stop() self.logger.info(f"模型停止: {self.name}") return True except ValueError as e: self._handle_error(str(e)) return False def pause(self) -> bool: """暂停模型(RUNNING → PAUSED)""" try: for model in self._sub_models: if not model.pause(): raise self._fsm.to_paused(self._sim_time) self._on_pause() self.logger.info(f"模型暂停: {self.name}") return True except ValueError as e: self._handle_error(str(e)) return False def resume(self) -> bool: """恢复模型(PAUSED → RUNNING)""" try: for model in self._sub_models: if not model.resume(): raise self._fsm.to_running(self._sim_time) self._on_resume() self.logger.info(f"模型恢复: {self.name}") return True except ValueError as e: self._handle_error(str(e)) return False def reset(self) -> bool: """ 重置模型(任意状态 → READY) 恢复初始状态、清零仿真时钟 """ try: for model in self._sub_models: if not model.reset(): raise self._fsm.reset(0.0) self._sim_time = 0.0 self._elapsed = 0.0 self._state = copy.deepcopy(self._initial_state) self._on_reset() self.logger.info(f"模型重置: {self.name}") return True except Exception as e: self._handle_error(str(e)) return False def run_task(self, task: Task): pass # ── 仿真驱动 ────────────────────────────────────────────────── def step(self, dt: float) -> Optional[BaseState]: """ 推进仿真 dt 秒(核心驱动方法) 内部执行顺序: ① 检查生命周期(非 RUNNING 则跳过) ② 调用 _do_step(dt)(子类实现运动/系统更新) ③ 调用 _check_constraints()(约束检查) ④ 更新 sim_time 和 elapsed_time ⑤ 发射 state_updated 信号 :param dt: 时间步长(秒,必须 > 0) :return: 更新后的状态快照,或 None(未在运行中) """ if not self._fsm.can_step: return None if dt <= 0: raise ValueError(f"dt 必须大于 0,当前值: {dt}") try: sub_states = [] for model in self._sub_models: sub_state = model.step(dt) if not sub_state: raise sub_states.append(sub_state) # ② 子类核心更新 self._do_step(dt) # ③ 约束检查 self._check_constraints() # ④ 更新时钟 self._sim_time += dt self._elapsed += dt # 同步状态时间戳 if self._state is not None: self._state.timestamp = self._sim_time self._state.elapsed_time = self._elapsed self._state.lifecycle = self._fsm.state.value self._state.sub_states = sub_states # ⑤ 发射信号 if self._state is not None: self.state_updated.emit(self._state) return self._state except Exception as e: self._handle_error(f"step 执行异常: {e}") return None def step_to(self, t: float, dt: float = 0.1) -> Optional[BaseState]: """ 推进到绝对时刻 t :param t: 目标时刻(秒) :param dt: 内部步长(秒) """ for model in self._sub_models: model.step_to(t, dt) if t <= self._sim_time: return self._state remaining = t - self._sim_time while remaining > 1e-9: step_dt = min(dt, remaining) self.step(step_dt) remaining -= step_dt return self._state def get_state(self) -> Optional[BaseState]: """获取当前状态快照(深拷贝)""" return self._state.copy_with() if self._state else None # ── 参数管理 ────────────────────────────────────────────────── def set_params(self, params: EquipmentParams) -> None: """整体替换装备参数""" old = self._params self._params = params errors = self.validate_params() if errors: self._params = old raise ValueError(f"新参数校验失败: {errors}") self.logger.info(f"参数已整体替换: {params}") def update_param(self, key: str, value: Any) -> None: """ 动态更新单个参数 自动触发 params_changed 信号和约束重校验 """ old_value = self._params.get(key) self._params.set(key, value) # 重新校验 errors = self.validate_params() if errors: # 回滚 self._params.set(key, old_value) raise ValueError( f"参数 {key}={value} 校验失败: {errors}" ) self.logger.debug(f"参数更新: {key} = {value}") self.params_changed.emit(key, value) self._on_params_changed(key, value) def get_param(self, key: str, default: Any = None) -> Any: """读取单个参数值""" return self._params.get(key, default) def validate_params(self) -> List[str]: """校验当前参数合法性""" return self._params.validate() # ── 参数快照 ────────────────────────────────────────────────── def save_snapshot(self) -> int: """ 保存当前参数快照 :return: 快照索引 """ snapshot = copy.deepcopy(self._params) self._snapshots.append(snapshot) idx = len(self._snapshots) - 1 self.logger.debug(f"参数快照已保存: index={idx}") return idx def restore_snapshot(self, index: int = -1) -> bool: """ 恢复参数快照 :param index: 快照索引(-1=最新) :return: True=成功 """ if not self._snapshots: self.logger.warning("没有可用的参数快照") return False try: snapshot = self._snapshots[index] self._params = copy.deepcopy(snapshot) self.logger.info(f"参数快照已恢复: index={index}") return True except IndexError: self.logger.error(f"快照索引越界: {index}") return False def clear_snapshots(self) -> None: """清除所有参数快照""" self._snapshots.clear() # ── 抽象方法(子类必须实现)────────────────────────────────── @abstractmethod def _do_step(self, dt: float) -> None: """ 核心仿真推进(子类实现) 必须更新 self._state 的所有相关字段 """ ... @abstractmethod def _build_initial_state(self) -> BaseState: """ 构建初始状态快照(子类实现) 在 __init__ 和 reset() 时调用 """ ... # ── 可选覆盖钩子 ────────────────────────────────────────────── def _on_start(self) -> None: pass def _on_stop(self) -> None: pass def _on_pause(self) -> None: pass def _on_resume(self) -> None: pass def _on_reset(self) -> None: pass def _on_params_changed(self, key: str, value: Any) -> None: """参数变更时的响应钩子(子类可覆盖)""" pass def _check_constraints(self) -> None: """ 约束检查钩子(子类可覆盖) 检查速度/高度等是否越界,发射 error_occurred 告警 """ pass # ── 内部工具 ────────────────────────────────────────────────── def _on_fsm_changed(self, old: str, new: str) -> None: """FSM 状态变化回调 → 发射 lifecycle_changed 信号""" self.lifecycle_changed.emit(old, new) self.logger.debug(f"生命周期变化: {old} → {new}") def _handle_error(self, msg: str) -> None: """统一错误处理:记录日志 + 转 ERROR 状态 + 发射信号""" self.logger.error(msg) self._fsm.to_error(self._sim_time) self.error_occurred.emit(msg) def set_initial_position( self, lat: float, lon: float, alt: float = 0.0, heading: float = 0.0, ) -> None: """ 设置初始位置(必须在 start() 之前调用) 同时更新 _state 和 _initial_state """ for s in (self._state, self._initial_state): if s is not None: s.latitude = lat s.longitude = lon s.altitude = alt s.heading = heading def __repr__(self) -> str: return (f"{self.__class__.__name__}(" f"id={self.equipment_id}, " f"name={self.name}, " f"lc={self.lifecycle.value})") class NetworkModel: def __init__(self, network_id, url, model: BaseEquipmentModel): self.network_id = network_id self.url = url self.model = model async def _publish_state(self, state): await self._comm_adapter.publish() async def on_call(self, msg: Message): logger.warning(f"🚨 告警消息 | priority={msg.priority.name} | {msg.payload}") async def subscribe_actions(self, adapter:CommBaseAdapter, model: BaseEquipmentModel): for action in model.actions: await adapter.subscribe(f"{self.network_id}/{model.equipment_id}/{action}", self.on_call) async def register_network_actions(self, adapter:CommBaseAdapter, model: BaseEquipmentModel): for action in model.actions: await adapter.publish(Message(topic=f"{self.network_id}", payload={ "model_id": model.equipment_id, "action": action, "description": "" })) async def _process(self): async with CommAdapterFactory.create(self.url) as adapter: await self.register_network_actions(adapter, self.model) await adapter.subscribe(f"{self.network_id}/{self.model.equipment_id}") await self.subscribe_actions(adapter, self.model) for model in self.model.sub_models: await self.register_network_actions(adapter, model) await self.subscribe_actions(adapter, model) def start(self): self.model.start() asyncio.run(self._process())