USB-Meter / USB Meter / Model / Measurements.swift
Newer Older
185 lines | 5.528kb
Bogdan Timofte authored 2 weeks ago
1
//
2
//  Measurements.swift
3
//  USB Meter
4
//
5
//  Created by Bogdan Timofte on 07/05/2020.
6
//  Copyright © 2020 Bogdan Timofte. All rights reserved.
7
//
8

            
9
import Foundation
10
import CoreGraphics
11

            
12
class Measurements : ObservableObject {
13

            
14
    class Measurement : ObservableObject {
15
        struct Point : Identifiable , Hashable {
Bogdan Timofte authored a week ago
16
            enum Kind: Hashable {
17
                case sample
18
                case discontinuity
19
            }
20

            
Bogdan Timofte authored 2 weeks ago
21
            var id : Int
22
            var timestamp: Date
23
            var value: Double
Bogdan Timofte authored a week ago
24
            var kind: Kind = .sample
25

            
26
            var isSample: Bool {
27
                kind == .sample
28
            }
29

            
30
            var isDiscontinuity: Bool {
31
                kind == .discontinuity
32
            }
33

            
Bogdan Timofte authored 2 weeks ago
34
            func point() -> CGPoint {
35
                return CGPoint(x: timestamp.timeIntervalSince1970, y: value)
36
            }
37
        }
38

            
39
        var points: [Point] = []
40
        var context = ChartContext()
41

            
Bogdan Timofte authored a week ago
42
        var samplePoints: [Point] {
43
            points.filter { $0.isSample }
44
        }
45

            
46
        private func rebuildContext() {
47
            context.reset()
48
            for point in points where point.isSample {
49
                context.include(point: point.point())
50
            }
51
        }
52

            
53
        private func appendPoint(timestamp: Date, value: Double, kind: Point.Kind) {
54
            let newPoint = Measurement.Point(id: points.count, timestamp: timestamp, value: value, kind: kind)
55
            points.append(newPoint)
56
            if newPoint.isSample {
57
                context.include(point: newPoint.point())
58
            }
59
            self.objectWillChange.send()
60
        }
61

            
Bogdan Timofte authored 2 weeks ago
62
        func removeValue(index: Int) {
63
            points.remove(at: index)
Bogdan Timofte authored a week ago
64
            for index in points.indices {
65
                points[index].id = index
Bogdan Timofte authored 2 weeks ago
66
            }
Bogdan Timofte authored a week ago
67
            rebuildContext()
Bogdan Timofte authored 2 weeks ago
68
            self.objectWillChange.send()
69
        }
70

            
71
        func addPoint(timestamp: Date, value: Double) {
Bogdan Timofte authored a week ago
72
            appendPoint(timestamp: timestamp, value: value, kind: .sample)
73
        }
74

            
75
        func addDiscontinuity(timestamp: Date) {
76
            guard !points.isEmpty else { return }
77
            guard points.last?.isDiscontinuity == false else { return }
78
            appendPoint(timestamp: timestamp, value: points.last?.value ?? 0, kind: .discontinuity)
Bogdan Timofte authored 2 weeks ago
79
        }
80

            
Bogdan Timofte authored a week ago
81
        func resetSeries() {
Bogdan Timofte authored 2 weeks ago
82
            points.removeAll()
83
            context.reset()
84
            self.objectWillChange.send()
85
        }
Bogdan Timofte authored 2 weeks ago
86

            
87
        func trim(before cutoff: Date) {
88
            points = points
89
                .filter { $0.timestamp >= cutoff }
90
                .enumerated()
91
                .map { index, point in
Bogdan Timofte authored a week ago
92
                    Measurement.Point(id: index, timestamp: point.timestamp, value: point.value, kind: point.kind)
Bogdan Timofte authored 2 weeks ago
93
                }
Bogdan Timofte authored a week ago
94
            rebuildContext()
Bogdan Timofte authored 2 weeks ago
95
            self.objectWillChange.send()
96
        }
Bogdan Timofte authored 2 weeks ago
97
    }
98

            
99
    @Published var power = Measurement()
100
    @Published var voltage = Measurement()
101
    @Published var current = Measurement()
102

            
Bogdan Timofte authored a week ago
103
    private var pendingBucketSecond: Int?
104
    private var pendingBucketTimestamp: Date?
Bogdan Timofte authored 2 weeks ago
105

            
106
    private var itemsInSum: Double = 0
107
    private var powerSum: Double = 0
108
    private var voltageSum: Double = 0
109
    private var currentSum: Double = 0
Bogdan Timofte authored 2 weeks ago
110

            
Bogdan Timofte authored a week ago
111
    private func resetPendingAggregation() {
112
        pendingBucketSecond = nil
113
        pendingBucketTimestamp = nil
Bogdan Timofte authored 2 weeks ago
114
        itemsInSum = 0
115
        powerSum = 0
116
        voltageSum = 0
117
        currentSum = 0
Bogdan Timofte authored a week ago
118
    }
119

            
120
    private func flushPendingValues() {
121
        guard let pendingBucketTimestamp, itemsInSum > 0 else { return }
122
        self.power.addPoint(timestamp: pendingBucketTimestamp, value: powerSum / itemsInSum)
123
        self.voltage.addPoint(timestamp: pendingBucketTimestamp, value: voltageSum / itemsInSum)
124
        self.current.addPoint(timestamp: pendingBucketTimestamp, value: currentSum / itemsInSum)
125
        resetPendingAggregation()
Bogdan Timofte authored 2 weeks ago
126
        self.objectWillChange.send()
127
    }
Bogdan Timofte authored a week ago
128

            
129
    func resetSeries() {
130
        power.resetSeries()
131
        voltage.resetSeries()
132
        current.resetSeries()
133
        resetPendingAggregation()
134
        self.objectWillChange.send()
135
    }
136

            
137
    func reset() {
138
        resetSeries()
139
    }
Bogdan Timofte authored 2 weeks ago
140

            
141
    func remove(at idx: Int) {
142
        power.removeValue(index: idx)
143
        voltage.removeValue(index: idx)
144
        current.removeValue(index: idx)
145
        self.objectWillChange.send()
146
    }
147

            
Bogdan Timofte authored 2 weeks ago
148
    func trim(before cutoff: Date) {
Bogdan Timofte authored a week ago
149
        flushPendingValues()
Bogdan Timofte authored 2 weeks ago
150
        power.trim(before: cutoff)
151
        voltage.trim(before: cutoff)
152
        current.trim(before: cutoff)
153
        self.objectWillChange.send()
154
    }
155

            
Bogdan Timofte authored 2 weeks ago
156
    func addValues(timestamp: Date, power: Double, voltage: Double, current: Double) {
157
        let valuesTimestamp = timestamp.timeIntervalSinceReferenceDate.intValue
Bogdan Timofte authored a week ago
158

            
159
        if pendingBucketSecond == valuesTimestamp {
160
            pendingBucketTimestamp = timestamp
Bogdan Timofte authored 2 weeks ago
161
            itemsInSum += 1
Bogdan Timofte authored 2 weeks ago
162
            powerSum += power
Bogdan Timofte authored 2 weeks ago
163
            voltageSum += voltage
164
            currentSum += current
Bogdan Timofte authored a week ago
165
            return
Bogdan Timofte authored 2 weeks ago
166
        }
Bogdan Timofte authored a week ago
167

            
168
        flushPendingValues()
169

            
170
        pendingBucketSecond = valuesTimestamp
171
        pendingBucketTimestamp = timestamp
172
        itemsInSum = 1
173
        powerSum = power
174
        voltageSum = voltage
175
        currentSum = current
176
    }
177

            
178
    func markDiscontinuity(at timestamp: Date) {
179
        flushPendingValues()
180
        power.addDiscontinuity(timestamp: timestamp)
181
        voltage.addDiscontinuity(timestamp: timestamp)
182
        current.addDiscontinuity(timestamp: timestamp)
183
        self.objectWillChange.send()
Bogdan Timofte authored 2 weeks ago
184
    }
185
}