Izolarea porţiunii relevante a curbei de încărcare: eliminarea zgomotului pre/post-charging.
Raw data from meter:
00:00 - 0.0W (pre-charge noise)
00:30 - 0.5W (background draw)
01:00 - 15.0W ← REAL START
01:30 - 18.5W
02:00 - 16.2W
...
05:00 - 2.5W (charging optimizer active)
05:30 - 1.0W
06:00 - 0.2W ← REAL END
06:15 - 0.0W (post-charge, device removed)
Sarcini: 1. Find real start (@ 01:00) 2. Find real end (@ 06:00) 3. Extract interval [01:00, 06:00] 4. Ignore pre/post noise
real_start_time > measurement_start_time (or equal)real_end_time <= measurement_end_time (or equal)real_start < real_endfunc findChargeStart(measurements: [Measurement]) -> Int? {
let powerThreshold = 5.0 // Watts
let minConsecutive = 3 // Measure must sustain > threshold for 3 samples
var sustainCount = 0
for (i, measurement) in measurements.enumerated() {
if measurement.power > powerThreshold {
sustainCount += 1
if sustainCount >= minConsecutive {
return i - minConsecutive + 1
}
} else {
sustainCount = 0
}
}
return nil
}
func findChargeEnd(measurements: [Measurement]) -> Int? {
let powerThreshold = 1.0 // Watts (lower at end, trickle charge)
let minConsecutive = 5 // Stay below threshold for 5 samples
var sustainCount = 0
for i in stride(from: measurements.count - 1, through: 0, by: -1) {
let measurement = measurements[i]
if measurement.power < powerThreshold {
sustainCount += 1
if sustainCount >= minConsecutive {
return i + 1
}
} else {
sustainCount = 0
}
}
return nil
}
Thresholds: - Start: 5W (must exceed noise level) - End: 1W (lower, allows trickle detection) - Sustain: 3 samples @start, 5 @end (hysteresis)
Dacă dispositiv raportează procent:
func findChargeStartByBattery(measurements: [Measurement]) -> Int? {
// Find first battery level jump
for i in 1..<measurements.count {
let prev = measurements[i-1]
let curr = measurements[i]
let batteryChange = (curr.batteryPercent ?? 0) - (prev.batteryPercent ?? 0)
if batteryChange > 1.0 { // >1% jump = real charge
return i - 1
}
}
return nil
}
func findChargeEndByBattery(measurements: [Measurement]) -> Int? {
// Find last battery level change
for i in stride(from: measurements.count - 1, through: 1, by: -1) {
let prev = measurements[i-1]
let curr = measurements[i]
let batteryChange = (curr.batteryPercent ?? 0) - (prev.batteryPercent ?? 0)
if batteryChange > 0.5 { // Still charging
return i
}
}
return nil
}
func isolateChargeRange(measurements: [Measurement]) -> (start: Int, end: Int)? {
// Primary: use power threshold
guard var start = findChargeStart(measurements: measurements),
var end = findChargeEnd(measurements: measurements) else {
// Fallback: use battery level
guard var start = findChargeStartByBattery(measurements: measurements),
var end = findChargeEndByBattery(measurements: measurements) else {
return nil
}
return (start, end)
}
// Validate result
guard start < end && (end - start) >= 10 else {
return nil // Too short (< 10 seconds @ 1Hz)
}
return (start, end)
}
// Detection
func findChargeStart(measurements: [Measurement]) -> Int?
func findChargeEnd(measurements: [Measurement]) -> Int?
func isolateChargeRange(measurements: [Measurement]) -> (start: Int, end: Int)?
// Extraction
func extractIsolatedCurve(measurements: [Measurement])
-> [Measurement]?
// Validation
func isValidChargeRange(start: Int, end: Int, total: Int) -> Bool
func estimateNoisePercentage(measurements: [Measurement]) -> Double
struct IsolatedChargeData {
let originalMeasurements: [Measurement]
let isolatedMeasurements: [Measurement]
let startIndex: Int
let endIndex: Int
let realStartTime: Date
let realEndTime: Date
let noisePercentagePre: Double
let noisePercentagePost: Double
var effectiveChargeTime: TimeInterval {
realEndTime.timeIntervalSince(realStartTime)
}
}
Problem: threshold prea mare
Threshold: 10W
Reality: charger 12W on iPhone 15
Result: detectează corect ✓
But: low-power charger 5W → missed (fails)
Problem: threshold prea mic
Threshold: 0.5W
Reality: background AC noise 1.5W
Result: detectează noise, invalid start (fails)
Solution: Adaptive threshold:
swift
let baselineNoise = measurements.prefix(10).map { $0.power }.max() ?? 0
let powerThreshold = baselineNoise * 1.5 + 2.0 // 50% margin + 2W
Charge duration: 30 seconds (e.g., quick top-up)
Measurements: 30 samples @ 1Hz
Noise isolation: 3 samples start + 5 samples end = 8 samples
Remaining: 22 samples (73% valid)
⟹ Valid
SHOULD: Accept short charges MUST: Still isolate pre/post
Pre-charge noise: 2W baseline (poor outlet)
Charge power: 18W
Power jump: 16W (obvious)
⟹ Easy to detect
Vs.
Pre-charge noise: 8W (strong interference)
Charge power: 10W
Power jump: 2W (ambiguous!)
⟹ Hard, need fallback (battery level)
SHOULD: Use combined method (power + battery) MUST: Fall back if ambiguous
Curve:
- 0-1h: 20W (fast charge phase)
- 1-2h: 10W (medium phase)
- 2-3h: 3W (taper phase)
- 3h+: 0.5W (trickle/done)
Isolated region: 0h-3h (valid)
Threshold: 1W (catches all phases)
⟹ Correct
MUST: Detect end even with tapering
test_findChargeStart_ValidThreshold()
test_findChargeStart_NoiseIgnored()
test_findChargeEnd_ValidThreshold()
test_findChargeEnd_WithTrickleCharge()
test_isolateChargeRange_ValidData()
test_isolateChargeRange_TooShortIgnored()
test_isolateChargeRange_HighNoise_FallbackToBattery()
test_isValidChargeRange_StartBeforeEnd()
test_isValidChargeRange_MinimumDuration()
test_estimateNoisePercentage()
test_extractIsolatedCurve_PreservesMeasurements()
Measurements: all ~0W
Start detection: fails (no power jump)
End detection: fails (no charging detected)
Result: (nil, nil)
⟹ Skip session (valid, no charging occurred)
MUST: Handle gracefully
Recording: 03:00-07:00 (4 hours)
Real charge: 01:00-06:00 (5 hours, but missed first 2h)
Measurements: 10W constant (already in charge phase)
Start: 03:00 (best guess)
End: 06:00 (detected from power drop)
⟹ Partial isolation OK
SHOULD: Use what data we have MAY: Mark as "incomplete start"
Time 00:00: Meter ON, no device
Time 00:10: Device plugged in, negotiation 100ms
Time 00:12: Charging starts (power jump 20W)
Samples: ?, ?, [19.5W, 19.8W, ...] ← isolated start
⟹ Correct
SHOULD: OK, negotiation time negligible