FIWARE: Open APIs for Open Minds

FIWARE: Open APIs for Open Minds

V parte: Implementazione del sistema di ricarica

Questo articolo presenta i dettagli implementativi di un sistema di gestione delle ricariche per veicoli elettrici basato su FIWARE. Gli esempi di codice coprono il backend distribuito e l'applicazione mobile, mostrando l'integrazione fra i componenti e la gestione dei principali flussi di dati.

Architettura del sistema

Dopo aver visto nella parte precedente le caratteristiche generali di un sistema intelligente di ricarica delle auto elettriche, con i vari elementi in gioco, in questo ultimo articolo della serie illustreremo una possibile implementazione basata su componenti FIWARE.

Componenti FIWARE

Il sistema si basa su diversi Generic Enablers FIWARE:

  • Orion-LD Context Broker per la gestione del contesto
  • IoT Agent per l'integrazione con le colonnine
  • KeyRock per identity management
  • Quantum Leap per la storicizzazione dei dati temporali
Schema dell'architettura del sistema
Schema dell'architettura del sistema

Implementazione dei Componenti Chiave

1. Modelli di Dati NGSI-LD

// Definizione delle interfacce TypeScript per i modelli NGSI-LD
interface EVChargingStation {
    id: string;
    type: 'EVChargingStation';
    location: GeoProperty;
    status: Property<'available' | 'occupied' | 'outOfService'>;
    connectorType: Property<string[]>;
    maxPower: Property<number>;
    currentPower: Property<number>;
    pricePerKWh: Property<number>;
    operator: Relationship;
    reservations: Property<Reservation[]>;
}

interface EVCharger {
    id: string;
    type: 'EVCharger';
    location: GeoProperty;
    batteryLevel: Property<number>;
    estimatedRange: Property<number>;
    preferredStations: Relationship[];
    owner: Relationship;
    lastUpdate: Property<Date>;
}

2. Backend Services

Servizio di Pianificazione Ricariche

from datetime import datetime, timedelta
from typing import List, Optional
import asyncio

class ChargingPlanner:
    def __init__(self, context_broker: ContextBroker):
        self.context_broker = context_broker
        self.optimization_service = OptimizationService()

    async def plan_daily_charging(self, user_id: str, date: datetime) -> ChargingPlan:
        # Recupera gli appuntamenti del giorno
        calendar_events = await self.get_calendar_events(user_id, date)
        
        # Recupera lo stato del veicolo
        vehicle = await self.get_vehicle_status(user_id)
        
        # Calcola il fabbisogno energetico
        energy_needs = self.calculate_energy_needs(
            current_charge=vehicle.battery_level,
            planned_routes=self.extract_routes(calendar_events)
        )
        
        if not energy_needs.requires_charging:
            return ChargingPlan(needed=False)
        
        # Trova le finestre temporali disponibili
        charging_windows = self.find_charging_windows(calendar_events)
        
        # Ottimizza il piano di ricarica
        optimal_plan = await self.optimization_service.optimize_charging(
            energy_needs=energy_needs,
            charging_windows=charging_windows,
            available_stations=await self.get_available_stations()
        )
        
        return optimal_plan

    def calculate_energy_needs(self, current_charge: float, planned_routes: List[Route]) -> EnergyNeeds:
        total_distance = sum(route.distance for route in planned_routes)
        estimated_consumption = self.estimate_consumption(
            total_distance,
            routes=planned_routes,
            weather=self.get_weather_forecast()
        )
        
        return EnergyNeeds(
            current_level=current_charge,
            required_energy=estimated_consumption,
            safety_margin=10.0  # percentuale
        )

Gestore delle Prenotazioni

class ReservationManager:
    def __init__(self, context_broker: ContextBroker):
        self.context_broker = context_broker

    async def create_reservation(self, request: ReservationRequest) -> Reservation:
        # Verifica disponibilità
        station = await self.context_broker.get_entity(request.station_id)
        if not self.is_slot_available(station, request.time_slot):
            raise SlotNotAvailableError()
        
        # Crea la prenotazione
        reservation = Reservation(
            id=generate_uuid(),
            station_id=request.station_id,
            user_id=request.user_id,
            time_slot=request.time_slot,
            status='confirmed'
        )
        
        # Aggiorna il Context Broker
        await self.context_broker.update_entity(
            entity_id=request.station_id,
            attrs={
                'reservations': {
                    'type': 'Property',
                    'value': [...station.reservations, reservation]
                }
            }
        )
        
        # Notifica l'utente
        await self.notify_user(request.user_id, reservation)
        
        return reservation

3. Implementazione iOS

Gestione del Calendario

class CalendarManager {
    private let eventStore = EKEventStore()
    
    func requestAccess() async throws -> Bool {
        return try await eventStore.requestAccess(to: .event)
    }
    
