fix downstairs

This commit is contained in:
Xuwznln
2025-09-28 20:23:11 +08:00
parent 16d2fec0ac
commit 6a3b312e41
6 changed files with 203 additions and 173 deletions

View File

@@ -72,7 +72,7 @@ class ElevatorAPIClient:
p95_wait_time=metrics_data.get("p95_wait", 0), p95_wait_time=metrics_data.get("p95_wait", 0),
average_system_time=metrics_data.get("avg_system", 0), average_system_time=metrics_data.get("avg_system", 0),
p95_system_time=metrics_data.get("p95_system", 0), p95_system_time=metrics_data.get("p95_system", 0),
total_energy_consumption=metrics_data.get("energy_total", 0), # total_energy_consumption=metrics_data.get("energy_total", 0),
) )
else: else:
metrics = PerformanceMetrics() metrics = PerformanceMetrics()
@@ -106,7 +106,20 @@ class ElevatorAPIClient:
if "error" not in response_data: if "error" not in response_data:
# 使用服务端返回的真实数据 # 使用服务端返回的真实数据
events_data = response_data.get("events", []) events_data = response_data.get("events", [])
events = [SimulationEvent.from_dict(event) for event in events_data] events = []
for event_data in events_data:
# 手动转换type字段从字符串到EventType枚举
event_dict = event_data.copy()
if "type" in event_dict and isinstance(event_dict["type"], str):
# 尝试将字符串转换为EventType枚举
try:
from elevator_saga.core.models import EventType
event_dict["type"] = EventType(event_dict["type"])
except ValueError:
debug_log(f"Unknown event type: {event_dict['type']}")
continue
events.append(SimulationEvent.from_dict(event_dict))
step_response = StepResponse( step_response = StepResponse(
success=True, success=True,
@@ -122,7 +135,7 @@ class ElevatorAPIClient:
def send_elevator_command(self, command: Union[GoToFloorCommand, SetIndicatorsCommand]) -> bool: def send_elevator_command(self, command: Union[GoToFloorCommand, SetIndicatorsCommand]) -> bool:
"""发送电梯命令""" """发送电梯命令"""
endpoint = self._get_elevator_endpoint(command) endpoint = self._get_elevator_endpoint(command)
debug_log(f"Sending elevator command: {command.command_type} to elevator {command.elevator_id}") debug_log(f"Sending elevator command: {command.command_type} to elevator {command.elevator_id} To:F{command.floor}")
response_data = self._send_post_request(endpoint, command.parameters) response_data = self._send_post_request(endpoint, command.parameters)
@@ -191,10 +204,10 @@ class ElevatorAPIClient:
debug_log(f"Reset failed: {e}") debug_log(f"Reset failed: {e}")
return False return False
def next_traffic_round(self) -> bool: def next_traffic_round(self, full_reset = False) -> bool:
"""切换到下一个流量文件""" """切换到下一个流量文件"""
try: try:
response_data = self._send_post_request("/api/traffic/next", {}) response_data = self._send_post_request("/api/traffic/next", {"full_reset": full_reset})
success = response_data.get("success", False) success = response_data.get("success", False)
if success: if success:
# 清空缓存,因为流量文件已切换,状态会改变 # 清空缓存,因为流量文件已切换,状态会改变
@@ -211,7 +224,6 @@ class ElevatorAPIClient:
"""获取当前流量文件信息""" """获取当前流量文件信息"""
try: try:
response_data = self._send_get_request("/api/traffic/info") response_data = self._send_get_request("/api/traffic/info")
debug_log(str())
if "error" not in response_data: if "error" not in response_data:
return response_data return response_data
else: else:
@@ -231,7 +243,7 @@ class ElevatorAPIClient:
req = urllib.request.Request(url, data=request_body, headers={"Content-Type": "application/json"}) req = urllib.request.Request(url, data=request_body, headers={"Content-Type": "application/json"})
try: try:
with urllib.request.urlopen(req, timeout=60) as response: with urllib.request.urlopen(req, timeout=600) as response:
response_data = json.loads(response.read().decode("utf-8")) response_data = json.loads(response.read().decode("utf-8"))
# debug_log(f"POST {url} -> {response.status}") # debug_log(f"POST {url} -> {response.status}")
return response_data return response_data

View File

@@ -3,6 +3,8 @@
Elevator Controller Base Class Elevator Controller Base Class
电梯调度基础控制器类 - 提供面向对象的算法开发接口 电梯调度基础控制器类 - 提供面向对象的算法开发接口
""" """
import os
import time
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from pprint import pprint from pprint import pprint
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
@@ -34,9 +36,9 @@ class ElevatorController(ABC):
self.debug = debug self.debug = debug
self.elevators: List[Any] = [] self.elevators: List[Any] = []
self.floors: List[Any] = [] self.floors: List[Any] = []
self.current_tick = -2 self.current_tick = 0
self.is_running = False self.is_running = False
self.current_traffic_max_tick: Optional[int] = None self.current_traffic_max_tick: int = 0
# 初始化API客户端 # 初始化API客户端
self.api_client = ElevatorAPIClient(server_url) self.api_client = ElevatorAPIClient(server_url)
@@ -91,7 +93,7 @@ class ElevatorController(ABC):
print(f"停止 {self.__class__.__name__} 算法") print(f"停止 {self.__class__.__name__} 算法")
@abstractmethod @abstractmethod
def on_passenger_call(self, floor: ProxyFloor, direction: str): def on_passenger_call(self, passenger: ProxyPassenger, floor: ProxyFloor, direction: str):
""" """
乘客呼叫时的回调 - 可选实现 乘客呼叫时的回调 - 可选实现
@@ -125,7 +127,7 @@ class ElevatorController(ABC):
@abstractmethod @abstractmethod
def on_passenger_board(self, elevator: ProxyElevator, passenger: ProxyPassenger): def on_passenger_board(self, elevator: ProxyElevator, passenger: ProxyPassenger):
""" """
乘客上时的回调 - 可选实现 乘客上时的回调 - 可选实现
Args: Args:
elevator: 电梯代理对象 elevator: 电梯代理对象
@@ -173,7 +175,7 @@ class ElevatorController(ABC):
"""内部初始化方法""" """内部初始化方法"""
self.elevators = elevators self.elevators = elevators
self.floors = floors self.floors = floors
self.current_tick = -2 self.current_tick = 0
# 调用用户的初始化方法 # 调用用户的初始化方法
self.on_init(elevators, floors) self.on_init(elevators, floors)
@@ -213,25 +215,37 @@ class ElevatorController(ABC):
def _run_event_driven_simulation(self): def _run_event_driven_simulation(self):
"""运行事件驱动的模拟""" """运行事件驱动的模拟"""
try: try:
# 获取初始状态并初始化 # 获取初始状态并初始化默认从0开始
state = self.api_client.get_state() try:
state = self.api_client.get_state()
except ConnectionResetError as ex:
print(f"模拟器可能并没有开启,请检查模拟器是否启动 {self.api_client.base_url}")
os._exit(1)
if state.tick > 0:
print("模拟器可能已经开始了一次模拟,执行重置...")
self.api_client.reset()
time.sleep(0.3)
return self._run_event_driven_simulation()
self._update_wrappers(state, init=True) self._update_wrappers(state, init=True)
# 获取当前流量文件的最大tick数 # 获取当前流量文件的最大tick数
self._update_traffic_info() self._update_traffic_info()
if self.current_tick >= self.current_traffic_max_tick: if self.current_traffic_max_tick == 0:
return print("模拟器接收到的最大tick时间为0可能所有的测试案例已用完请求重置...")
self.api_client.next_traffic_round(full_reset=True)
time.sleep(0.3)
return self._run_event_driven_simulation()
# if self.current_tick >= self.current_traffic_max_tick:
# return
self._internal_init(self.elevators, self.floors) self._internal_init(self.elevators, self.floors)
self.api_client.mark_tick_processed()
tick_count = 0
while self.is_running: while self.is_running:
# 检查是否达到最大tick数 # 检查是否达到最大tick数
if tick_count >= self.current_traffic_max_tick: if self.current_tick >= self.current_traffic_max_tick:
break break
# 执行一个tick的模拟 # 执行一个tick的模拟从1开始
step_response = self.api_client.step(1) step_response = self.api_client.step(1)
# 更新当前状态 # 更新当前状态
self.current_tick = step_response.tick self.current_tick = step_response.tick
@@ -256,28 +270,21 @@ class ElevatorController(ABC):
# 事件执行后回调 # 事件执行后回调
self.on_event_execute_end(self.current_tick, events, self.elevators, self.floors) self.on_event_execute_end(self.current_tick, events, self.elevators, self.floors)
# 标记tick处理完成使API客户端缓存失效 # 标记tick处理完成使API客户端缓存失效
self.api_client.mark_tick_processed() self.api_client.mark_tick_processed()
tick_count += 1
# 检查是否需要切换流量文件 # 检查是否需要切换流量文件
if self.current_tick >= self.current_traffic_max_tick: if self.current_tick >= self.current_traffic_max_tick:
pprint(state.metrics.to_dict()) pprint(state.metrics.to_dict())
if not self.api_client.next_traffic_round(): if not self.api_client.next_traffic_round():
# 如果没有更多流量文件,退出
break break
# 重置并重新初始化 # 重置并重新初始化
self._reset_and_reinit() self._reset_and_reinit()
tick_count = 0
except Exception as e: except Exception as e:
print(f"模拟运行错误: {e}") print(f"模拟运行错误: {e}")
raise raise
def _update_wrappers(self, state: SimulationState, init=False) -> None: def _update_wrappers(self, state: SimulationState, init: bool = False) -> None:
"""更新电梯和楼层代理对象""" """更新电梯和楼层代理对象"""
self.current_tick = state.tick self.current_tick = state.tick
# 检查电梯数量是否发生变化,只有变化时才重新创建 # 检查电梯数量是否发生变化,只有变化时才重新创建
@@ -301,42 +308,46 @@ class ElevatorController(ABC):
debug_log(f"Updated traffic info - max_tick: {self.current_traffic_max_tick}") debug_log(f"Updated traffic info - max_tick: {self.current_traffic_max_tick}")
else: else:
debug_log("Failed to get traffic info") debug_log("Failed to get traffic info")
self.current_traffic_max_tick = None self.current_traffic_max_tick = 0
except Exception as e: except Exception as e:
debug_log(f"Error updating traffic info: {e}") debug_log(f"Error updating traffic info: {e}")
self.current_traffic_max_tick = None self.current_traffic_max_tick = 0
def _handle_single_event(self, event: SimulationEvent): def _handle_single_event(self, event: SimulationEvent):
"""处理单个事件""" """处理单个事件"""
if event.type == EventType.UP_BUTTON_PRESSED.value: if event.type == EventType.UP_BUTTON_PRESSED:
floor_id = event.data.get("floor") floor_id = event.data["floor"]
passenger_id = event.data["passenger"]
if floor_id is not None: if floor_id is not None:
floor_proxy = ProxyFloor(floor_id, self.api_client) floor_proxy = ProxyFloor(floor_id, self.api_client)
self.on_passenger_call(floor_proxy, "up") passenger_proxy = ProxyPassenger(passenger_id, self.api_client)
self.on_passenger_call(passenger_proxy, floor_proxy, "up")
elif event.type == EventType.DOWN_BUTTON_PRESSED.value: elif event.type == EventType.DOWN_BUTTON_PRESSED:
floor_id = event.data.get("floor") floor_id = event.data["floor"]
passenger_id = event.data["passenger"]
if floor_id is not None: if floor_id is not None:
floor_proxy = ProxyFloor(floor_id, self.api_client) floor_proxy = ProxyFloor(floor_id, self.api_client)
self.on_passenger_call(floor_proxy, "down") passenger_proxy = ProxyPassenger(passenger_id, self.api_client)
self.on_passenger_call(passenger_proxy, floor_proxy, "down")
elif event.type == EventType.STOPPED_AT_FLOOR.value: elif event.type == EventType.STOPPED_AT_FLOOR:
elevator_id = event.data.get("elevator") elevator_id = event.data.get("elevator")
floor_id = event.data.get("floor") floor_id = event.data["floor"]
if elevator_id is not None and floor_id is not None: if elevator_id is not None and floor_id is not None:
elevator_proxy = ProxyElevator(elevator_id, self.api_client) elevator_proxy = ProxyElevator(elevator_id, self.api_client)
floor_proxy = ProxyFloor(floor_id, self.api_client) floor_proxy = ProxyFloor(floor_id, self.api_client)
self.on_elevator_stopped(elevator_proxy, floor_proxy) self.on_elevator_stopped(elevator_proxy, floor_proxy)
elif event.type == EventType.IDLE.value: elif event.type == EventType.IDLE:
elevator_id = event.data.get("elevator") elevator_id = event.data.get("elevator")
if elevator_id is not None: if elevator_id is not None:
elevator_proxy = ProxyElevator(elevator_id, self.api_client) elevator_proxy = ProxyElevator(elevator_id, self.api_client)
self.on_elevator_idle(elevator_proxy) self.on_elevator_idle(elevator_proxy)
elif event.type == EventType.PASSING_FLOOR.value: elif event.type == EventType.PASSING_FLOOR:
elevator_id = event.data.get("elevator") elevator_id = event.data.get("elevator")
floor_id = event.data.get("floor") floor_id = event.data["floor"]
direction = event.data.get("direction") direction = event.data.get("direction")
if elevator_id is not None and floor_id is not None and direction is not None: 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) elevator_proxy = ProxyElevator(elevator_id, self.api_client)
@@ -345,9 +356,9 @@ class ElevatorController(ABC):
direction_str = direction if isinstance(direction, str) else direction.value direction_str = direction if isinstance(direction, str) else direction.value
self.on_elevator_passing_floor(elevator_proxy, floor_proxy, direction_str) self.on_elevator_passing_floor(elevator_proxy, floor_proxy, direction_str)
elif event.type == EventType.ELEVATOR_APPROACHING.value: elif event.type == EventType.ELEVATOR_APPROACHING:
elevator_id = event.data.get("elevator") elevator_id = event.data.get("elevator")
floor_id = event.data.get("floor") floor_id = event.data["floor"]
direction = event.data.get("direction") direction = event.data.get("direction")
if elevator_id is not None and floor_id is not None and direction is not None: 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) elevator_proxy = ProxyElevator(elevator_id, self.api_client)
@@ -356,7 +367,7 @@ class ElevatorController(ABC):
direction_str = direction if isinstance(direction, str) else direction.value direction_str = direction if isinstance(direction, str) else direction.value
self.on_elevator_approaching(elevator_proxy, floor_proxy, direction_str) self.on_elevator_approaching(elevator_proxy, floor_proxy, direction_str)
elif event.type == EventType.PASSENGER_BOARD.value: elif event.type == EventType.PASSENGER_BOARD:
elevator_id = event.data.get("elevator") elevator_id = event.data.get("elevator")
passenger_id = event.data.get("passenger") passenger_id = event.data.get("passenger")
if elevator_id is not None and passenger_id is not None: if elevator_id is not None and passenger_id is not None:
@@ -364,10 +375,10 @@ class ElevatorController(ABC):
passenger_proxy = ProxyPassenger(passenger_id, self.api_client) passenger_proxy = ProxyPassenger(passenger_id, self.api_client)
self.on_passenger_board(elevator_proxy, passenger_proxy) self.on_passenger_board(elevator_proxy, passenger_proxy)
elif event.type == EventType.PASSENGER_ALIGHT.value: elif event.type == EventType.PASSENGER_ALIGHT:
elevator_id = event.data.get("elevator") elevator_id = event.data.get("elevator")
passenger_id = event.data.get("passenger") passenger_id = event.data.get("passenger")
floor_id = event.data.get("floor") floor_id = event.data["floor"]
if elevator_id is not None and passenger_id is not None and floor_id is not None: 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) elevator_proxy = ProxyElevator(elevator_id, self.api_client)
passenger_proxy = ProxyPassenger(passenger_id, self.api_client) passenger_proxy = ProxyPassenger(passenger_id, self.api_client)
@@ -379,7 +390,7 @@ class ElevatorController(ABC):
try: try:
# 重置服务器状态 # 重置服务器状态
self.api_client.reset() self.api_client.reset()
self.current_tick = 0
# 获取新的初始状态 # 获取新的初始状态
state = self.api_client.get_state() state = self.api_client.get_state()
self._update_wrappers(state) self._update_wrappers(state)

