import asyncio import logging import app.state as state logger = logging.getLogger(__name__) # Constants matching ESP_Code.ino FSM timings (in seconds) TIME_PER_FLOOR = 2.0 TIME_DOOR_OPENING = 1.5 TIME_DOORS_OPEN = 4.0 TIME_DOOR_CLOSING = 1.5 async def run_elevator_simulator(): """ Virtual Elevator Simulator that runs as a background task. Monitors state.elevator_cmd_event, updates state.elevator_state, and broadcasts state transitions via Server-Sent Events (SSE). """ logger.info("Virtual Elevator Simulator started") # Initialize state state.elevator_state.Floor = 1 state.elevator_state.Dir = "IDLE" state.elevator_state.Doors = "CLOSED" state.elevator_state.State = "IDLE" while True: try: # Wait until a command is set await state.elevator_cmd_event.wait() cmd = state.elevator_cmd if not cmd or not cmd.startswith("CALL_"): state.elevator_cmd_event.clear() await asyncio.sleep(0.1) continue try: target_floor = int(cmd.split("_")[1]) except (IndexError, ValueError): logger.error("Invalid elevator command format: %s", cmd) state.elevator_cmd = None state.elevator_cmd_event.clear() continue current_floor = state.elevator_state.Floor if target_floor == current_floor: logger.info("Elevator is already at target floor %d", target_floor) state.elevator_cmd = None state.elevator_cmd_event.clear() continue # Consume the command state.elevator_cmd = None state.elevator_cmd_event.clear() # 1. State: MOVING state.elevator_state.State = "MOVING" state.elevator_state.Dir = "UP" if target_floor > current_floor else "DOWN" state.elevator_state.Doors = "CLOSED" logger.info("Elevator moving from floor %d to %d (%s)", current_floor, target_floor, state.elevator_state.Dir) await state.broadcast("elevator", { "Floor": state.elevator_state.Floor, "Dir": state.elevator_state.Dir, "Doors": state.elevator_state.Doors, "State": state.elevator_state.State, }) # Move floor-by-floor step = 1 if target_floor > current_floor else -1 while state.elevator_state.Floor != target_floor: await asyncio.sleep(TIME_PER_FLOOR) state.elevator_state.Floor += step logger.info("Elevator reached floor %d", state.elevator_state.Floor) await state.broadcast("elevator", { "Floor": state.elevator_state.Floor, "Dir": state.elevator_state.Dir, "Doors": state.elevator_state.Doors, "State": state.elevator_state.State, }) # 2. State: DOORS_OPENING state.elevator_state.Dir = "IDLE" state.elevator_state.State = "DOORS_OPENING" state.elevator_state.Doors = "OPENING" logger.info("Elevator arrived at floor %d. Opening doors...", state.elevator_state.Floor) await state.broadcast("elevator", { "Floor": state.elevator_state.Floor, "Dir": state.elevator_state.Dir, "Doors": state.elevator_state.Doors, "State": state.elevator_state.State, }) await asyncio.sleep(TIME_DOOR_OPENING) # 3. State: DOORS_OPEN state.elevator_state.State = "DOORS_OPEN" state.elevator_state.Doors = "OPEN" logger.info("Elevator doors are OPEN at floor %d", state.elevator_state.Floor) await state.broadcast("elevator", { "Floor": state.elevator_state.Floor, "Dir": state.elevator_state.Dir, "Doors": state.elevator_state.Doors, "State": state.elevator_state.State, }) await asyncio.sleep(TIME_DOORS_OPEN) # 4. State: DOORS_CLOSING state.elevator_state.State = "DOORS_CLOSING" state.elevator_state.Doors = "CLOSING" logger.info("Elevator doors are CLOSING at floor %d", state.elevator_state.Floor) await state.broadcast("elevator", { "Floor": state.elevator_state.Floor, "Dir": state.elevator_state.Dir, "Doors": state.elevator_state.Doors, "State": state.elevator_state.State, }) await asyncio.sleep(TIME_DOOR_CLOSING) # 5. State: IDLE / CLOSED state.elevator_state.State = "IDLE" state.elevator_state.Doors = "CLOSED" logger.info("Elevator is now IDLE at floor %d", state.elevator_state.Floor) await state.broadcast("elevator", { "Floor": state.elevator_state.Floor, "Dir": state.elevator_state.Dir, "Doors": state.elevator_state.Doors, "State": state.elevator_state.State, }) except asyncio.CancelledError: logger.info("Elevator simulator task cancelled") break except Exception as e: logger.error("Exception in elevator simulator: %s", e, exc_info=True) await asyncio.sleep(1.0)