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.