View File

@@ -33,13 +33,15 @@ class PassengerStatus(Enum):
class ElevatorStatus(Enum): # OK class ElevatorStatus(Enum): # OK
""" """
电梯stopped -1tick> (回调启动) start_up -1tick> constant_speed -?tick> start_down -1tick> 结束 电梯运行状态机:stopped -1tick> start_up -1tick> constant_speed -?tick> start_down -1tick> stopped
注意START_UP/START_DOWN表示加速/减速状态,不表示移动方向
实际移动方向由target_floor_direction属性决定
""" """
START_UP = "start_up" # 即将启动tick结束时会从stopped转换成start_up START_UP = "start_up" # 启动加速状态(不表示向上方向)
START_DOWN = "start_down" # 即将到站tick结束时会触发may passing floor START_DOWN = "start_down" # 减速状态(不表示向下方向)
CONSTANT_SPEED = "constant_speed" CONSTANT_SPEED = "constant_speed" # 匀速状态
STOPPED = "stopped" STOPPED = "stopped" # 停止状态
class EventType(Enum): class EventType(Enum):
@@ -49,7 +51,7 @@ class EventType(Enum):
DOWN_BUTTON_PRESSED = "down_button_pressed" DOWN_BUTTON_PRESSED = "down_button_pressed"
PASSING_FLOOR = "passing_floor" PASSING_FLOOR = "passing_floor"
STOPPED_AT_FLOOR = "stopped_at_floor" STOPPED_AT_FLOOR = "stopped_at_floor"
ELEVATOR_APPROACHING = "elevator_approaching" # 电梯即将到达START_DOWN状态 ELEVATOR_APPROACHING = "elevator_approaching" # 电梯即将经过某层楼START_DOWN状态
IDLE = "idle" IDLE = "idle"
PASSENGER_BOARD = "passenger_board" PASSENGER_BOARD = "passenger_board"
PASSENGER_ALIGHT = "passenger_alight" PASSENGER_ALIGHT = "passenger_alight"
@@ -117,7 +119,7 @@ class Position(SerializableModel):
self.floor_up_position -= 10 self.floor_up_position -= 10
# 处理向下楼层跨越 # 处理向下楼层跨越
while self.floor_up_position < 0: while self.floor_up_position <= -10:
self.current_floor -= 1 self.current_floor -= 1
self.floor_up_position += 10 self.floor_up_position += 10
@@ -260,29 +262,10 @@ class ElevatorState(SerializableModel):
"""按下的楼层(基于当前乘客的目的地动态计算)""" """按下的楼层(基于当前乘客的目的地动态计算)"""
return sorted(list(set(self.passenger_destinations.values()))) return sorted(list(set(self.passenger_destinations.values())))
def add_destination(self, floor: int, immediate: bool = False):
"""添加目标楼层"""
self.next_target_floor = floor
def clear_destinations(self): def clear_destinations(self):
"""清空目标队列""" """清空目标队列"""
self.next_target_floor = None self.next_target_floor = None
def add_passenger(self, passenger_id: int, destination_floor: int):
"""添加乘客"""
if not self.is_full:
self.passengers.append(passenger_id)
self.passenger_destinations[passenger_id] = destination_floor
return True
return False
def remove_passenger(self, passenger_id: int, floor: int):
"""移除乘客"""
if passenger_id in self.passengers:
self.passengers.remove(passenger_id)
# 从乘客目的地映射中移除
self.passenger_destinations.pop(passenger_id, None)
@dataclass @dataclass
class FloorState(SerializableModel): class FloorState(SerializableModel):
@@ -346,10 +329,7 @@ class PerformanceMetrics(SerializableModel):
p95_wait_time: float = 0.0 p95_wait_time: float = 0.0
average_system_time: float = 0.0 average_system_time: float = 0.0
p95_system_time: float = 0.0 p95_system_time: float = 0.0
total_energy_consumption: float = 0.0 # total_energy_consumption: float = 0.0
total_movement_distance: float = 0.0
total_stops: int = 0
efficiency_score: float = 0.0
@property @property
def completion_rate(self) -> float: def completion_rate(self) -> float:
@@ -358,12 +338,12 @@ class PerformanceMetrics(SerializableModel):
return 0.0 return 0.0
return self.completed_passengers / self.total_passengers return self.completed_passengers / self.total_passengers
@property # @property
def energy_per_passenger(self) -> float: # def energy_per_passenger(self) -> float:
"""每位乘客能耗""" # """每位乘客能耗"""
if self.completed_passengers == 0: # if self.completed_passengers == 0:
return 0.0 # return 0.0
return self.total_energy_consumption / self.completed_passengers # return self.total_energy_consumption / self.completed_passengers
@dataclass @dataclass

