1 contributor
245 lines | 6.627kb

Charging Monitoring Operation

Monitorizarea unei sesiuni de încărcare: colectarea măsurătorilor, izolarea curbei şi calculul energiei.

Responsabilități

  • Orchestrează o sesiune de încărcare complet (start → record → end)
  • Colectează măsurători de la meter la intervale regulate (~1Hz)
  • Izolează porţiunea relevantă a curbei (fără pre/post charging noise)
  • Calculează totalul de energie consumată (Wh)
  • Persistă date în Core Data + iCloud

Invarianţi

  • MUST: Doar o sesiune activă per meter la orice moment
  • MUST: Măsurătorile sunt cronologice (sortate după timestamp)
  • MUST: Start time < end time (sauf pentru sesiunile active)
  • MUST: Energia totală ≥ 0 (nu poate fi negativă)
  • MUST: O sesiune completată este imutabilă
  • SHOULD: O sesiune durează între 5 minute şi 48 ore
  • MAY: Sesiuni orphane (meter deconectat) pot fi finalizate automat

Stadiile sesiunii

idle
  ↓
startSession() → starting
  ↓
recordMeasurement() → active (repeat)
  ↓
endSession() → ended
  ↓
completed (immutable)

Lifecycle

1. Start sesiune

let session = meter.startChargeRecord(for: device)
// ⟹ ChargeRecord(
//    id: UUID(),
//    sessionID: UUID(),
//    startTime: Date(),
//    chargedDeviceID: device.id,
//    meterID: meter.id,
//    measurements: [],
//    totalEnergy: 0
// )

MUST: - startTime = Date.now - sessionID = UUID unic - measurements array = empty - totalEnergy = 0 - Marchez sesiunea ca "active"

SHOULD: - Notify UI: "Recording started" - Start periodic measurement requests (1Hz timer) - Log sesiune în analytics

2. Record measurement

while session.isActive {
    let measurement = meter.lastDataPoint
    session.addMeasurement(measurement)
}

Frecvenţă: 1Hz (1000ms interval)

MUST: - measurement.timestamp > session.startTime - Măsurătorile ordonate cronologic - Ignore duplicate timestamps - Validare: voltage, current ≥ 0

SHOULD: - Drop măsurători dacă queue > 500 items - Skip interval dacă meter nu are date nouă - Log invalid measurements (don't crash)

MAY: - Adjust frecvenţă dacă battery low (500ms → 2s) - Throttle dacă temperatura critică

3. End sesiune

meter.endChargeRecord(session)

MUST: - endTime = Date.now - Calculate totalEnergy = ∑(V × A × Δt) pentru fiecare interval - Call Charge Curve Isolation pentru obţine valid range - Marchez sesiunea ca "completed" - Salvează în Core Data sync

SHOULD: - Recalculează media power (watts) - Calculează duration (seconds) - Notify UI: "Recording completed" - Archive sesiunea (backup iCloud)

MAY: - Trigger conflict resolution (dacă detecta duplicate-uri) - Trigger capacity learning (dacă sesiune completă)

API Public

Proprietăţi sesiune

Proprietate Tip Descriere
id UUID Identificator unic
sessionID UUID Reference în persistență
chargedDeviceID UUID Device monitorizat
meterID UUID Meter folosit
startTime Date Moment start
endTime Date? Moment end (nil dacă active)
measurements [Measurement] Puncte colectate
totalEnergy Double Wh total
isActive Bool Status curent
duration TimeInterval end - start
peakPower Double Max watts
averagePower Double Mean watts

Metode

// Orchestrare
func startChargeRecord(for device: ChargedDevice) -> ChargeRecord
func recordMeasurement(_ measurement: Measurement, in session: ChargeRecord)
func endChargeRecord(_ session: ChargeRecord) -> ChargeRecord

// Calculaţii
func calculateTotalEnergy(measurements: [Measurement]) -> Double
func calculatePeakPower(measurements: [Measurement]) -> Double
func calculateAveragePower(measurements: [Measurement]) -> Double

// Persistență
func saveSession(_ record: ChargeRecord)
func loadSession(id: UUID) -> ChargeRecord?

Comportamente critice

Timeout pe sesiuni

  • MUST: Dacă nu primim măsurători > 5 min, sesiunea e considerat "stale"
  • SHOULD: Notifică utilizator: "No data received, disconnect?"
  • MAY: Auto-finalizează după 10 min inactivitate

Validare măsurători

func isValidMeasurement(_ m: Measurement) -> Bool {
    // Voltage: 0V — 30V (USB + other)
    // Current: 0A — 10A (typical chargers)
    // Power: 0W — 300W (practical limit)
    return m.voltage >= 0 && m.voltage <= 30 &&
           m.current >= 0 && m.current <= 10 &&
           m.power >= 0 && m.power <= 300
}

MUST: Drop invalide measurements SHOULD: Log warnings pentru edge-case values

Merge duplicate sessions

Se declanşează când: - Două sesiuni cu overlap temporal > 50% - Aceeaşi device + meter

Rezoluţie: - MUST: Keep sesiunea cu mai multe măsurători - MUST: Combină energiile: total = session1.energy + session2.energy - SHOULD: Archive old session version

Energia negativă (edge case)

Cauze posibile: 1. Meter inversează polarity (rare) 2. Măsurători corupte 3. Bug în parsing

SHOULD: Log warning MAY: Absolute value: energy = abs(energy) NEVER: Discard sesiune

Testare

Unit tests

// Start/end
test_startSessionCreatesValidRecord()
test_endSessionCalculatesTotalEnergy()

// Measurements
test_recordMeasurement_AddsToSession()
test_recordMeasurement_FailsIfSessionEnded()
test_measurementsAreOrdered()
test_invalidMeasurement_IsDropped()

// Calculations
test_calculateTotalEnergy_WithValidData()
test_calculateTotalEnergy_WithGaps()
test_calculatePeakPower()
test_calculateAveragePower()

// Validation
test_sessionTimeValidity()
test_sessionDuration_Max48Hours()
test_activeSessions_OnlyOne()

Integration tests

  • [ ] Full cycle: start → record 100 samples → end
  • [ ] Sesiune pe device A, apoi device B (same meter)
  • [ ] Meter disconnect → auto-finalize
  • [ ] Energy = ∑ = match manual calculation
  • [ ] Sesiune persistent după app restart

Dependenţe

Notes

  • Time-series resolution: 1Hz (1000ms)
  • Energy formula: E = ∑(V × A × Δt) (integral numerical)
  • Legat: Charge Session Integrity