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 di tale sistema, basato su componenti FIWARE.
Componenti FIWARE
Il sistema si basa infatti 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.
Riportiamo di seguito l’implementazione dei componenti chiave.
Modelli di Dati NGSI-LD
```typescript // 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>; } ```
Backend Services
I servizi di backend prevedono il servizio di pianificazione delle ricariche e quello di gestione delle prenotazioni.
Servizio di pianificazione ricariche
```python 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
```python 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] } } ) # Invia notifica all’utente await self.notify_user(request.user_id, reservation) return reservation ```
Implementazione iOS
Vediamo ora i passi necessari per una implementazione su sistemi iOS.
Gestione del calendario
```swift 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
```swift 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
```swift 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) } } ```
Gestione della Privacy
Per la gestione dei dati riservati, sarà necessario creare una classe PrivacyManager nel modo che segue.
```swift 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
Vediamo infine i componenti che si occuperanno dei test di unità e del monitoraggio.
Unit Testing
```swift 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
```python @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.
Giovanni Puliti ha lavorato per oltre 20 anni come consulente nel settore dell’IT e attualmente svolge la professione di Agile Coach. Nel 1996, insieme ad altri collaboratori, crea MokaByte, la prima rivista italiana web dedicata a Java. Autore di numerosi articoli pubblicate sia su MokaByte.it che su riviste del settore, ha partecipato a diversi progetti editoriali e prende parte regolarmente a conference in qualità di speaker. Dopo aver a lungo lavorato all’interno di progetti di web enterprise, come esperto di tecnologie e architetture, è passato a erogare consulenze in ambito di project management. Da diversi anni ha abbracciato le metodologie agili offrendo ad aziende e organizzazioni il suo supporto sia come coach agile che come business coach. È cofondatore di AgileReloaded, l’azienda italiana per il coaching agile.