    func fetchEvents(for date: Date) async throws -> [EKEvent] {
        let calendar = Calendar.current
        let startDate = calendar.startOfDay(for: date)
        let endDate = calendar.date(byAdding: .day, value: 1, to: startDate)!
        
        let predicate = eventStore.predicateForEvents(
            withStart: startDate,
            end: endDate,
            calendars: nil
        )
        
        return eventStore.events(matching: predicate)
    }
    
    func analyzeEvents(_ events: [EKEvent]) -> [TripPlan] {
        return events.map { event in
            TripPlan(
                startTime: event.startDate,
                endTime: event.endDate,
                location: event.location,
                title: event.title
            )
        }
    }
}

Integrazione con FIWARE

class FIWAREClient {
    private let baseURL: URL
    private let session: URLSession
    
    func subscribeToUpdates() async throws {
        let subscription = NGSISubscription(
            entities: [EntityType.chargingStation],
            watchedAttributes: ["status", "currentPower"],
            notification: NotificationEndpoint(
                uri: "my-app-scheme://charging-update",
                accept: "application/json"
            )
        )
        
        try await createSubscription(subscription)
    }
    
    func updateVehicleStatus(_ status: VehicleStatus) async throws {
        let entity = NGSIEntity(
            id: "urn:ngsi-ld:Vehicle:\(status.vehicleId)",
            type: "Vehicle",
            attributes: [
                "batteryLevel": .property(status.batteryLevel),
                "location": .geoProperty(status.location),
                "lastUpdate": .property(Date())
            ]
        )
        
        try await updateEntity(entity)
    }
}

User Interface

class ChargingPlanViewController: UIViewController {
    private let mapView: MKMapView
    private let timelineView: TimelineView
    private let chargingManager: ChargingManager
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupViews()
        Task {
            await loadChargingPlan()
        }
    }
    
    private func loadChargingPlan() async {
        do {
            let plan = try await chargingManager.getDailyPlan()
            await MainActor.run {
                updateUI(with: plan)
            }
        } catch {
            await showError(error)
        }
    }
    
    private func updateUI(with plan: ChargingPlan) {
        // Aggiorna la mappa con le stazioni di ricarica
        mapView.showChargingStations(plan.stations)
        
        // Aggiorna la timeline con gli eventi e le ricariche pianificate
        timelineView.update(with: plan.schedule)
        
        // Aggiorna le informazioni di riepilogo
        updateSummaryView(plan.summary)
    }
}

4. Gestione della Privacy

class PrivacyManager {
    func filterCalendarData(_ events: [EKEvent]) -> [LocationTimeSlot] {
        return events.map { event in
            LocationTimeSlot(
                timeSlot: TimeSlot(start: event.startDate, end: event.endDate),
                location: anonymizeLocation(event.location),
                type: classifyEventType(event)
            )
        }
    }
    
    private func anonymizeLocation(_ location: EKStructuredLocation?) -> AnonymizedLocation {
        guard let location = location else { return .unknown }
        
        return AnonymizedLocation(
            coordinates: roundCoordinates(location.geoLocation),
            area: determineArea(location),
            type: classifyLocationType(location)
        )
    }
}

Testing e Monitoraggio

Unit Testing

class ChargingPlannerTests: XCTestCase {
    var planner: ChargingPlanner!
    var mockContextBroker: MockContextBroker!
    
    override func setUp() {
        mockContextBroker = MockContextBroker()
        planner = ChargingPlanner(contextBroker: mockContextBroker)
    }
    
    func testChargingPlanGeneration() async throws {
        // Setup test data
        let events = createTestCalendarEvents()
        let vehicleStatus = createTestVehicleStatus()
        
        // Execute
        let plan = try await planner.generatePlan(
            forEvents: events,
            vehicleStatus: vehicleStatus
        )
        
        // Verify
        XCTAssertNotNil(plan)
        XCTAssertTrue(plan.isValid)
        XCTAssertTrue(plan.meetsEnergyNeeds(vehicleStatus))
    }
}

Performance Monitoring

@contextlib.contextmanager
def monitor_performance(operation_name: str):
    start_time = time.time()
    try:
        yield
    finally:
        duration = time.time() - start_time
        prometheus_client.OPERATION_DURATION.labels(
            operation=operation_name
        ).observe(duration)

Conclusioni

L'implementazione dimostra come FIWARE possa essere utilizzato per creare un sistema complesso che integra dati da diverse fonti e coordina attività in tempo reale. L'architettura modulare permette di gestire le sfide tecniche mantenendo il sistema flessibile e scalabile.

La combinazione di un backend FIWARE robusto con un'applicazione mobile intuitiva crea un'esperienza utente fluida che nasconde la complessità sottostante del sistema. L'approccio adottato permette di estendere facilmente il sistema con nuove funzionalità e di adattarlo a diverse esigenze degli utenti.