# main.py - Waterkloof Microgrid: PV + V2G + Grid + Multi-Storage + LED Bars
# Compatible with Wokwi
# Author: Tsumbedzo Maxwell Sikhau (2025)
# Includes PV failover to V2G, then Grid, and simultaneous storage management
import time
import network
from machine import Pin, ADC
# ==========================================================
# === Network Simulation ===================================
# ==========================================================
WIFI_SSID = "Waterkloof_Microgrid"
WIFI_PASS = "secure_password_123"
# ==========================================================
# === Storage Definitions ==================================
# ==========================================================
LI_CAPACITY_KWH = 20.0
LI_MAX_POWER_KW = 8.0
LI_EFF = 0.90
li_soc = 80.0
VRFB_CAPACITY_KWH = 50.0
VRFB_MAX_POWER_KW = 15.0
VRFB_EFF = 0.75
vrfb_soc = 85.0
THERMAL_CAPACITY_KWH = 15.0
THERMAL_MAX_POWER_KW = 4.0
THERMAL_EFF = 0.85
thermal_soc = 70.0
# ==========================================================
# === Inverter & Modes =====================================
# ==========================================================
V2G_MAX_POWER_KW = 10.0
GRID_MAX_POWER_KW = 25.0
# ==========================================================
# === LED Bars =============================================
# ==========================================================
led_li = [Pin(i, Pin.OUT) for i in range(0, 10)]
led_vrfb = [Pin(i, Pin.OUT) for i in range(10, 20)]
led_thermal = [Pin(i, Pin.OUT) for i in range(20, 25)]
def update_led_bar(led_list, soc):
    num_leds = len(led_list)
    num_on = int((soc / 100) * num_leds + 0.5)
    for i, led in enumerate(led_list):
        led.value(1 if i < num_on else 0)
def update_all_leds():
    update_led_bar(led_li, li_soc)
    update_led_bar(led_vrfb, vrfb_soc)
    update_led_bar(led_thermal, thermal_soc)
