Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion core/power_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -836,6 +836,11 @@ def _store_network_state(self):
import json

try:
# Guard: Check if db_manager and get_session are available
if not hasattr(db_manager, 'get_session') or not callable(getattr(db_manager, 'get_session', None)):
# Database not configured - skip storage (this is optional for simulation)
return

with db_manager.get_session() as session:
state = NetworkState(
simulation_time=int(self.network.snapshots[0].timestamp()),
Expand All @@ -859,7 +864,8 @@ def _store_network_state(self):
session.commit()

except Exception as e:
logger.error(f"Failed to store network state: {e}")
# Don't spam logs - database storage is optional for demo
pass
def _calculate_health_score(self) -> float:
"""Calculate overall system health score (0-100)"""

Expand Down
27 changes: 15 additions & 12 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<!-- Mapbox GL JS -->
<link href="https://api.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>

<!-- Typography -->
<link rel="preconnect" href="https://fonts.googleapis.com">
Expand All @@ -19,8 +20,8 @@
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.8/dist/purify.min.js"></script>

<!-- Custom Styles -->
<link href="/static/styles.css" rel="stylesheet">
<link href="/static/chatbot-enhanced-ui.css" rel="stylesheet">
<link href="/static/styles.css?v=nocache_1769983928" rel="stylesheet">
<link href="/static/chatbot-enhanced-ui.css?v=nocache_1769983928" rel="stylesheet">
</head>
<body data-tab="overview">
<!-- Advanced Background Effects -->
Expand Down Expand Up @@ -531,18 +532,20 @@ <h4 style="color:var(--text-secondary);margin-bottom:12px;font-size:14px;font-we
</div>
</div>



<!-- Custom JavaScript -->
<script src="/static/script.js"></script>
<script src="/static/ai-enhanced.js"></script>
<script src="/static/ai-functions.js"></script>
<script src="/static/world-class-map.js"></script>
<script src="/static/scenario-director.js"></script>
<script src="/static/chatbot-scenarios.js"></script>
<script src="/static/script.js?v=nocache_1769983928"></script>
<script src="/static/ai-enhanced.js?v=nocache_1769983928"></script>
<script src="/static/ai-functions.js?v=nocache_1769983928"></script>
<script src="/static/world-class-map.js?v=nocache_1769983928"></script>
<script src="/static/scenario-director.js?v=nocache_1769983928"></script>
<script src="/static/chatbot-scenarios.js?v=nocache_1769983928"></script>

<!-- Scenario Controller -->
<script src="/static/traffic-patterns.js"></script>
<script src="/static/time-vehicle-manager.js"></script>
<script src="/static/scenario-controls.js"></script>
<script src="/static/chatbot-scenario-llm.js"></script>
<script src="/static/traffic-patterns.js?v=nocache_1769983928"></script>
<script src="/static/time-vehicle-manager.js?v=nocache_1769983928"></script>
<script src="/static/scenario-controls.js?v=nocache_1769983928"></script>
<script src="/static/chatbot-scenario-llm.js?v=nocache_1769983928"></script>
</body>
</html>
180 changes: 163 additions & 17 deletions main_complete_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from flask import Flask, render_template_string, jsonify, request
from flask_cors import CORS
from flask_socketio import SocketIO, emit
import json
import threading
import time
Expand Down Expand Up @@ -43,7 +44,22 @@ def load_dotenv(*args, **kwargs):

load_dotenv()
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
CORS(app)
socketio = SocketIO(app, async_mode='threading', cors_allowed_origins="*")

# System state - Defined early for access
from core.sumo_manager import SimulationScenario
system_state = {
'running': True,
'sumo_running': False,
'simulation_speed': 1.0,
'current_time': 0,
'scenario': SimulationScenario.MIDDAY
}

# Asynchronous vehicle spawn queue (prevents UI freezing)
vehicle_spawn_queue = []

# Initialize systems
print("=" * 60)
Expand Down Expand Up @@ -101,6 +117,32 @@ def load_dotenv(*args, **kwargs):
print("Initializing V2G energy trading system...")
v2g_manager = V2GManager(integrated_system, sumo_manager)

# Register WebSocket callback for V2G state changes
def v2g_websocket_callback(event_type, data):
"""Emit V2G events via WebSocket"""
if event_type == 'restoration_complete':
# Calculate participating vehicles
vehicles = []
for vid, session in v2g_manager.active_sessions.items():
if session.substation_id == data['substation']:
vehicles.append({
'id': vid,
'earnings': session.earnings,
'energy_delivered': session.power_delivered_kwh
})

# Emit restoration complete event
socketio.emit('v2g_restoration_complete', {
'substation': data['substation'],
'energy_delivered': data['energy_delivered'],
'revenue': data['total_revenue'],
'vehicles': vehicles
})
print(f"[WebSocket] Emitted v2g_restoration_complete for {data['substation']}")

v2g_manager.register_notification_callback(v2g_websocket_callback)
print("V2G WebSocket notifications enabled")

sumo_manager.set_v2g_manager(v2g_manager)

# Initialize Enhanced ML Engine with V2G integration
Expand Down Expand Up @@ -130,7 +172,11 @@ def load_dotenv(*args, **kwargs):

# Initialize OpenAI client (optional if key provided)
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
openai_client = OpenAI(api_key=OPENAI_API_KEY) if (OPENAI_API_KEY and OpenAI) else None
try:
openai_client = OpenAI(api_key=OPENAI_API_KEY) if (OPENAI_API_KEY and OpenAI) else None
except Exception as e:
print(f"OpenAI client initialization skipped: {e}")
openai_client = None

# Initialize REALISTIC LOAD MODEL and SCENARIO CONTROLLER
print("=" * 60)
Expand All @@ -146,11 +192,90 @@ def load_dotenv(*args, **kwargs):
load_model = RealisticLoadModel(integrated_system)

print("Initializing scenario controller...")

# Define WebSocket broadcast callback
# Define WebSocket broadcast callback - WORLD CLASS REAL-TIME UPDATES
def broadcast_state(scenario_status):
try:
# 1. Get Base Network State (Substations, Cables, etc.)
# This should be fast as it reads from internal memory structure
state = integrated_system.get_network_state()

# 2. Add Scenario Controller Data (Time, Weather, Stats)
state['scenario'] = scenario_status

# 3. Add SUMO Running Flag (needed for Start/Stop button state)
state['sumo_running'] = system_state.get('sumo_running', False)

# 4. Add Real-Time Vehicle Data if SUMO is running
if system_state.get('sumo_running', False) and sumo_manager.running:
try:
# Use the complete vehicle visualization method that includes all data
# (battery_percent, soc, is_ev, is_charging, etc.)
vehicles = sumo_manager.get_vehicle_positions_for_visualization()
state['vehicles'] = vehicles
state['vehicle_count'] = len(vehicles)

# Get pending vehicles (in insertion queue)
import traci
try:
# Get vehicles waiting to enter the network
pending_count = traci.simulation.getPendingVehicles().getIDCount()
except:
# Fallback if API doesn't work
pending_count = 0

# Add vehicle statistics (for charging count, etc.)
vehicle_stats = {
'active_vehicles': len(vehicles), # Currently on road
'pending_vehicles': pending_count, # Waiting in queue
'total_configured': len(vehicles) + pending_count, # Total spawned
'total_vehicles': len(vehicles), # Legacy field
'ev_vehicles': sum(1 for v in vehicles if v.get('is_ev', False)),
'gas_vehicles': sum(1 for v in vehicles if not v.get('is_ev', False)),
'vehicles_charging': sum(1 for v in vehicles if v.get('is_charging', False)),
'vehicles_low_battery': sum(1 for v in vehicles if v.get('is_ev', False) and v.get('battery_percent', 100) < 20),
'vehicles_medium_battery': sum(1 for v in vehicles if v.get('is_ev', False) and 20 <= v.get('battery_percent', 100) < 50),
'vehicles_high_battery': sum(1 for v in vehicles if v.get('is_ev', False) and v.get('battery_percent', 100) >= 50),
}
state['vehicle_stats'] = vehicle_stats

except Exception as e:
print(f"Socket vehicle update error: {e}")

# 4. Add V2G Data if initialized
if 'v2g_manager' in globals() and v2g_manager:
try:
# Get the full dash data - this is efficient as it uses internal state
v2g_data = v2g_manager.get_v2g_dashboard_data()
state['v2g'] = v2g_data
except Exception as e:
print(f"Socket V2G update error: {e}")

# 5. Add AI Map Focus Data if available
if 'ai_map_focus_data' in globals() and ai_map_focus_data:
try:
state['ai_focus'] = {
'has_update': True,
'focus_data': ai_map_focus_data
}
except Exception as e:
print(f"Socket AI focus update error: {e}")
else:
state['ai_focus'] = {'has_update': False}

# Emit unified system update event
socketio.emit('system_update', state)

except Exception as e:
print(f"Broadcast error: {e}")

scenario_controller = ScenarioController(
integrated_system=integrated_system,
load_model=load_model,
power_grid=power_grid,
sumo_manager=sumo_manager
sumo_manager=sumo_manager,
on_update_callback=broadcast_state
)

# Start automatic monitoring
Expand Down Expand Up @@ -206,14 +331,7 @@ def preload_edge_shapes(max_edges: int | None = None) -> int:
return count
return count

# System state
system_state = {
'running': True,
'sumo_running': False,
'simulation_speed': 1.0,
'current_time': 0,
'scenario': SimulationScenario.MIDDAY
}


# EV Configuration
current_ev_config = {
Expand Down Expand Up @@ -293,6 +411,23 @@ def simulation_loop():
sumo_time = (time_module.perf_counter() - sumo_start) * 1000
perf_stats['sumo_step'].append(sumo_time)

# ASYNC VEHICLE SPAWNING: Process spawn queue in batches (max 5 per tick)
# This prevents UI freezing when bulk spawning vehicles
global vehicle_spawn_queue
if vehicle_spawn_queue:
batch_size = min(5, len(vehicle_spawn_queue))
for _ in range(batch_size):
config = vehicle_spawn_queue.pop(0)
try:
sumo_manager.spawn_vehicles(
count=1,
ev_percentage=config['ev_percentage'],
battery_min_soc=config['battery_min_soc'],
battery_max_soc=config['battery_max_soc']
)
except Exception as e:
print(f"[QUEUE] Spawn error: {e}")

# REALISTIC: V2G updates every 60 seconds (vehicle-to-grid state changes)
if system_state['current_time'] - last_v2g_update >= V2G_STEPS:
v2g_manager.update_v2g_sessions()
Expand Down Expand Up @@ -877,23 +1012,34 @@ def stop_sumo():

@app.route('/api/sumo/spawn', methods=['POST'])
def spawn_vehicles():
"""Spawn additional vehicles"""
"""Spawn additional vehicles (async queue)"""
global vehicle_spawn_queue

if not system_state['sumo_running']:
return jsonify({'success': False, 'message': 'SUMO not running'})

# Extract parameters from frontend request
data = request.json or {}
count = data.get('count', 5)
ev_percentage = data.get('ev_percentage', 0.7)
battery_min_soc = data.get('battery_min_soc', 0.2)
battery_max_soc = data.get('battery_max_soc', 0.9)

spawned = sumo_manager.spawn_vehicles(count, ev_percentage, battery_min_soc, battery_max_soc)

# Queue individual vehicles with frontend-specified configs
for i in range(count):
vehicle_spawn_queue.append({
'ev_percentage': ev_percentage,
'battery_min_soc': battery_min_soc,
'battery_max_soc': battery_max_soc
})

# Return immediately (202 Accepted) - vehicles will spawn in background
return jsonify({
'success': True,
'spawned': spawned,
'total_vehicles': sumo_manager.stats['total_vehicles']
})
'message': f'{count} vehicles queued for spawning',
'queued': len(vehicle_spawn_queue),
'total_vehicles': sumo_manager.stats.get('total_vehicles', 0)
}), 202

@app.route('/api/sumo/scenario', methods=['POST'])
def set_scenario():
Expand Down Expand Up @@ -2036,4 +2182,4 @@ def load_html_template():
print(" - Fail substations to see EV stations go offline")
print("=" * 60)

app.run(debug=False, port=5000)
socketio.run(app, debug=False, port=5000, allow_unsafe_werkzeug=True)
Loading