148 lines
5.4 KiB
Python
148 lines
5.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
IO 数据模型 + 输入/输出抽象基类
|
|
所有 Input / Output 适配器必须继承对应基类
|
|
"""
|
|
|
|
import logging
|
|
import time
|
|
import uuid
|
|
from abc import abstractmethod
|
|
from dataclasses import dataclass, field
|
|
from enum import Enum
|
|
from typing import Any, Optional, Dict
|
|
|
|
from PyQt5.QtCore import QObject
|
|
from uas.io.signals import io_signals
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────
|
|
# 枚举定义
|
|
# ─────────────────────────────────────────────────────────────────
|
|
|
|
class IOType(Enum):
|
|
TEXT = "text"
|
|
SPEECH = "speech"
|
|
FILE = "file"
|
|
IMAGE = "image"
|
|
BINARY = "binary"
|
|
CUSTOM = "custom"
|
|
|
|
|
|
class IOStatus(Enum):
|
|
PENDING = "pending"
|
|
PROCESSING = "processing"
|
|
SUCCESS = "success"
|
|
ERROR = "error"
|
|
CANCELLED = "cancelled"
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────
|
|
# 统一数据包
|
|
# ─────────────────────────────────────────────────────────────────
|
|
|
|
@dataclass
|
|
class IOData:
|
|
"""统一 IO 数据包,贯穿整个输入→处理→输出管道"""
|
|
io_type: IOType
|
|
raw: Any
|
|
data_id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
|
timestamp: float = field(default_factory=time.time)
|
|
source: str = ""
|
|
metadata: Dict = field(default_factory=dict)
|
|
status: IOStatus = IOStatus.PENDING
|
|
error: Optional[str] = None
|
|
processed: Any = None # 处理器填充
|
|
|
|
def mark_processing(self):
|
|
self.status = IOStatus.PROCESSING
|
|
io_signals.status_changed.emit(self.data_id, self.status.value)
|
|
|
|
def mark_success(self, result: Any = None):
|
|
self.status = IOStatus.SUCCESS
|
|
self.processed = result if result is not None else self.raw
|
|
io_signals.status_changed.emit(self.data_id, self.status.value)
|
|
|
|
def mark_error(self, msg: str):
|
|
self.status = IOStatus.ERROR
|
|
self.error = msg
|
|
io_signals.status_changed.emit(self.data_id, self.status.value)
|
|
io_signals.error_occurred.emit(self.data_id, self.source, msg)
|
|
|
|
def __repr__(self):
|
|
return (f"IOData(id={self.data_id[:8]}, "
|
|
f"type={self.io_type.value}, status={self.status.value})")
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────
|
|
# 抽象基类
|
|
# ─────────────────────────────────────────────────────────────────
|
|
|
|
class BaseInput(QObject):
|
|
"""
|
|
输入模块抽象基类
|
|
子类实现 start() / stop() / _do_read()
|
|
读取到数据后调用 self._emit_input(data) 即可
|
|
"""
|
|
|
|
def __init__(self, source_name: str, io_type: IOType, parent=None):
|
|
super().__init__(parent)
|
|
self.source_name = source_name
|
|
self.io_type = io_type
|
|
self._running = False
|
|
self.logger = logging.getLogger(self.__class__.__name__)
|
|
|
|
@abstractmethod
|
|
def start(self) -> None:
|
|
"""启动输入监听"""
|
|
...
|
|
|
|
@abstractmethod
|
|
def stop(self) -> None:
|
|
"""停止输入监听"""
|
|
...
|
|
|
|
def _emit_input(self, raw_data: Any, metadata: Dict = None) -> IOData:
|
|
"""子类调用此方法将原始数据注入信号管道"""
|
|
data = IOData(
|
|
io_type = self.io_type,
|
|
raw = raw_data,
|
|
source = self.source_name,
|
|
metadata = metadata or {},
|
|
)
|
|
self.logger.debug(f"输入到达: {data}")
|
|
io_signals.input_received.emit(data.data_id, data.io_type.value, data)
|
|
return data
|
|
|
|
|
|
class BaseOutput(QObject):
|
|
"""
|
|
输出模块抽象基类
|
|
连接 io_signals.output_ready 信号,根据 io_type 过滤并渲染
|
|
"""
|
|
|
|
def __init__(self, target_name: str, accepted_types: list, parent=None):
|
|
super().__init__(parent)
|
|
self.target_name = target_name
|
|
self.accepted_types = [t.value if isinstance(t, IOType) else t
|
|
for t in accepted_types]
|
|
self.logger = logging.getLogger(self.__class__.__name__)
|
|
# 自动连接输出信号
|
|
io_signals.output_ready.connect(self._on_output_ready)
|
|
|
|
def _on_output_ready(self, data_id: str, io_type: str, output_data: object):
|
|
"""信号槽:过滤类型后调用子类 render"""
|
|
if io_type not in self.accepted_types:
|
|
return
|
|
try:
|
|
self.render(output_data)
|
|
except Exception as e:
|
|
self.logger.error(f"输出渲染异常: {e}", exc_info=True)
|
|
io_signals.error_occurred.emit(data_id, self.target_name, str(e))
|
|
|
|
@abstractmethod
|
|
def render(self, data: IOData) -> None:
|
|
"""子类实现具体渲染逻辑"""
|
|
... |