View File

@@ -7,7 +7,7 @@ from typing import Dict, List
from elevator_saga.client.base_controller import ElevatorController from elevator_saga.client.base_controller import ElevatorController
from elevator_saga.client.proxy_models import ProxyElevator, ProxyFloor, ProxyPassenger from elevator_saga.client.proxy_models import ProxyElevator, ProxyFloor, ProxyPassenger
from elevator_saga.core.models import SimulationEvent from elevator_saga.core.models import SimulationEvent, Direction
class ElevatorBusController(ElevatorController): class ElevatorBusController(ElevatorController):
@@ -43,9 +43,9 @@ class ElevatorBusController(ElevatorController):
self, tick: int, events: List[SimulationEvent], elevators: List[ProxyElevator], floors: List[ProxyFloor] self, tick: int, events: List[SimulationEvent], elevators: List[ProxyElevator], floors: List[ProxyFloor]
) -> None: ) -> None:
"""事件执行前的回调""" """事件执行前的回调"""
print(f"Tick {tick}: 即将处理 {len(events)} 个事件 {[e.type for e in events]}", end="") print(f"Tick {tick}: 即将处理 {len(events)} 个事件 {[e.type.value for e in events]}")
for i in elevators: for i in elevators:
print(f"电梯{i.id}[{i.target_floor_direction.value}] 位置{i.current_floor_float}/{i.target_floor}, ", end="") print(f"\t{i.id}[{i.target_floor_direction.value},{i.current_floor_float}/{i.target_floor}]" + "👦" * len(i.passengers), end="")
print() print()
def on_event_execute_end( def on_event_execute_end(
@@ -55,29 +55,31 @@ class ElevatorBusController(ElevatorController):
# print(f"✅ Tick {tick}: 已处理 {len(events)} 个事件") # print(f"✅ Tick {tick}: 已处理 {len(events)} 个事件")
pass pass
def on_passenger_call(self, floor: ProxyFloor, direction: str) -> None: def on_passenger_call(self, passenger:ProxyPassenger, floor: ProxyFloor, direction: str) -> None:
""" """
乘客呼叫时的回调 乘客呼叫时的回调
公交车模式下,电梯已经在循环运行,无需特别响应呼叫 公交车模式下,电梯已经在循环运行,无需特别响应呼叫
""" """
print(f"📞 楼层 {floor.floor} 有乘客呼叫 ({direction}) - 公交车将按既定路线服务") print(f"乘客 {passenger.id} F{floor.floor} 请求 {passenger.origin} -> {passenger.destination} ({direction})")
def on_elevator_idle(self, elevator: ProxyElevator) -> None: def on_elevator_idle(self, elevator: ProxyElevator) -> None:
""" """
电梯空闲时的回调 电梯空闲时的回调
让空闲的电梯继续执行公交车循环路线,每次移动一层楼 让空闲的电梯继续执行公交车循环路线,每次移动一层楼
""" """
print(f"🛑 电梯 {elevator.id} 空闲 {elevator.current_floor} = {elevator.target_floor}") print(f"🛑 电梯 E{elevator.id} 在 F{elevator.current_floor} 层空闲")
# 设置指示器让乘客知道电梯的行进方向 # 设置指示器让乘客知道电梯的行进方向
if self.elevator_directions[elevator.id] == "down" and elevator.current_floor != 0: if self.elevator_directions[elevator.id] == "down" and elevator.current_floor != 0:
elevator.go_to_floor(elevator.current_floor - 1, immediate=True) elevator.go_to_floor(elevator.current_floor - 1, immediate=True)
current_direction = self.elevator_directions[elevator.id] # elevator.set_up_indicator(True)
if current_direction == "up": elevator.go_to_floor(1)
elevator.set_up_indicator(True) # current_direction = self.elevator_directions[elevator.id]
elevator.set_down_indicator(False) # if current_direction == "up":
else: # elevator.set_up_indicator(True)
elevator.set_up_indicator(False) # elevator.set_down_indicator(False)
elevator.set_down_indicator(True) # else:
# elevator.set_up_indicator(False)
# elevator.set_down_indicator(True)
def on_elevator_stopped(self, elevator: ProxyElevator, floor: ProxyFloor) -> None: def on_elevator_stopped(self, elevator: ProxyElevator, floor: ProxyFloor) -> None:
""" """
@@ -85,7 +87,7 @@ class ElevatorBusController(ElevatorController):
公交车模式下,在每一层都停下,然后继续下一站 公交车模式下,在每一层都停下,然后继续下一站
需要注意的是stopped会比idle先触发 需要注意的是stopped会比idle先触发
""" """
print(f"🛑 电梯 {elevator.id} 停靠在 {floor.floor}") print(f"🛑 电梯 E{elevator.id} 停靠在 F{floor.floor}")
if self.elevator_directions[elevator.id] == "up" and elevator.current_floor == self.max_floor: if self.elevator_directions[elevator.id] == "up" and elevator.current_floor == self.max_floor:
elevator.go_to_floor(elevator.current_floor - 1, immediate=True) elevator.go_to_floor(elevator.current_floor - 1, immediate=True)
self.elevator_directions[elevator.id] = "down" self.elevator_directions[elevator.id] = "down"
@@ -93,41 +95,47 @@ class ElevatorBusController(ElevatorController):
elevator.go_to_floor(elevator.current_floor + 1, immediate=True) elevator.go_to_floor(elevator.current_floor + 1, immediate=True)
self.elevator_directions[elevator.id] = "up" self.elevator_directions[elevator.id] = "up"
elif self.elevator_directions[elevator.id] == "up": elif self.elevator_directions[elevator.id] == "up":
if elevator.id == 0:
raise ValueError("这里故意要求0号电梯不可能触发非两端停止通过on_elevator_approaching实现")
elevator.go_to_floor(elevator.current_floor + 1, immediate=True) elevator.go_to_floor(elevator.current_floor + 1, immediate=True)
# 这里故意少写下降的情况用于了解stopped会先于idle触发 # 这里故意少写下降的情况用于了解stopped会先于idle触发
# elif self.elevator_directions[elevator.id] == "down": # elif self.elevator_directions[elevator.id] == "down":
# elevator.go_to_floor(elevator.current_floor - 1, immediate=True) # elevator.go_to_floor(elevator.current_floor - 1, immediate=True)
# self.elevator_directions[elevator.id] = "down" # self.elevator_directions[elevator.id] = "down"
def on_passenger_board(self, elevator: ProxyElevator, passenger: ProxyPassenger) -> None: def on_passenger_board(self, elevator: ProxyElevator, passenger: ProxyPassenger) -> None:
""" """
乘客上时的回调 乘客上时的回调
打印乘客上信息 打印乘客上信息
""" """
print(f"⬆️ 乘客 {passenger.id} 上车 - 电梯 {elevator.id} - 楼层 {elevator.current_floor} - 目标楼层: {passenger.destination}") print(
f" 乘客{passenger.id} E{elevator.id}⬆️ F{elevator.current_floor} -> F{passenger.destination}"
)
def on_passenger_alight(self, elevator: ProxyElevator, passenger: ProxyPassenger, floor: ProxyFloor) -> None: def on_passenger_alight(self, elevator: ProxyElevator, passenger: ProxyPassenger, floor: ProxyFloor) -> None:
""" """
乘客下车时的回调 乘客下车时的回调
打印乘客下车信息 打印乘客下车信息
""" """
print(f"⬇️ 乘客 {passenger.id} {floor.floor} 楼下车 - 电梯 {elevator.id}") print(f" 乘客{passenger.id} E{elevator.id}⬇️ F{floor.floor}")
def on_elevator_passing_floor(self, elevator: ProxyElevator, floor: ProxyFloor, direction: str) -> None: def on_elevator_passing_floor(self, elevator: ProxyElevator, floor: ProxyFloor, direction: str) -> None:
""" """
电梯经过楼层时的回调 电梯经过楼层时的回调
打印经过楼层的信息 打印经过楼层的信息
""" """
print(f"🔄 电梯 {elevator.id} 经过 {floor.floor} (方向: {direction})") print(f"🔄 电梯 E{elevator.id} 经过 F{floor.floor} (方向: {direction})")
def on_elevator_approaching(self, elevator: ProxyElevator, floor: ProxyFloor, direction: str) -> None: def on_elevator_approaching(self, elevator: ProxyElevator, floor: ProxyFloor, direction: str) -> None:
""" """
电梯即将到达时的回调 (START_DOWN事件) 电梯即将到达时的回调 (START_DOWN事件)
电梯开始减速,即将到达目标楼层 电梯开始减速,即将到达目标楼层
""" """
print(f"🎯 电梯 {elevator.id} 即将到达 {floor.floor} (方向: {direction})") print(f"🎯 电梯 E{elevator.id} 即将到达 F{floor.floor} (方向: {direction})")
if elevator.target_floor == floor.floor and elevator.target_floor_direction == Direction.UP: # 电梯的目标楼层就是即将停靠的楼层
if elevator.id == 0: # 这里为了测试让0号电梯往上一层就新加一层上行永远不会开门
elevator.go_to_floor(elevator.target_floor + 1, immediate=True)
print(f" 不让0号电梯上行停站设定新目标楼层 {elevator.target_floor + 1}")
if __name__ == "__main__": if __name__ == "__main__":
algorithm = ElevatorBusController(debug=True) algorithm = ElevatorBusController(debug=True)

View File

@@ -124,7 +124,7 @@ class SimulationStateResponse(SerializableModel):
class ElevatorSimulation: class ElevatorSimulation:
traffic_queue: List[TrafficEntry] traffic_queue: List[TrafficEntry] # type: ignore
next_passenger_id: int next_passenger_id: int
max_duration_ticks: int max_duration_ticks: int
@@ -184,7 +184,7 @@ class ElevatorSimulation:
traffic_file = self.traffic_files[self.current_traffic_index] traffic_file = self.traffic_files[self.current_traffic_index]
server_debug_log(f"Loading traffic from {traffic_file.name}") server_debug_log(f"Loading traffic from {traffic_file.name}")
try: try:
with open(traffic_file, "r", encoding="utf-8")as f: with open(traffic_file, "r", encoding="utf-8") as f:
file_data = json.load(f) file_data = json.load(f)
building_config = file_data["building"] building_config = file_data["building"]
server_debug_log(f"Building config: {building_config}") server_debug_log(f"Building config: {building_config}")
@@ -208,7 +208,7 @@ class ElevatorSimulation:
except Exception as e: except Exception as e:
server_debug_log(f"Error loading traffic file {traffic_file}: {e}") server_debug_log(f"Error loading traffic file {traffic_file}: {e}")
def next_traffic_round(self) -> bool: def next_traffic_round(self, full_reset: bool = False) -> bool:
"""切换到下一个流量文件,返回是否成功切换""" """切换到下一个流量文件,返回是否成功切换"""
if not self.traffic_files: if not self.traffic_files:
return False return False
@@ -216,7 +216,10 @@ class ElevatorSimulation:
# 检查是否还有下一个文件 # 检查是否还有下一个文件
next_index = self.current_traffic_index + 1 next_index = self.current_traffic_index + 1
if next_index >= len(self.traffic_files): if next_index >= len(self.traffic_files):
return False # 没有更多流量文件,停止模拟 if full_reset:
self.current_traffic_index = -1
return self.next_traffic_round()
return False # 没有更多测试案例,停止模拟
self.current_traffic_index = next_index self.current_traffic_index = next_index
self.load_current_traffic() # 加载新的流量文件 self.load_current_traffic() # 加载新的流量文件
@@ -229,7 +232,7 @@ class ElevatorSimulation:
server_debug_log(f"Loading traffic from {traffic_file}, {len(traffic_data)} entries") server_debug_log(f"Loading traffic from {traffic_file}, {len(traffic_data)} entries")
self.traffic_queue = [] self.traffic_queue: List[TrafficEntry] = [] # type: ignore[reportRedeclaration]
for entry in traffic_data: for entry in traffic_data:
# Create TrafficEntry from JSON data # Create TrafficEntry from JSON data
traffic_entry = TrafficEntry( traffic_entry = TrafficEntry(
@@ -245,39 +248,6 @@ class ElevatorSimulation:
self.traffic_queue.sort(key=lambda p: p.tick) self.traffic_queue.sort(key=lambda p: p.tick)
server_debug_log(f"Traffic loaded and sorted, next passenger ID: {self.next_passenger_id}") server_debug_log(f"Traffic loaded and sorted, next passenger ID: {self.next_passenger_id}")
def add_passenger(
self,
origin: int,
destination: int,
tick: Optional[int] = None,
) -> int:
"""Add a passenger to the simulation using unified data models"""
if tick is None:
tick = self.tick
passenger = PassengerInfo(
id=self.next_passenger_id,
origin=origin,
destination=destination,
arrive_tick=tick,
)
self.passengers[passenger.id] = passenger
# Add to floor queue
floor_state = self.state.get_floor_by_number(origin)
if floor_state:
if destination > origin:
floor_state.add_waiting_passenger(passenger.id, Direction.UP)
self._emit_event(EventType.UP_BUTTON_PRESSED, {"floor": origin})
else:
floor_state.add_waiting_passenger(passenger.id, Direction.DOWN)
self._emit_event(EventType.DOWN_BUTTON_PRESSED, {"floor": origin})
self.next_passenger_id += 1
server_debug_log(f"Added passenger {passenger.id}: {origin}{destination} at tick {tick}")
return passenger.id
def _emit_event(self, event_type: EventType, data: Dict[str, Any]) -> None: def _emit_event(self, event_type: EventType, data: Dict[str, Any]) -> None:
"""Emit an event to be sent to clients using unified data models""" """Emit an event to be sent to clients using unified data models"""
self.state.add_event(event_type, data) self.state.add_event(event_type, data)
@@ -286,9 +256,9 @@ class ElevatorSimulation:
def step(self, num_ticks: int = 1) -> List[SimulationEvent]: def step(self, num_ticks: int = 1) -> List[SimulationEvent]:
with self.lock: with self.lock:
new_events: List[SimulationEvent] = [] new_events: List[SimulationEvent] = []
for i in range(num_ticks): for _ in range(num_ticks):
self.state.tick += 1 self.state.tick += 1
# server_debug_log(f"Processing tick {self.tick} (step {i+1}/{num_ticks})") # currently one tick per step # server_debug_log(f"Processing tick {self.tick}") # currently one tick per step
tick_events = self._process_tick() tick_events = self._process_tick()
new_events.extend(tick_events) new_events.extend(tick_events)
# server_debug_log(f"Tick {self.tick} completed - Generated {len(tick_events)} events") # currently one tick per step # server_debug_log(f"Tick {self.tick} completed - Generated {len(tick_events)} events") # currently one tick per step
@@ -345,13 +315,26 @@ class ElevatorSimulation:
target_pos = elevator.target_floor * 10 target_pos = elevator.target_floor * 10
return abs(target_pos - current_pos) return abs(target_pos - current_pos)
def _calculate_distance_to_near_stop(self, elevator: ElevatorState) -> float:
"""计算到最近楼层的距离以floor_up_position为单位"""
if elevator.position.floor_up_position < 0:
return 10 + elevator.position.floor_up_position
elif elevator.position.floor_up_position > 0:
return 10 - elevator.position.floor_up_position
else:
return 0
def _should_start_deceleration(self, elevator: ElevatorState) -> bool: def _should_start_deceleration(self, elevator: ElevatorState) -> bool:
"""判断是否应该开始减速 """判断是否应该开始减速
减速需要1个tick移动1个位置单位所以当距离目标<=3时开始减速 减速需要1个tick移动1个位置单位所以当距离目标<=3时开始减速
这样可以保证有一个完整的减速周期 这样可以保证有一个完整的减速周期
""" """
distance = self._calculate_distance_to_target(elevator) distance = self._calculate_distance_to_target(elevator)
return distance <= 3 return distance == 1
def _near_next_stop(self, elevator: ElevatorState) -> bool:
distance = self._calculate_distance_to_near_stop(elevator)
return distance == 1
def _get_movement_speed(self, elevator: ElevatorState) -> int: def _get_movement_speed(self, elevator: ElevatorState) -> int:
"""根据电梯状态获取移动速度""" """根据电梯状态获取移动速度"""
@@ -375,15 +358,27 @@ class ElevatorSimulation:
return return
if elevator.run_status == ElevatorStatus.STOPPED: if elevator.run_status == ElevatorStatus.STOPPED:
# 从停止状态启动 # 从停止状态启动 - 注意START_UP表示启动加速状态不表示方向
# 实际移动方向由target_floor_direction决定
elevator.run_status = ElevatorStatus.START_UP elevator.run_status = ElevatorStatus.START_UP
elif elevator.run_status == ElevatorStatus.START_UP: elif elevator.run_status == ElevatorStatus.START_UP:
# 从启动状态切换到匀速 # 从启动状态切换到匀速
elevator.run_status = ElevatorStatus.CONSTANT_SPEED elevator.run_status = ElevatorStatus.CONSTANT_SPEED
elif elevator.run_status == ElevatorStatus.CONSTANT_SPEED: elif elevator.run_status == ElevatorStatus.CONSTANT_SPEED:
# 检查是否需要开始减速 # 检查是否需要开始减速这里加速减速设置路程为1匀速路程为2这样能够保证不会匀速恰好到达必须加减速
# 如果速度超出,则预期的逻辑是,恰好到达/超出0等会强制触发start_down多走一次才能stop目前没有实现这部分逻辑
if self._should_start_deceleration(elevator): if self._should_start_deceleration(elevator):
elevator.run_status = ElevatorStatus.START_DOWN elevator.run_status = ElevatorStatus.START_DOWN
# 发送电梯即将经过某层楼事件
if self._near_next_stop(elevator):
self._emit_event(
EventType.ELEVATOR_APPROACHING,
{
"elevator": elevator.id,
"floor": elevator.target_floor,
"direction": elevator.target_floor_direction.value,
},
)
# START_DOWN状态会在到达目标时自动切换为STOPPED # START_DOWN状态会在到达目标时自动切换为STOPPED
def _move_elevators(self) -> None: def _move_elevators(self) -> None:
@@ -403,9 +398,6 @@ class ElevatorSimulation:
elevator.run_status = ElevatorStatus.STOPPED elevator.run_status = ElevatorStatus.STOPPED
continue continue
# 更新电梯状态
self._update_elevator_status(elevator)
# 获取移动速度 # 获取移动速度
movement_speed = self._get_movement_speed(elevator) movement_speed = self._get_movement_speed(elevator)
@@ -423,8 +415,11 @@ class ElevatorSimulation:
# 向下移动 # 向下移动
new_floor = elevator.position.floor_up_position_add(-movement_speed) new_floor = elevator.position.floor_up_position_add(-movement_speed)
# 更新电梯状态
old_status = elevator.run_status.value
self._update_elevator_status(elevator)
server_debug_log( server_debug_log(
f"电梯{elevator.id} 状态:{elevator.run_status.value} 速度:{movement_speed} " f"电梯{elevator.id} 状态:{old_status}->{elevator.run_status.value} 方向:{elevator.target_floor_direction.value} 速度:{movement_speed} "
f"位置:{elevator.position.current_floor_float:.1f} 目标:{target_floor}" f"位置:{elevator.position.current_floor_float:.1f} 目标:{target_floor}"
) )
@@ -444,7 +439,7 @@ class ElevatorSimulation:
if target_floor == new_floor and elevator.position.floor_up_position == 0: if target_floor == new_floor and elevator.position.floor_up_position == 0:
elevator.run_status = ElevatorStatus.STOPPED elevator.run_status = ElevatorStatus.STOPPED
self._emit_event(EventType.STOPPED_AT_FLOOR, {"elevator": elevator.id, "floor": new_floor}) self._emit_event(EventType.STOPPED_AT_FLOOR, {"elevator": elevator.id, "floor": new_floor})
elevator.indicators.set_direction(elevator.target_floor_direction) # 抵达目标楼层说明该取消floor了
# elevator.energy_consumed += abs(direction * elevator.speed_pre_tick) * 0.5 # elevator.energy_consumed += abs(direction * elevator.speed_pre_tick) * 0.5
def _process_elevator_stops(self) -> None: def _process_elevator_stops(self) -> None:
@@ -474,7 +469,7 @@ class ElevatorSimulation:
passengers_to_board: List[int] = [] passengers_to_board: List[int] = []
if not elevator.indicators.up and not elevator.indicators.down: if not elevator.indicators.up and not elevator.indicators.down:
if elevator.next_target_floor is not None: if elevator.next_target_floor is not None:
elevator.position.target_floor = elevator.next_target_floor self._set_elevator_target_floor(elevator, elevator.next_target_floor)
elevator.next_target_floor = None elevator.next_target_floor = None
elevator.indicators.set_direction(elevator.target_floor_direction) elevator.indicators.set_direction(elevator.target_floor_direction)
@@ -508,14 +503,37 @@ class ElevatorSimulation:
{"elevator": elevator.id, "floor": current_floor, "passenger": passenger_id}, {"elevator": elevator.id, "floor": current_floor, "passenger": passenger_id},
) )
def _set_elevator_target_floor(self, elevator: ElevatorState, floor: int):
original_target_floor = elevator.target_floor
elevator.position.target_floor = floor
server_debug_log(f"电梯 E{elevator.id} 被设定为立刻前往 F{floor}")
new_target_floor_should_accel = self._should_start_deceleration(elevator)
if not new_target_floor_should_accel:
if elevator.run_status == ElevatorStatus.START_DOWN: # 不应该加速但是加了
elevator.run_status = ElevatorStatus.CONSTANT_SPEED
server_debug_log(f"电梯 E{elevator.id} 被设定为匀速")
elif new_target_floor_should_accel:
if elevator.run_status == ElevatorStatus.CONSTANT_SPEED: # 应该减速了,但是之前是匀速
elevator.run_status = ElevatorStatus.START_DOWN
server_debug_log(f"电梯 E{elevator.id} 被设定为减速")
if elevator.current_floor != floor or elevator.position.floor_up_position != 0:
old_status = elevator.run_status.value
self._update_elevator_status(elevator)
server_debug_log(
f"电梯{elevator.id} 状态:{old_status}->{elevator.run_status.value} 方向:{elevator.target_floor_direction.value} "
f"位置:{elevator.position.current_floor_float:.1f} 目标:{floor}"
)
def elevator_go_to_floor(self, elevator_id: int, floor: int, immediate: bool = False) -> None: def elevator_go_to_floor(self, elevator_id: int, floor: int, immediate: bool = False) -> None:
"""Command elevator to go to specified floor""" """Command elevator to go to specified floor"""
if 0 <= elevator_id < len(self.elevators) and 0 <= floor < len(self.floors): if 0 <= elevator_id < len(self.elevators) and 0 <= floor < len(self.floors):
elevator = self.elevators[elevator_id] elevator = self.elevators[elevator_id]
if immediate: if immediate:
elevator.position.target_floor = floor self._set_elevator_target_floor(elevator, floor)
elevator.indicators.set_direction(elevator.target_floor_direction)
else: else:
elevator.next_target_floor = floor elevator.next_target_floor = floor
server_debug_log(f"电梯 E{elevator_id} 下一目的地设定为 F{floor}")
def elevator_set_indicators(self, elevator_id: int, up: Optional[bool] = None, down: Optional[bool] = None) -> None: def elevator_set_indicators(self, elevator_id: int, up: Optional[bool] = None, down: Optional[bool] = None) -> None:
"""Set elevator direction indicators""" """Set elevator direction indicators"""
@@ -590,15 +608,14 @@ class ElevatorSimulation:
def force_complete_remaining_passengers(self) -> int: def force_complete_remaining_passengers(self) -> int:
"""强制完成所有未完成的乘客,返回完成的乘客数量""" """强制完成所有未完成的乘客,返回完成的乘客数量"""
with self.lock: completed_count = 0
completed_count = 0 current_tick = self.tick
current_tick = self.tick for passenger in self.state.passengers.values():
for passenger in self.state.passengers.values(): if passenger.dropoff_tick == 0:
if passenger.dropoff_tick == 0: passenger.dropoff_tick = current_tick
passenger.dropoff_tick = current_tick if passenger.pickup_tick == 0:
if passenger.pickup_tick == 0: passenger.pickup_tick = current_tick
passenger.pickup_tick = current_tick return completed_count
return completed_count
def reset(self) -> None: def reset(self) -> None:
"""Reset simulation to initial state""" """Reset simulation to initial state"""
@@ -641,13 +658,14 @@ def step_simulation() -> Response | tuple[Response, int]:
try: try:
data: Dict[str, Any] = request.get_json() or {} data: Dict[str, Any] = request.get_json() or {}
ticks = data.get("ticks", 1) ticks = data.get("ticks", 1)
server_debug_log(f"HTTP /api/step request - ticks: {ticks}") server_debug_log("")
server_debug_log(f"HTTP /api/step request ----- ticks: {ticks}")
events = simulation.step(ticks) events = simulation.step(ticks)
server_debug_log(f"HTTP /api/step response - tick: {simulation.tick}, events: {len(events)}") server_debug_log(f"HTTP /api/step response ----- tick: {simulation.tick}, events: {len(events)}\n")
return json_response( return json_response(
{ {
"tick": simulation.tick, "tick": simulation.tick,
"events": [{"tick": e.tick, "type": e.type.value, "data": e.data} for e in events], "events": events,
} }
) )
except Exception as e: except Exception as e:
@@ -691,7 +709,8 @@ def elevator_set_indicators(elevator_id: int) -> Response | tuple[Response, int]
def next_traffic_round() -> Response | tuple[Response, int]: def next_traffic_round() -> Response | tuple[Response, int]:
"""切换到下一个流量文件""" """切换到下一个流量文件"""
try: try:
success = simulation.next_traffic_round() full_reset = request.get_json()["full_reset"]
success = simulation.next_traffic_round(full_reset)
if success: if success:
return json_response({"success": True}) return json_response({"success": True})
else: else:

View File

@@ -7,14 +7,14 @@
"scale": "large", "scale": "large",
"description": "楼层间流量 - 适合小建筑 (large规模)", "description": "楼层间流量 - 适合小建筑 (large规模)",
"expected_passengers": 76, "expected_passengers": 76,
"duration": 300 "duration": 100
}, },
"traffic": [ "traffic": [
{ {
"id": 1, "id": 1,
"origin": 3, "origin": 0,
"destination": 8, "destination": 1,
"tick": 7 "tick": 1
}, },
{ {
"id": 2, "id": 2,