# ==========================================================
# === Household Load Profiles ==============================
# ==========================================================
HOUSEHOLD_LOADS = {
    'house1': [1.2,1.0,0.8,0.7,0.8,1.0,1.5,2.0,1.8,1.5,1.3,1.2,
               1.1,1.0,1.1,1.3,1.8,2.2,2.5,2.8,2.0,1.5,1.3,1.2],
    'house2': [0.8,0.6,0.5,0.4,0.5,0.6,1.0,1.5,1.2,1.0,0.8,0.7,
               0.6,0.5,0.6,0.8,1.2,1.5,1.8,1.5,1.0,0.8,0.7,0.6],
    'house3': [0.5,0.4,0.3,0.3,0.4,0.5,0.8,1.0,0.7,0.6,0.5,0.4,
               0.3,0.3,0.4,0.5,0.8,1.0,1.2,0.9,0.6,0.5,0.4,0.4]
}
# ==========================================================
# === Inputs (Simulated Pins) ===============================
# ==========================================================
grid_status_pin = Pin(16, Pin.IN, Pin.PULL_DOWN)
v2g_status_pin = Pin(17, Pin.IN, Pin.PULL_DOWN)
# ==========================================================
# === Time Simulation ======================================
# ==========================================================
start_time = time.ticks_ms()
simulated_hour = 7
def get_simulated_time():
    global simulated_hour
    elapsed_seconds = time.ticks_diff(time.ticks_ms(), start_time) // 1000
    simulated_hour = (7 + (elapsed_seconds // 30)) % 24
    return simulated_hour
# ==========================================================
# === PV Generation ========================================
# ==========================================================
def read_pv_power():
    hour = get_simulated_time()
    if 6 <= hour <= 18:
        offset = abs(hour - 12)
        return max(0.0, 8.0 - 0.6 * offset)
    return 0.0
# ==========================================================
# === Household Loads ======================================
# ==========================================================
def get_household_loads():
    hour = get_simulated_time()
    current_loads = {h: profile[hour] for h, profile in HOUSEHOLD_LOADS.items()}
    total_load = sum(current_loads.values())
    return current_loads, total_load
# ==========================================================
# === Connectivity Simulation ===============================
# ==========================================================
def connect_wifi():
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.disconnect()
    print("✅ Wi-Fi initialized (simulation mode).")
def check_v2g_availability():
    return bool(v2g_status_pin.value())
def check_grid_availability():
    return bool(grid_status_pin.value())
# ==========================================================
# === Storage Management ===================================
# ==========================================================
def charge_all_batteries(power_kw, efficiency=0.9):
    global li_soc, vrfb_soc, thermal_soc
    total_cap = LI_MAX_POWER_KW + VRFB_MAX_POWER_KW + THERMAL_MAX_POWER_KW
    li_soc += ((power_kw * LI_MAX_POWER_KW / total_cap) * efficiency / LI_CAPACITY_KWH) * 100 * 0.25
    vrfb_soc += ((power_kw * VRFB_MAX_POWER_KW / total_cap) * efficiency / VRFB_CAPACITY_KWH) * 100 * 0.25
    thermal_soc += ((power_kw * THERMAL_MAX_POWER_KW / total_cap) * efficiency / THERMAL_CAPACITY_KWH) * 100 * 0.25
def discharge_all_batteries(deficit_kw):
    global li_soc, vrfb_soc, thermal_soc
    total_cap = LI_MAX_POWER_KW + VRFB_MAX_POWER_KW + THERMAL_MAX_POWER_KW
    li_soc -= ((deficit_kw * LI_MAX_POWER_KW / total_cap) / LI_EFF / LI_CAPACITY_KWH) * 100 * 0.25
    vrfb_soc -= ((deficit_kw * VRFB_MAX_POWER_KW / total_cap) / VRFB_EFF / VRFB_CAPACITY_KWH) * 100 * 0.25
    thermal_soc -= ((deficit_kw * THERMAL_MAX_POWER_KW / total_cap) / THERMAL_EFF / THERMAL_CAPACITY_KWH) * 100 * 0.25
def update_storage(pv_kw, total_load_kw):
    global li_soc, vrfb_soc, thermal_soc
    v2g_available = check_v2g_availability()
    grid_available = check_grid_availability()
    net_power = pv_kw - total_load_kw
    if pv_kw > 0:
        print("☀️ PV active — supplying load and charging batteries.")
        if net_power > 0:
            charge_all_batteries(net_power)
        else:
            discharge_all_batteries(abs(net_power))
    elif v2g_available:
        print("🔋 PV failed — activating V2G inverter to charge batteries.")
        charge_all_batteries(V2G_MAX_POWER_KW)
    elif grid_available:
        print("⚡ PV & V2G failed — activating Grid inverter to charge batteries.")
        charge_all_batteries(GRID_MAX_POWER_KW)
    else:
        print("🚨 PV, V2G, and Grid all unavailable — discharging storages to supply load.")
        discharge_all_batteries(total_load_kw)
    # Bound SOCs
    li_soc = max(20, min(95, li_soc))
    vrfb_soc = max(15, min(90, vrfb_soc))
    thermal_soc = max(10, min(100, thermal_soc))
# ==========================================================
# === MAIN LOOP ============================================
# ==========================================================
def main():
    print("=== Waterkloof Microgrid Simulation (PV + V2G + Grid Failover) ===")
    connect_wifi()
    led = Pin("LED", Pin.OUT)
    while True:
        try:
            led.toggle()
            hour = get_simulated_time()
            pv_power = read_pv_power()
            household_loads, total_load = get_household_loads()
            update_storage(pv_power, total_load)
            update_all_leds()
            print(f"\n🕒 {hour:02d}:00 | ☀️ PV={pv_power:.1f} kW | 🏠 Load={total_load:.1f} kW")
            print(f"Li-ion={li_soc:.1f}% | VRFB={vrfb_soc:.1f}% | Thermal={thermal_soc:.1f}%")
            print("🏡 Household Loads:")
            for h, l in household_loads.items():
                print(f"   {h}: {l:.2f} kW")
            time.sleep(10)
        except Exception as e:
            print("⚠️ Error:", e)
            time.sleep(5)
# ==========================================================
# === Run ==================================================
# ==========================================================
main()