Skip to content

Commit

Permalink
[STAD-596] Update Handling of Measurement Finished (#169)
Browse files Browse the repository at this point in the history
* Add new finished event after data storage finished

This is necessary, since now when the stopped event is received by the
app, the measurement inside the database is not necessarily stopped and
thus will not be eligeble for data synchronization.

* Remove copied redeclaration of Double equal function.

* Add new finished event after CoreData Storage completed

* Update Documentation for DefaultUploadProcess

* Actually use the finish handler of the CapturedDataStorage

No finish event as implemented in the last commit is necessary, since our API has an explicit handler on the CapturedDataStorage, that is called as soon as data storage has finished. This should be used to store everything to the database and carry out clean up code at the end of a measurement.

* Update RFR App with correct handling of finishing measurements

* Removed obsolete Sessions database from Resources

* Remove obsolete finished event from Message

* Fix after merge issues
  • Loading branch information
muthenberg authored Mar 25, 2024
1 parent 5b6bc2b commit b02aa8e
Show file tree
Hide file tree
Showing 17 changed files with 479 additions and 349 deletions.
1 change: 0 additions & 1 deletion DataCapturing/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ let package = Package(
],
exclude: ["Support/Info.plist"],
resources: [
.process("Synchronization/Upload/Background/SessionRegistry.xcdatamodeld"),
.process("Model/Migrations/V3toV4/V3toV4.xcmappingmodel"),
.process("Model/CyfaceModel.xcdatamodeld"),
.process("Model/Migrations/V10toV11/V10toV11.xcmappingmodel"),
Expand Down
29 changes: 16 additions & 13 deletions DataCapturing/Sources/DataCapturing/Capturing/Measurement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ This protocol defines a measurements data together with its lifecycle during dat
- Since: 12.0.0
*/
public protocol Measurement {
// TODO: It should not be possible to send messages via this variable. So this should be a publisher instead of a PasstroughSubject
/// A combine subject used to receive messages during data capturing and forwarding them, to whoever wants to listen.
var measurementMessages: PassthroughSubject<Message, Never> { get }
var measurementMessages: AnyPublisher<Message, Never> { get }
/// A flag to get information about whether this measurement is currently running (`true`) or not (`false`).
var isRunning: Bool { get }
/// A flag to get information about whether this measurement is currently paused (`true`) or not (`false`).
Expand Down Expand Up @@ -75,7 +76,6 @@ public class MeasurementImpl {
/// `true` if data capturing was running but is currently paused; `false` otherwise.
public var isPaused:Bool

// TODO: This should probably be carried out using an actor: See the talk "Protect mutable state with Swift actors" from WWDC 2021
/// The background queue used to capture data.
private let capturingQueue: DispatchQueue

Expand All @@ -85,15 +85,15 @@ public class MeasurementImpl {
private let locationCapturer: LocationCapturer

// TODO: Switch to Combine --> Make this a publisher on its own. Will have to read up on how to achieve this.
public var measurementMessages: PassthroughSubject<Message, Never>
public var messagesSubject: PassthroughSubject<Message, Never>

// TODO: This should probably be carried out using an actor: See the talk "Protect mutable state with Swift actors" from WWDC 2021
/**
A queue used to synchronize calls to the lifecycle methods `start`, `pause`, `resume` and `stop`.
Using such a queue prevents successiv calls to these methods to interrupt each other.
*/
private let lifecycleQueue: DispatchQueue
private var messageCancellable: AnyCancellable? = nil
private var finishedEventCancellable: AnyCancellable? = nil

// MARK: - Initializers

Expand All @@ -114,15 +114,14 @@ public class MeasurementImpl {
manager.activityType = .other
manager.showsBackgroundLocationIndicator = true
manager.distanceFilter = kCLDistanceFilterNone
//manager.requestAlwaysAuthorization()
return manager
}
) {
self.capturingQueue = capturingQueue
self.lifecycleQueue = DispatchQueue(label: "lifecycle")
self.sensorCapturer = SensorCapturer(capturingQueue: capturingQueue)
self.locationCapturer = LocationCapturer(lifecycleQueue: lifecycleQueue, locationManagerFactory: locationManagerFactory)
measurementMessages = PassthroughSubject<Message, Never>()
messagesSubject = PassthroughSubject<Message, Never>()

self.isRunning = false
self.isPaused = false
Expand Down Expand Up @@ -152,7 +151,7 @@ public class MeasurementImpl {

messageCancellable = locationCapturer.start().receive(on: lifecycleQueue).merge(
with: sensorCapturer.start()
).subscribe(measurementMessages)
).subscribe(messagesSubject)

self.isRunning = true
}
Expand All @@ -174,6 +173,10 @@ public class MeasurementImpl {
// MARK: - Measurement

extension MeasurementImpl: Measurement {
public var measurementMessages: AnyPublisher<Message, Never> {
return messagesSubject.eraseToAnyPublisher()
}

/**
Starts the capturing process.

Expand All @@ -194,7 +197,7 @@ Starting data capturing on paused service. Finishing paused measurements and sta
}

try startCapturing()
measurementMessages.send(.started(timestamp: Date()))
messagesSubject.send(.started(timestamp: Date()))
}
}

Expand All @@ -218,8 +221,8 @@ Starting data capturing on paused service. Finishing paused measurements and sta
stopCapturing()
isPaused = false

measurementMessages.send(.stopped(timestamp: Date()))
measurementMessages.send(completion: .finished)
messagesSubject.send(.stopped(timestamp: Date()))
messagesSubject.send(completion: .finished)
}
}

Expand All @@ -242,7 +245,7 @@ Starting data capturing on paused service. Finishing paused measurements and sta
stopCapturing()
isPaused = true

measurementMessages.send(.paused(timestamp: Date()))
messagesSubject.send(.paused(timestamp: Date()))
}
}

Expand All @@ -267,7 +270,7 @@ Starting data capturing on paused service. Finishing paused measurements and sta
isPaused = false
isRunning = true

measurementMessages.send(.resumed(timestamp: Date()))
messagesSubject.send(.resumed(timestamp: Date()))
}
}

Expand All @@ -279,7 +282,7 @@ Starting data capturing on paused service. Finishing paused measurements and sta
*/
public func changeModality(to modality: String) {
lifecycleQueue.sync {
measurementMessages.send(.modalityChanged(to: modality))
messagesSubject.send(.modalityChanged(to: modality))
}
}
}
Expand Down
36 changes: 36 additions & 0 deletions DataCapturing/Sources/DataCapturing/Capturing/Model/Message.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public enum Message: CustomStringConvertible {
/// The message sent if a new direction was captured.
case capturedDirection(SensorValue)
case started(timestamp: Date)
/// Sent after all sensor have stopped capturing data.
case stopped(timestamp: Date)
case paused(timestamp: Date)
case resumed(timestamp: Date)
Expand All @@ -77,3 +78,38 @@ public enum Message: CustomStringConvertible {
case modalityChanged(to: String)
case receivedNothingYet
}

extension Message: Equatable {
public static func == (lhs: Message, rhs: Message) -> Bool {
switch (lhs, rhs) {
case (.capturedLocation(let geoLocationLhs), .capturedLocation(let geoLocationRhs)):
return geoLocationLhs == geoLocationRhs
case (.capturedAltitude(let altitudeLhs), .capturedAltitude(let altitudeRhs)):
return altitudeLhs == altitudeRhs
case (.capturedAcceleration(let sensorValueLhs), .capturedAcceleration(let sensorValueRhs)):
return sensorValueLhs == sensorValueRhs
case (.capturedRotation(let sensorValueLhs), .capturedRotation(let sensorValueRhs)):
return sensorValueLhs == sensorValueRhs
case (.capturedDirection(let sensorValueLhs), .capturedDirection(let sensorValueRhs)):
return sensorValueLhs == sensorValueRhs
case (.started(let timestampLhs), .started(let timestampRhs)):
return timestampLhs == timestampRhs
case (.stopped(let timestampLhs), .stopped(let timestampRhs)):
return timestampLhs == timestampRhs
case (.paused(let timestampLhs), .paused(let timestampRhs)):
return timestampLhs == timestampRhs
case (.resumed(let timestampLhs), .resumed(let timestampRhs)):
return timestampLhs == timestampRhs
case (.hasFix, .hasFix):
return true
case (.fixLost, .fixLost):
return true
case (.modalityChanged(let toLhs), .modalityChanged(to: let toRhs)):
return toLhs == toRhs
case (.receivedNothingYet, .receivedNothingYet):
return true
default:
return false
}
}
}
13 changes: 13 additions & 0 deletions DataCapturing/Sources/DataCapturing/Model/Altitude.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import CoreData
A struct to wrap all the information associated with a measured altitude provided by an altimeter.

- Author: Klemens Muthmann
- version: 1.0.0
*/
public class Altitude: CustomStringConvertible {
/// The relative altitude change since the last measured value, in meters.
Expand All @@ -47,3 +48,15 @@ public class Altitude: CustomStringConvertible {
self.time = time
}
}

extension Altitude: Equatable {
public static func == (lhs: Altitude, rhs: Altitude) -> Bool {
if lhs===rhs {
return true
} else {
return lhs.relativeAltitude.equal(rhs.relativeAltitude, precise: 3) &&
lhs.pressure.equal(rhs.pressure, precise: 3) &&
lhs.time == rhs.time
}
}
}
22 changes: 22 additions & 0 deletions DataCapturing/Sources/DataCapturing/Model/Double.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// File.swift
//
//
// Created by Klemens Muthmann on 18.03.24.
//

import Foundation

public extension Double {
public func equal(_ value: Double, precise: Int) -> Bool {
let denominator: Double = pow(10.0, Double(precise))
let maxDiff: Double = 1 / denominator
let realDiff: Double = self - value

if fabs(realDiff) <= maxDiff {
return true
} else {
return false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public class FinishedMeasurement: Hashable, Equatable {
- parameter managedObject: The managed CoreData object to initialize this `Measurement` from.
- throws: `InconstantData.locationOrderViolation` if the timestamps of the locations in this measurement are not strongly monotonically increasing.
*/
convenience init(managedObject: MeasurementMO) throws {
public convenience init(managedObject: MeasurementMO) throws {
let accelerationFile = SensorValueFile(fileType: .accelerationValueType, qualifier: String(managedObject.unsignedIdentifier))
let directionFile = SensorValueFile(fileType: .directionValueType, qualifier: String(managedObject.unsignedIdentifier))
let rotationFile = SensorValueFile(fileType: .rotationValueType, qualifier: String(managedObject.unsignedIdentifier))
Expand Down
16 changes: 16 additions & 0 deletions DataCapturing/Sources/DataCapturing/Model/GeoLocation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,19 @@ public class GeoLocation: CustomStringConvertible {
return distance(from: CLLocation(latitude: previousLocation.latitude, longitude: previousLocation.longitude))
}
}

extension GeoLocation: Equatable {
public static func == (lhs: GeoLocation, rhs: GeoLocation) -> Bool {
if lhs === rhs {
return true
} else {
return lhs.latitude.equal(rhs.latitude, precise: 6) &&
lhs.longitude.equal(rhs.longitude, precise: 6) &&
lhs.speed.equal(rhs.speed, precise: 2) &&
lhs.accuracy.equal(rhs.accuracy, precise: 3) &&
lhs.time == rhs.time &&
lhs.altitude.equal(rhs.altitude, precise: 3) &&
lhs.verticalAccuracy.equal(rhs.verticalAccuracy, precise: 3)
}
}
}
Loading

0 comments on commit b02aa8e

Please sign in to comment.