From 7a44316a30e18440e24f4255d8b45e3a70353b70 Mon Sep 17 00:00:00 2001 From: Jesse Johnston Date: Sat, 3 Sep 2022 11:43:47 -0700 Subject: [PATCH 1/9] Set Prediction UART message --- .../BleData/PredictionMode.swift | 34 ++++++++++++++++ Sources/CombustionBLE/UART/MessageType.swift | 1 + Sources/CombustionBLE/UART/Response.swift | 2 + .../CombustionBLE/UART/SetPrediction.swift | 40 +++++++++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 Sources/CombustionBLE/BleData/PredictionMode.swift create mode 100644 Sources/CombustionBLE/UART/SetPrediction.swift diff --git a/Sources/CombustionBLE/BleData/PredictionMode.swift b/Sources/CombustionBLE/BleData/PredictionMode.swift new file mode 100644 index 0000000..3c51f0a --- /dev/null +++ b/Sources/CombustionBLE/BleData/PredictionMode.swift @@ -0,0 +1,34 @@ +// PredictionMode.swift + +/*-- +MIT License + +Copyright (c) 2021 Combustion Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +--*/ + +import Foundation + +public enum PredictionMode: UInt8, CaseIterable { + case NONE = 0x00 + case TIME_TO_REMOVAL = 0x01 + case REMOVAL_AND_RESTING = 0x02 + case RESERVED = 0x03 +} diff --git a/Sources/CombustionBLE/UART/MessageType.swift b/Sources/CombustionBLE/UART/MessageType.swift index 5622069..790207d 100644 --- a/Sources/CombustionBLE/UART/MessageType.swift +++ b/Sources/CombustionBLE/UART/MessageType.swift @@ -31,4 +31,5 @@ enum MessageType: UInt8 { case SetColor = 2 case SessionInfo = 3 case Log = 4 + case SetPrediction = 5 } diff --git a/Sources/CombustionBLE/UART/Response.swift b/Sources/CombustionBLE/UART/Response.swift index aa404f5..1cd0249 100644 --- a/Sources/CombustionBLE/UART/Response.swift +++ b/Sources/CombustionBLE/UART/Response.swift @@ -126,6 +126,8 @@ extension Response { return SetColorResponse(success: success, payLoadLength: Int(payloadLength)) case .SessionInfo: return SessionInfoResponse.fromRaw(data: data, success: success, payloadLength: Int(payloadLength)) + case .SetPrediction: + return SetPredictionResponse(success: success, payLoadLength: Int(payloadLength)) } } } diff --git a/Sources/CombustionBLE/UART/SetPrediction.swift b/Sources/CombustionBLE/UART/SetPrediction.swift new file mode 100644 index 0000000..28787e6 --- /dev/null +++ b/Sources/CombustionBLE/UART/SetPrediction.swift @@ -0,0 +1,40 @@ +// SetPrediction.swift + +/*-- +MIT License + +Copyright (c) 2021 Combustion Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +--*/ + +import Foundation + +class SetPredictionRequest: Request { + init(setPointCelsius: Float, mode: PredictionMode) { + let rawSetPoint = UInt16(setPointCelsius / 0.1) + var rawPayload = (UInt16(mode.rawValue) << 10) | (rawSetPoint & 0x3FF) + + let payload = Data(bytes: &rawPayload, count: MemoryLayout.size(ofValue: rawPayload)) + + super.init(payload: payload, type: .SetPrediction) + } +} + +class SetPredictionResponse : Response { } From fb34f5f424a6631820051fa9b465ab74311beb04 Mon Sep 17 00:00:00 2001 From: Jesse Johnston Date: Wed, 7 Sep 2022 21:07:47 -0700 Subject: [PATCH 2/9] Decode Hop count. Rename device status to probe status --- .../BleData/AdvertisingData.swift | 36 ++++++++------- .../CombustionBLE/BleData/BatteryStatus.swift | 6 +-- Sources/CombustionBLE/BleData/HopCount.swift | 44 +++++++++++++++++++ .../CombustionBLE/BleData/ProbeColor.swift | 6 +-- Sources/CombustionBLE/BleData/ProbeID.swift | 6 +-- Sources/CombustionBLE/BleData/ProbeMode.swift | 6 +-- .../{DeviceStatus.swift => ProbeStatus.swift} | 40 +++++++++-------- Sources/CombustionBLE/BleManager.swift | 4 +- Sources/CombustionBLE/DeviceManager.swift | 6 ++- .../CombustionBLE/LoggedProbeDataPoint.swift | 4 +- Sources/CombustionBLE/Probe.swift | 4 +- Sources/CombustionBLE/SimulatedProbe.swift | 7 +-- 12 files changed, 109 insertions(+), 60 deletions(-) create mode 100644 Sources/CombustionBLE/BleData/HopCount.swift rename Sources/CombustionBLE/BleData/{DeviceStatus.swift => ProbeStatus.swift} (70%) diff --git a/Sources/CombustionBLE/BleData/AdvertisingData.swift b/Sources/CombustionBLE/BleData/AdvertisingData.swift index e37a97c..b05f86d 100644 --- a/Sources/CombustionBLE/BleData/AdvertisingData.swift +++ b/Sources/CombustionBLE/BleData/AdvertisingData.swift @@ -34,21 +34,23 @@ public enum CombustionProductType: UInt8 { } /// Struct containing advertising data received from device. -public struct AdvertisingData { +struct AdvertisingData { /// Type of Combustion product - public let type: CombustionProductType + let type: CombustionProductType /// Product serial number - public let serialNumber: UInt32 + let serialNumber: UInt32 /// Latest temperatures read by device - public let temperatures: ProbeTemperatures + let temperatures: ProbeTemperatures /// Prode ID - public let id: ProbeID + let id: ProbeID /// Probe Color - public let color: ProbeColor + let color: ProbeColor /// Probe mode - public let mode: ProbeMode + let mode: ProbeMode /// Battery Status - public let batteryStatus: BatteryStatus + let batteryStatus: BatteryStatus + /// Hop Count + let hopCount: HopCount private enum Constants { // Locations of data in advertising packets @@ -56,7 +58,7 @@ public struct AdvertisingData { static let SERIAL_RANGE = 3..<7 static let TEMPERATURE_RANGE = 7..<20 static let MODE_COLOR_ID_RANGE = 20..<21 - static let BATTERY_STATUS_RANGE = 21..<22 + static let DEVICE_STATUS_RANGE = 21..<22 } } @@ -93,10 +95,10 @@ extension AdvertisingData { // Decode Probe ID and Color if its present in the advertising packet if(data.count >= 21) { - let modeIdColorData = data.subdata(in: Constants.MODE_COLOR_ID_RANGE) - id = ProbeID.fromRawData(data: modeIdColorData) - color = ProbeColor.fromRawData(data: modeIdColorData) - mode = ProbeMode.fromRawData(data: modeIdColorData) + let modeIdColorByte = data.subdata(in: Constants.MODE_COLOR_ID_RANGE)[0] + id = ProbeID.from(modeIdColorByte: modeIdColorByte) + color = ProbeColor.from(modeIdColorByte: modeIdColorByte) + mode = ProbeMode.from(modeIdColorByte: modeIdColorByte) } else { id = .ID1 @@ -106,11 +108,13 @@ extension AdvertisingData { // Decode battery status if its present in the advertising packet if(data.count >= 22) { - let statusData = data.subdata(in: Constants.BATTERY_STATUS_RANGE) - batteryStatus = BatteryStatus.fromRawData(data: statusData) + let deviceStatusByte = data.subdata(in: Constants.DEVICE_STATUS_RANGE)[0] + batteryStatus = BatteryStatus.from(deviceStatusByte: deviceStatusByte) + hopCount = HopCount.from(deviceStatusByte: deviceStatusByte) } else { batteryStatus = .OK + hopCount = .HOP1 } } } @@ -126,6 +130,7 @@ extension AdvertisingData { color = .COLOR1 mode = .Normal batteryStatus = .OK + hopCount = .HOP1 } // Fake data initializer for Simulated Probe @@ -137,5 +142,6 @@ extension AdvertisingData { color = .COLOR1 mode = .Normal batteryStatus = .OK + hopCount = .HOP1 } } diff --git a/Sources/CombustionBLE/BleData/BatteryStatus.swift b/Sources/CombustionBLE/BleData/BatteryStatus.swift index 3c8d5f8..64e8bda 100644 --- a/Sources/CombustionBLE/BleData/BatteryStatus.swift +++ b/Sources/CombustionBLE/BleData/BatteryStatus.swift @@ -34,10 +34,8 @@ public enum BatteryStatus: UInt8 { static let BATTERY_MASK: UInt8 = 0x3 } - static func fromRawData(data: Data) -> BatteryStatus { - let statusBytes = [UInt8](data) - - let rawStatus = (statusBytes[0] & (Constants.BATTERY_MASK)) + static func from(deviceStatusByte: UInt8) -> BatteryStatus { + let rawStatus = (deviceStatusByte & (Constants.BATTERY_MASK)) return BatteryStatus(rawValue: rawStatus) ?? .OK } } diff --git a/Sources/CombustionBLE/BleData/HopCount.swift b/Sources/CombustionBLE/BleData/HopCount.swift new file mode 100644 index 0000000..7c08bda --- /dev/null +++ b/Sources/CombustionBLE/BleData/HopCount.swift @@ -0,0 +1,44 @@ +// HopCount.swift + +/*-- +MIT License + +Copyright (c) 2022 Combustion Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +--*/ + +import Foundation + +public enum HopCount: UInt8, CaseIterable { + case HOP1 = 0x00 + case HOP2 = 0x01 + case HOP3 = 0x02 + case HOP4 = 0x03 + + private enum Constants { + static let HOP_COUNT_MASK: UInt8 = 0x3 + static let HOP_COUNT_SHIFT: UInt8 = 6 + } + + static func from(deviceStatusByte: UInt8) -> HopCount { + let rawHopCount = (deviceStatusByte >> Constants.HOP_COUNT_SHIFT) & Constants.HOP_COUNT_MASK + return HopCount(rawValue: rawHopCount) ?? .HOP1 + } +} diff --git a/Sources/CombustionBLE/BleData/ProbeColor.swift b/Sources/CombustionBLE/BleData/ProbeColor.swift index 860ed3d..be106e2 100644 --- a/Sources/CombustionBLE/BleData/ProbeColor.swift +++ b/Sources/CombustionBLE/BleData/ProbeColor.swift @@ -41,10 +41,8 @@ public enum ProbeColor: UInt8, CaseIterable { static let PRODE_COLOR_SHIFT: UInt8 = 2 } - static func fromRawData(data: Data) -> ProbeColor { - let modeIdColorBytes = [UInt8](data) - - let rawProbeColor = (modeIdColorBytes[0] & (Constants.PRODE_COLOR_MASK << Constants.PRODE_COLOR_SHIFT)) >> Constants.PRODE_COLOR_SHIFT + static func from(modeIdColorByte: UInt8) -> ProbeColor { + let rawProbeColor = (modeIdColorByte >> Constants.PRODE_COLOR_SHIFT) & Constants.PRODE_COLOR_MASK return ProbeColor(rawValue: rawProbeColor) ?? .COLOR1 } } diff --git a/Sources/CombustionBLE/BleData/ProbeID.swift b/Sources/CombustionBLE/BleData/ProbeID.swift index 742ad93..b6fd9cd 100644 --- a/Sources/CombustionBLE/BleData/ProbeID.swift +++ b/Sources/CombustionBLE/BleData/ProbeID.swift @@ -41,10 +41,8 @@ public enum ProbeID: UInt8, CaseIterable { static let PRODE_ID_SHIFT: UInt8 = 5 } - static func fromRawData(data: Data) -> ProbeID { - let modeIdColorBytes = [UInt8](data) - - let rawProbeID = (modeIdColorBytes[0] & (Constants.PRODE_ID_MASK << Constants.PRODE_ID_SHIFT)) >> Constants.PRODE_ID_SHIFT + static func from(modeIdColorByte: UInt8) -> ProbeID { + let rawProbeID = (modeIdColorByte >> Constants.PRODE_ID_SHIFT ) & Constants.PRODE_ID_MASK return ProbeID(rawValue: rawProbeID) ?? .ID1 } } diff --git a/Sources/CombustionBLE/BleData/ProbeMode.swift b/Sources/CombustionBLE/BleData/ProbeMode.swift index ddb0728..c801012 100644 --- a/Sources/CombustionBLE/BleData/ProbeMode.swift +++ b/Sources/CombustionBLE/BleData/ProbeMode.swift @@ -36,10 +36,8 @@ public enum ProbeMode: UInt8, CaseIterable { static let PRODE_MODE_MASK: UInt8 = 0x3 } - static func fromRawData(data: Data) -> ProbeMode { - let modeIdColorBytes = [UInt8](data) - - let rawProbeID = modeIdColorBytes[0] & (Constants.PRODE_MODE_MASK) + static func from(modeIdColorByte: UInt8) -> ProbeMode { + let rawProbeID = modeIdColorByte & (Constants.PRODE_MODE_MASK) return ProbeMode(rawValue: rawProbeID) ?? .Normal } } diff --git a/Sources/CombustionBLE/BleData/DeviceStatus.swift b/Sources/CombustionBLE/BleData/ProbeStatus.swift similarity index 70% rename from Sources/CombustionBLE/BleData/DeviceStatus.swift rename to Sources/CombustionBLE/BleData/ProbeStatus.swift index 7d10d31..e4c2871 100644 --- a/Sources/CombustionBLE/BleData/DeviceStatus.swift +++ b/Sources/CombustionBLE/BleData/ProbeStatus.swift @@ -27,21 +27,23 @@ SOFTWARE. import Foundation /// Message containing Probe status information. -public struct DeviceStatus { +struct ProbeStatus { /// Minimum sequence number of records in Probe's memory. - public let minSequenceNumber: UInt32 + let minSequenceNumber: UInt32 /// Maximum sequence number of records in Probe's memory. - public let maxSequenceNumber: UInt32 + let maxSequenceNumber: UInt32 /// Current temperatures sent by Probe. - public let temperatures: ProbeTemperatures + let temperatures: ProbeTemperatures /// Prode ID - public let id: ProbeID + let id: ProbeID /// Probe Color - public let color: ProbeColor + let color: ProbeColor /// Probe mode - public let mode: ProbeMode + let mode: ProbeMode /// Battery Status - public let batteryStatus: BatteryStatus + let batteryStatus: BatteryStatus + /// Hop Count + let hopCount: HopCount private enum Constants { // Locations of data in status packet @@ -49,11 +51,11 @@ public struct DeviceStatus { static let MAX_SEQ_RANGE = 4..<8 static let TEMPERATURE_RANGE = 8..<21 static let MODE_COLOR_ID_RANGE = 21..<22 - static let BATTERY_STATUS_RANGE = 22..<23 + static let DEVICE_STATUS_RANGE = 22..<23 } } -extension DeviceStatus { +extension ProbeStatus { init?(fromData data: Data) { guard data.count >= 21 else { return nil } @@ -71,12 +73,12 @@ extension DeviceStatus { let tempData = data.subdata(in: Constants.TEMPERATURE_RANGE) temperatures = ProbeTemperatures.fromRawData(data: tempData) - // Decode Probe ID and Color if its present in the advertising packet + // Decode Probe ID and Color if its present in the status notification if(data.count >= 22) { - let modeIdColorData = data.subdata(in: Constants.MODE_COLOR_ID_RANGE) - id = ProbeID.fromRawData(data: modeIdColorData) - color = ProbeColor.fromRawData(data: modeIdColorData) - mode = ProbeMode.fromRawData(data: modeIdColorData) + let modeIdColorByte = data.subdata(in: Constants.MODE_COLOR_ID_RANGE)[0] + id = ProbeID.from(modeIdColorByte: modeIdColorByte) + color = ProbeColor.from(modeIdColorByte: modeIdColorByte) + mode = ProbeMode.from(modeIdColorByte: modeIdColorByte) } else { id = .ID1 @@ -84,13 +86,15 @@ extension DeviceStatus { mode = .Normal } - // Decode battery status if its present in the advertising packet + // Decode battery status if its present in the status notification if(data.count >= 23) { - let statusData = data.subdata(in: Constants.BATTERY_STATUS_RANGE) - batteryStatus = BatteryStatus.fromRawData(data: statusData) + let deviceStatusByte = data.subdata(in: Constants.DEVICE_STATUS_RANGE)[0] + batteryStatus = BatteryStatus.from(deviceStatusByte: deviceStatusByte) + hopCount = HopCount.from(deviceStatusByte: deviceStatusByte) } else { batteryStatus = .OK + hopCount = .HOP1 } } } diff --git a/Sources/CombustionBLE/BleManager.swift b/Sources/CombustionBLE/BleManager.swift index 2985565..34bebd8 100644 --- a/Sources/CombustionBLE/BleManager.swift +++ b/Sources/CombustionBLE/BleManager.swift @@ -39,7 +39,7 @@ protocol BleManagerDelegate: AnyObject { func updateDeviceWithAdvertising(advertising: AdvertisingData, isConnectable: Bool, rssi: NSNumber, identifier: UUID) func updateDeviceWithLogResponse(identifier: UUID, logResponse: LogResponse) func updateDeviceWithSessionInformation(identifier: UUID, sessionInformation: SessionInformation) - func updateDeviceWithStatus(identifier: UUID, status: DeviceStatus) + func updateDeviceWithStatus(identifier: UUID, status: ProbeStatus) func updateDeviceFwVersion(identifier: UUID, fwVersion: String) func updateDeviceHwRevision(identifier: UUID, hwRevision: String) } @@ -258,7 +258,7 @@ extension BleManager: CBPeripheralDelegate { handleUartData(data: data, identifier: peripheral.identifier) } else if characteristic.uuid == Constants.DEVICE_STATUS_CHAR { - if let status = DeviceStatus(fromData: data) { + if let status = ProbeStatus(fromData: data) { delegate?.updateDeviceWithStatus(identifier: peripheral.identifier, status: status) } } diff --git a/Sources/CombustionBLE/DeviceManager.swift b/Sources/CombustionBLE/DeviceManager.swift index fc51ef5..d8a641b 100644 --- a/Sources/CombustionBLE/DeviceManager.swift +++ b/Sources/CombustionBLE/DeviceManager.swift @@ -163,7 +163,9 @@ public class DeviceManager : ObservableObject { public func setProbeColor(_ device: Device, color: ProbeColor, completionHandler: @escaping (Bool) -> Void) { setColorCompetionHandlers[device.identifier] = MessageHandler(timeSent: Date(), handler: completionHandler) - let request = SetColorRequest(color: color) + // TODO JDJ DO not commit +// let request = SetColorRequest(color: color) + let request = SetPredictionRequest(setPointCelsius: 67.8, mode: .REMOVAL_AND_RESTING) BleManager.shared.sendRequest(identifier: device.identifier, request: request) } @@ -222,7 +224,7 @@ extension DeviceManager : BleManagerDelegate { setIDCompetionHandlers.removeValue(forKey: identifier.uuidString) } - func updateDeviceWithStatus(identifier: UUID, status: DeviceStatus) { + func updateDeviceWithStatus(identifier: UUID, status: ProbeStatus) { if let probe = devices[identifier.uuidString] as? Probe { probe.updateProbeStatus(deviceStatus: status) } diff --git a/Sources/CombustionBLE/LoggedProbeDataPoint.swift b/Sources/CombustionBLE/LoggedProbeDataPoint.swift index 4d9b107..28987f6 100644 --- a/Sources/CombustionBLE/LoggedProbeDataPoint.swift +++ b/Sources/CombustionBLE/LoggedProbeDataPoint.swift @@ -35,8 +35,8 @@ public struct LoggedProbeDataPoint: Equatable { extension LoggedProbeDataPoint { /// Generates a LoggedProbeDataPoint from a previously-parsed DeviceStatus record. - /// - parameter deviceStatus: DeviceStatus instance - static func fromDeviceStatus(deviceStatus: DeviceStatus) -> LoggedProbeDataPoint { + /// - parameter ProbeStatus: ProbeStatus instance + static func fromDeviceStatus(deviceStatus: ProbeStatus) -> LoggedProbeDataPoint { return LoggedProbeDataPoint(sequenceNum: deviceStatus.maxSequenceNumber, temperatures: deviceStatus.temperatures) } diff --git a/Sources/CombustionBLE/Probe.swift b/Sources/CombustionBLE/Probe.swift index 6908dfc..4b790a4 100644 --- a/Sources/CombustionBLE/Probe.swift +++ b/Sources/CombustionBLE/Probe.swift @@ -70,7 +70,7 @@ public class Probe : Device { /// Time at which probe instant read was last updated internal var lastInstantRead: Date? - public init(_ advertising: AdvertisingData, isConnectable: Bool, RSSI: NSNumber, identifier: UUID) { + init(_ advertising: AdvertisingData, isConnectable: Bool, RSSI: NSNumber, identifier: UUID) { serialNumber = advertising.serialNumber id = advertising.id color = advertising.color @@ -134,7 +134,7 @@ extension Probe { } /// Updates the Device based on newly-received DeviceStatus message. Requests missing records. - func updateProbeStatus(deviceStatus: DeviceStatus) { + func updateProbeStatus(deviceStatus: ProbeStatus) { minSequenceNumber = deviceStatus.minSequenceNumber maxSequenceNumber = deviceStatus.maxSequenceNumber id = deviceStatus.id diff --git a/Sources/CombustionBLE/SimulatedProbe.swift b/Sources/CombustionBLE/SimulatedProbe.swift index 810e877..c01c45e 100644 --- a/Sources/CombustionBLE/SimulatedProbe.swift +++ b/Sources/CombustionBLE/SimulatedProbe.swift @@ -81,14 +81,15 @@ class SimulatedProbe: Probe { lastSequence = 0 } - let deviceStatus = DeviceStatus(minSequenceNumber: firstSeq, + let probeStatus = ProbeStatus(minSequenceNumber: firstSeq, maxSequenceNumber: lastSequence, temperatures: ProbeTemperatures.withRandomData(), id: .ID1, color: .COLOR1, mode: .Normal, - batteryStatus: .OK) + batteryStatus: .OK, + hopCount: .HOP1) - updateProbeStatus(deviceStatus: deviceStatus) + updateProbeStatus(deviceStatus: probeStatus) } } From 828d595f8595c8a0632a496e9f8b6794bb85b782 Mon Sep 17 00:00:00 2001 From: Jesse Johnston Date: Fri, 23 Sep 2022 16:28:56 -0700 Subject: [PATCH 3/9] Refactor BLE data objects --- .../BleData/AdvertisingData.swift | 56 ++++------- ...wift => BatteryStatusVirtualSensors.swift} | 31 ++++++- .../{ProbeColor.swift => ModeId.swift} | 57 +++++++++++- Sources/CombustionBLE/BleData/ProbeID.swift | 48 ---------- Sources/CombustionBLE/BleData/ProbeMode.swift | 43 --------- .../CombustionBLE/BleData/ProbeStatus.swift | 43 +++------ .../BleData/VirtualSensors.swift | 92 +++++++++++++++++++ Sources/CombustionBLE/Probe.swift | 24 ++--- Sources/CombustionBLE/SimulatedProbe.swift | 7 +- 9 files changed, 217 insertions(+), 184 deletions(-) rename Sources/CombustionBLE/BleData/{BatteryStatus.swift => BatteryStatusVirtualSensors.swift} (58%) rename Sources/CombustionBLE/BleData/{ProbeColor.swift => ModeId.swift} (50%) delete mode 100644 Sources/CombustionBLE/BleData/ProbeID.swift delete mode 100644 Sources/CombustionBLE/BleData/ProbeMode.swift create mode 100644 Sources/CombustionBLE/BleData/VirtualSensors.swift diff --git a/Sources/CombustionBLE/BleData/AdvertisingData.swift b/Sources/CombustionBLE/BleData/AdvertisingData.swift index b05f86d..a3acd87 100644 --- a/Sources/CombustionBLE/BleData/AdvertisingData.swift +++ b/Sources/CombustionBLE/BleData/AdvertisingData.swift @@ -41,16 +41,10 @@ struct AdvertisingData { let serialNumber: UInt32 /// Latest temperatures read by device let temperatures: ProbeTemperatures - /// Prode ID - let id: ProbeID - /// Probe Color - let color: ProbeColor - /// Probe mode - let mode: ProbeMode - /// Battery Status - let batteryStatus: BatteryStatus - /// Hop Count - let hopCount: HopCount + // ModeId (Probe color, ID, and mode) + let modeId: ModeId + /// Battery Status and Virtual Sensors + let batteryStatusVirtualSensors: BatteryStatusVirtualSensors private enum Constants { // Locations of data in advertising packets @@ -93,28 +87,20 @@ extension AdvertisingData { let tempData = data.subdata(in: Constants.TEMPERATURE_RANGE) temperatures = ProbeTemperatures.fromRawData(data: tempData) - // Decode Probe ID and Color if its present in the advertising packet + // Decode ModeId byte if present in the advertising packet if(data.count >= 21) { - let modeIdColorByte = data.subdata(in: Constants.MODE_COLOR_ID_RANGE)[0] - id = ProbeID.from(modeIdColorByte: modeIdColorByte) - color = ProbeColor.from(modeIdColorByte: modeIdColorByte) - mode = ProbeMode.from(modeIdColorByte: modeIdColorByte) - } - else { - id = .ID1 - color = .COLOR1 - mode = .Normal + let byte = data.subdata(in: Constants.MODE_COLOR_ID_RANGE)[0] + modeId = ModeId.fromByte(byte) + } else { + modeId = ModeId() } - // Decode battery status if its present in the advertising packet + // Decode battery status & virutal sensors if present in the advertising packet if(data.count >= 22) { - let deviceStatusByte = data.subdata(in: Constants.DEVICE_STATUS_RANGE)[0] - batteryStatus = BatteryStatus.from(deviceStatusByte: deviceStatusByte) - hopCount = HopCount.from(deviceStatusByte: deviceStatusByte) - } - else { - batteryStatus = .OK - hopCount = .HOP1 + let byte = data.subdata(in: Constants.DEVICE_STATUS_RANGE)[0] + batteryStatusVirtualSensors = BatteryStatusVirtualSensors.fromByte(byte) + } else { + batteryStatusVirtualSensors = BatteryStatusVirtualSensors() } } } @@ -126,11 +112,8 @@ extension AdvertisingData { type = .PROBE temperatures = ProbeTemperatures.withFakeData() serialNumber = fakeSerial - id = .ID1 - color = .COLOR1 - mode = .Normal - batteryStatus = .OK - hopCount = .HOP1 + modeId = ModeId() + batteryStatusVirtualSensors = BatteryStatusVirtualSensors() } // Fake data initializer for Simulated Probe @@ -138,10 +121,7 @@ extension AdvertisingData { type = .PROBE temperatures = fakeTemperatures serialNumber = fakeSerial - id = .ID1 - color = .COLOR1 - mode = .Normal - batteryStatus = .OK - hopCount = .HOP1 + modeId = ModeId() + batteryStatusVirtualSensors = BatteryStatusVirtualSensors() } } diff --git a/Sources/CombustionBLE/BleData/BatteryStatus.swift b/Sources/CombustionBLE/BleData/BatteryStatusVirtualSensors.swift similarity index 58% rename from Sources/CombustionBLE/BleData/BatteryStatus.swift rename to Sources/CombustionBLE/BleData/BatteryStatusVirtualSensors.swift index 64e8bda..55d17e8 100644 --- a/Sources/CombustionBLE/BleData/BatteryStatus.swift +++ b/Sources/CombustionBLE/BleData/BatteryStatusVirtualSensors.swift @@ -1,4 +1,4 @@ -// BatteryStatus.swift +// BatteryStatusVirtualSensors.swift /*-- MIT License @@ -30,12 +30,33 @@ public enum BatteryStatus: UInt8 { case OK = 0x00 case LOW = 0x01 + static let MASK: UInt8 = 0x3 +} + +class BatteryStatusVirtualSensors { + let batteryStatus: BatteryStatus + let virtualSensors: VirtualSensors + private enum Constants { - static let BATTERY_MASK: UInt8 = 0x3 + static let VIRTUAL_SENSORS_SHIFT: UInt8 = 2 + } + + init() { + batteryStatus = .OK + virtualSensors = VirtualSensors() + } + + init(batteryStatus: BatteryStatus, + virtualSensors: VirtualSensors) { + self.batteryStatus = batteryStatus + self.virtualSensors = virtualSensors } - static func from(deviceStatusByte: UInt8) -> BatteryStatus { - let rawStatus = (deviceStatusByte & (Constants.BATTERY_MASK)) - return BatteryStatus(rawValue: rawStatus) ?? .OK + static func fromByte(_ byte: UInt8) -> BatteryStatusVirtualSensors { + let rawStatus = (byte & (BatteryStatus.MASK)) + let battery = BatteryStatus(rawValue: rawStatus) ?? .OK + let virtualSensors = VirtualSensors.fromByte(byte >> Constants.VIRTUAL_SENSORS_SHIFT) + + return BatteryStatusVirtualSensors(batteryStatus: battery, virtualSensors: virtualSensors) } } diff --git a/Sources/CombustionBLE/BleData/ProbeColor.swift b/Sources/CombustionBLE/BleData/ModeId.swift similarity index 50% rename from Sources/CombustionBLE/BleData/ProbeColor.swift rename to Sources/CombustionBLE/BleData/ModeId.swift index be106e2..5642e9a 100644 --- a/Sources/CombustionBLE/BleData/ProbeColor.swift +++ b/Sources/CombustionBLE/BleData/ModeId.swift @@ -1,9 +1,9 @@ -// ProbeColor.swift +// ModeId.swift /*-- MIT License -Copyright (c) 2021 Combustion Inc. +Copyright (c) 2022 Combustion Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -26,6 +26,17 @@ SOFTWARE. import Foundation +public enum ProbeID: UInt8, CaseIterable { + case ID1 = 0x00 + case ID2 = 0x01 + case ID3 = 0x02 + case ID4 = 0x03 + case ID5 = 0x04 + case ID6 = 0x05 + case ID7 = 0x06 + case ID8 = 0x07 +} + public enum ProbeColor: UInt8, CaseIterable { case COLOR1 = 0x00 case COLOR2 = 0x01 @@ -35,14 +46,50 @@ public enum ProbeColor: UInt8, CaseIterable { case COLOR6 = 0x05 case COLOR7 = 0x06 case COLOR8 = 0x07 +} + +public enum ProbeMode: UInt8, CaseIterable { + case Normal = 0x00 + case InstantRead = 0x01 + case Reserved = 0x02 + case Error = 0x03 +} + +class ModeId { + let id: ProbeID + let color: ProbeColor + let mode: ProbeMode private enum Constants { + static let PRODE_ID_MASK: UInt8 = 0x7 + static let PRODE_ID_SHIFT: UInt8 = 5 static let PRODE_COLOR_MASK: UInt8 = 0x7 static let PRODE_COLOR_SHIFT: UInt8 = 2 + static let PRODE_MODE_MASK: UInt8 = 0x3 + } + + init() { + id = .ID1 + color = .COLOR1 + mode = .Normal + } + + init(id: ProbeID, color: ProbeColor, mode: ProbeMode) { + self.id = id + self.color = color + self.mode = mode } - static func from(modeIdColorByte: UInt8) -> ProbeColor { - let rawProbeColor = (modeIdColorByte >> Constants.PRODE_COLOR_SHIFT) & Constants.PRODE_COLOR_MASK - return ProbeColor(rawValue: rawProbeColor) ?? .COLOR1 + static func fromByte(_ byte: UInt8) -> ModeId { + let rawProbeID = (byte >> Constants.PRODE_ID_SHIFT ) & Constants.PRODE_ID_MASK + let id = ProbeID(rawValue: rawProbeID) ?? .ID1 + + let rawProbeColor = (byte >> Constants.PRODE_COLOR_SHIFT) & Constants.PRODE_COLOR_MASK + let color = ProbeColor(rawValue: rawProbeColor) ?? .COLOR1 + + let rawMode = byte & (Constants.PRODE_MODE_MASK) + let mode = ProbeMode(rawValue: rawMode) ?? .Normal + + return ModeId(id: id, color: color, mode: mode) } } diff --git a/Sources/CombustionBLE/BleData/ProbeID.swift b/Sources/CombustionBLE/BleData/ProbeID.swift deleted file mode 100644 index b6fd9cd..0000000 --- a/Sources/CombustionBLE/BleData/ProbeID.swift +++ /dev/null @@ -1,48 +0,0 @@ -// ProbeID.swift - -/*-- -MIT License - -Copyright (c) 2021 Combustion Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ---*/ - -import Foundation - -public enum ProbeID: UInt8, CaseIterable { - case ID1 = 0x00 - case ID2 = 0x01 - case ID3 = 0x02 - case ID4 = 0x03 - case ID5 = 0x04 - case ID6 = 0x05 - case ID7 = 0x06 - case ID8 = 0x07 - - private enum Constants { - static let PRODE_ID_MASK: UInt8 = 0x7 - static let PRODE_ID_SHIFT: UInt8 = 5 - } - - static func from(modeIdColorByte: UInt8) -> ProbeID { - let rawProbeID = (modeIdColorByte >> Constants.PRODE_ID_SHIFT ) & Constants.PRODE_ID_MASK - return ProbeID(rawValue: rawProbeID) ?? .ID1 - } -} diff --git a/Sources/CombustionBLE/BleData/ProbeMode.swift b/Sources/CombustionBLE/BleData/ProbeMode.swift deleted file mode 100644 index c801012..0000000 --- a/Sources/CombustionBLE/BleData/ProbeMode.swift +++ /dev/null @@ -1,43 +0,0 @@ -// ProbeMode.swift - -/*-- -MIT License - -Copyright (c) 2021 Combustion Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ---*/ - -import Foundation - -public enum ProbeMode: UInt8, CaseIterable { - case Normal = 0x00 - case InstantRead = 0x01 - case Reserved = 0x02 - case Error = 0x03 - - private enum Constants { - static let PRODE_MODE_MASK: UInt8 = 0x3 - } - - static func from(modeIdColorByte: UInt8) -> ProbeMode { - let rawProbeID = modeIdColorByte & (Constants.PRODE_MODE_MASK) - return ProbeMode(rawValue: rawProbeID) ?? .Normal - } -} diff --git a/Sources/CombustionBLE/BleData/ProbeStatus.swift b/Sources/CombustionBLE/BleData/ProbeStatus.swift index e4c2871..6e3262f 100644 --- a/Sources/CombustionBLE/BleData/ProbeStatus.swift +++ b/Sources/CombustionBLE/BleData/ProbeStatus.swift @@ -34,16 +34,10 @@ struct ProbeStatus { let maxSequenceNumber: UInt32 /// Current temperatures sent by Probe. let temperatures: ProbeTemperatures - /// Prode ID - let id: ProbeID - /// Probe Color - let color: ProbeColor - /// Probe mode - let mode: ProbeMode - /// Battery Status - let batteryStatus: BatteryStatus - /// Hop Count - let hopCount: HopCount + // ModeId (Probe color, ID, and mode) + let modeId: ModeId + /// Battery Status and Virtual Sensors + let batteryStatusVirtualSensors: BatteryStatusVirtualSensors private enum Constants { // Locations of data in status packet @@ -73,28 +67,21 @@ extension ProbeStatus { let tempData = data.subdata(in: Constants.TEMPERATURE_RANGE) temperatures = ProbeTemperatures.fromRawData(data: tempData) - // Decode Probe ID and Color if its present in the status notification + // Decode ModeId byte if present in the advertising packet if(data.count >= 22) { - let modeIdColorByte = data.subdata(in: Constants.MODE_COLOR_ID_RANGE)[0] - id = ProbeID.from(modeIdColorByte: modeIdColorByte) - color = ProbeColor.from(modeIdColorByte: modeIdColorByte) - mode = ProbeMode.from(modeIdColorByte: modeIdColorByte) - } - else { - id = .ID1 - color = .COLOR1 - mode = .Normal + let byte = data.subdata(in: Constants.MODE_COLOR_ID_RANGE)[0] + modeId = ModeId.fromByte(byte) + } else { + modeId = ModeId() } - // Decode battery status if its present in the status notification + // Decode battery status & virutal sensors if present in the advertising packet if(data.count >= 23) { - let deviceStatusByte = data.subdata(in: Constants.DEVICE_STATUS_RANGE)[0] - batteryStatus = BatteryStatus.from(deviceStatusByte: deviceStatusByte) - hopCount = HopCount.from(deviceStatusByte: deviceStatusByte) - } - else { - batteryStatus = .OK - hopCount = .HOP1 + let byte = data.subdata(in: Constants.DEVICE_STATUS_RANGE)[0] + batteryStatusVirtualSensors = BatteryStatusVirtualSensors.fromByte(byte) + } else { + batteryStatusVirtualSensors = BatteryStatusVirtualSensors() } + } } diff --git a/Sources/CombustionBLE/BleData/VirtualSensors.swift b/Sources/CombustionBLE/BleData/VirtualSensors.swift new file mode 100644 index 0000000..b4ba73e --- /dev/null +++ b/Sources/CombustionBLE/BleData/VirtualSensors.swift @@ -0,0 +1,92 @@ +// VirtualSensors.swift + +/*-- +MIT License + +Copyright (c) 2022 Combustion Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +--*/ + +import Foundation + +public enum VirtualCoreSensor: UInt8 { + case T1 = 0x00 + case T2 = 0x01 + case T3 = 0x02 + case T4 = 0x03 + case T5 = 0x04 + case T6 = 0x05 + + static let MASK: UInt8 = 0x7 +} + +public enum VirtualSurfaceSensor: UInt8 { + case T4 = 0x00 + case T5 = 0x01 + case T6 = 0x02 + case T7 = 0x03 + + static let MASK: UInt8 = 0x3 +} + +public enum VirtualAmbientSensor: UInt8 { + case T5 = 0x00 + case T6 = 0x01 + case T7 = 0x02 + case T8 = 0x03 + + static let MASK: UInt8 = 0x3 +} + +class VirtualSensors { + let virtualCore: VirtualCoreSensor + let virtualSurface: VirtualSurfaceSensor + let virtualAmbient: VirtualAmbientSensor + + private enum Constants { + static let VIRTUAL_SURFACE_SHIFT: UInt8 = 3 + static let VIRTUAL_AMBIENT_SHIFT: UInt8 = 5 + } + + init() { + virtualCore = .T1 + virtualSurface = .T4 + virtualAmbient = .T5 + } + + init(virtualCore: VirtualCoreSensor, virtualSurface: VirtualSurfaceSensor, virtualAmbient: VirtualAmbientSensor) { + self.virtualCore = virtualCore + self.virtualSurface = virtualSurface + self.virtualAmbient = virtualAmbient + } + + static func fromByte(_ byte: UInt8) -> VirtualSensors { + let rawVirtualCore = (byte) & VirtualCoreSensor.MASK + let virtualCore = VirtualCoreSensor(rawValue: rawVirtualCore) ?? .T1 + + let rawVirtualSurface = (byte >> Constants.VIRTUAL_SURFACE_SHIFT) & VirtualSurfaceSensor.MASK + let virtualSurface = VirtualSurfaceSensor(rawValue: rawVirtualSurface) ?? .T4 + + let rawVirtualAmbient = (byte >> Constants.VIRTUAL_AMBIENT_SHIFT) & VirtualAmbientSensor.MASK + let virtualAmbient = VirtualAmbientSensor(rawValue: rawVirtualAmbient) ?? .T5 + + return VirtualSensors(virtualCore: virtualCore, virtualSurface: virtualSurface, virtualAmbient: virtualAmbient) + } +} diff --git a/Sources/CombustionBLE/Probe.swift b/Sources/CombustionBLE/Probe.swift index 4b790a4..c45adc3 100644 --- a/Sources/CombustionBLE/Probe.swift +++ b/Sources/CombustionBLE/Probe.swift @@ -72,8 +72,8 @@ public class Probe : Device { init(_ advertising: AdvertisingData, isConnectable: Bool, RSSI: NSNumber, identifier: UUID) { serialNumber = advertising.serialNumber - id = advertising.id - color = advertising.color + id = advertising.modeId.id + color = advertising.modeId.color super.init(identifier: identifier, RSSI: RSSI) @@ -117,17 +117,17 @@ extension Probe { // notifications to update data if(connectionState != .connected) { - if(advertising.mode == .Normal) { + if(advertising.modeId.mode == .Normal) { currentTemperatures = advertising.temperatures } - else if(advertising.mode == .InstantRead ){ + else if(advertising.modeId.mode == .InstantRead ){ updateInstantRead(advertising.temperatures.values[0]) } - id = advertising.id - color = advertising.color - batteryStatus = advertising.batteryStatus + id = advertising.modeId.id + color = advertising.modeId.color + batteryStatus = advertising.batteryStatusVirtualSensors.batteryStatus lastUpdateTime = Date() } @@ -137,18 +137,18 @@ extension Probe { func updateProbeStatus(deviceStatus: ProbeStatus) { minSequenceNumber = deviceStatus.minSequenceNumber maxSequenceNumber = deviceStatus.maxSequenceNumber - id = deviceStatus.id - color = deviceStatus.color - batteryStatus = deviceStatus.batteryStatus + id = deviceStatus.modeId.id + color = deviceStatus.modeId.color + batteryStatus = deviceStatus.batteryStatusVirtualSensors.batteryStatus - if(deviceStatus.mode == .Normal) { + if(deviceStatus.modeId.mode == .Normal) { currentTemperatures = deviceStatus.temperatures // Log the temperature data point for "Normal" status updates // Log the temperature data point addDataToLog(LoggedProbeDataPoint.fromDeviceStatus(deviceStatus: deviceStatus)) } - else if(deviceStatus.mode == .InstantRead ){ + else if(deviceStatus.modeId.mode == .InstantRead ){ updateInstantRead(deviceStatus.temperatures.values[0]) } diff --git a/Sources/CombustionBLE/SimulatedProbe.swift b/Sources/CombustionBLE/SimulatedProbe.swift index c01c45e..49ab5ea 100644 --- a/Sources/CombustionBLE/SimulatedProbe.swift +++ b/Sources/CombustionBLE/SimulatedProbe.swift @@ -84,11 +84,8 @@ class SimulatedProbe: Probe { let probeStatus = ProbeStatus(minSequenceNumber: firstSeq, maxSequenceNumber: lastSequence, temperatures: ProbeTemperatures.withRandomData(), - id: .ID1, - color: .COLOR1, - mode: .Normal, - batteryStatus: .OK, - hopCount: .HOP1) + modeId: ModeId(), + batteryStatusVirtualSensors: BatteryStatusVirtualSensors()) updateProbeStatus(deviceStatus: probeStatus) } From 01c243444b171e255afcb0b552c1cc5fa7e48e5f Mon Sep 17 00:00:00 2001 From: Jesse Johnston Date: Thu, 29 Sep 2022 17:04:51 -0700 Subject: [PATCH 4/9] Decode sensors and prediction from log response --- .../VirtualSensorsPredictionState.swift | 71 +++++++++++++++++++ Sources/CombustionBLE/UART/LogResponse.swift | 29 ++++++-- .../CombustionBLE/Utilities/Data+CRC.swift | 4 ++ 3 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 Sources/CombustionBLE/BleData/VirtualSensorsPredictionState.swift diff --git a/Sources/CombustionBLE/BleData/VirtualSensorsPredictionState.swift b/Sources/CombustionBLE/BleData/VirtualSensorsPredictionState.swift new file mode 100644 index 0000000..9a8e26e --- /dev/null +++ b/Sources/CombustionBLE/BleData/VirtualSensorsPredictionState.swift @@ -0,0 +1,71 @@ +// BatteryStatusVirtualSensors.swift + +/*-- +MIT License + +Copyright (c) 2022 Combustion Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +--*/ + +import Foundation + +/// Enumeration of Battery status +public enum PredictionState: UInt8 { + case ProbeNotInserted = 0x00 + case ProbeInserted = 0x01 + case Warming = 0x02 + case Predicting = 0x03 + case RemovalPredictionDone = 0x04 +// * 5: Reserved State 5 +// * 6: Reserved State 6 +// ... +// * 14: Reserved State 14 + case Unknown = 0x0F + + + static let MASK: UInt8 = 0xF +} + +class VirtualSensorsPredictionState { + let predictionState: PredictionState + let virtualSensors: VirtualSensors + + + init() { + predictionState = .Unknown + virtualSensors = VirtualSensors() + } + + init(predictionState: PredictionState, + virtualSensors: VirtualSensors) { + self.predictionState = predictionState + self.virtualSensors = virtualSensors + } + + static func fromBytes(_ rawValue: UInt16) -> VirtualSensorsPredictionState { + let lowerByte = UInt8(rawValue & 0xFF) + let virtualSensors = VirtualSensors.fromByte(lowerByte) + + let rawPrediction = UInt8((rawValue >> 7) & UInt16(PredictionState.MASK)) + let prediction = PredictionState(rawValue: rawPrediction) ?? .Unknown + + return VirtualSensorsPredictionState(predictionState: prediction, virtualSensors: virtualSensors) + } +} diff --git a/Sources/CombustionBLE/UART/LogResponse.swift b/Sources/CombustionBLE/UART/LogResponse.swift index f88fe98..1d9423e 100644 --- a/Sources/CombustionBLE/UART/LogResponse.swift +++ b/Sources/CombustionBLE/UART/LogResponse.swift @@ -28,24 +28,39 @@ import Foundation class LogResponse: Response { - static let PAYLOAD_LENGTH = 17 + enum Constants { + static let MINIMUM_PAYLOAD_LENGTH = 17 + + static let SEQUENCE_RANGE = Response.HEADER_LENGTH..<(Response.HEADER_LENGTH + 4) + static let TEMPERATURE_RANGE = (Response.HEADER_LENGTH + 4)..<(Response.HEADER_LENGTH + 17) + static let SENSOR_RANGE = (Response.HEADER_LENGTH + 17)..<(Response.HEADER_LENGTH + 19) + } let sequenceNumber: UInt32 let temperatures: ProbeTemperatures + let virtualSensorsPredictionState: VirtualSensorsPredictionState? init(data: Data, success: Bool, payloadLength: Int) { - let sequenceByteIndex = Response.HEADER_LENGTH - let sequenceRaw = data.subdata(in: sequenceByteIndex..<(sequenceByteIndex + 4)) + let sequenceRaw = data.subdata(in: Constants.SEQUENCE_RANGE) sequenceNumber = sequenceRaw.withUnsafeBytes { $0.load(as: UInt32.self) } // Temperatures (8 13-bit) values - let tempData = data.subdata(in: (Response.HEADER_LENGTH + 4)..<(Response.HEADER_LENGTH + 17)) + let tempData = data.subdata(in: Constants.TEMPERATURE_RANGE) temperatures = ProbeTemperatures.fromRawData(data: tempData) - // print("******** Received response!") - // print("Sequence = \(sequenceNumber) : Temperature = \(temperatures)") + // Virtual sensors and Prediction state + if(payloadLength >= 19) { + let sensorData = data.subdata(in: Constants.SENSOR_RANGE) + let sensorValue = sensorData.withUnsafeBytes { + $0.load(as: UInt16.self) + } + virtualSensorsPredictionState = VirtualSensorsPredictionState.fromBytes(sensorValue) + } + else { + virtualSensorsPredictionState = nil + } super.init(success: success, payLoadLength: payloadLength) } @@ -54,7 +69,7 @@ class LogResponse: Response { extension LogResponse { static func fromRaw(data: Data, success: Bool, payloadLength: Int) -> LogResponse? { - if(payloadLength < PAYLOAD_LENGTH) { + if(payloadLength < Constants.MINIMUM_PAYLOAD_LENGTH) { return nil } diff --git a/Sources/CombustionBLE/Utilities/Data+CRC.swift b/Sources/CombustionBLE/Utilities/Data+CRC.swift index 8caaa51..3bb8ccc 100644 --- a/Sources/CombustionBLE/Utilities/Data+CRC.swift +++ b/Sources/CombustionBLE/Utilities/Data+CRC.swift @@ -43,4 +43,8 @@ extension Data { } return crc & 0xffff } + + var hexDescription: String { + return reduce("") {$0 + String(format: "%02x", $1)} + } } From c889b3ac0a34b9c129e18da6275a35f9fee899ba Mon Sep 17 00:00:00 2001 From: Jesse Johnston Date: Mon, 3 Oct 2022 14:31:07 -0700 Subject: [PATCH 5/9] Decode Prediction status --- .../BleData/AdvertisingData.swift | 30 ++++---- .../BleData/BatteryStatusVirtualSensors.swift | 27 +++---- Sources/CombustionBLE/BleData/HopCount.swift | 14 ++-- Sources/CombustionBLE/BleData/ModeId.swift | 50 ++++++------- .../BleData/PredictionMode.swift | 10 ++- .../BleData/PredictionState.swift | 44 +++++++++++ .../BleData/PredictionStatus.swift | 73 +++++++++++++++++++ .../BleData/PredictionType.swift | 36 +++++++++ .../CombustionBLE/BleData/ProbeStatus.swift | 32 +++++--- .../BleData/ProbeTemperatures.swift | 4 - .../BleData/VirtualSensors.swift | 18 +---- .../VirtualSensorsPredictionState.swift | 37 ++-------- Sources/CombustionBLE/BleManager.swift | 5 +- Sources/CombustionBLE/DeviceManager.swift | 4 +- Sources/CombustionBLE/Probe.swift | 10 +-- Sources/CombustionBLE/SimulatedProbe.swift | 9 ++- Sources/CombustionBLE/UART/LogRequest.swift | 2 +- Sources/CombustionBLE/UART/MessageType.swift | 10 +-- Sources/CombustionBLE/UART/Response.swift | 10 +-- Sources/CombustionBLE/UART/SessionInfo.swift | 2 +- Sources/CombustionBLE/UART/SetColor.swift | 2 +- Sources/CombustionBLE/UART/SetID.swift | 2 +- .../CombustionBLE/UART/SetPrediction.swift | 2 +- 23 files changed, 274 insertions(+), 159 deletions(-) create mode 100644 Sources/CombustionBLE/BleData/PredictionState.swift create mode 100644 Sources/CombustionBLE/BleData/PredictionStatus.swift create mode 100644 Sources/CombustionBLE/BleData/PredictionType.swift diff --git a/Sources/CombustionBLE/BleData/AdvertisingData.swift b/Sources/CombustionBLE/BleData/AdvertisingData.swift index a3acd87..0524b48 100644 --- a/Sources/CombustionBLE/BleData/AdvertisingData.swift +++ b/Sources/CombustionBLE/BleData/AdvertisingData.swift @@ -28,9 +28,9 @@ import Foundation /// Enumeration of Combustion, Inc. product types. public enum CombustionProductType: UInt8 { - case UNKNOWN = 0x00 - case PROBE = 0x01 - case NODE = 0x02 + case unknown = 0x00 + case probe = 0x01 + case node = 0x02 } /// Struct containing advertising data received from device. @@ -45,7 +45,9 @@ struct AdvertisingData { let modeId: ModeId /// Battery Status and Virtual Sensors let batteryStatusVirtualSensors: BatteryStatusVirtualSensors +} +extension AdvertisingData { private enum Constants { // Locations of data in advertising packets static let PRODUCT_TYPE_RANGE = 2..<3 @@ -54,9 +56,7 @@ struct AdvertisingData { static let MODE_COLOR_ID_RANGE = 20..<21 static let DEVICE_STATUS_RANGE = 21..<22 } -} - -extension AdvertisingData { + init?(fromData : Data?) { guard let data = fromData else { return nil } guard data.count >= 20 else { return nil } @@ -64,7 +64,7 @@ extension AdvertisingData { // Product type (1 byte) let rawType = data.subdata(in: Constants.PRODUCT_TYPE_RANGE) let typeByte = [UInt8](rawType) - type = CombustionProductType(rawValue: typeByte[0]) ?? .UNKNOWN + type = CombustionProductType(rawValue: typeByte[0]) ?? .unknown // Device Serial number (4 bytes) // Reverse the byte order (this is a little-endian packed bitfield) @@ -92,7 +92,7 @@ extension AdvertisingData { let byte = data.subdata(in: Constants.MODE_COLOR_ID_RANGE)[0] modeId = ModeId.fromByte(byte) } else { - modeId = ModeId() + modeId = ModeId.defaultValues() } // Decode battery status & virutal sensors if present in the advertising packet @@ -100,7 +100,7 @@ extension AdvertisingData { let byte = data.subdata(in: Constants.DEVICE_STATUS_RANGE)[0] batteryStatusVirtualSensors = BatteryStatusVirtualSensors.fromByte(byte) } else { - batteryStatusVirtualSensors = BatteryStatusVirtualSensors() + batteryStatusVirtualSensors = BatteryStatusVirtualSensors.defaultValues() } } } @@ -109,19 +109,19 @@ extension AdvertisingData { extension AdvertisingData { // Fake data initializer for previews public init(fakeSerial: UInt32) { - type = .PROBE + type = .probe temperatures = ProbeTemperatures.withFakeData() serialNumber = fakeSerial - modeId = ModeId() - batteryStatusVirtualSensors = BatteryStatusVirtualSensors() + modeId = ModeId.defaultValues() + batteryStatusVirtualSensors = BatteryStatusVirtualSensors.defaultValues() } // Fake data initializer for Simulated Probe public init(fakeSerial: UInt32, fakeTemperatures: ProbeTemperatures) { - type = .PROBE + type = .probe temperatures = fakeTemperatures serialNumber = fakeSerial - modeId = ModeId() - batteryStatusVirtualSensors = BatteryStatusVirtualSensors() + modeId = ModeId.defaultValues() + batteryStatusVirtualSensors = BatteryStatusVirtualSensors.defaultValues() } } diff --git a/Sources/CombustionBLE/BleData/BatteryStatusVirtualSensors.swift b/Sources/CombustionBLE/BleData/BatteryStatusVirtualSensors.swift index 55d17e8..66db196 100644 --- a/Sources/CombustionBLE/BleData/BatteryStatusVirtualSensors.swift +++ b/Sources/CombustionBLE/BleData/BatteryStatusVirtualSensors.swift @@ -27,36 +27,33 @@ import Foundation /// Enumeration of Battery status public enum BatteryStatus: UInt8 { - case OK = 0x00 - case LOW = 0x01 + case ok = 0x00 + case low = 0x01 static let MASK: UInt8 = 0x3 } -class BatteryStatusVirtualSensors { +struct BatteryStatusVirtualSensors { let batteryStatus: BatteryStatus let virtualSensors: VirtualSensors +} + +extension BatteryStatusVirtualSensors { private enum Constants { static let VIRTUAL_SENSORS_SHIFT: UInt8 = 2 } - init() { - batteryStatus = .OK - virtualSensors = VirtualSensors() - } - - init(batteryStatus: BatteryStatus, - virtualSensors: VirtualSensors) { - self.batteryStatus = batteryStatus - self.virtualSensors = virtualSensors - } - static func fromByte(_ byte: UInt8) -> BatteryStatusVirtualSensors { let rawStatus = (byte & (BatteryStatus.MASK)) - let battery = BatteryStatus(rawValue: rawStatus) ?? .OK + let battery = BatteryStatus(rawValue: rawStatus) ?? .ok let virtualSensors = VirtualSensors.fromByte(byte >> Constants.VIRTUAL_SENSORS_SHIFT) return BatteryStatusVirtualSensors(batteryStatus: battery, virtualSensors: virtualSensors) } + + static func defaultValues() -> BatteryStatusVirtualSensors { + return BatteryStatusVirtualSensors(batteryStatus: .ok, + virtualSensors: VirtualSensors(virtualCore: .T1, virtualSurface: .T4, virtualAmbient: .T5)) + } } diff --git a/Sources/CombustionBLE/BleData/HopCount.swift b/Sources/CombustionBLE/BleData/HopCount.swift index 7c08bda..2ada9f7 100644 --- a/Sources/CombustionBLE/BleData/HopCount.swift +++ b/Sources/CombustionBLE/BleData/HopCount.swift @@ -27,11 +27,13 @@ SOFTWARE. import Foundation public enum HopCount: UInt8, CaseIterable { - case HOP1 = 0x00 - case HOP2 = 0x01 - case HOP3 = 0x02 - case HOP4 = 0x03 - + case hop1 = 0x00 + case hop2 = 0x01 + case hop3 = 0x02 + case hop4 = 0x03 +} + +extension HopCount { private enum Constants { static let HOP_COUNT_MASK: UInt8 = 0x3 static let HOP_COUNT_SHIFT: UInt8 = 6 @@ -39,6 +41,6 @@ public enum HopCount: UInt8, CaseIterable { static func from(deviceStatusByte: UInt8) -> HopCount { let rawHopCount = (deviceStatusByte >> Constants.HOP_COUNT_SHIFT) & Constants.HOP_COUNT_MASK - return HopCount(rawValue: rawHopCount) ?? .HOP1 + return HopCount(rawValue: rawHopCount) ?? .hop1 } } diff --git a/Sources/CombustionBLE/BleData/ModeId.swift b/Sources/CombustionBLE/BleData/ModeId.swift index 5642e9a..05b3acc 100644 --- a/Sources/CombustionBLE/BleData/ModeId.swift +++ b/Sources/CombustionBLE/BleData/ModeId.swift @@ -38,28 +38,30 @@ public enum ProbeID: UInt8, CaseIterable { } public enum ProbeColor: UInt8, CaseIterable { - case COLOR1 = 0x00 - case COLOR2 = 0x01 - case COLOR3 = 0x02 - case COLOR4 = 0x03 - case COLOR5 = 0x04 - case COLOR6 = 0x05 - case COLOR7 = 0x06 - case COLOR8 = 0x07 + case color1 = 0x00 + case color2 = 0x01 + case color3 = 0x02 + case color4 = 0x03 + case color5 = 0x04 + case color6 = 0x05 + case color7 = 0x06 + case color8 = 0x07 } public enum ProbeMode: UInt8, CaseIterable { - case Normal = 0x00 - case InstantRead = 0x01 - case Reserved = 0x02 - case Error = 0x03 + case normal = 0x00 + case instantRead = 0x01 + case reserved = 0x02 + case error = 0x03 } -class ModeId { +struct ModeId { let id: ProbeID let color: ProbeColor let mode: ProbeMode - +} + +extension ModeId { private enum Constants { static let PRODE_ID_MASK: UInt8 = 0x7 static let PRODE_ID_SHIFT: UInt8 = 5 @@ -68,28 +70,20 @@ class ModeId { static let PRODE_MODE_MASK: UInt8 = 0x3 } - init() { - id = .ID1 - color = .COLOR1 - mode = .Normal - } - - init(id: ProbeID, color: ProbeColor, mode: ProbeMode) { - self.id = id - self.color = color - self.mode = mode - } - static func fromByte(_ byte: UInt8) -> ModeId { let rawProbeID = (byte >> Constants.PRODE_ID_SHIFT ) & Constants.PRODE_ID_MASK let id = ProbeID(rawValue: rawProbeID) ?? .ID1 let rawProbeColor = (byte >> Constants.PRODE_COLOR_SHIFT) & Constants.PRODE_COLOR_MASK - let color = ProbeColor(rawValue: rawProbeColor) ?? .COLOR1 + let color = ProbeColor(rawValue: rawProbeColor) ?? .color1 let rawMode = byte & (Constants.PRODE_MODE_MASK) - let mode = ProbeMode(rawValue: rawMode) ?? .Normal + let mode = ProbeMode(rawValue: rawMode) ?? .normal return ModeId(id: id, color: color, mode: mode) } + + static func defaultValues() -> ModeId { + return ModeId(id: .ID1, color: .color1, mode: .normal) + } } diff --git a/Sources/CombustionBLE/BleData/PredictionMode.swift b/Sources/CombustionBLE/BleData/PredictionMode.swift index 3c51f0a..2d2d01a 100644 --- a/Sources/CombustionBLE/BleData/PredictionMode.swift +++ b/Sources/CombustionBLE/BleData/PredictionMode.swift @@ -27,8 +27,10 @@ SOFTWARE. import Foundation public enum PredictionMode: UInt8, CaseIterable { - case NONE = 0x00 - case TIME_TO_REMOVAL = 0x01 - case REMOVAL_AND_RESTING = 0x02 - case RESERVED = 0x03 + case none = 0x00 + case timeToRemoval = 0x01 + case removalAndResting = 0x02 + case reserved = 0x03 + + static let MASK: UInt8 = 0x3 } diff --git a/Sources/CombustionBLE/BleData/PredictionState.swift b/Sources/CombustionBLE/BleData/PredictionState.swift new file mode 100644 index 0000000..fa4c1a0 --- /dev/null +++ b/Sources/CombustionBLE/BleData/PredictionState.swift @@ -0,0 +1,44 @@ +// PredictionState.swift + +/*-- +MIT License + +Copyright (c) 2022 Combustion Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +--*/ + +import Foundation + +/// Enumeration of Battery status +public enum PredictionState: UInt8 { + case probeNotInserted = 0x00 + case probeInserted = 0x01 + case warming = 0x02 + case predicting = 0x03 + case removalPredictionDone = 0x04 +// * 5: Reserved State 5 +// * 6: Reserved State 6 +// ... +// * 14: Reserved State 14 + case unknown = 0x0F + + + static let MASK: UInt8 = 0xF +} diff --git a/Sources/CombustionBLE/BleData/PredictionStatus.swift b/Sources/CombustionBLE/BleData/PredictionStatus.swift new file mode 100644 index 0000000..31d10cc --- /dev/null +++ b/Sources/CombustionBLE/BleData/PredictionStatus.swift @@ -0,0 +1,73 @@ +// PredictionStatus.swift + +/*-- +MIT License + +Copyright (c) 2022 Combustion Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +--*/ + +import Foundation + +struct PredictionStatus { + let predictionState: PredictionState + let predictionMode: PredictionMode + let predictionType: PredictionType + let predictionSetPointTemperature: Float + let heatStartTemperature: Float + let predictionValueSeconds: UInt + let estimatedCoreTemperature: Float +} + +extension PredictionStatus { + static func fromBytes(_ bytes: [UInt8]) -> PredictionStatus { + let rawPredictionState = bytes[0] & PredictionState.MASK + let predictionState = PredictionState(rawValue: rawPredictionState) ?? .unknown + + let rawPredictionMode = (bytes[0] >> 4) & PredictionMode.MASK + let predictionMode = PredictionMode(rawValue: rawPredictionMode) ?? .none + + let rawPredictionType = (bytes[0] >> 6) & PredictionType.MASK + let predictionType = PredictionType(rawValue: rawPredictionType) ?? .none + + // 10 bit field + let rawSetPoint = UInt16(bytes[2] & 0x03) << 8 | UInt16(bytes[1]) + let setPoint = Float(rawSetPoint) * 0.1 + + // 10 bit field + let rawHeatStart = UInt16(bytes[3] & 0x0F) << 6 | UInt16(bytes[2] & 0xFC) >> 2 + let heatStart = Float(rawHeatStart) * 0.1 + + // 17 bit field + let seconds = UInt32(bytes[5] & 0x1F) << 12 | UInt32(bytes[4]) << 4 | UInt32(bytes[3] & 0xF0) >> 4 + + // 11 bit field + let rawCore = UInt16(bytes[6]) << 3 | UInt16(bytes[5] & 0xE0) >> 5 + let estimatedCore = (Float(rawCore) * 0.1) - 20.0 + + return PredictionStatus(predictionState: predictionState, + predictionMode: predictionMode, + predictionType: predictionType, + predictionSetPointTemperature: setPoint, + heatStartTemperature: heatStart, + predictionValueSeconds: UInt(seconds), + estimatedCoreTemperature: estimatedCore) + } +} diff --git a/Sources/CombustionBLE/BleData/PredictionType.swift b/Sources/CombustionBLE/BleData/PredictionType.swift new file mode 100644 index 0000000..f646846 --- /dev/null +++ b/Sources/CombustionBLE/BleData/PredictionType.swift @@ -0,0 +1,36 @@ +// PredictionType.swift + +/*-- +MIT License + +Copyright (c) 2022 Combustion Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +--*/ + +import Foundation + +public enum PredictionType: UInt8, CaseIterable { + case none = 0x00 + case removal = 0x01 + case resting = 0x02 + case reserved = 0x03 + + static let MASK: UInt8 = 0x3 +} diff --git a/Sources/CombustionBLE/BleData/ProbeStatus.swift b/Sources/CombustionBLE/BleData/ProbeStatus.swift index 6e3262f..4d8aa29 100644 --- a/Sources/CombustionBLE/BleData/ProbeStatus.swift +++ b/Sources/CombustionBLE/BleData/ProbeStatus.swift @@ -38,7 +38,11 @@ struct ProbeStatus { let modeId: ModeId /// Battery Status and Virtual Sensors let batteryStatusVirtualSensors: BatteryStatusVirtualSensors - + // Prediction Status + let predictionStatus: PredictionStatus? +} + +extension ProbeStatus { private enum Constants { // Locations of data in status packet static let MIN_SEQ_RANGE = 0..<4 @@ -46,12 +50,11 @@ struct ProbeStatus { static let TEMPERATURE_RANGE = 8..<21 static let MODE_COLOR_ID_RANGE = 21..<22 static let DEVICE_STATUS_RANGE = 22..<23 + static let PREDICTION_STATUS_RANGE = 23..<30 } -} - -extension ProbeStatus { + init?(fromData data: Data) { - guard data.count >= 21 else { return nil } + guard data.count >= Constants.TEMPERATURE_RANGE.endIndex else { return nil } let minRaw = data.subdata(in: Constants.MIN_SEQ_RANGE) minSequenceNumber = minRaw.withUnsafeBytes { @@ -67,21 +70,28 @@ extension ProbeStatus { let tempData = data.subdata(in: Constants.TEMPERATURE_RANGE) temperatures = ProbeTemperatures.fromRawData(data: tempData) - // Decode ModeId byte if present in the advertising packet - if(data.count >= 22) { + // Decode ModeId byte if present + if(data.count >= Constants.MODE_COLOR_ID_RANGE.endIndex) { let byte = data.subdata(in: Constants.MODE_COLOR_ID_RANGE)[0] modeId = ModeId.fromByte(byte) } else { - modeId = ModeId() + modeId = ModeId.defaultValues() } - // Decode battery status & virutal sensors if present in the advertising packet - if(data.count >= 23) { + // Decode battery status & virutal sensors if present + if(data.count >= Constants.DEVICE_STATUS_RANGE.endIndex) { let byte = data.subdata(in: Constants.DEVICE_STATUS_RANGE)[0] batteryStatusVirtualSensors = BatteryStatusVirtualSensors.fromByte(byte) } else { - batteryStatusVirtualSensors = BatteryStatusVirtualSensors() + batteryStatusVirtualSensors = BatteryStatusVirtualSensors.defaultValues() } + // Decode Prediction Status if present + if(data.count >= Constants.PREDICTION_STATUS_RANGE.endIndex) { + let bytes = [UInt8](data.subdata(in: Constants.PREDICTION_STATUS_RANGE)) + predictionStatus = PredictionStatus.fromBytes(bytes) + } else { + predictionStatus = nil + } } } diff --git a/Sources/CombustionBLE/BleData/ProbeTemperatures.swift b/Sources/CombustionBLE/BleData/ProbeTemperatures.swift index 8322e0f..463b2ef 100644 --- a/Sources/CombustionBLE/BleData/ProbeTemperatures.swift +++ b/Sources/CombustionBLE/BleData/ProbeTemperatures.swift @@ -31,10 +31,6 @@ public struct ProbeTemperatures: Equatable { /// Array of probe temperatures. /// Index 0 is the tip sensor, 7 is the handle (ambient) sensor. public let values: [Double] - - public init(values: [Double]) { - self.values = values - } } extension ProbeTemperatures { diff --git a/Sources/CombustionBLE/BleData/VirtualSensors.swift b/Sources/CombustionBLE/BleData/VirtualSensors.swift index b4ba73e..ccb63a7 100644 --- a/Sources/CombustionBLE/BleData/VirtualSensors.swift +++ b/Sources/CombustionBLE/BleData/VirtualSensors.swift @@ -55,28 +55,18 @@ public enum VirtualAmbientSensor: UInt8 { static let MASK: UInt8 = 0x3 } -class VirtualSensors { +struct VirtualSensors { let virtualCore: VirtualCoreSensor let virtualSurface: VirtualSurfaceSensor let virtualAmbient: VirtualAmbientSensor - +} + +extension VirtualSensors { private enum Constants { static let VIRTUAL_SURFACE_SHIFT: UInt8 = 3 static let VIRTUAL_AMBIENT_SHIFT: UInt8 = 5 } - init() { - virtualCore = .T1 - virtualSurface = .T4 - virtualAmbient = .T5 - } - - init(virtualCore: VirtualCoreSensor, virtualSurface: VirtualSurfaceSensor, virtualAmbient: VirtualAmbientSensor) { - self.virtualCore = virtualCore - self.virtualSurface = virtualSurface - self.virtualAmbient = virtualAmbient - } - static func fromByte(_ byte: UInt8) -> VirtualSensors { let rawVirtualCore = (byte) & VirtualCoreSensor.MASK let virtualCore = VirtualCoreSensor(rawValue: rawVirtualCore) ?? .T1 diff --git a/Sources/CombustionBLE/BleData/VirtualSensorsPredictionState.swift b/Sources/CombustionBLE/BleData/VirtualSensorsPredictionState.swift index 9a8e26e..d5fbbd6 100644 --- a/Sources/CombustionBLE/BleData/VirtualSensorsPredictionState.swift +++ b/Sources/CombustionBLE/BleData/VirtualSensorsPredictionState.swift @@ -26,45 +26,18 @@ SOFTWARE. import Foundation -/// Enumeration of Battery status -public enum PredictionState: UInt8 { - case ProbeNotInserted = 0x00 - case ProbeInserted = 0x01 - case Warming = 0x02 - case Predicting = 0x03 - case RemovalPredictionDone = 0x04 -// * 5: Reserved State 5 -// * 6: Reserved State 6 -// ... -// * 14: Reserved State 14 - case Unknown = 0x0F - - - static let MASK: UInt8 = 0xF -} - -class VirtualSensorsPredictionState { +struct VirtualSensorsPredictionState { let predictionState: PredictionState let virtualSensors: VirtualSensors - - - init() { - predictionState = .Unknown - virtualSensors = VirtualSensors() - } - - init(predictionState: PredictionState, - virtualSensors: VirtualSensors) { - self.predictionState = predictionState - self.virtualSensors = virtualSensors - } - +} + +extension VirtualSensorsPredictionState { static func fromBytes(_ rawValue: UInt16) -> VirtualSensorsPredictionState { let lowerByte = UInt8(rawValue & 0xFF) let virtualSensors = VirtualSensors.fromByte(lowerByte) let rawPrediction = UInt8((rawValue >> 7) & UInt16(PredictionState.MASK)) - let prediction = PredictionState(rawValue: rawPrediction) ?? .Unknown + let prediction = PredictionState(rawValue: rawPrediction) ?? .unknown return VirtualSensorsPredictionState(predictionState: prediction, virtualSensors: virtualSensors) } diff --git a/Sources/CombustionBLE/BleManager.swift b/Sources/CombustionBLE/BleManager.swift index 34bebd8..5c9953a 100644 --- a/Sources/CombustionBLE/BleManager.swift +++ b/Sources/CombustionBLE/BleManager.swift @@ -97,7 +97,7 @@ class BleManager : NSObject { initiator.progressDelegate = device - initiator.start(target: connectionPeripheral) + _ = initiator.start(target: connectionPeripheral) } } @@ -141,8 +141,7 @@ extension BleManager: CBCentralManagerDelegate{ if let advData = AdvertisingData(fromData: manufatureData) { // For now, only add probes. - if advData.type == .PROBE { - + if advData.type == .probe { // Store peripheral reference for later use peripherals.insert(peripheral) diff --git a/Sources/CombustionBLE/DeviceManager.swift b/Sources/CombustionBLE/DeviceManager.swift index d8a641b..95b3daa 100644 --- a/Sources/CombustionBLE/DeviceManager.swift +++ b/Sources/CombustionBLE/DeviceManager.swift @@ -163,9 +163,7 @@ public class DeviceManager : ObservableObject { public func setProbeColor(_ device: Device, color: ProbeColor, completionHandler: @escaping (Bool) -> Void) { setColorCompetionHandlers[device.identifier] = MessageHandler(timeSent: Date(), handler: completionHandler) - // TODO JDJ DO not commit -// let request = SetColorRequest(color: color) - let request = SetPredictionRequest(setPointCelsius: 67.8, mode: .REMOVAL_AND_RESTING) + let request = SetColorRequest(color: color) BleManager.shared.sendRequest(identifier: device.identifier, request: request) } diff --git a/Sources/CombustionBLE/Probe.swift b/Sources/CombustionBLE/Probe.swift index c45adc3..8a3130f 100644 --- a/Sources/CombustionBLE/Probe.swift +++ b/Sources/CombustionBLE/Probe.swift @@ -45,7 +45,7 @@ public class Probe : Device { @Published public private(set) var id: ProbeID @Published public private(set) var color: ProbeColor - @Published public private(set) var batteryStatus: BatteryStatus = .OK + @Published public private(set) var batteryStatus: BatteryStatus = .ok private var sessionInformation: SessionInformation? @@ -117,10 +117,10 @@ extension Probe { // notifications to update data if(connectionState != .connected) { - if(advertising.modeId.mode == .Normal) { + if(advertising.modeId.mode == .normal) { currentTemperatures = advertising.temperatures } - else if(advertising.modeId.mode == .InstantRead ){ + else if(advertising.modeId.mode == .instantRead ){ updateInstantRead(advertising.temperatures.values[0]) } @@ -141,14 +141,14 @@ extension Probe { color = deviceStatus.modeId.color batteryStatus = deviceStatus.batteryStatusVirtualSensors.batteryStatus - if(deviceStatus.modeId.mode == .Normal) { + if(deviceStatus.modeId.mode == .normal) { currentTemperatures = deviceStatus.temperatures // Log the temperature data point for "Normal" status updates // Log the temperature data point addDataToLog(LoggedProbeDataPoint.fromDeviceStatus(deviceStatus: deviceStatus)) } - else if(deviceStatus.modeId.mode == .InstantRead ){ + else if(deviceStatus.modeId.mode == .instantRead ){ updateInstantRead(deviceStatus.temperatures.values[0]) } diff --git a/Sources/CombustionBLE/SimulatedProbe.swift b/Sources/CombustionBLE/SimulatedProbe.swift index 49ab5ea..c41fef2 100644 --- a/Sources/CombustionBLE/SimulatedProbe.swift +++ b/Sources/CombustionBLE/SimulatedProbe.swift @@ -82,10 +82,11 @@ class SimulatedProbe: Probe { } let probeStatus = ProbeStatus(minSequenceNumber: firstSeq, - maxSequenceNumber: lastSequence, - temperatures: ProbeTemperatures.withRandomData(), - modeId: ModeId(), - batteryStatusVirtualSensors: BatteryStatusVirtualSensors()) + maxSequenceNumber: lastSequence, + temperatures: ProbeTemperatures.withRandomData(), + modeId: ModeId.defaultValues(), + batteryStatusVirtualSensors: BatteryStatusVirtualSensors.defaultValues(), + predictionStatus: nil) updateProbeStatus(deviceStatus: probeStatus) } diff --git a/Sources/CombustionBLE/UART/LogRequest.swift b/Sources/CombustionBLE/UART/LogRequest.swift index b77cec1..58acafc 100644 --- a/Sources/CombustionBLE/UART/LogRequest.swift +++ b/Sources/CombustionBLE/UART/LogRequest.swift @@ -36,6 +36,6 @@ class LogRequest: Request { var max = maxSequence payload.append(Data(bytes: &max, count: MemoryLayout.size(ofValue: max))) - super.init(payload: payload, type: .Log) + super.init(payload: payload, type: .log) } } diff --git a/Sources/CombustionBLE/UART/MessageType.swift b/Sources/CombustionBLE/UART/MessageType.swift index 790207d..7ae0c4f 100644 --- a/Sources/CombustionBLE/UART/MessageType.swift +++ b/Sources/CombustionBLE/UART/MessageType.swift @@ -27,9 +27,9 @@ SOFTWARE. import Foundation enum MessageType: UInt8 { - case SetID = 1 - case SetColor = 2 - case SessionInfo = 3 - case Log = 4 - case SetPrediction = 5 + case setID = 1 + case setColor = 2 + case sessionInfo = 3 + case log = 4 + case setPrediction = 5 } diff --git a/Sources/CombustionBLE/UART/Response.swift b/Sources/CombustionBLE/UART/Response.swift index 1cd0249..8439f0b 100644 --- a/Sources/CombustionBLE/UART/Response.swift +++ b/Sources/CombustionBLE/UART/Response.swift @@ -118,15 +118,15 @@ extension Response { // print("Success: \(success), payloadLength: \(payloadLength)") switch messageType { - case .Log: + case .log: return LogResponse.fromRaw(data: data, success: success, payloadLength: Int(payloadLength)) - case .SetID: + case .setID: return SetIDResponse(success: success, payLoadLength: Int(payloadLength)) - case .SetColor: + case .setColor: return SetColorResponse(success: success, payLoadLength: Int(payloadLength)) - case .SessionInfo: + case .sessionInfo: return SessionInfoResponse.fromRaw(data: data, success: success, payloadLength: Int(payloadLength)) - case .SetPrediction: + case .setPrediction: return SetPredictionResponse(success: success, payLoadLength: Int(payloadLength)) } } diff --git a/Sources/CombustionBLE/UART/SessionInfo.swift b/Sources/CombustionBLE/UART/SessionInfo.swift index d82eb82..db7dabc 100644 --- a/Sources/CombustionBLE/UART/SessionInfo.swift +++ b/Sources/CombustionBLE/UART/SessionInfo.swift @@ -33,7 +33,7 @@ public struct SessionInformation { class SessionInfoRequest: Request { init() { - super.init(payload: Data(), type: .SessionInfo) + super.init(payload: Data(), type: .sessionInfo) } } diff --git a/Sources/CombustionBLE/UART/SetColor.swift b/Sources/CombustionBLE/UART/SetColor.swift index 411e71c..70b12ff 100644 --- a/Sources/CombustionBLE/UART/SetColor.swift +++ b/Sources/CombustionBLE/UART/SetColor.swift @@ -31,7 +31,7 @@ class SetColorRequest: Request { var payload = Data() payload.append(color.rawValue) - super.init(payload: payload, type: .SetColor) + super.init(payload: payload, type: .setColor) } } diff --git a/Sources/CombustionBLE/UART/SetID.swift b/Sources/CombustionBLE/UART/SetID.swift index 0717a1e..9c04b40 100644 --- a/Sources/CombustionBLE/UART/SetID.swift +++ b/Sources/CombustionBLE/UART/SetID.swift @@ -31,7 +31,7 @@ class SetIDRequest: Request { var payload = Data() payload.append(id.rawValue) - super.init(payload: payload, type: .SetID) + super.init(payload: payload, type: .setID) } } diff --git a/Sources/CombustionBLE/UART/SetPrediction.swift b/Sources/CombustionBLE/UART/SetPrediction.swift index 28787e6..e9f4fe1 100644 --- a/Sources/CombustionBLE/UART/SetPrediction.swift +++ b/Sources/CombustionBLE/UART/SetPrediction.swift @@ -33,7 +33,7 @@ class SetPredictionRequest: Request { let payload = Data(bytes: &rawPayload, count: MemoryLayout.size(ofValue: rawPayload)) - super.init(payload: payload, type: .SetPrediction) + super.init(payload: payload, type: .setPrediction) } } From 6e461dc7af2b5d02dde31dec49148689cda3181a Mon Sep 17 00:00:00 2001 From: Jesse Johnston Date: Tue, 4 Oct 2022 16:44:51 -0700 Subject: [PATCH 6/9] Add methods to start and stop predictions --- Sources/CombustionBLE/DeviceManager.swift | 48 +++++++++++++++++-- Sources/CombustionBLE/SimulatedProbe.swift | 4 +- .../CombustionBLE/UART/SetPrediction.swift | 2 +- 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/Sources/CombustionBLE/DeviceManager.swift b/Sources/CombustionBLE/DeviceManager.swift index 95b3daa..bd7e235 100644 --- a/Sources/CombustionBLE/DeviceManager.swift +++ b/Sources/CombustionBLE/DeviceManager.swift @@ -34,14 +34,18 @@ public class DeviceManager : ObservableObject { /// Singleton accessor for class public static let shared = DeviceManager() + public enum Constants { + static let MINIMUM_PREDICTION_SETPOINT_CELSIUS = 0.0 + static let MAXIMUM_PREDICTION_SETPOINT_CELSIUS = 102.0 + } + /// Dictionary of discovered devices. /// key = string representation of device identifier (UUID) @Published public private(set) var devices : [String: Device] = [String: Device]() - /// Dictionary of discovered probes (subset of devices). /// key = string representation of device identifier (UUID) - public var probes : [String: Probe] { + private var probes : [String: Probe] { get { devices.filter { $0.value is Probe }.mapValues { $0 as! Probe } } @@ -56,11 +60,10 @@ public class DeviceManager : ObservableObject { let handler: (Bool) -> Void } - // Completion handlers for Set ID BLE message + // Completion handlers for Set ID, Set Color, and Set Prediction BLE messages private var setIDCompetionHandlers : [String: MessageHandler] = [:] - - // Completion handlers for Set Color BLE message private var setColorCompetionHandlers : [String: MessageHandler] = [:] + private var setPredictionCompetionHandlers : [String: MessageHandler] = [:] public func addSimulatedProbe() { addDevice(device: SimulatedProbe()) @@ -81,6 +84,7 @@ public class DeviceManager : ObservableObject { Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [self] _ in checkForMessageTimeout(messageHandlers: &setIDCompetionHandlers) checkForMessageTimeout(messageHandlers: &setColorCompetionHandlers) + checkForMessageTimeout(messageHandlers: &setPredictionCompetionHandlers) } } @@ -150,6 +154,7 @@ public class DeviceManager : ObservableObject { /// Set Probe ID on specified device. /// - parameter device: Device to set ID on /// - parameter ProbeID: New Probe ID + /// - parameter completionHandler: Completion handler to be called operation is complete public func setProbeID(_ device: Device, id: ProbeID, completionHandler: @escaping (Bool) -> Void ) { setIDCompetionHandlers[device.identifier] = MessageHandler(timeSent: Date(), handler: completionHandler) @@ -160,6 +165,7 @@ public class DeviceManager : ObservableObject { /// Set Probe Color on specified device. /// - parameter device: Device to set Color on /// - parameter ProbeColor: New Probe color + /// - parameter completionHandler: Completion handler to be called operation is complete public func setProbeColor(_ device: Device, color: ProbeColor, completionHandler: @escaping (Bool) -> Void) { setColorCompetionHandlers[device.identifier] = MessageHandler(timeSent: Date(), handler: completionHandler) @@ -167,6 +173,38 @@ public class DeviceManager : ObservableObject { BleManager.shared.sendRequest(identifier: device.identifier, request: request) } + /// Sends a request to the device to set/change the set point temperature for the time to + /// removal prediction. If a prediction is not currently active, it will be started. If a + /// removal prediction is currently active, then the set point will be modified. If another + /// type of prediction is active, then the probe will start predicting removal. + /// + /// - parameter device: Device to set prediction on + /// - parameter removalTemperatureC: the target removal temperature in Celsius + /// - parameter completionHandler: Completion handler to be called operation is complete + public func setRemovalPrediction(_ device: Device, removalTemperatureC: Double, completionHandler: @escaping (Bool) -> Void) { + guard removalTemperatureC < Constants.MAXIMUM_PREDICTION_SETPOINT_CELSIUS, + removalTemperatureC > Constants.MINIMUM_PREDICTION_SETPOINT_CELSIUS else { + completionHandler(false) + return + } + + setPredictionCompetionHandlers[device.identifier] = MessageHandler(timeSent: Date(), handler: completionHandler) + + let request = SetPredictionRequest(setPointCelsius: removalTemperatureC, mode: .timeToRemoval) + BleManager.shared.sendRequest(identifier: device.identifier, request: request) + } + + + /// Sends a request to the device to set the prediction mode to none, stopping any active prediction. + /// + /// - parameter device: Device to cancel prediction on + /// - parameter completionHandler: Completion handler to be called operation is complete + public func cancelPrediction(_ device: Device, completionHandler: @escaping (Bool) -> Void) { + let request = SetPredictionRequest(setPointCelsius: 0.0, mode: .timeToRemoval) + BleManager.shared.sendRequest(identifier: device.identifier, request: request) + } + + public func runSoftwareUpgrade(_ device: Device, otaFile: URL) -> Bool { do { let dfu = try DFUFirmware(urlToZipFile: otaFile) diff --git a/Sources/CombustionBLE/SimulatedProbe.swift b/Sources/CombustionBLE/SimulatedProbe.swift index c41fef2..6827db1 100644 --- a/Sources/CombustionBLE/SimulatedProbe.swift +++ b/Sources/CombustionBLE/SimulatedProbe.swift @@ -27,8 +27,8 @@ SOFTWARE. import Foundation -class SimulatedProbe: Probe { - init() { +public class SimulatedProbe: Probe { + public init() { let advertising = AdvertisingData(fakeSerial: UInt32.random(in: 0 ..< UINT32_MAX), fakeTemperatures: ProbeTemperatures.withRandomData()) super.init(advertising, isConnectable: true, RSSI: SimulatedProbe.randomeRSSI(), identifier: UUID()) diff --git a/Sources/CombustionBLE/UART/SetPrediction.swift b/Sources/CombustionBLE/UART/SetPrediction.swift index e9f4fe1..512910e 100644 --- a/Sources/CombustionBLE/UART/SetPrediction.swift +++ b/Sources/CombustionBLE/UART/SetPrediction.swift @@ -27,7 +27,7 @@ SOFTWARE. import Foundation class SetPredictionRequest: Request { - init(setPointCelsius: Float, mode: PredictionMode) { + init(setPointCelsius: Double, mode: PredictionMode) { let rawSetPoint = UInt16(setPointCelsius / 0.1) var rawPayload = (UInt16(mode.rawValue) << 10) | (rawSetPoint & 0x3FF) From cc1c92a908110199207684fa8c72fa3374ba6c3b Mon Sep 17 00:00:00 2001 From: Jesse Johnston Date: Thu, 6 Oct 2022 16:05:39 -0700 Subject: [PATCH 7/9] Add prediction information to Probe --- .../BleData/BatteryStatusVirtualSensors.swift | 2 +- .../BleData/PredictionStatus.swift | 22 ++++++------- .../BleData/ProbeTemperatures.swift | 4 +++ .../BleData/VirtualSensors.swift | 8 ++--- Sources/CombustionBLE/DeviceManager.swift | 2 ++ Sources/CombustionBLE/Probe.swift | 33 ++++++++++++++++++- 6 files changed, 54 insertions(+), 17 deletions(-) diff --git a/Sources/CombustionBLE/BleData/BatteryStatusVirtualSensors.swift b/Sources/CombustionBLE/BleData/BatteryStatusVirtualSensors.swift index 66db196..2dff02d 100644 --- a/Sources/CombustionBLE/BleData/BatteryStatusVirtualSensors.swift +++ b/Sources/CombustionBLE/BleData/BatteryStatusVirtualSensors.swift @@ -41,7 +41,7 @@ struct BatteryStatusVirtualSensors { extension BatteryStatusVirtualSensors { private enum Constants { - static let VIRTUAL_SENSORS_SHIFT: UInt8 = 2 + static let VIRTUAL_SENSORS_SHIFT: UInt8 = 1 } static func fromByte(_ byte: UInt8) -> BatteryStatusVirtualSensors { diff --git a/Sources/CombustionBLE/BleData/PredictionStatus.swift b/Sources/CombustionBLE/BleData/PredictionStatus.swift index 31d10cc..0a3f24a 100644 --- a/Sources/CombustionBLE/BleData/PredictionStatus.swift +++ b/Sources/CombustionBLE/BleData/PredictionStatus.swift @@ -26,14 +26,14 @@ SOFTWARE. import Foundation -struct PredictionStatus { - let predictionState: PredictionState - let predictionMode: PredictionMode - let predictionType: PredictionType - let predictionSetPointTemperature: Float - let heatStartTemperature: Float - let predictionValueSeconds: UInt - let estimatedCoreTemperature: Float +public struct PredictionStatus { + public let predictionState: PredictionState + public let predictionMode: PredictionMode + public let predictionType: PredictionType + public let predictionSetPointTemperature: Double + public let heatStartTemperature: Double + public let predictionValueSeconds: UInt + public let estimatedCoreTemperature: Double } extension PredictionStatus { @@ -49,18 +49,18 @@ extension PredictionStatus { // 10 bit field let rawSetPoint = UInt16(bytes[2] & 0x03) << 8 | UInt16(bytes[1]) - let setPoint = Float(rawSetPoint) * 0.1 + let setPoint = Double(rawSetPoint) * 0.1 // 10 bit field let rawHeatStart = UInt16(bytes[3] & 0x0F) << 6 | UInt16(bytes[2] & 0xFC) >> 2 - let heatStart = Float(rawHeatStart) * 0.1 + let heatStart = Double(rawHeatStart) * 0.1 // 17 bit field let seconds = UInt32(bytes[5] & 0x1F) << 12 | UInt32(bytes[4]) << 4 | UInt32(bytes[3] & 0xF0) >> 4 // 11 bit field let rawCore = UInt16(bytes[6]) << 3 | UInt16(bytes[5] & 0xE0) >> 5 - let estimatedCore = (Float(rawCore) * 0.1) - 20.0 + let estimatedCore = (Double(rawCore) * 0.1) - 20.0 return PredictionStatus(predictionState: predictionState, predictionMode: predictionMode, diff --git a/Sources/CombustionBLE/BleData/ProbeTemperatures.swift b/Sources/CombustionBLE/BleData/ProbeTemperatures.swift index 463b2ef..8322e0f 100644 --- a/Sources/CombustionBLE/BleData/ProbeTemperatures.swift +++ b/Sources/CombustionBLE/BleData/ProbeTemperatures.swift @@ -31,6 +31,10 @@ public struct ProbeTemperatures: Equatable { /// Array of probe temperatures. /// Index 0 is the tip sensor, 7 is the handle (ambient) sensor. public let values: [Double] + + public init(values: [Double]) { + self.values = values + } } extension ProbeTemperatures { diff --git a/Sources/CombustionBLE/BleData/VirtualSensors.swift b/Sources/CombustionBLE/BleData/VirtualSensors.swift index ccb63a7..00a7cd5 100644 --- a/Sources/CombustionBLE/BleData/VirtualSensors.swift +++ b/Sources/CombustionBLE/BleData/VirtualSensors.swift @@ -55,10 +55,10 @@ public enum VirtualAmbientSensor: UInt8 { static let MASK: UInt8 = 0x3 } -struct VirtualSensors { - let virtualCore: VirtualCoreSensor - let virtualSurface: VirtualSurfaceSensor - let virtualAmbient: VirtualAmbientSensor +public struct VirtualSensors { + public let virtualCore: VirtualCoreSensor + public let virtualSurface: VirtualSurfaceSensor + public let virtualAmbient: VirtualAmbientSensor } extension VirtualSensors { diff --git a/Sources/CombustionBLE/DeviceManager.swift b/Sources/CombustionBLE/DeviceManager.swift index bd7e235..1d175e6 100644 --- a/Sources/CombustionBLE/DeviceManager.swift +++ b/Sources/CombustionBLE/DeviceManager.swift @@ -200,6 +200,8 @@ public class DeviceManager : ObservableObject { /// - parameter device: Device to cancel prediction on /// - parameter completionHandler: Completion handler to be called operation is complete public func cancelPrediction(_ device: Device, completionHandler: @escaping (Bool) -> Void) { + setPredictionCompetionHandlers[device.identifier] = MessageHandler(timeSent: Date(), handler: completionHandler) + let request = SetPredictionRequest(setPointCelsius: 0.0, mode: .timeToRemoval) BleManager.shared.sendRequest(identifier: device.identifier, request: request) } diff --git a/Sources/CombustionBLE/Probe.swift b/Sources/CombustionBLE/Probe.swift index 8a3130f..d6042db 100644 --- a/Sources/CombustionBLE/Probe.swift +++ b/Sources/CombustionBLE/Probe.swift @@ -47,7 +47,33 @@ public class Probe : Device { @Published public private(set) var batteryStatus: BatteryStatus = .ok - private var sessionInformation: SessionInformation? + @Published public private(set) var virtualSensors: VirtualSensors? + @Published public private(set) var predictionStatus: PredictionStatus? + + public var coreTemperature: Double? { + guard let virtualSensors = virtualSensors, + let currentTemperatures = currentTemperatures else { return nil } + + return currentTemperatures.values[Int(virtualSensors.virtualCore.rawValue)] + } + + public var surfaceTemperature: Double? { + guard let virtualSensors = virtualSensors, + let currentTemperatures = currentTemperatures else { return nil } + + // Surface range is T4 - T7, therefore add 3 + let sensorNumber = Int(virtualSensors.virtualSurface.rawValue) + 3 + return currentTemperatures.values[sensorNumber] + } + + public var ambientTemperature: Double? { + guard let virtualSensors = virtualSensors, + let currentTemperatures = currentTemperatures else { return nil } + + // Ambient range is T5 - T8, therefore add 4 + let sensorNumber = Int(virtualSensors.virtualAmbient.rawValue) + 4 + return currentTemperatures.values[sensorNumber] + } /// Stores historical values of probe temperatures public private(set) var temperatureLogs: [ProbeTemperatureLog] = [] @@ -67,6 +93,8 @@ public class Probe : Device { return String(format: "%012llX", macAddress) } + private var sessionInformation: SessionInformation? + /// Time at which probe instant read was last updated internal var lastInstantRead: Date? @@ -128,6 +156,7 @@ extension Probe { id = advertising.modeId.id color = advertising.modeId.color batteryStatus = advertising.batteryStatusVirtualSensors.batteryStatus + virtualSensors = advertising.batteryStatusVirtualSensors.virtualSensors lastUpdateTime = Date() } @@ -140,6 +169,8 @@ extension Probe { id = deviceStatus.modeId.id color = deviceStatus.modeId.color batteryStatus = deviceStatus.batteryStatusVirtualSensors.batteryStatus + virtualSensors = deviceStatus.batteryStatusVirtualSensors.virtualSensors + predictionStatus = deviceStatus.predictionStatus if(deviceStatus.modeId.mode == .normal) { currentTemperatures = deviceStatus.temperatures From 7d4f838e4e4dbb06658549b40d60dbcaf1974d6e Mon Sep 17 00:00:00 2001 From: Jesse Johnston Date: Thu, 13 Oct 2022 15:13:16 -0700 Subject: [PATCH 8/9] Handle Set prediction response --- .../BleData/PredictionState.swift | 2 +- Sources/CombustionBLE/BleManager.swift | 20 +------ Sources/CombustionBLE/DeviceManager.swift | 58 ++++++++++++++----- Sources/CombustionBLE/Probe.swift | 8 +-- 4 files changed, 49 insertions(+), 39 deletions(-) diff --git a/Sources/CombustionBLE/BleData/PredictionState.swift b/Sources/CombustionBLE/BleData/PredictionState.swift index fa4c1a0..64eb8c7 100644 --- a/Sources/CombustionBLE/BleData/PredictionState.swift +++ b/Sources/CombustionBLE/BleData/PredictionState.swift @@ -30,7 +30,7 @@ import Foundation public enum PredictionState: UInt8 { case probeNotInserted = 0x00 case probeInserted = 0x01 - case warming = 0x02 + case cooking = 0x02 case predicting = 0x03 case removalPredictionDone = 0x04 // * 5: Reserved State 5 diff --git a/Sources/CombustionBLE/BleManager.swift b/Sources/CombustionBLE/BleManager.swift index 5c9953a..a9fd65f 100644 --- a/Sources/CombustionBLE/BleManager.swift +++ b/Sources/CombustionBLE/BleManager.swift @@ -34,12 +34,9 @@ protocol BleManagerDelegate: AnyObject { func didConnectTo(identifier: UUID) func didFailToConnectTo(identifier: UUID) func didDisconnectFrom(identifier: UUID) - func handleSetIDResponse(identifier: UUID, success: Bool) - func handleSetColorResponse(identifier: UUID, success: Bool) func updateDeviceWithAdvertising(advertising: AdvertisingData, isConnectable: Bool, rssi: NSNumber, identifier: UUID) - func updateDeviceWithLogResponse(identifier: UUID, logResponse: LogResponse) - func updateDeviceWithSessionInformation(identifier: UUID, sessionInformation: SessionInformation) func updateDeviceWithStatus(identifier: UUID, status: ProbeStatus) + func handleUARTResponse(identifier: UUID, response: Response) func updateDeviceFwVersion(identifier: UUID, fwVersion: String) func updateDeviceHwRevision(identifier: UUID, hwRevision: String) } @@ -289,20 +286,7 @@ extension BleManager: CBPeripheralDelegate { let responses = Response.fromData(data) for response in responses { - if let logResponse = response as? LogResponse { - delegate?.updateDeviceWithLogResponse(identifier: identifier, logResponse: logResponse) - } - else if let setIDResponse = response as? SetIDResponse { - delegate?.handleSetIDResponse(identifier: identifier, success: setIDResponse.success) - } - else if let setColorResponse = response as? SetColorResponse { - delegate?.handleSetColorResponse(identifier: identifier, success: setColorResponse.success) - } - else if let sessionResponse = response as? SessionInfoResponse { - if(sessionResponse.success) { - delegate?.updateDeviceWithSessionInformation(identifier: identifier, sessionInformation: sessionResponse.info) - } - } + delegate?.handleUARTResponse(identifier: identifier, response: response) } } } diff --git a/Sources/CombustionBLE/DeviceManager.swift b/Sources/CombustionBLE/DeviceManager.swift index 1d175e6..e199942 100644 --- a/Sources/CombustionBLE/DeviceManager.swift +++ b/Sources/CombustionBLE/DeviceManager.swift @@ -268,14 +268,6 @@ extension DeviceManager : BleManagerDelegate { } } - func updateDeviceWithLogResponse(identifier: UUID, logResponse: LogResponse) { - guard logResponse.success else { return } - - if let probe = devices[identifier.uuidString] as? Probe { - probe.processLogResponse(logResponse: logResponse) - } - } - func updateDeviceWithAdvertising(advertising: AdvertisingData, isConnectable: Bool, rssi: NSNumber, identifier: UUID) { if devices[identifier.uuidString] != nil { if let probe = devices[identifier.uuidString] as? Probe { @@ -288,12 +280,6 @@ extension DeviceManager : BleManagerDelegate { } } - func updateDeviceWithSessionInformation(identifier: UUID, sessionInformation: SessionInformation) { - if let probe = devices[identifier.uuidString] as? Probe { - probe.updateWithSessionInformation(sessionInformation) - } - } - func updateDeviceFwVersion(identifier: UUID, fwVersion: String) { if let device = devices[identifier.uuidString] { device.firmareVersion = fwVersion @@ -314,15 +300,55 @@ extension DeviceManager : BleManagerDelegate { } } - func handleSetIDResponse(identifier: UUID, success: Bool) { + func handleUARTResponse(identifier: UUID, response: Response) { + if let logResponse = response as? LogResponse { + updateDeviceWithLogResponse(identifier: identifier, logResponse: logResponse) + } + else if let setIDResponse = response as? SetIDResponse { + handleSetIDResponse(identifier: identifier, success: setIDResponse.success) + } + else if let setColorResponse = response as? SetColorResponse { + handleSetColorResponse(identifier: identifier, success: setColorResponse.success) + } + else if let sessionResponse = response as? SessionInfoResponse { + if(sessionResponse.success) { + updateDeviceWithSessionInformation(identifier: identifier, sessionInformation: sessionResponse.info) + } + } + else if let setPredictionResponse = response as? SetPredictionResponse { + handleSetPredictionRespone(identifier: identifier, success: setPredictionResponse.success) + } + } + + private func updateDeviceWithLogResponse(identifier: UUID, logResponse: LogResponse) { + guard logResponse.success else { return } + + if let probe = devices[identifier.uuidString] as? Probe { + probe.processLogResponse(logResponse: logResponse) + } + } + + private func updateDeviceWithSessionInformation(identifier: UUID, sessionInformation: SessionInformation) { + if let probe = devices[identifier.uuidString] as? Probe { + probe.updateWithSessionInformation(sessionInformation) + } + } + + private func handleSetIDResponse(identifier: UUID, success: Bool) { setIDCompetionHandlers[identifier.uuidString]?.handler(success) setIDCompetionHandlers.removeValue(forKey: identifier.uuidString) } - func handleSetColorResponse(identifier: UUID, success: Bool) { + private func handleSetColorResponse(identifier: UUID, success: Bool) { setColorCompetionHandlers[identifier.uuidString]?.handler(success) setColorCompetionHandlers.removeValue(forKey: identifier.uuidString) } + + private func handleSetPredictionRespone(identifier: UUID, success: Bool) { + setPredictionCompetionHandlers[identifier.uuidString]?.handler(success) + + setPredictionCompetionHandlers.removeValue(forKey: identifier.uuidString) + } } diff --git a/Sources/CombustionBLE/Probe.swift b/Sources/CombustionBLE/Probe.swift index d6042db..e090b04 100644 --- a/Sources/CombustionBLE/Probe.swift +++ b/Sources/CombustionBLE/Probe.swift @@ -169,14 +169,14 @@ extension Probe { id = deviceStatus.modeId.id color = deviceStatus.modeId.color batteryStatus = deviceStatus.batteryStatusVirtualSensors.batteryStatus - virtualSensors = deviceStatus.batteryStatusVirtualSensors.virtualSensors - predictionStatus = deviceStatus.predictionStatus if(deviceStatus.modeId.mode == .normal) { - currentTemperatures = deviceStatus.temperatures + // Prediction status and virtual sensors are only transmitter in "Normal" status updated + predictionStatus = deviceStatus.predictionStatus + virtualSensors = deviceStatus.batteryStatusVirtualSensors.virtualSensors // Log the temperature data point for "Normal" status updates - // Log the temperature data point + currentTemperatures = deviceStatus.temperatures addDataToLog(LoggedProbeDataPoint.fromDeviceStatus(deviceStatus: deviceStatus)) } else if(deviceStatus.modeId.mode == .instantRead ){ From ab0ad5a9b723e10c656a85220e4863a3077fd5e4 Mon Sep 17 00:00:00 2001 From: Jason Machacek Date: Mon, 17 Oct 2022 20:04:26 -0700 Subject: [PATCH 9/9] Fixed issue where predictions weren't properly canceling. --- Sources/CombustionBLE/DeviceManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CombustionBLE/DeviceManager.swift b/Sources/CombustionBLE/DeviceManager.swift index e199942..35757bc 100644 --- a/Sources/CombustionBLE/DeviceManager.swift +++ b/Sources/CombustionBLE/DeviceManager.swift @@ -202,7 +202,7 @@ public class DeviceManager : ObservableObject { public func cancelPrediction(_ device: Device, completionHandler: @escaping (Bool) -> Void) { setPredictionCompetionHandlers[device.identifier] = MessageHandler(timeSent: Date(), handler: completionHandler) - let request = SetPredictionRequest(setPointCelsius: 0.0, mode: .timeToRemoval) + let request = SetPredictionRequest(setPointCelsius: 0.0, mode: .none) BleManager.shared.sendRequest(identifier: device.identifier, request: request) }