Files
Uni-Lab-OS/unilabos/devices/workstation/workstation_http_service.py
ZiWei a625a86e3e HR物料同步,前端展示位置修复 (#135)
* 更新Bioyond工作站配置,添加新的物料类型映射和载架定义,优化物料查询逻辑

* 添加Bioyond实验配置文件,定义物料类型映射和设备配置

* 更新bioyond_warehouse_reagent_stack方法,修正试剂堆栈尺寸和布局描述

* 更新Bioyond实验配置,修正物料类型映射,优化设备配置

* 更新Bioyond资源同步逻辑,优化物料入库流程,增强错误处理和日志记录

* 更新Bioyond资源,添加配液站和反应站专用载架,优化仓库工厂函数的排序方式

* 更新Bioyond资源,添加配液站和反应站相关载架,优化试剂瓶和样品瓶配置

* 更新Bioyond实验配置,修正试剂瓶载架ID,确保与设备匹配

* 更新Bioyond资源,移除反应站单烧杯载架,添加反应站单烧瓶载架分类

* Refactor Bioyond resource synchronization and update bottle carrier definitions

- Removed traceback printing in error handling for Bioyond synchronization.
- Enhanced logging for existing Bioyond material ID usage during synchronization.
- Added new bottle carrier definitions for single flask and updated existing ones.
- Refactored dispensing station and reaction station bottle definitions for clarity and consistency.
- Improved resource mapping and error handling in graphio for Bioyond resource conversion.
- Introduced layout parameter in warehouse factory for better warehouse configuration.

* 更新Bioyond仓库工厂,添加排序方式支持,优化坐标计算逻辑

* 更新Bioyond载架和甲板配置,调整样品板尺寸和仓库坐标

* 更新Bioyond资源同步,增强占用位置日志信息,修正坐标转换逻辑

* 更新Bioyond反应站和分配站配置,调整材料类型映射和ID,移除不必要的项

* support name change during materials change

* fix json dumps

* correct tip

* 优化调度器API路径,更新相关方法描述

* 更新 BIOYOND 载架相关文档,调整 API 以支持自带试剂瓶的载架类型,修复资源获取时的子物料处理逻辑

* 实现资源删除时的同步处理,优化出库操作逻辑

* 修复 ItemizedCarrier 中的可见性逻辑

* 保存 Bioyond 原始信息到 unilabos_extra,以便出库时查询

* 根据 resource.capacity 判断是试剂瓶(载架)还是多瓶载架,走不同的奔曜转换

* Fix bioyond bottle_carriers ordering

* 优化 Bioyond 物料同步逻辑,增强坐标解析和位置更新处理

* disable slave connect websocket

* correct remove_resource stats

* change uuid logger to trace level

* enable slave mode

* refactor(bioyond): 统一资源命名并优化物料同步逻辑

- 将DispensingStation和ReactionStation资源统一为PolymerStation命名
- 优化物料同步逻辑,支持耗材类型(typeMode=0)的查询
- 添加物料默认参数配置功能
- 调整仓库坐标布局
- 清理废弃资源定义

* feat(warehouses): 为仓库函数添加col_offset和layout参数

* refactor: 更新实验配置中的物料类型映射命名

将DispensingStation和ReactionStation的物料类型映射统一更名为PolymerStation,保持命名一致性

* fix: 更新实验配置中的载体名称从6VialCarrier到6StockCarrier

* feat(bioyond): 实现物料创建与入库分离逻辑

将物料同步流程拆分为两个独立阶段:transfer阶段只创建物料,add阶段执行入库
简化状态检查接口,仅返回连接状态

* fix(reaction_station): 修正液体进料烧杯体积单位并增强返回结果

将液体进料烧杯的体积单位从μL改为g以匹配实际使用场景
在返回结果中添加merged_workflow和order_params字段,提供更完整的工作流信息

* feat(dispensing_station): 在任务创建返回结果中添加order_params信息

在create_order方法返回结果中增加order_params字段,以便调用方获取完整的任务参数

* fix(dispensing_station): 修改90%物料分配逻辑从分成3份改为直接使用

原逻辑将主称固体平均分成3份作为90%物料,现改为直接使用main_portion

* feat(bioyond): 添加任务编码和任务ID的输出,支持批量任务创建后的状态监控

* refactor(registry): 简化设备配置中的任务结果处理逻辑

将多个单独的任务编码和ID字段合并为统一的return_info字段
更新相关描述以反映新的数据结构

* feat(工作站): 添加HTTP报送服务和任务完成状态跟踪

- 在graphio.py中添加API必需字段
- 实现工作站HTTP服务启动和停止逻辑
- 添加任务完成状态跟踪字典和等待方法
- 重写任务完成报送处理方法记录状态
- 支持批量任务完成等待和报告获取

* refactor(dispensing_station): 移除wait_for_order_completion_and_get_report功能

该功能已被wait_for_multiple_orders_and_get_reports替代,简化代码结构

* fix: 更新任务报告API错误

* fix(workstation_http_service): 修复状态查询中device_id获取逻辑

处理状态查询时安全获取device_id,避免因属性不存在导致的异常

* fix(bioyond_studio): 改进物料入库失败时的错误处理和日志记录

在物料入库API调用失败时,添加更详细的错误信息打印
同时修正station.py中对空响应和失败情况的判断逻辑

* refactor(bioyond): 优化瓶架载体的分配逻辑和注释说明

重构瓶架载体的分配逻辑,使用嵌套循环替代硬编码索引分配
添加更详细的坐标映射说明,明确PLR与Bioyond坐标的对应关系

* fix(bioyond_rpc): 修复物料入库成功时无data字段返回空的问题

当API返回成功但无data字段时,返回包含success标识的字典而非空字典

---------

Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com>
Co-authored-by: Junhan Chang <changjh@dp.tech>
2025-11-15 03:11:34 +08:00

727 lines
29 KiB
Python
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.

"""
工作站HTTP服务模块
Workstation HTTP Service Module
统一的工作站报送接收服务基于LIMS协议规范
1. 步骤完成报送 - POST /report/step_finish
2. 通量完成报送 - POST /report/sample_finish
3. 任务完成报送 - POST /report/order_finish
4. 批量更新报送 - POST /report/batch_update
5. 物料变更报送 - POST /report/material_change
6. 错误处理报送 - POST /report/error_handling
7. 健康检查和状态查询
统一使用LIMS协议字段规范简化接口避免功能重复
"""
import json
import threading
import time
import traceback
from typing import Dict, Any, Optional, List
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse
from dataclasses import dataclass, asdict
from datetime import datetime
from unilabos.utils.log import logger
@dataclass
class WorkstationReportRequest:
"""统一工作站报送请求基于LIMS协议规范"""
token: str # 授权令牌
request_time: str # 请求时间格式2024-12-12 12:12:12.xxx
data: Dict[str, Any] # 报送数据
@dataclass
class MaterialUsage:
"""物料使用记录"""
materialId: str # 物料IdGUID
locationId: str # 库位IdGUID
typeMode: str # 物料类型样品1、试剂2、耗材0
usedQuantity: float # 使用的数量(数字)
@dataclass
class HttpResponse:
"""HTTP响应"""
success: bool
message: str
data: Optional[Dict[str, Any]] = None
acknowledgment_id: Optional[str] = None
class WorkstationHTTPHandler(BaseHTTPRequestHandler):
"""工作站HTTP请求处理器"""
def __init__(self, workstation_instance, *args, **kwargs):
self.workstation = workstation_instance
super().__init__(*args, **kwargs)
def do_POST(self):
"""处理POST请求 - 统一的工作站报送接口"""
try:
# 解析请求路径
parsed_path = urlparse(self.path)
endpoint = parsed_path.path
# 读取请求体
content_length = int(self.headers.get('Content-Length', 0))
if content_length > 0:
post_data = self.rfile.read(content_length)
request_data = json.loads(post_data.decode('utf-8'))
else:
request_data = {}
logger.info(f"收到工作站报送: {endpoint} - {request_data.get('token', 'unknown')}")
# 统一的报送端点路由基于LIMS协议规范
if endpoint == '/report/step_finish':
response = self._handle_step_finish_report(request_data)
elif endpoint == '/report/sample_finish':
response = self._handle_sample_finish_report(request_data)
elif endpoint == '/report/order_finish':
response = self._handle_order_finish_report(request_data)
elif endpoint == '/report/batch_update':
response = self._handle_batch_update_report(request_data)
# 扩展报送端点
elif endpoint == '/report/material_change':
response = self._handle_material_change_report(request_data)
elif endpoint == '/report/error_handling':
response = self._handle_error_handling_report(request_data)
# 保留LIMS协议端点以兼容现有系统
elif endpoint == '/LIMS/step_finish':
response = self._handle_step_finish_report(request_data)
elif endpoint == '/LIMS/preintake_finish':
response = self._handle_sample_finish_report(request_data)
elif endpoint == '/LIMS/order_finish':
response = self._handle_order_finish_report(request_data)
else:
response = HttpResponse(
success=False,
message=f"不支持的报送端点: {endpoint}",
data={"supported_endpoints": [
"/report/step_finish",
"/report/sample_finish",
"/report/order_finish",
"/report/batch_update",
"/report/material_change",
"/report/error_handling"
]}
)
# 发送响应
self._send_response(response)
except Exception as e:
logger.error(f"处理工作站报送失败: {e}\\n{traceback.format_exc()}")
error_response = HttpResponse(
success=False,
message=f"请求处理失败: {str(e)}"
)
self._send_response(error_response)
def do_GET(self):
"""处理GET请求 - 健康检查和状态查询"""
try:
parsed_path = urlparse(self.path)
endpoint = parsed_path.path
if endpoint == '/status':
response = self._handle_status_check()
elif endpoint == '/health':
response = HttpResponse(success=True, message="服务健康")
else:
response = HttpResponse(
success=False,
message=f"不支持的查询端点: {endpoint}",
data={"supported_endpoints": ["/status", "/health"]}
)
self._send_response(response)
except Exception as e:
logger.error(f"GET请求处理失败: {e}")
error_response = HttpResponse(
success=False,
message=f"GET请求处理失败: {str(e)}"
)
self._send_response(error_response)
def do_OPTIONS(self):
"""处理OPTIONS请求 - CORS预检请求"""
try:
# 发送CORS响应头
self.send_response(200)
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
self.send_header('Access-Control-Max-Age', '86400')
self.end_headers()
except Exception as e:
logger.error(f"OPTIONS请求处理失败: {e}")
self.send_response(500)
self.end_headers()
def _handle_step_finish_report(self, request_data: Dict[str, Any]) -> HttpResponse:
"""处理步骤完成报送统一LIMS协议规范"""
try:
# 验证基本字段
required_fields = ['token', 'request_time', 'data']
if missing_fields := [field for field in required_fields if field not in request_data]:
return HttpResponse(
success=False,
message=f"缺少必要字段: {', '.join(missing_fields)}"
)
# 验证data字段内容
data = request_data['data']
data_required_fields = ['orderCode', 'orderName', 'stepName', 'stepId', 'sampleId', 'startTime', 'endTime']
if data_missing_fields := [field for field in data_required_fields if field not in data]:
return HttpResponse(
success=False,
message=f"data字段缺少必要内容: {', '.join(data_missing_fields)}"
)
# 创建统一请求对象
report_request = WorkstationReportRequest(
token=request_data['token'],
request_time=request_data['request_time'],
data=data
)
# 调用工作站处理方法
result = self.workstation.process_step_finish_report(report_request)
return HttpResponse(
success=True,
message=f"步骤完成报送已处理: {data['stepName']} ({data['orderCode']})",
acknowledgment_id=f"STEP_{int(time.time() * 1000)}_{data['stepId']}",
data=result
)
except Exception as e:
logger.error(f"处理步骤完成报送失败: {e}")
return HttpResponse(
success=False,
message=f"步骤完成报送处理失败: {str(e)}"
)
def _handle_sample_finish_report(self, request_data: Dict[str, Any]) -> HttpResponse:
"""处理通量完成报送统一LIMS协议规范"""
try:
# 验证基本字段
required_fields = ['token', 'request_time', 'data']
if missing_fields := [field for field in required_fields if field not in request_data]:
return HttpResponse(
success=False,
message=f"缺少必要字段: {', '.join(missing_fields)}"
)
# 验证data字段内容
data = request_data['data']
data_required_fields = ['orderCode', 'orderName', 'sampleId', 'startTime', 'endTime', 'status']
if data_missing_fields := [field for field in data_required_fields if field not in data]:
return HttpResponse(
success=False,
message=f"data字段缺少必要内容: {', '.join(data_missing_fields)}"
)
# 创建统一请求对象
report_request = WorkstationReportRequest(
token=request_data['token'],
request_time=request_data['request_time'],
data=data
)
# 调用工作站处理方法
result = self.workstation.process_sample_finish_report(report_request)
status_names = {
"0": "待生产", "2": "进样", "10": "开始",
"20": "完成", "-2": "异常停止", "-3": "人工停止"
}
status_desc = status_names.get(str(data['status']), f"状态{data['status']}")
return HttpResponse(
success=True,
message=f"通量完成报送已处理: {data['sampleId']} ({data['orderCode']}) - {status_desc}",
acknowledgment_id=f"SAMPLE_{int(time.time() * 1000)}_{data['sampleId']}",
data=result
)
except Exception as e:
logger.error(f"处理通量完成报送失败: {e}")
return HttpResponse(
success=False,
message=f"通量完成报送处理失败: {str(e)}"
)
def _handle_order_finish_report(self, request_data: Dict[str, Any]) -> HttpResponse:
"""处理任务完成报送统一LIMS协议规范"""
try:
# 验证基本字段
required_fields = ['token', 'request_time', 'data']
if missing_fields := [field for field in required_fields if field not in request_data]:
return HttpResponse(
success=False,
message=f"缺少必要字段: {', '.join(missing_fields)}"
)
# 验证data字段内容
data = request_data['data']
data_required_fields = ['orderCode', 'orderName', 'startTime', 'endTime', 'status']
if data_missing_fields := [field for field in data_required_fields if field not in data]:
return HttpResponse(
success=False,
message=f"data字段缺少必要内容: {', '.join(data_missing_fields)}"
)
# 处理物料使用记录
used_materials = []
if 'usedMaterials' in data:
for material_data in data['usedMaterials']:
material = MaterialUsage(
materialId=material_data.get('materialId', ''),
locationId=material_data.get('locationId', ''),
typeMode=material_data.get('typeMode', ''),
usedQuantity=material_data.get('usedQuantity', 0.0)
)
used_materials.append(material)
# 创建统一请求对象
report_request = WorkstationReportRequest(
token=request_data['token'],
request_time=request_data['request_time'],
data=data
)
# 调用工作站处理方法
result = self.workstation.process_order_finish_report(report_request, used_materials)
status_names = {"30": "完成", "-11": "异常停止", "-12": "人工停止"}
status_desc = status_names.get(str(data['status']), f"状态{data['status']}")
return HttpResponse(
success=True,
message=f"任务完成报送已处理: {data['orderName']} ({data['orderCode']}) - {status_desc}",
acknowledgment_id=f"ORDER_{int(time.time() * 1000)}_{data['orderCode']}",
data=result
)
except Exception as e:
logger.error(f"处理任务完成报送失败: {e}")
return HttpResponse(
success=False,
message=f"任务完成报送处理失败: {str(e)}"
)
def _handle_batch_update_report(self, request_data: Dict[str, Any]) -> HttpResponse:
"""处理批量报送"""
try:
step_updates = request_data.get('step_updates', [])
sample_updates = request_data.get('sample_updates', [])
order_updates = request_data.get('order_updates', [])
results = {
'step_results': [],
'sample_results': [],
'order_results': [],
'total_processed': 0,
'total_failed': 0
}
# 处理批量步骤更新
for step_data in step_updates:
try:
step_data['token'] = request_data.get('token', step_data.get('token'))
step_data['request_time'] = request_data.get('request_time', step_data.get('request_time'))
result = self._handle_step_finish_report(step_data)
results['step_results'].append(result)
if result.success:
results['total_processed'] += 1
else:
results['total_failed'] += 1
except Exception as e:
results['step_results'].append(HttpResponse(success=False, message=str(e)))
results['total_failed'] += 1
# 处理批量通量更新
for sample_data in sample_updates:
try:
sample_data['token'] = request_data.get('token', sample_data.get('token'))
sample_data['request_time'] = request_data.get('request_time', sample_data.get('request_time'))
result = self._handle_sample_finish_report(sample_data)
results['sample_results'].append(result)
if result.success:
results['total_processed'] += 1
else:
results['total_failed'] += 1
except Exception as e:
results['sample_results'].append(HttpResponse(success=False, message=str(e)))
results['total_failed'] += 1
# 处理批量任务更新
for order_data in order_updates:
try:
order_data['token'] = request_data.get('token', order_data.get('token'))
order_data['request_time'] = request_data.get('request_time', order_data.get('request_time'))
result = self._handle_order_finish_report(order_data)
results['order_results'].append(result)
if result.success:
results['total_processed'] += 1
else:
results['total_failed'] += 1
except Exception as e:
results['order_results'].append(HttpResponse(success=False, message=str(e)))
results['total_failed'] += 1
return HttpResponse(
success=results['total_failed'] == 0,
message=f"批量报送处理完成: {results['total_processed']} 成功, {results['total_failed']} 失败",
acknowledgment_id=f"BATCH_{int(time.time() * 1000)}",
data=results
)
except Exception as e:
logger.error(f"处理批量报送失败: {e}")
return HttpResponse(
success=False,
message=f"批量报送处理失败: {str(e)}"
)
def _handle_material_change_report(self, request_data: Dict[str, Any]) -> HttpResponse:
"""处理物料变更报送"""
try:
# 验证必需字段
if 'brand' in request_data:
if request_data['brand'] == "bioyond": # 奔曜
error_msg = request_data["text"]
logger.info(f"收到奔曜错误处理报送: {error_msg}")
return HttpResponse(
success=True,
message=f"错误处理报送已收到: {error_msg}",
acknowledgment_id=f"ERROR_{int(time.time() * 1000)}_{error_msg.get('action_id', 'unknown')}",
data=None
)
else:
return HttpResponse(
success=False,
message=f"缺少厂家信息brand字段"
)
required_fields = ['workstation_id', 'timestamp', 'resource_id', 'change_type']
if missing_fields := [field for field in required_fields if field not in request_data]:
return HttpResponse(
success=False,
message=f"缺少必要字段: {', '.join(missing_fields)}"
)
# 调用工作站的处理方法
result = self.workstation.process_material_change_report(request_data)
return HttpResponse(
success=True,
message=f"物料变更报送已处理: {request_data['resource_id']} ({request_data['change_type']})",
acknowledgment_id=f"MATERIAL_{int(time.time() * 1000)}_{request_data['resource_id']}",
data=result
)
except Exception as e:
logger.error(f"处理物料变更报送失败: {e}")
return HttpResponse(
success=False,
message=f"物料变更报送处理失败: {str(e)}"
)
def _handle_error_handling_report(self, request_data: Dict[str, Any]) -> HttpResponse:
"""处理错误处理报送"""
try:
# 检查是否为奔曜格式的错误报送
if 'brand' in request_data and str(request_data['brand']).lower() == "bioyond":
# 奔曜格式处理
if 'text' not in request_data:
return HttpResponse(
success=False,
message="奔曜格式缺少text字段"
)
error_data = request_data["text"]
logger.info(f"收到奔曜错误处理报送: {error_data}")
# 调用工作站的处理方法
result = self.workstation.handle_external_error(error_data)
return HttpResponse(
success=True,
message=f"错误处理报送已收到: 任务{error_data.get('task', 'unknown')}, 错误代码{error_data.get('code', 'unknown')}",
acknowledgment_id=f"ERROR_{int(time.time() * 1000)}_{error_data.get('task', 'unknown')}",
data=result
)
else:
# 标准格式处理
required_fields = ['workstation_id', 'timestamp', 'error_type', 'error_message']
if missing_fields := [field for field in required_fields if field not in request_data]:
return HttpResponse(
success=False,
message=f"缺少必要字段: {', '.join(missing_fields)}"
)
# 调用工作站的处理方法
result = self.workstation.handle_external_error(request_data)
return HttpResponse(
success=True,
message=f"错误处理报送已处理: {request_data['error_type']} - {request_data['error_message']}",
acknowledgment_id=f"ERROR_{int(time.time() * 1000)}_{request_data.get('action_id', 'unknown')}",
data=result
)
except Exception as e:
logger.error(f"处理错误处理报送失败: {e}")
return HttpResponse(
success=False,
message=f"错误处理报送处理失败: {str(e)}"
)
def _handle_status_check(self) -> HttpResponse:
"""处理状态查询"""
try:
# 安全地获取 device_id
device_id = "unknown"
if hasattr(self.workstation, 'device_id'):
device_id = self.workstation.device_id
elif hasattr(self.workstation, '_ros_node') and hasattr(self.workstation._ros_node, 'device_id'):
device_id = self.workstation._ros_node.device_id
return HttpResponse(
success=True,
message="工作站报送服务正常运行",
data={
"workstation_id": device_id,
"service_type": "unified_reporting_service",
"uptime": time.time() - getattr(self.workstation, '_start_time', time.time()),
"reports_received": getattr(self.workstation, '_reports_received_count', 0),
"supported_endpoints": [
"POST /report/step_finish",
"POST /report/sample_finish",
"POST /report/order_finish",
"POST /report/batch_update",
"POST /report/material_change",
"POST /report/error_handling",
"GET /status",
"GET /health"
]
}
)
except Exception as e:
logger.error(f"处理状态查询失败: {e}")
return HttpResponse(
success=False,
message=f"状态查询失败: {str(e)}"
)
def _send_response(self, response: HttpResponse):
"""发送响应"""
try:
# 设置响应状态码
status_code = 200 if response.success else 400
self.send_response(status_code)
# 设置响应头
self.send_header('Content-Type', 'application/json; charset=utf-8')
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
self.end_headers()
# 发送响应体
response_json = json.dumps(asdict(response), ensure_ascii=False, indent=2)
self.wfile.write(response_json.encode('utf-8'))
except Exception as e:
logger.error(f"发送响应失败: {e}")
def log_message(self, format, *args):
"""重写日志方法"""
logger.debug(f"HTTP请求: {format % args}")
class WorkstationHTTPService:
"""工作站HTTP服务"""
def __init__(self, workstation_instance, host: str = "127.0.0.1", port: int = 8080):
self.workstation = workstation_instance
self.host = host
self.port = port
self.server = None
self.server_thread = None
self.running = False
# 初始化统计信息
self.workstation._start_time = time.time()
self.workstation._reports_received_count = 0
def start(self):
"""启动HTTP服务"""
try:
# 创建处理器工厂函数
def handler_factory(*args, **kwargs):
return WorkstationHTTPHandler(self.workstation, *args, **kwargs)
# 创建HTTP服务器
self.server = HTTPServer((self.host, self.port), handler_factory)
# 安全地获取 device_id 用于线程命名
device_id = "unknown"
if hasattr(self.workstation, 'device_id'):
device_id = self.workstation.device_id
elif hasattr(self.workstation, '_ros_node') and hasattr(self.workstation._ros_node, 'device_id'):
device_id = self.workstation._ros_node.device_id
# 在单独线程中运行服务器
self.server_thread = threading.Thread(
target=self._run_server,
daemon=True,
name=f"WorkstationHTTP-{device_id}"
)
self.running = True
self.server_thread.start()
logger.info(f"工作站HTTP报送服务已启动: http://{self.host}:{self.port}")
logger.info("统一的报送端点 (基于LIMS协议规范):")
logger.info(" - POST /report/step_finish # 步骤完成报送")
logger.info(" - POST /report/sample_finish # 通量完成报送")
logger.info(" - POST /report/order_finish # 任务完成报送")
logger.info(" - POST /report/batch_update # 批量更新报送")
logger.info("扩展报送端点:")
logger.info(" - POST /report/material_change # 物料变更报送")
logger.info(" - POST /report/error_handling # 错误处理报送")
logger.info("兼容端点:")
logger.info(" - POST /LIMS/step_finish # 兼容LIMS步骤完成")
logger.info(" - POST /LIMS/preintake_finish # 兼容LIMS通量完成")
logger.info(" - POST /LIMS/order_finish # 兼容LIMS任务完成")
logger.info("服务端点:")
logger.info(" - GET /status # 服务状态查询")
logger.info(" - GET /health # 健康检查")
except Exception as e:
logger.error(f"启动HTTP服务失败: {e}")
raise
def stop(self):
"""停止HTTP服务"""
try:
if self.running and self.server:
logger.info("正在停止工作站HTTP报送服务...")
self.running = False
# 停止serve_forever循环
self.server.shutdown()
# 等待服务器线程结束
if self.server_thread and self.server_thread.is_alive():
self.server_thread.join(timeout=5.0)
# 关闭服务器套接字
self.server.server_close()
logger.info("工作站HTTP报送服务已停止")
except Exception as e:
logger.error(f"停止HTTP服务失败: {e}")
def _run_server(self):
"""运行HTTP服务器"""
try:
# 使用serve_forever()让服务持续运行
self.server.serve_forever()
except Exception as e:
if self.running: # 只在非正常停止时记录错误
logger.error(f"HTTP服务运行错误: {e}")
finally:
logger.info("HTTP服务器线程已退出")
@property
def is_running(self) -> bool:
"""检查服务是否正在运行"""
return self.running and self.server_thread and self.server_thread.is_alive()
@property
def service_url(self) -> str:
"""获取服务URL"""
return f"http://{self.host}:{self.port}"
# 导出主要类 - 保持向后兼容
@dataclass
class MaterialChangeReport:
"""已废弃物料变更报送请使用统一的WorkstationReportRequest"""
pass
@dataclass
class TaskExecutionReport:
"""已废弃任务执行报送请使用统一的WorkstationReportRequest"""
pass
# 导出列表
__all__ = [
'WorkstationReportRequest',
'MaterialUsage',
'HttpResponse',
'WorkstationHTTPService',
# 向后兼容
'MaterialChangeReport',
'TaskExecutionReport'
]
if __name__ == "__main__":
# 简单测试HTTP服务
class BioyondWorkstation:
device_id = "WS-001"
def process_step_finish_report(self, report_request):
return {"processed": True}
def process_sample_finish_report(self, report_request):
return {"processed": True}
def process_order_finish_report(self, report_request, used_materials):
return {"processed": True}
def process_material_change_report(self, report_data):
return {"processed": True}
def handle_external_error(self, error_data):
return {"handled": True}
workstation = BioyondWorkstation()
http_service = WorkstationHTTPService(workstation)
try:
http_service.start()
print(f"测试服务器已启动: {http_service.service_url}")
print("按 Ctrl+C 停止服务器")
print("服务将持续运行等待接收HTTP请求...")
# 保持服务器运行 - 使用更好的等待机制
try:
while http_service.is_running:
time.sleep(1)
except KeyboardInterrupt:
print("\n接收到停止信号...")
except KeyboardInterrupt:
print("\n正在停止服务器...")
http_service.stop()
print("服务器已停止")
except Exception as e:
print(f"服务器运行错误: {e}")
http_service.stop()