mirror of
https://github.com/ZGCA-Forge/Elevator.git
synced 2026-02-04 13:25:23 +00:00
init version
This commit is contained in:
3
elevator_saga/client/__init__.py
Normal file
3
elevator_saga/client/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Elevator scheduling client and algorithms
|
||||
"""
|
||||
258
elevator_saga/client/api_client.py
Normal file
258
elevator_saga/client/api_client.py
Normal file
@@ -0,0 +1,258 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Unified API Client for Elevator Saga
|
||||
使用统一数据模型的客户端API封装
|
||||
"""
|
||||
import json
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from typing import Any, Dict, Optional, Union
|
||||
|
||||
from elevator_saga.core.models import (
|
||||
ElevatorState,
|
||||
FloorState,
|
||||
GoToFloorCommand,
|
||||
PassengerInfo,
|
||||
PerformanceMetrics,
|
||||
SetIndicatorsCommand,
|
||||
SimulationEvent,
|
||||
SimulationState,
|
||||
StepResponse,
|
||||
)
|
||||
from elevator_saga.utils.debug import debug_log
|
||||
|
||||
|
||||
class ElevatorAPIClient:
|
||||
"""统一的电梯API客户端"""
|
||||
|
||||
def __init__(self, base_url: str):
|
||||
self.base_url = base_url.rstrip("/")
|
||||
# 缓存相关字段
|
||||
self._cached_state: Optional[SimulationState] = None
|
||||
self._cached_tick: int = -1
|
||||
self._tick_processed: bool = False # 标记当前tick是否已处理完成
|
||||
debug_log(f"API Client initialized for {self.base_url}")
|
||||
|
||||
def get_state(self, force_reload: bool = False) -> SimulationState:
|
||||
"""获取模拟状态
|
||||
|
||||
Args:
|
||||
force_reload: 是否强制重新加载,忽略缓存
|
||||
"""
|
||||
# 如果不强制重载且缓存有效(当前tick未处理完成),返回缓存
|
||||
if not force_reload and self._cached_state is not None and not self._tick_processed:
|
||||
return self._cached_state
|
||||
|
||||
# debug_log(f"Fetching new state (force_reload={force_reload}, tick_processed={self._tick_processed})")
|
||||
response_data = self._send_get_request("/api/state")
|
||||
if "error" not in response_data:
|
||||
# 直接使用服务端返回的真实数据创建SimulationState
|
||||
elevators = [ElevatorState.from_dict(e) for e in response_data.get("elevators", [])]
|
||||
floors = [FloorState.from_dict(f) for f in response_data.get("floors", [])]
|
||||
|
||||
# 使用服务端返回的passengers和metrics数据
|
||||
passengers_data = response_data.get("passengers", {})
|
||||
if isinstance(passengers_data, dict) and "completed" in passengers_data:
|
||||
# 如果是PassengerSummary格式,则创建空的passengers字典
|
||||
passengers: Dict[int, PassengerInfo] = {}
|
||||
else:
|
||||
# 如果是真实的passengers数据,则转换
|
||||
passengers = {
|
||||
int(k): PassengerInfo.from_dict(v) for k, v in passengers_data.items() if isinstance(v, dict)
|
||||
}
|
||||
|
||||
# 使用服务端返回的metrics数据
|
||||
metrics_data = response_data.get("metrics", {})
|
||||
if metrics_data:
|
||||
# 转换为PerformanceMetrics格式
|
||||
metrics = PerformanceMetrics(
|
||||
completed_passengers=metrics_data.get("done", 0),
|
||||
total_passengers=metrics_data.get("total", 0),
|
||||
average_wait_time=metrics_data.get("avg_wait", 0),
|
||||
p95_wait_time=metrics_data.get("p95_wait", 0),
|
||||
average_system_time=metrics_data.get("avg_system", 0),
|
||||
p95_system_time=metrics_data.get("p95_system", 0),
|
||||
total_energy_consumption=metrics_data.get("energy_total", 0),
|
||||
)
|
||||
else:
|
||||
metrics = PerformanceMetrics()
|
||||
|
||||
simulation_state = SimulationState(
|
||||
tick=response_data.get("tick", 0),
|
||||
elevators=elevators,
|
||||
floors=floors,
|
||||
passengers=passengers,
|
||||
metrics=metrics,
|
||||
events=[],
|
||||
)
|
||||
|
||||
# 更新缓存
|
||||
self._cached_state = simulation_state
|
||||
self._cached_tick = simulation_state.tick
|
||||
self._tick_processed = False # 重置处理标志,表示新tick开始
|
||||
|
||||
return simulation_state
|
||||
else:
|
||||
raise RuntimeError(f"Failed to get state: {response_data.get('error')}")
|
||||
|
||||
def mark_tick_processed(self) -> None:
|
||||
"""标记当前tick处理完成,使缓存在下次get_state时失效"""
|
||||
self._tick_processed = True
|
||||
|
||||
def step(self, ticks: int = 1) -> StepResponse:
|
||||
"""执行步进"""
|
||||
response_data = self._send_post_request("/api/step", {"ticks": ticks})
|
||||
|
||||
if "error" not in response_data:
|
||||
# 使用服务端返回的真实数据
|
||||
events_data = response_data.get("events", [])
|
||||
events = [SimulationEvent.from_dict(event) for event in events_data]
|
||||
|
||||
step_response = StepResponse(
|
||||
success=True,
|
||||
tick=response_data.get("tick", 0),
|
||||
events=events,
|
||||
)
|
||||
|
||||
# debug_log(f"Step response: tick={step_response.tick}, events={len(events)}")
|
||||
return step_response
|
||||
else:
|
||||
raise RuntimeError(f"Step failed: {response_data.get('error')}")
|
||||
|
||||
def send_elevator_command(self, command: Union[GoToFloorCommand, SetIndicatorsCommand]) -> bool:
|
||||
"""发送电梯命令"""
|
||||
endpoint = self._get_elevator_endpoint(command)
|
||||
debug_log(f"Sending elevator command: {command.command_type} to elevator {command.elevator_id}")
|
||||
|
||||
response_data = self._send_post_request(endpoint, command.parameters)
|
||||
|
||||
if response_data.get("success"):
|
||||
return response_data["success"]
|
||||
else:
|
||||
raise RuntimeError(f"Command failed: {response_data.get('error_message')}")
|
||||
|
||||
def go_to_floor(self, elevator_id: int, floor: int, immediate: bool = False) -> bool:
|
||||
"""电梯前往指定楼层"""
|
||||
command = GoToFloorCommand(elevator_id=elevator_id, floor=floor, immediate=immediate)
|
||||
|
||||
try:
|
||||
response = self.send_elevator_command(command)
|
||||
return response
|
||||
except Exception as e:
|
||||
debug_log(f"Go to floor failed: {e}")
|
||||
return False
|
||||
|
||||
def set_indicators(self, elevator_id: int, up: Optional[bool] = None, down: Optional[bool] = None) -> bool:
|
||||
"""设置电梯指示灯"""
|
||||
command = SetIndicatorsCommand(elevator_id=elevator_id, up=up, down=down)
|
||||
|
||||
try:
|
||||
response = self.send_elevator_command(command)
|
||||
return response
|
||||
except Exception as e:
|
||||
debug_log(f"Set indicators failed: {e}")
|
||||
return False
|
||||
|
||||
def _get_elevator_endpoint(self, command: Union[GoToFloorCommand, SetIndicatorsCommand]) -> str:
|
||||
"""获取电梯命令端点"""
|
||||
base = f"/api/elevators/{command.elevator_id}"
|
||||
|
||||
if isinstance(command, GoToFloorCommand):
|
||||
return f"{base}/go_to_floor"
|
||||
else: # SetIndicatorsCommand
|
||||
return f"{base}/set_indicators"
|
||||
|
||||
def _send_get_request(self, endpoint: str) -> Dict[str, Any]:
|
||||
"""发送GET请求"""
|
||||
url = f"{self.base_url}{endpoint}"
|
||||
# debug_log(f"GET {url}")
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(url, timeout=60) as response:
|
||||
data = json.loads(response.read().decode("utf-8"))
|
||||
# debug_log(f"GET {url} -> {response.status}")
|
||||
return data
|
||||
except urllib.error.URLError as e:
|
||||
raise RuntimeError(f"GET {url} failed: {e}")
|
||||
|
||||
def reset(self) -> bool:
|
||||
"""重置模拟"""
|
||||
try:
|
||||
response_data = self._send_post_request("/api/reset", {})
|
||||
success = response_data.get("success", False)
|
||||
if success:
|
||||
# 清空缓存,因为状态已重置
|
||||
self._cached_state = None
|
||||
self._cached_tick = -1
|
||||
self._tick_processed = False
|
||||
debug_log("Cache cleared after reset")
|
||||
return success
|
||||
except Exception as e:
|
||||
debug_log(f"Reset failed: {e}")
|
||||
return False
|
||||
|
||||
def next_traffic_round(self) -> bool:
|
||||
"""切换到下一个流量文件"""
|
||||
try:
|
||||
response_data = self._send_post_request("/api/traffic/next", {})
|
||||
success = response_data.get("success", False)
|
||||
if success:
|
||||
# 清空缓存,因为流量文件已切换,状态会改变
|
||||
self._cached_state = None
|
||||
self._cached_tick = -1
|
||||
self._tick_processed = False
|
||||
debug_log("Cache cleared after traffic round switch")
|
||||
return success
|
||||
except Exception as e:
|
||||
debug_log(f"Next traffic round failed: {e}")
|
||||
return False
|
||||
|
||||
def get_traffic_info(self) -> Optional[Dict[str, Any]]:
|
||||
"""获取当前流量文件信息"""
|
||||
try:
|
||||
response_data = self._send_get_request("/api/traffic/info")
|
||||
debug_log(str())
|
||||
if "error" not in response_data:
|
||||
return response_data
|
||||
else:
|
||||
debug_log(f"Get traffic info failed: {response_data.get('error')}")
|
||||
return None
|
||||
except Exception as e:
|
||||
debug_log(f"Get traffic info failed: {e}")
|
||||
return None
|
||||
|
||||
def force_complete_remaining_passengers(self) -> Optional[int]:
|
||||
"""强制完成所有未完成的乘客,返回完成的乘客数量"""
|
||||
try:
|
||||
response_data = self._send_post_request("/api/force_complete", {})
|
||||
if response_data.get("success"):
|
||||
completed_count = response_data.get("completed_count", 0)
|
||||
debug_log(f"Force completed {completed_count} passengers")
|
||||
# 强制完成后清空缓存
|
||||
self._cached_state = None
|
||||
self._cached_tick = -1
|
||||
self._tick_processed = False
|
||||
return completed_count
|
||||
else:
|
||||
debug_log(f"Force complete failed: {response_data.get('error')}")
|
||||
return None
|
||||
except Exception as e:
|
||||
debug_log(f"Force complete failed: {e}")
|
||||
return None
|
||||
|
||||
def _send_post_request(self, endpoint: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""发送POST请求"""
|
||||
url = f"{self.base_url}{endpoint}"
|
||||
request_body = json.dumps(data).encode("utf-8")
|
||||
|
||||
# debug_log(f"POST {url} with data: {data}")
|
||||
|
||||
req = urllib.request.Request(url, data=request_body, headers={"Content-Type": "application/json"})
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=60) as response:
|
||||
response_data = json.loads(response.read().decode("utf-8"))
|
||||
# debug_log(f"POST {url} -> {response.status}")
|
||||
return response_data
|
||||
except urllib.error.URLError as e:
|
||||
raise RuntimeError(f"POST {url} failed: {e}")
|
||||
395
elevator_saga/client/base_controller.py
Normal file
395
elevator_saga/client/base_controller.py
Normal file
@@ -0,0 +1,395 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Elevator Controller Base Class
|
||||
电梯调度基础控制器类 - 提供面向对象的算法开发接口
|
||||
"""
|
||||
from abc import ABC, abstractmethod
|
||||
from pprint import pprint
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from elevator_saga.client.api_client import ElevatorAPIClient
|
||||
from elevator_saga.client.proxy_models import ProxyElevator, ProxyFloor, ProxyPassenger
|
||||
from elevator_saga.core.models import EventType, SimulationEvent, SimulationState
|
||||
|
||||
# 避免循环导入,使用运行时导入
|
||||
from elevator_saga.utils.debug import debug_log
|
||||
|
||||
|
||||
class ElevatorController(ABC):
|
||||
"""
|
||||
电梯调度控制器基类
|
||||
|
||||
用户通过继承此类并实现 abstract 方法来创建自己的调度算法
|
||||
"""
|
||||
|
||||
def __init__(self, server_url: str = "http://127.0.0.1:8000", debug: bool = False):
|
||||
"""
|
||||
初始化控制器
|
||||
|
||||
Args:
|
||||
server_url: 服务器URL
|
||||
debug: 是否启用debug模式
|
||||
"""
|
||||
self.server_url = server_url
|
||||
self.debug = debug
|
||||
self.elevators: List[Any] = []
|
||||
self.floors: List[Any] = []
|
||||
self.current_tick = 0
|
||||
self.is_running = False
|
||||
self.current_traffic_max_tick: Optional[int] = None
|
||||
|
||||
# 初始化API客户端
|
||||
self.api_client = ElevatorAPIClient(server_url)
|
||||
|
||||
@abstractmethod
|
||||
def on_init(self, elevators: List[Any], floors: List[Any]):
|
||||
"""
|
||||
算法初始化方法 - 必须由子类实现
|
||||
|
||||
Args:
|
||||
elevators: 电梯列表
|
||||
floors: 楼层列表
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def on_event_execute_start(self, tick: int, events: List[Any], elevators: List[Any], floors: List[Any]):
|
||||
"""
|
||||
事件执行前的回调 - 必须由子类实现
|
||||
|
||||
Args:
|
||||
tick: 当前时间tick
|
||||
events: 即将执行的事件列表
|
||||
elevators: 电梯列表
|
||||
floors: 楼层列表
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def on_event_execute_end(self, tick: int, events: List[Any], elevators: List[Any], floors: List[Any]):
|
||||
"""
|
||||
事件执行后的回调 - 必须由子类实现
|
||||
|
||||
Args:
|
||||
tick: 当前时间tick
|
||||
events: 已执行的事件列表
|
||||
elevators: 电梯列表
|
||||
floors: 楼层列表
|
||||
"""
|
||||
pass
|
||||
|
||||
def on_start(self):
|
||||
"""
|
||||
算法启动前的回调 - 可选实现
|
||||
"""
|
||||
print(f"启动 {self.__class__.__name__} 算法")
|
||||
|
||||
def on_stop(self):
|
||||
"""
|
||||
算法停止后的回调 - 可选实现
|
||||
"""
|
||||
print(f"停止 {self.__class__.__name__} 算法")
|
||||
|
||||
@abstractmethod
|
||||
def on_passenger_call(self, floor: ProxyFloor, direction: str):
|
||||
"""
|
||||
乘客呼叫时的回调 - 可选实现
|
||||
|
||||
Args:
|
||||
floor: 呼叫楼层代理对象
|
||||
direction: 方向 ("up" 或 "down")
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def on_elevator_idle(self, elevator: ProxyElevator):
|
||||
"""
|
||||
电梯空闲时的回调 - 可选实现
|
||||
|
||||
Args:
|
||||
elevator: 空闲的电梯代理对象
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def on_elevator_stopped(self, elevator: ProxyElevator, floor: ProxyFloor):
|
||||
"""
|
||||
电梯停靠时的回调 - 可选实现
|
||||
|
||||
Args:
|
||||
elevator: 停靠的电梯代理对象
|
||||
floor: 停靠楼层代理对象
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def on_passenger_board(self, elevator: ProxyElevator, passenger: ProxyPassenger):
|
||||
"""
|
||||
乘客上车时的回调 - 可选实现
|
||||
|
||||
Args:
|
||||
elevator: 电梯代理对象
|
||||
passenger: 乘客代理对象
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def on_passenger_alight(self, elevator: ProxyElevator, passenger: ProxyPassenger, floor: ProxyFloor):
|
||||
"""
|
||||
乘客下车时的回调 - 可选实现
|
||||
|
||||
Args:
|
||||
elevator: 电梯代理对象
|
||||
passenger: 乘客代理对象
|
||||
floor: 下车楼层代理对象
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def on_elevator_passing_floor(self, elevator: ProxyElevator, floor: ProxyFloor, direction: str):
|
||||
"""
|
||||
电梯经过楼层时的回调 - 可选实现
|
||||
|
||||
Args:
|
||||
elevator: 电梯代理对象
|
||||
floor: 经过的楼层代理对象
|
||||
direction: 移动方向
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def on_elevator_approaching(self, elevator: ProxyElevator, floor: ProxyFloor, direction: str):
|
||||
"""
|
||||
电梯即将到达时的回调 - 可选实现
|
||||
|
||||
Args:
|
||||
elevator: 电梯代理对象
|
||||
floor: 即将到达的楼层代理对象
|
||||
direction: 移动方向
|
||||
"""
|
||||
pass
|
||||
|
||||
def _internal_init(self, elevators: List[Any], floors: List[Any]):
|
||||
"""内部初始化方法"""
|
||||
self.elevators = elevators
|
||||
self.floors = floors
|
||||
self.current_tick = 0
|
||||
|
||||
# 调用用户的初始化方法
|
||||
self.on_init(elevators, floors)
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
启动控制器
|
||||
"""
|
||||
self.on_start()
|
||||
self.is_running = True
|
||||
|
||||
try:
|
||||
self._run_event_driven_simulation()
|
||||
except KeyboardInterrupt:
|
||||
print("\n用户中断了算法运行")
|
||||
except Exception as e:
|
||||
print(f"算法运行出错: {e}")
|
||||
raise
|
||||
finally:
|
||||
self.is_running = False
|
||||
self.on_stop()
|
||||
|
||||
def stop(self):
|
||||
"""停止控制器"""
|
||||
self.is_running = False
|
||||
print(f"停止 {self.__class__.__name__}")
|
||||
|
||||
def on_simulation_complete(self, final_state: Dict[str, Any]):
|
||||
"""
|
||||
模拟完成时的回调 - 可选实现
|
||||
|
||||
Args:
|
||||
final_state: 最终状态数据
|
||||
"""
|
||||
pass
|
||||
|
||||
def _run_event_driven_simulation(self):
|
||||
"""运行事件驱动的模拟"""
|
||||
try:
|
||||
# 获取初始状态并初始化
|
||||
state = self.api_client.get_state()
|
||||
self._update_wrappers(state, init=True)
|
||||
|
||||
# 获取当前流量文件的最大tick数
|
||||
self._update_traffic_info()
|
||||
if self.current_tick >= self.current_traffic_max_tick:
|
||||
return
|
||||
|
||||
self._internal_init(self.elevators, self.floors)
|
||||
|
||||
tick_count = 0
|
||||
|
||||
while self.is_running:
|
||||
# 检查是否达到最大tick数
|
||||
if tick_count >= self.current_traffic_max_tick:
|
||||
break
|
||||
|
||||
# 执行一个tick的模拟
|
||||
step_response = self.api_client.step(1)
|
||||
# 更新当前状态
|
||||
self.current_tick = step_response.tick
|
||||
# 获取事件列表
|
||||
events = step_response.events
|
||||
|
||||
# 获取当前状态
|
||||
state = self.api_client.get_state()
|
||||
self._update_wrappers(state)
|
||||
|
||||
# 事件执行前回调
|
||||
self.on_event_execute_start(self.current_tick, events, self.elevators, self.floors)
|
||||
|
||||
# 处理事件
|
||||
if events:
|
||||
for event in events:
|
||||
self._handle_single_event(event)
|
||||
|
||||
# 获取更新后的状态
|
||||
state = self.api_client.get_state()
|
||||
self._update_wrappers(state)
|
||||
|
||||
# 事件执行后回调
|
||||
self.on_event_execute_end(self.current_tick, events, self.elevators, self.floors)
|
||||
|
||||
# 标记tick处理完成,使API客户端缓存失效
|
||||
self.api_client.mark_tick_processed()
|
||||
|
||||
tick_count += 1
|
||||
|
||||
# 检查是否需要切换流量文件
|
||||
if self.current_tick >= self.current_traffic_max_tick:
|
||||
pprint(state.metrics.to_dict())
|
||||
if not self.api_client.next_traffic_round():
|
||||
# 如果没有更多流量文件,退出
|
||||
break
|
||||
|
||||
# 重置并重新初始化
|
||||
self._reset_and_reinit()
|
||||
tick_count = 0
|
||||
|
||||
except Exception as e:
|
||||
print(f"模拟运行错误: {e}")
|
||||
raise
|
||||
|
||||
def _update_wrappers(self, state: SimulationState, init=False) -> None:
|
||||
"""更新电梯和楼层代理对象"""
|
||||
self.current_tick = state.tick
|
||||
# 检查电梯数量是否发生变化,只有变化时才重新创建
|
||||
if len(self.elevators) != len(state.elevators):
|
||||
if not init:
|
||||
raise ValueError(f"Elevator number mismatch: {len(self.elevators)} != {len(state.elevators)}")
|
||||
self.elevators = [ProxyElevator(elevator_state.id, self.api_client) for elevator_state in state.elevators]
|
||||
|
||||
# 检查楼层数量是否发生变化,只有变化时才重新创建
|
||||
if len(self.floors) != len(state.floors):
|
||||
if not init:
|
||||
raise ValueError(f"Floor number mismatch: {len(self.floors)} != {len(state.floors)}")
|
||||
self.floors = [ProxyFloor(floor_state.floor, self.api_client) for floor_state in state.floors]
|
||||
|
||||
def _update_traffic_info(self) -> None:
|
||||
"""更新当前流量文件信息"""
|
||||
try:
|
||||
traffic_info = self.api_client.get_traffic_info()
|
||||
if traffic_info:
|
||||
self.current_traffic_max_tick = traffic_info["max_tick"]
|
||||
debug_log(f"Updated traffic info - max_tick: {self.current_traffic_max_tick}")
|
||||
else:
|
||||
debug_log("Failed to get traffic info")
|
||||
self.current_traffic_max_tick = None
|
||||
except Exception as e:
|
||||
debug_log(f"Error updating traffic info: {e}")
|
||||
self.current_traffic_max_tick = None
|
||||
|
||||
def _handle_single_event(self, event: SimulationEvent):
|
||||
"""处理单个事件"""
|
||||
if event.type == EventType.UP_BUTTON_PRESSED.value:
|
||||
floor_id = event.data.get("floor")
|
||||
if floor_id is not None:
|
||||
floor_proxy = ProxyFloor(floor_id, self.api_client)
|
||||
self.on_passenger_call(floor_proxy, "up")
|
||||
|
||||
elif event.type == EventType.DOWN_BUTTON_PRESSED.value:
|
||||
floor_id = event.data.get("floor")
|
||||
if floor_id is not None:
|
||||
floor_proxy = ProxyFloor(floor_id, self.api_client)
|
||||
self.on_passenger_call(floor_proxy, "down")
|
||||
|
||||
elif event.type == EventType.STOPPED_AT_FLOOR.value:
|
||||
elevator_id = event.data.get("elevator")
|
||||
floor_id = event.data.get("floor")
|
||||
if elevator_id is not None and floor_id is not None:
|
||||
elevator_proxy = ProxyElevator(elevator_id, self.api_client)
|
||||
floor_proxy = ProxyFloor(floor_id, self.api_client)
|
||||
self.on_elevator_stopped(elevator_proxy, floor_proxy)
|
||||
|
||||
elif event.type == EventType.IDLE.value:
|
||||
elevator_id = event.data.get("elevator")
|
||||
if elevator_id is not None:
|
||||
elevator_proxy = ProxyElevator(elevator_id, self.api_client)
|
||||
self.on_elevator_idle(elevator_proxy)
|
||||
|
||||
elif event.type == EventType.PASSING_FLOOR.value:
|
||||
elevator_id = event.data.get("elevator")
|
||||
floor_id = event.data.get("floor")
|
||||
direction = event.data.get("direction")
|
||||
if elevator_id is not None and floor_id is not None and direction is not None:
|
||||
elevator_proxy = ProxyElevator(elevator_id, self.api_client)
|
||||
floor_proxy = ProxyFloor(floor_id, self.api_client)
|
||||
# 服务端发送的direction是字符串,直接使用
|
||||
direction_str = direction if isinstance(direction, str) else direction.value
|
||||
self.on_elevator_passing_floor(elevator_proxy, floor_proxy, direction_str)
|
||||
|
||||
elif event.type == EventType.ELEVATOR_APPROACHING.value:
|
||||
elevator_id = event.data.get("elevator")
|
||||
floor_id = event.data.get("floor")
|
||||
direction = event.data.get("direction")
|
||||
if elevator_id is not None and floor_id is not None and direction is not None:
|
||||
elevator_proxy = ProxyElevator(elevator_id, self.api_client)
|
||||
floor_proxy = ProxyFloor(floor_id, self.api_client)
|
||||
# 服务端发送的direction是字符串,直接使用
|
||||
direction_str = direction if isinstance(direction, str) else direction.value
|
||||
self.on_elevator_approaching(elevator_proxy, floor_proxy, direction_str)
|
||||
|
||||
elif event.type == EventType.PASSENGER_BOARD.value:
|
||||
elevator_id = event.data.get("elevator")
|
||||
passenger_id = event.data.get("passenger")
|
||||
if elevator_id is not None and passenger_id is not None:
|
||||
elevator_proxy = ProxyElevator(elevator_id, self.api_client)
|
||||
passenger_proxy = ProxyPassenger(passenger_id, self.api_client)
|
||||
self.on_passenger_board(elevator_proxy, passenger_proxy)
|
||||
|
||||
elif event.type == EventType.PASSENGER_ALIGHT.value:
|
||||
elevator_id = event.data.get("elevator")
|
||||
passenger_id = event.data.get("passenger")
|
||||
floor_id = event.data.get("floor")
|
||||
if elevator_id is not None and passenger_id is not None and floor_id is not None:
|
||||
elevator_proxy = ProxyElevator(elevator_id, self.api_client)
|
||||
passenger_proxy = ProxyPassenger(passenger_id, self.api_client)
|
||||
floor_proxy = ProxyFloor(floor_id, self.api_client)
|
||||
self.on_passenger_alight(elevator_proxy, passenger_proxy, floor_proxy)
|
||||
|
||||
def _reset_and_reinit(self):
|
||||
"""重置并重新初始化"""
|
||||
try:
|
||||
# 重置服务器状态
|
||||
self.api_client.reset()
|
||||
|
||||
# 获取新的初始状态
|
||||
state = self.api_client.get_state()
|
||||
self._update_wrappers(state)
|
||||
|
||||
# 更新流量信息(切换到新流量文件后需要重新获取最大tick)
|
||||
self._update_traffic_info()
|
||||
|
||||
# 重新初始化用户算法
|
||||
self._internal_init(self.elevators, self.floors)
|
||||
|
||||
except Exception as e:
|
||||
debug_log(f"重置失败: {e}")
|
||||
raise
|
||||
173
elevator_saga/client/proxy_models.py
Normal file
173
elevator_saga/client/proxy_models.py
Normal file
@@ -0,0 +1,173 @@
|
||||
from typing import Any
|
||||
|
||||
from elevator_saga.client.api_client import ElevatorAPIClient
|
||||
from elevator_saga.core.models import ElevatorState, FloorState, PassengerInfo
|
||||
|
||||
|
||||
class ProxyFloor(FloorState):
|
||||
"""
|
||||
楼层动态代理类
|
||||
直接使用 FloorState 数据模型实例,提供完整的类型安全访问
|
||||
"""
|
||||
|
||||
init_ok = False
|
||||
|
||||
def __init__(self, floor_id: int, api_client: ElevatorAPIClient):
|
||||
self._floor_id = floor_id
|
||||
self._api_client = api_client
|
||||
self._cached_instance = None
|
||||
self.init_ok = True
|
||||
|
||||
def _get_floor_state(self) -> FloorState:
|
||||
"""获取 FloorState 实例"""
|
||||
# 获取当前状态
|
||||
state = self._api_client.get_state()
|
||||
floor_data = next((f for f in state.floors if f.floor == self._floor_id), None)
|
||||
|
||||
if floor_data is None:
|
||||
raise AttributeError(f"Floor {self._floor_id} not found")
|
||||
|
||||
# 如果是字典,转换为 FloorState 实例
|
||||
if isinstance(floor_data, dict):
|
||||
return FloorState.from_dict(floor_data)
|
||||
else:
|
||||
# 如果已经是 FloorState 实例,直接返回
|
||||
return floor_data
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
"""动态获取楼层属性"""
|
||||
floor_state = self._get_floor_state()
|
||||
try:
|
||||
if hasattr(floor_state, name):
|
||||
attr = getattr(floor_state, name)
|
||||
# 如果是 property 或方法,调用并返回结果
|
||||
if callable(attr):
|
||||
return attr()
|
||||
else:
|
||||
return attr
|
||||
except AttributeError:
|
||||
|
||||
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
|
||||
|
||||
def __setattr__(self, name: str, value: Any) -> None:
|
||||
"""禁止修改属性,保持只读特性"""
|
||||
if not self.init_ok:
|
||||
object.__setattr__(self, name, value)
|
||||
else:
|
||||
raise AttributeError(f"Cannot modify read-only attribute '{name}'")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"ProxyFloor(floor={self._floor_id})"
|
||||
|
||||
|
||||
class ProxyElevator(ElevatorState):
|
||||
"""
|
||||
电梯动态代理类
|
||||
直接使用 ElevatorState 数据模型实例,提供完整的类型安全访问和操作方法
|
||||
"""
|
||||
|
||||
init_ok = False
|
||||
|
||||
def __init__(self, elevator_id: int, api_client: ElevatorAPIClient):
|
||||
self._elevator_id = elevator_id
|
||||
self._api_client = api_client
|
||||
self.init_ok = True
|
||||
|
||||
def _get_elevator_state(self) -> ElevatorState:
|
||||
"""获取 ElevatorState 实例"""
|
||||
# 获取当前状态
|
||||
state = self._api_client.get_state()
|
||||
elevator_data = next((e for e in state.elevators if e.id == self._elevator_id), None)
|
||||
|
||||
if elevator_data is None:
|
||||
raise AttributeError(f"Elevator {self._elevator_id} not found")
|
||||
|
||||
# 如果是字典,转换为 ElevatorState 实例
|
||||
if isinstance(elevator_data, dict):
|
||||
return ElevatorState.from_dict(elevator_data)
|
||||
else:
|
||||
# 如果已经是 ElevatorState 实例,直接返回
|
||||
return elevator_data
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
"""动态获取电梯属性"""
|
||||
try:
|
||||
elevator_state = self._get_elevator_state()
|
||||
# 直接从 ElevatorState 实例获取属性
|
||||
return getattr(elevator_state, name)
|
||||
|
||||
except AttributeError:
|
||||
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
|
||||
|
||||
def go_to_floor(self, floor: int, immediate: bool = False) -> bool:
|
||||
"""前往指定楼层"""
|
||||
return self._api_client.go_to_floor(self._elevator_id, floor, immediate)
|
||||
|
||||
def set_up_indicator(self, value: bool) -> None:
|
||||
"""设置上行指示器"""
|
||||
self._api_client.set_indicators(self._elevator_id, up=value)
|
||||
|
||||
def set_down_indicator(self, value: bool) -> None:
|
||||
"""设置下行指示器"""
|
||||
self._api_client.set_indicators(self._elevator_id, down=value)
|
||||
|
||||
def __setattr__(self, name: str, value: Any) -> None:
|
||||
"""禁止修改属性,保持只读特性"""
|
||||
if not self.init_ok:
|
||||
object.__setattr__(self, name, value)
|
||||
else:
|
||||
raise AttributeError(f"Cannot modify read-only attribute '{name}'")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"ProxyElevator(id={self._elevator_id})"
|
||||
|
||||
|
||||
class ProxyPassenger(PassengerInfo):
|
||||
"""
|
||||
乘客动态代理类
|
||||
直接使用 PassengerInfo 数据模型实例,提供完整的类型安全访问
|
||||
"""
|
||||
|
||||
init_ok = False
|
||||
|
||||
def __init__(self, passenger_id: int, api_client: ElevatorAPIClient):
|
||||
self._passenger_id = passenger_id
|
||||
self._api_client = api_client
|
||||
self.init_ok = True
|
||||
|
||||
def _get_passenger_info(self) -> PassengerInfo:
|
||||
"""获取 PassengerInfo 实例"""
|
||||
# 获取当前状态
|
||||
state = self._api_client.get_state()
|
||||
|
||||
# 在乘客字典中查找
|
||||
passenger_data = state.passengers.get(self._passenger_id)
|
||||
if passenger_data is None:
|
||||
raise AttributeError(f"Passenger {self._passenger_id} not found")
|
||||
|
||||
# 如果是字典,转换为 PassengerInfo 实例
|
||||
if isinstance(passenger_data, dict):
|
||||
return PassengerInfo.from_dict(passenger_data)
|
||||
else:
|
||||
# 如果已经是 PassengerInfo 实例,直接返回
|
||||
return passenger_data
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
"""动态获取乘客属性"""
|
||||
try:
|
||||
passenger_info = self._get_passenger_info()
|
||||
# 直接从 PassengerInfo 实例获取属性
|
||||
return getattr(passenger_info, name)
|
||||
|
||||
except AttributeError:
|
||||
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
|
||||
|
||||
def __setattr__(self, name: str, value: Any) -> None:
|
||||
"""禁止修改属性,保持只读特性"""
|
||||
if not self.init_ok:
|
||||
object.__setattr__(self, name, value)
|
||||
else:
|
||||
raise AttributeError(f"Cannot modify read-only attribute '{name}'")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"ProxyPassenger(id={self._passenger_id})"
|
||||
Reference in New Issue
Block a user