mirror of
https://github.com/AU-COVIDSafe/mobile-ios.git
synced 2025-04-19 13:05:21 +00:00
COVIDSafe code from version 2.0 (#37)
This commit is contained in:
parent
cf93ea43c0
commit
8b75c1fc6f
55 changed files with 4624 additions and 1117 deletions
50
CovidSafe/Herald/Sensor/Data/BatteryLog.swift
Normal file
50
CovidSafe/Herald/Sensor/Data/BatteryLog.swift
Normal file
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// BatteryLog.swift
|
||||
//
|
||||
// Copyright 2020 VMware, Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import NotificationCenter
|
||||
import os
|
||||
|
||||
/// Battery log for monitoring battery level over time
|
||||
class BatteryLog {
|
||||
private let logger = ConcreteSensorLogger(subsystem: "Sensor", category: "BatteryLog")
|
||||
private let textFile: TextFile
|
||||
private let dateFormatter = DateFormatter()
|
||||
private let updateInterval = TimeInterval(30)
|
||||
|
||||
init(filename: String) {
|
||||
textFile = TextFile(filename: filename)
|
||||
if textFile.empty() {
|
||||
textFile.write("time,source,level")
|
||||
}
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||
UIDevice.current.isBatteryMonitoringEnabled = true
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(batteryLevelDidChange), name: UIDevice.batteryLevelDidChangeNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(batteryStateDidChange), name: UIDevice.batteryStateDidChangeNotification, object: nil)
|
||||
let _ = Timer.scheduledTimer(timeInterval: updateInterval, target: self, selector: #selector(update), userInfo: nil, repeats: true)
|
||||
}
|
||||
|
||||
private func timestamp() -> String {
|
||||
let timestamp = dateFormatter.string(from: Date())
|
||||
return timestamp
|
||||
}
|
||||
|
||||
@objc func update() {
|
||||
let powerSource = (UIDevice.current.batteryState == .unplugged ? "battery" : "external")
|
||||
let batteryLevel = Float(UIDevice.current.batteryLevel * 100).description
|
||||
textFile.write(timestamp() + "," + powerSource + "," + batteryLevel)
|
||||
logger.debug("update (powerSource=\(powerSource),batteryLevel=\(batteryLevel))");
|
||||
}
|
||||
|
||||
@objc func batteryLevelDidChange(_ sender: NotificationCenter) {
|
||||
update()
|
||||
}
|
||||
|
||||
@objc func batteryStateDidChange(_ sender: NotificationCenter) {
|
||||
update()
|
||||
}
|
||||
}
|
57
CovidSafe/Herald/Sensor/Data/ContactLog.swift
Normal file
57
CovidSafe/Herald/Sensor/Data/ContactLog.swift
Normal file
|
@ -0,0 +1,57 @@
|
|||
//
|
||||
// ContactLog.swift
|
||||
//
|
||||
// Copyright 2020 VMware, Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// CSV contact log for post event analysis and visualisation
|
||||
class ContactLog: NSObject, SensorDelegate {
|
||||
private let textFile: TextFile
|
||||
private let dateFormatter = DateFormatter()
|
||||
|
||||
init(filename: String) {
|
||||
textFile = TextFile(filename: filename)
|
||||
if textFile.empty() {
|
||||
textFile.write("time,sensor,id,detect,read,measure,share,visit,data")
|
||||
}
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||
}
|
||||
|
||||
private func timestamp() -> String {
|
||||
let timestamp = dateFormatter.string(from: Date())
|
||||
return timestamp
|
||||
}
|
||||
|
||||
private func csv(_ value: String) -> String {
|
||||
return TextFile.csv(value)
|
||||
}
|
||||
|
||||
// MARK:- SensorDelegate
|
||||
|
||||
func sensor(_ sensor: SensorType, didDetect: TargetIdentifier) {
|
||||
textFile.write(timestamp() + "," + sensor.rawValue + "," + csv(didDetect) + ",1,,,,,")
|
||||
}
|
||||
|
||||
func sensor(_ sensor: SensorType, didRead: PayloadData, fromTarget: TargetIdentifier) {
|
||||
textFile.write(timestamp() + "," + sensor.rawValue + "," + csv(fromTarget) + ",,2,,,," + csv(didRead.shortName))
|
||||
}
|
||||
|
||||
func sensor(_ sensor: SensorType, didRead: PayloadData, fromTarget: TargetIdentifier, atProximity: Proximity, withTxPower: Int?) {
|
||||
textFile.write(timestamp() + "," + sensor.rawValue + "," + csv(fromTarget) + ",,2,,,," + csv(didRead.shortName))
|
||||
}
|
||||
|
||||
func sensor(_ sensor: SensorType, didMeasure: Proximity, fromTarget: TargetIdentifier) {
|
||||
textFile.write(timestamp() + "," + sensor.rawValue + "," + csv(fromTarget) + ",,,3,,," + csv(didMeasure.description))
|
||||
}
|
||||
|
||||
func sensor(_ sensor: SensorType, didShare: [PayloadData], fromTarget: TargetIdentifier, atProximity: Proximity) {
|
||||
let prefix = timestamp() + "," + sensor.rawValue + "," + csv(fromTarget)
|
||||
didShare.forEach() { payloadData in
|
||||
textFile.write(prefix + ",,,,4,," + csv(payloadData.shortName))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
88
CovidSafe/Herald/Sensor/Data/DetectionLog.swift
Normal file
88
CovidSafe/Herald/Sensor/Data/DetectionLog.swift
Normal file
|
@ -0,0 +1,88 @@
|
|||
//
|
||||
// DetectionLog.swift
|
||||
//
|
||||
// Copyright 2020 VMware, Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
/// CSV contact log for post event analysis and visualisation
|
||||
class DetectionLog: NSObject, SensorDelegate {
|
||||
private let logger = ConcreteSensorLogger(subsystem: "Sensor", category: "Data.DetectionLog")
|
||||
private let textFile: TextFile
|
||||
private let payloadData: PayloadData
|
||||
private let deviceName = UIDevice.current.name
|
||||
private let deviceOS = UIDevice.current.systemVersion
|
||||
private var payloads: Set<String> = []
|
||||
private let queue = DispatchQueue(label: "Sensor.Data.DetectionLog.Queue")
|
||||
|
||||
init(filename: String, payloadData: PayloadData) {
|
||||
textFile = TextFile(filename: filename)
|
||||
self.payloadData = payloadData
|
||||
super.init()
|
||||
write()
|
||||
}
|
||||
|
||||
private func csv(_ value: String) -> String {
|
||||
return TextFile.csv(value)
|
||||
}
|
||||
|
||||
private func write() {
|
||||
var content = "\(csv(deviceName)),iOS,\(csv(deviceOS)),\(csv(payloadData.shortName))"
|
||||
var payloadList: [String] = []
|
||||
payloads.forEach() { payload in
|
||||
guard payload != payloadData.shortName else {
|
||||
return
|
||||
}
|
||||
payloadList.append(payload)
|
||||
}
|
||||
payloadList.sort()
|
||||
payloadList.forEach() { payload in
|
||||
content.append(",")
|
||||
content.append(csv(payload))
|
||||
}
|
||||
logger.debug("write (content=\(content))")
|
||||
content.append("\n")
|
||||
textFile.overwrite(content)
|
||||
}
|
||||
|
||||
// MARK:- SensorDelegate
|
||||
|
||||
func sensor(_ sensor: SensorType, didDetect: TargetIdentifier) {
|
||||
}
|
||||
|
||||
func sensor(_ sensor: SensorType, didRead: PayloadData, fromTarget: TargetIdentifier) {
|
||||
queue.async {
|
||||
if self.payloads.insert(didRead.shortName).inserted {
|
||||
self.logger.debug("didRead (payload=\(didRead.shortName))")
|
||||
self.write()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sensor(_ sensor: SensorType, didRead: PayloadData, fromTarget: TargetIdentifier, atProximity: Proximity, withTxPower: Int?) {
|
||||
queue.async {
|
||||
if self.payloads.insert(didRead.shortName).inserted {
|
||||
self.logger.debug("didRead (payload=\(didRead.shortName))")
|
||||
self.write()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sensor(_ sensor: SensorType, didMeasure: Proximity, fromTarget: TargetIdentifier) {
|
||||
}
|
||||
|
||||
func sensor(_ sensor: SensorType, didShare: [PayloadData], fromTarget: TargetIdentifier, atProximity: Proximity) {
|
||||
didShare.forEach() { payloadData in
|
||||
queue.async {
|
||||
if self.payloads.insert(payloadData.shortName).inserted {
|
||||
self.logger.debug("didShare (payload=\(payloadData.shortName))")
|
||||
self.write()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
96
CovidSafe/Herald/Sensor/Data/SensorLogger.swift
Normal file
96
CovidSafe/Herald/Sensor/Data/SensorLogger.swift
Normal file
|
@ -0,0 +1,96 @@
|
|||
//
|
||||
// Logger.swift
|
||||
//
|
||||
// Copyright 2020 VMware, Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import os
|
||||
|
||||
protocol SensorLogger {
|
||||
init(subsystem: String, category: String)
|
||||
|
||||
func log(_ level: SensorLoggerLevel, _ message: String)
|
||||
|
||||
func debug(_ message: String)
|
||||
|
||||
func info(_ message: String)
|
||||
|
||||
func fault(_ message: String)
|
||||
}
|
||||
|
||||
enum SensorLoggerLevel: String {
|
||||
case debug, info, fault
|
||||
}
|
||||
|
||||
class ConcreteSensorLogger: NSObject, SensorLogger {
|
||||
private let subsystem: String
|
||||
private let category: String
|
||||
private let dateFormatter = DateFormatter()
|
||||
private let log: OSLog?
|
||||
private static let logFile = TextFile(filename: "log.txt")
|
||||
|
||||
required init(subsystem: String, category: String) {
|
||||
self.subsystem = subsystem
|
||||
self.category = category
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||
if #available(iOS 10.0, *) {
|
||||
log = OSLog(subsystem: subsystem, category: category)
|
||||
} else {
|
||||
log = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func suppress(_ level: SensorLoggerLevel) -> Bool {
|
||||
switch level {
|
||||
case .debug:
|
||||
return (BLESensorConfiguration.logLevel == .info || BLESensorConfiguration.logLevel == .fault);
|
||||
case .info:
|
||||
return (BLESensorConfiguration.logLevel == .fault);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
func log(_ level: SensorLoggerLevel, _ message: String) {
|
||||
guard !suppress(level) else {
|
||||
return
|
||||
}
|
||||
// Write to unified os log if available, else print to console
|
||||
let timestamp = dateFormatter.string(from: Date())
|
||||
let csvMessage = message.replacingOccurrences(of: "\"", with: "'")
|
||||
let quotedMessage = (message.contains(",") ? "\"" + csvMessage + "\"" : csvMessage)
|
||||
let entry = timestamp + "," + level.rawValue + "," + subsystem + "," + category + "," + quotedMessage
|
||||
ConcreteSensorLogger.logFile.write(entry)
|
||||
guard let log = log else {
|
||||
print(entry)
|
||||
return
|
||||
}
|
||||
if #available(iOS 10.0, *) {
|
||||
switch (level) {
|
||||
case .debug:
|
||||
os_log("%s", log: log, type: .debug, message)
|
||||
case .info:
|
||||
os_log("%s", log: log, type: .info, message)
|
||||
case .fault:
|
||||
os_log("%s", log: log, type: .fault, message)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func debug(_ message: String) {
|
||||
log(.debug, message)
|
||||
}
|
||||
|
||||
func info(_ message: String) {
|
||||
log(.debug, message)
|
||||
}
|
||||
|
||||
func fault(_ message: String) {
|
||||
log(.debug, message)
|
||||
}
|
||||
|
||||
}
|
95
CovidSafe/Herald/Sensor/Data/StatisticsLog.swift
Normal file
95
CovidSafe/Herald/Sensor/Data/StatisticsLog.swift
Normal file
|
@ -0,0 +1,95 @@
|
|||
//
|
||||
// StatisticsLog.swift
|
||||
//
|
||||
// Copyright 2020 VMware, Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// CSV contact log for post event analysis and visualisation
|
||||
class StatisticsLog: NSObject, SensorDelegate {
|
||||
private let textFile: TextFile
|
||||
private let payloadData: PayloadData
|
||||
private var identifierToPayload: [TargetIdentifier:String] = [:]
|
||||
private var payloadToTime: [String:Date] = [:]
|
||||
private var payloadToSample: [String:Sample] = [:]
|
||||
|
||||
init(filename: String, payloadData: PayloadData) {
|
||||
textFile = TextFile(filename: filename)
|
||||
self.payloadData = payloadData
|
||||
}
|
||||
|
||||
private func csv(_ value: String) -> String {
|
||||
return TextFile.csv(value)
|
||||
}
|
||||
|
||||
private func add(identifier: TargetIdentifier) {
|
||||
guard let payload = identifierToPayload[identifier] else {
|
||||
return
|
||||
}
|
||||
add(payload: payload)
|
||||
}
|
||||
|
||||
private func add(payload: String) {
|
||||
guard let time = payloadToTime[payload], let sample = payloadToSample[payload] else {
|
||||
payloadToTime[payload] = Date()
|
||||
payloadToSample[payload] = Sample()
|
||||
return
|
||||
}
|
||||
let now = Date()
|
||||
payloadToTime[payload] = now
|
||||
sample.add(Double(now.timeIntervalSince(time)))
|
||||
write()
|
||||
}
|
||||
|
||||
private func write() {
|
||||
var content = "payload,count,mean,sd,min,max\n"
|
||||
var payloadList: [String] = []
|
||||
payloadToSample.keys.forEach() { payload in
|
||||
guard payload != payloadData.shortName else {
|
||||
return
|
||||
}
|
||||
payloadList.append(payload)
|
||||
}
|
||||
payloadList.sort()
|
||||
payloadList.forEach() { payload in
|
||||
guard let sample = payloadToSample[payload] else {
|
||||
return
|
||||
}
|
||||
guard let mean = sample.mean, let sd = sample.standardDeviation, let min = sample.min, let max = sample.max else {
|
||||
return
|
||||
}
|
||||
content.append("\(csv(payload)),\(sample.count),\(mean),\(sd),\(min),\(max)\n")
|
||||
}
|
||||
textFile.overwrite(content)
|
||||
}
|
||||
|
||||
|
||||
// MARK:- SensorDelegate
|
||||
|
||||
func sensor(_ sensor: SensorType, didDetect: TargetIdentifier) {
|
||||
}
|
||||
|
||||
func sensor(_ sensor: SensorType, didRead: PayloadData, fromTarget: TargetIdentifier) {
|
||||
identifierToPayload[fromTarget] = didRead.shortName
|
||||
add(identifier: fromTarget)
|
||||
}
|
||||
|
||||
func sensor(_ sensor: SensorType, didRead: PayloadData, fromTarget: TargetIdentifier, atProximity: Proximity, withTxPower: Int?) {
|
||||
identifierToPayload[fromTarget] = didRead.shortName
|
||||
add(identifier: fromTarget)
|
||||
}
|
||||
|
||||
func sensor(_ sensor: SensorType, didMeasure: Proximity, fromTarget: TargetIdentifier) {
|
||||
add(identifier: fromTarget)
|
||||
}
|
||||
|
||||
func sensor(_ sensor: SensorType, didShare: [PayloadData], fromTarget: TargetIdentifier, atProximity: Proximity) {
|
||||
didShare.forEach() { payloadData in
|
||||
add(payload: payloadData.shortName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
71
CovidSafe/Herald/Sensor/Data/TextFile.swift
Normal file
71
CovidSafe/Herald/Sensor/Data/TextFile.swift
Normal file
|
@ -0,0 +1,71 @@
|
|||
//
|
||||
// TextFile.swift
|
||||
//
|
||||
// Copyright 2020 VMware, Inc.
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class TextFile {
|
||||
private let logger = ConcreteSensorLogger(subsystem: "Sensor", category: "Data.TextFile")
|
||||
private var file: URL?
|
||||
private let queue: DispatchQueue
|
||||
|
||||
init(filename: String) {
|
||||
file = try? FileManager.default
|
||||
.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
|
||||
.appendingPathComponent(filename)
|
||||
queue = DispatchQueue(label: "Sensor.Data.TextFile(\(filename))")
|
||||
}
|
||||
|
||||
func empty() -> Bool {
|
||||
guard let file = file else {
|
||||
return true
|
||||
}
|
||||
return !FileManager.default.fileExists(atPath: file.path)
|
||||
}
|
||||
|
||||
/// Append line to new or existing file
|
||||
func write(_ line: String) {
|
||||
queue.sync {
|
||||
guard let file = file else {
|
||||
return
|
||||
}
|
||||
guard let data = (line+"\n").data(using: .utf8) else {
|
||||
return
|
||||
}
|
||||
if FileManager.default.fileExists(atPath: file.path) {
|
||||
if let fileHandle = try? FileHandle(forWritingTo: file) {
|
||||
fileHandle.seekToEndOfFile()
|
||||
fileHandle.write(data)
|
||||
fileHandle.closeFile()
|
||||
}
|
||||
} else {
|
||||
try? data.write(to: file, options: .atomicWrite)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Overwrite file content
|
||||
func overwrite(_ content: String) {
|
||||
queue.sync {
|
||||
guard let file = file else {
|
||||
return
|
||||
}
|
||||
guard let data = content.data(using: .utf8) else {
|
||||
return
|
||||
}
|
||||
try? data.write(to: file, options: .atomicWrite)
|
||||
}
|
||||
}
|
||||
|
||||
/// Quote value for CSV output if required.
|
||||
static func csv(_ value: String) -> String {
|
||||
guard value.contains(",") || value.contains("\"") || value.contains("'") || value.contains("’") else {
|
||||
return value
|
||||
}
|
||||
return "\"" + value + "\""
|
||||
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue