Măsurarea consumului rezidual (standby) al unui dispozitiv încărcat la maxim.
După ce dispozitivul raportează 100% battery, încărcătorul nu se opreşte imediat:
Timeline:
00:00 — Device reaches 100%
00:00-00:15 — Top-up phase (unele devices încarcă semnificativ)
Current: 1.5A → 0.8A → 0.3A → 0.05A
00:15 — Top-up terminat, device intra în idle
00:15-∞ — Idle phase (consum rezidual constant)
Current: 0.05A constant
Power: V × 0.05A ≈ 0.25W
Necesar: - Distinge top-up phase de idle phase - Măsoară idle power real - Folosește-o pentru bilanț energetic final
Total energy from charger: 50 Wh (measured)
Time charging: 2 hours
Idle power: 0.25 W
Energy to idle consumption: 0.25W × 2h = 0.5 Wh
Real energy to device battery: 50 - 0.5 = 49.5 Wh
⟹ Capacity learning mai precis
⟹ Bilanț charger vs device vs loss
Utilizatorul: 1. Încarcă device la maxim (100%) 2. Lasă conectat la charger după 100% 3. Deschide app → "Measure idle consumption" 4. Selectează dispozitiv + charger type 5. Pornește măsurare
struct IdleConsumptionSession {
let id: UUID
let deviceID: UUID
let chargerType: ChargerType?
let startTime: Date
let measuredAt100Percent: Bool // MUST: true
let sessionID: UUID
}
Precondition: - Device must report 100% battery - Device connected to charger - Meter recording power
App observă power trend:
func detectIdlePhase(measurements: [Measurement]) -> Int? {
// Last 60 measurements (1 minute)
let recent = measurements.suffix(60)
// Calculate power variance in recent window
let powers = recent.map { $0.power }
let mean = powers.reduce(0, +) / Double(powers.count)
let variance = powers.map { pow($0 - mean, 2) }.reduce(0, +) / Double(powers.count)
let stdDev = sqrt(variance)
// Idle criteria:
// 1. Power < 5W (no active charging)
// 2. Variance < 10% of mean (stable)
// 3. Duration > 30s at this level
if mean < 5.0 && stdDev < (mean * 0.1) {
return measurements.count - 60 // Index of phase start
}
return nil
}
Detection criteria: - Power < 5W (no active charging) - StdDev < 10% of mean (stable, not fluctuating) - Duration > 30 seconds (confirm stability)
Timeline:
10:00 — User starts measurement
10:15 — Top-up ends, power drops to 0.25W
App auto-detects idle phase
10:15-10:45 — User reviews, confirms
MUST: Detect automatically when possible SHOULD: Notify user "Idle phase detected @ 10:15"
User vede graficul cu highlight pentru idle phase detectată:
Power (W)
20 ├───────────────────┐
│ Top-up phase │
10 ├───────────┐ │
│ │ │
5 ├───────────┤ │
│ ↓ Idle detected │
0.5 ├───────────────┐ ◄─ Auto-selected interval
│ │
└────┬──────────┬──┴───────────→ time
10:15 10:45
(start idle) (user confirmed)
User actions: - Tap to confirm auto-selected interval - Drag to refine start position - Drag to refine end position - Tap "Save idle profile"
MUST: Allow manual override SHOULD: Offer auto-selection if confidence high
func computeIdlePower(
measurements: [Measurement],
from startTime: Date,
to endTime: Date
) -> IdleConsumptionProfile {
let filtered = measurements.filter { m in
m.timestamp >= startTime && m.timestamp <= endTime
}
guard !filtered.isEmpty else { return nil }
let powers = filtered.map { $0.power }
let currents = filtered.map { $0.current }
let voltages = filtered.map { $0.voltage }
let avgPower = powers.reduce(0, +) / Double(powers.count)
let stdDev = sqrt(
powers.map { pow($0 - avgPower, 2) }.reduce(0, +) / Double(powers.count)
)
// Energy over idle period
let duration = endTime.timeIntervalSince(startTime)
let energy = avgPower * duration / 3600 // Wh
return IdleConsumptionProfile(
deviceID: device.id,
chargerID: charger?.id,
idlePower: avgPower, // W
stdDev: stdDev,
energy: energy,
sampleCount: filtered.count,
measuredAt: Date.now,
interval: (startTime, endTime)
)
}
Salvează:
swift
struct IdleConsumptionProfile {
let id: UUID
let deviceID: UUID
let chargerID: UUID? // Optional, may be unknown charger
let idlePower: Double // W, avg
let stdDev: Double // W, variance
let energy: Double // Wh, cumulative idle energy
let sampleCount: Int
let measuredAt: Date
let interval: (start: Date, end: Date)
let notes: String?
}
NU salvează: Raw measurements
Storage: Core Data DeviceIdleProfile table
MUST: Salvare explicită (user confirm) SHOULD: Allow update (measure again if suspicious)
// Start idle measurement session
func startIdleConsumptionSession(
for device: ChargedDevice,
charger: ChargerType?
) -> IdleConsumptionSession
// Detect idle phase automatically
func detectIdlePhase(
in measurements: [Measurement]
) -> (startIndex: Int, confidence: Double)?
// Compute idle profile for interval
func computeIdlePower(
measurements: [Measurement],
from: Date,
to: Date
) -> IdleConsumptionProfile?
// Save profile
func saveIdleProfile(_ profile: IdleConsumptionProfile) -> Bool
// Get idle profile for device
func loadIdleProfile(for device: ChargedDevice) -> IdleConsumptionProfile?
// Get all profiles for device (multiple charger types)
func loadIdleProfiles(for device: ChargedDevice)
-> [IdleConsumptionProfile]
// Get idle power estimate for device+charger combo
func getIdlePower(
device: ChargedDevice,
charger: ChargerType?
) -> Double?
// Update profile after new measurement
func updateIdleProfile(_ profile: IdleConsumptionProfile) -> Bool
// Delete old profile
func deleteIdleProfile(_ id: UUID) -> Bool
Timeline misreading:
User thinks 10:10 = idle start
But device still trickling charge @ 0.2A
Power = 1.0W (not idle yet!)
⟹ False idle profile
MUST: Require variance < 10% (not just low power) SHOULD: Alert user if variance high MAY: Auto-suggest earlier/later boundary
Device A measured with:
- Charger X: idle = 0.25W
- Charger Y: idle = 0.30W (different charger)
⟹ Store separate profiles
⟹ User selects charger type at measurement time
MUST: Key by (device, charger type) SHOULD: Allow null charger (unknown) SHOULD: Group by device in UI
User hits "save" while device still @ 100%
But charger delivering 0.5A (top-up)
Power = 2.5W
⟹ This is NOT idle, it's slow charge!
MUST: Warn if battery % still 100% during interval SHOULD: Reject if power > 3W (too high for idle) MAY: Ask "Are you sure this is idle?"
Device idle measurement: 1.5W
(Typical idle: 0.1-0.5W)
⟹ Something unusual:
- Screen still on?
- Background app running?
- Device malfunctioning?
SHOULD: Warn "Idle power seems high"
SHOULD: Suggest retry with device fully idle
MAY: Store with dataQuality: "warning"
Charging session started:
Start battery: 20%
Start energy: 0 Wh
During charging:
Monitor power trend
When power drops to idle level...
Idle detection trigger:
Power < idle_threshold (e.g., 0.3W)
Duration > 2 minutes stable
Battery = 100%
⟹ Auto-terminate session
⟹ Calculate final energy
Pseudocode: ```swift func shouldAutoTerminateCharging( currentPower: Double, currentBattery: Double, duration: TimeInterval ) -> Bool { let idleProfile = loadIdleProfile(for: device) let idleThreshold = (idleProfile?.idlePower ?? 0.5) * 1.5 // 50% margin
return currentPower < idleThreshold &&
currentBattery >= 99.0 &&
duration > 120 // 2 min stable
} ```
Charging session:
Meter energy: 50.0 Wh
Duration: 2 hours
Idle power: 0.25 W
Idle energy: 0.25W × 2h = 0.5 Wh
Charger idle: 0.10 W
Charger idle energy: 0.10W × 2h = 0.2 Wh
Energy to device: 50.0 - 0.5 - 0.2 = 49.3 Wh
Accuracy improvement: +1-2% fidelity
Device A (iPhone):
Normal idle: 0.15W
Today's measurement: 0.45W (3× higher!)
⟹ Alert user:
"Device drawing 3× idle power"
"Battery drain issue?"
"Check background apps"
| Metric | Unit | Descriere |
|---|---|---|
| Idle Power | W | Average power during idle |
| StdDev | W | Variance (should be low) |
| Idle Energy | Wh | Total over measurement period |
| Sample Count | # | Measurements in interval |
| Confidence | % | Stability metric |
| Data Quality | flag | "good" / "warning" / "poor" |
func calculateConfidence(stdDev: Double, mean: Double) -> Double {
let relativeStdDev = stdDev / mean
if relativeStdDev < 0.05 {
return 0.95 // Excellent
} else if relativeStdDev < 0.10 {
return 0.85 // Good
} else if relativeStdDev < 0.20 {
return 0.70 // Fair
} else {
return 0.50 // Poor (high variance)
}
}
test_detectIdlePhase_ValidData()
test_detectIdlePhase_HighVariance_NotDetected()
test_detectIdlePhase_HighPower_NotDetected()
test_detectIdlePhase_MinimumDuration()
test_computeIdlePower_ValidInterval()
test_computeIdlePower_EmptyInterval()
test_computeIdlePower_HighVariance_Warning()
test_saveIdleProfile_Persists()
test_loadIdleProfile_ByDevice()
test_multipleChargerTypes_SeparateProfiles()
test_shouldAutoTerminateCharging_WithIdleProfile()
test_energyBilantz_SubtractsIdleEnergy()
test_confidenceCalculation_RelativeVariance()
Device truly idle: 0.008W
Below detection threshold
⟹ Impossible to measure accurately
SHOULD: "Idle power too low to measure" MAY: Skip measurement
User powers off device mid-measurement
Power drops to 0W
⟹ Not idle, device is off
MUST: Detect power drop to 0 SHOULD: Discard measurement MAY: Suggest "Check device is still on"
User unplugs charger
Power drops immediately
⟹ Not valid idle state
SHOULD: Detect sudden power drop SHOULD: Alert user "Charger disconnected?"
User measures 24 hours of idle
Device behavior may change:
- OS updates background activity
- Bluetooth periodic sync
- GPS location update
⟹ Not constant idle
SHOULD: Warn if > 2 hours MAY: Suggest shorter interval (30 min typical)
{
"id": "UUID",
"deviceID": "UUID",
"chargerID": "UUID?",
"idlePower": 0.25,
"stdDev": 0.02,
"energy": 0.5,
"sampleCount": 3600,
"measuredAt": "2026-05-23T10:45:00Z",
"confidence": 0.85,
"dataQuality": "good"
}
❌ individual measurements
❌ raw power samples
❌ voltage/current timeseries
❌ interval timestamps (only duration)
Reason: Storage efficiency
DeviceIdleProfile entityfunc shouldAutoTerminate(
session: ChargeSession,
currentPower: Double,
battery: Double
) -> Bool {
guard battery >= 99.0 else { return false }
let idle = loadIdleProfile(for: session.device)
let threshold = (idle?.idlePower ?? 0.5) * 1.5
return currentPower < threshold
}
func correctedDeviceEnergy(
total: Double,
idleProfile: IdleConsumptionProfile,
duration: TimeInterval
) -> Double {
let idleEnergy = idleProfile.idlePower * duration / 3600
return total - idleEnergy
}