diff --git a/Sources/CombustionBLE/BleData/AdvertisingData.swift b/Sources/CombustionBLE/BleData/AdvertisingData.swift index e37a97c..0524b48 100644 --- a/Sources/CombustionBLE/BleData/AdvertisingData.swift +++ b/Sources/CombustionBLE/BleData/AdvertisingData.swift @@ -28,39 +28,35 @@ 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. -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 - /// Prode ID - public let id: ProbeID - /// Probe Color - public let color: ProbeColor - /// Probe mode - public let mode: ProbeMode - /// Battery Status - public let batteryStatus: BatteryStatus + let temperatures: ProbeTemperatures + // ModeId (Probe color, ID, and mode) + 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 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 } -} - -extension AdvertisingData { + init?(fromData : Data?) { guard let data = fromData else { return nil } guard data.count >= 20 else { return nil } @@ -68,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) @@ -91,26 +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 modeIdColorData = data.subdata(in: Constants.MODE_COLOR_ID_RANGE) - id = ProbeID.fromRawData(data: modeIdColorData) - color = ProbeColor.fromRawData(data: modeIdColorData) - mode = ProbeMode.fromRawData(data: modeIdColorData) - } - 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.defaultValues() } - // 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 statusData = data.subdata(in: Constants.BATTERY_STATUS_RANGE) - batteryStatus = BatteryStatus.fromRawData(data: statusData) - } - else { - batteryStatus = .OK + let byte = data.subdata(in: Constants.DEVICE_STATUS_RANGE)[0] + batteryStatusVirtualSensors = BatteryStatusVirtualSensors.fromByte(byte) + } else { + batteryStatusVirtualSensors = BatteryStatusVirtualSensors.defaultValues() } } } @@ -119,23 +109,19 @@ extension AdvertisingData { extension AdvertisingData { // Fake data initializer for previews public init(fakeSerial: UInt32) { - type = .PROBE + type = .probe temperatures = ProbeTemperatures.withFakeData() serialNumber = fakeSerial - id = .ID1 - color = .COLOR1 - mode = .Normal - batteryStatus = .OK + 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 - id = .ID1 - color = .COLOR1 - mode = .Normal - batteryStatus = .OK + modeId = ModeId.defaultValues() + batteryStatusVirtualSensors = BatteryStatusVirtualSensors.defaultValues() } } diff --git a/Sources/CombustionBLE/BleData/BatteryStatusVirtualSensors.swift b/Sources/CombustionBLE/BleData/BatteryStatusVirtualSensors.swift new file mode 100644 index 0000000..2dff02d --- /dev/null +++ b/Sources/CombustionBLE/BleData/BatteryStatusVirtualSensors.swift @@ -0,0 +1,59 @@ +// BatteryStatusVirtualSensors.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 + +/// Enumeration of Battery status +public enum BatteryStatus: UInt8 { + case ok = 0x00 + case low = 0x01 + + static let MASK: UInt8 = 0x3 +} + +struct BatteryStatusVirtualSensors { + let batteryStatus: BatteryStatus + let virtualSensors: VirtualSensors +} + +extension BatteryStatusVirtualSensors { + + private enum Constants { + static let VIRTUAL_SENSORS_SHIFT: UInt8 = 1 + } + + 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) + } + + static func defaultValues() -> BatteryStatusVirtualSensors { + return BatteryStatusVirtualSensors(batteryStatus: .ok, + virtualSensors: VirtualSensors(virtualCore: .T1, virtualSurface: .T4, virtualAmbient: .T5)) + } +} diff --git a/Sources/CombustionBLE/BleData/ProbeID.swift b/Sources/CombustionBLE/BleData/HopCount.swift similarity index 63% rename from Sources/CombustionBLE/BleData/ProbeID.swift rename to Sources/CombustionBLE/BleData/HopCount.swift index 742ad93..2ada9f7 100644 --- a/Sources/CombustionBLE/BleData/ProbeID.swift +++ b/Sources/CombustionBLE/BleData/HopCount.swift @@ -1,9 +1,9 @@ -// ProbeID.swift +// HopCount.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,25 +26,21 @@ 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 HopCount: UInt8, CaseIterable { + case hop1 = 0x00 + case hop2 = 0x01 + case hop3 = 0x02 + case hop4 = 0x03 +} + +extension HopCount { private enum Constants { - static let PRODE_ID_MASK: UInt8 = 0x7 - static let PRODE_ID_SHIFT: UInt8 = 5 + static let HOP_COUNT_MASK: UInt8 = 0x3 + static let HOP_COUNT_SHIFT: UInt8 = 6 } - 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 - return ProbeID(rawValue: rawProbeID) ?? .ID1 + 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/ModeId.swift b/Sources/CombustionBLE/BleData/ModeId.swift new file mode 100644 index 0000000..05b3acc --- /dev/null +++ b/Sources/CombustionBLE/BleData/ModeId.swift @@ -0,0 +1,89 @@ +// ModeId.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 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 + 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 +} + +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 + static let PRODE_COLOR_MASK: UInt8 = 0x7 + static let PRODE_COLOR_SHIFT: UInt8 = 2 + static let PRODE_MODE_MASK: UInt8 = 0x3 + } + + 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) + } + + static func defaultValues() -> ModeId { + return ModeId(id: .ID1, color: .color1, mode: .normal) + } +} diff --git a/Sources/CombustionBLE/BleData/BatteryStatus.swift b/Sources/CombustionBLE/BleData/PredictionMode.swift similarity index 70% rename from Sources/CombustionBLE/BleData/BatteryStatus.swift rename to Sources/CombustionBLE/BleData/PredictionMode.swift index 3c8d5f8..2d2d01a 100644 --- a/Sources/CombustionBLE/BleData/BatteryStatus.swift +++ b/Sources/CombustionBLE/BleData/PredictionMode.swift @@ -1,4 +1,4 @@ -// BatteryStatus.swift +// PredictionMode.swift /*-- MIT License @@ -23,21 +23,14 @@ 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 BatteryStatus: UInt8 { - case OK = 0x00 - case LOW = 0x01 - - private enum Constants { - static let BATTERY_MASK: UInt8 = 0x3 - } +public enum PredictionMode: UInt8, CaseIterable { + case none = 0x00 + case timeToRemoval = 0x01 + case removalAndResting = 0x02 + case reserved = 0x03 - static func fromRawData(data: Data) -> BatteryStatus { - let statusBytes = [UInt8](data) - - let rawStatus = (statusBytes[0] & (Constants.BATTERY_MASK)) - return BatteryStatus(rawValue: rawStatus) ?? .OK - } + 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..64eb8c7 --- /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 cooking = 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..0a3f24a --- /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 + +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 { + 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 = Double(rawSetPoint) * 0.1 + + // 10 bit field + let rawHeatStart = UInt16(bytes[3] & 0x0F) << 6 | UInt16(bytes[2] & 0xFC) >> 2 + 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 = (Double(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/DeviceStatus.swift b/Sources/CombustionBLE/BleData/ProbeStatus.swift similarity index 58% rename from Sources/CombustionBLE/BleData/DeviceStatus.swift rename to Sources/CombustionBLE/BleData/ProbeStatus.swift index 7d10d31..4d8aa29 100644 --- a/Sources/CombustionBLE/BleData/DeviceStatus.swift +++ b/Sources/CombustionBLE/BleData/ProbeStatus.swift @@ -27,35 +27,34 @@ 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 - /// Prode ID - public let id: ProbeID - /// Probe Color - public let color: ProbeColor - /// Probe mode - public let mode: ProbeMode - /// Battery Status - public let batteryStatus: BatteryStatus - + let temperatures: ProbeTemperatures + // ModeId (Probe color, ID, and mode) + 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 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 + static let PREDICTION_STATUS_RANGE = 23..<30 } -} - -extension DeviceStatus { + 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 { @@ -71,26 +70,28 @@ 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 - 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) - } - else { - id = .ID1 - color = .COLOR1 - mode = .Normal + // 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.defaultValues() } - // Decode battery status if its present in the advertising packet - if(data.count >= 23) { - let statusData = data.subdata(in: Constants.BATTERY_STATUS_RANGE) - batteryStatus = BatteryStatus.fromRawData(data: statusData) + // 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.defaultValues() } - else { - batteryStatus = .OK + + // 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/VirtualSensors.swift b/Sources/CombustionBLE/BleData/VirtualSensors.swift new file mode 100644 index 0000000..00a7cd5 --- /dev/null +++ b/Sources/CombustionBLE/BleData/VirtualSensors.swift @@ -0,0 +1,82 @@ +// 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 +} + +public struct VirtualSensors { + public let virtualCore: VirtualCoreSensor + public let virtualSurface: VirtualSurfaceSensor + public let virtualAmbient: VirtualAmbientSensor +} + +extension VirtualSensors { + private enum Constants { + static let VIRTUAL_SURFACE_SHIFT: UInt8 = 3 + static let VIRTUAL_AMBIENT_SHIFT: UInt8 = 5 + } + + 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/BleData/ProbeColor.swift b/Sources/CombustionBLE/BleData/VirtualSensorsPredictionState.swift similarity index 59% rename from Sources/CombustionBLE/BleData/ProbeColor.swift rename to Sources/CombustionBLE/BleData/VirtualSensorsPredictionState.swift index 860ed3d..d5fbbd6 100644 --- a/Sources/CombustionBLE/BleData/ProbeColor.swift +++ b/Sources/CombustionBLE/BleData/VirtualSensorsPredictionState.swift @@ -1,9 +1,9 @@ -// ProbeColor.swift +// BatteryStatusVirtualSensors.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,25 +26,19 @@ SOFTWARE. import Foundation -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 - - private enum Constants { - static let PRODE_COLOR_MASK: UInt8 = 0x7 - static let PRODE_COLOR_SHIFT: UInt8 = 2 - } - - static func fromRawData(data: Data) -> ProbeColor { - let modeIdColorBytes = [UInt8](data) +struct VirtualSensorsPredictionState { + let predictionState: PredictionState + let 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 rawProbeColor = (modeIdColorBytes[0] & (Constants.PRODE_COLOR_MASK << Constants.PRODE_COLOR_SHIFT)) >> Constants.PRODE_COLOR_SHIFT - return ProbeColor(rawValue: rawProbeColor) ?? .COLOR1 + return VirtualSensorsPredictionState(predictionState: prediction, virtualSensors: virtualSensors) } } diff --git a/Sources/CombustionBLE/BleManager.swift b/Sources/CombustionBLE/BleManager.swift index 2985565..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: DeviceStatus) + func updateDeviceWithStatus(identifier: UUID, status: ProbeStatus) + func handleUARTResponse(identifier: UUID, response: Response) func updateDeviceFwVersion(identifier: UUID, fwVersion: String) func updateDeviceHwRevision(identifier: UUID, hwRevision: String) } @@ -97,7 +94,7 @@ class BleManager : NSObject { initiator.progressDelegate = device - initiator.start(target: connectionPeripheral) + _ = initiator.start(target: connectionPeripheral) } } @@ -141,8 +138,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) @@ -258,7 +254,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) } } @@ -290,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 fc51ef5..35757bc 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,40 @@ 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) { + setPredictionCompetionHandlers[device.identifier] = MessageHandler(timeSent: Date(), handler: completionHandler) + + let request = SetPredictionRequest(setPointCelsius: 0.0, mode: .none) + BleManager.shared.sendRequest(identifier: device.identifier, request: request) + } + + public func runSoftwareUpgrade(_ device: Device, otaFile: URL) -> Bool { do { let dfu = try DFUFirmware(urlToZipFile: otaFile) @@ -222,20 +262,12 @@ 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) } } - 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 { @@ -248,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 @@ -274,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/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..e090b04 100644 --- a/Sources/CombustionBLE/Probe.swift +++ b/Sources/CombustionBLE/Probe.swift @@ -45,9 +45,35 @@ 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? + @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,13 +93,15 @@ 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? - 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 + id = advertising.modeId.id + color = advertising.modeId.color super.init(identifier: identifier, RSSI: RSSI) @@ -117,38 +145,41 @@ 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 + virtualSensors = advertising.batteryStatusVirtualSensors.virtualSensors lastUpdateTime = Date() } } /// 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 - color = deviceStatus.color - batteryStatus = deviceStatus.batteryStatus + id = deviceStatus.modeId.id + color = deviceStatus.modeId.color + batteryStatus = deviceStatus.batteryStatusVirtualSensors.batteryStatus - if(deviceStatus.mode == .Normal) { - currentTemperatures = deviceStatus.temperatures + if(deviceStatus.modeId.mode == .normal) { + // 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.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 810e877..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()) @@ -81,14 +81,13 @@ class SimulatedProbe: Probe { lastSequence = 0 } - let deviceStatus = DeviceStatus(minSequenceNumber: firstSeq, - maxSequenceNumber: lastSequence, - temperatures: ProbeTemperatures.withRandomData(), - id: .ID1, - color: .COLOR1, - mode: .Normal, - batteryStatus: .OK) + let probeStatus = ProbeStatus(minSequenceNumber: firstSeq, + maxSequenceNumber: lastSequence, + temperatures: ProbeTemperatures.withRandomData(), + modeId: ModeId.defaultValues(), + batteryStatusVirtualSensors: BatteryStatusVirtualSensors.defaultValues(), + predictionStatus: nil) - updateProbeStatus(deviceStatus: deviceStatus) + 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/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/UART/MessageType.swift b/Sources/CombustionBLE/UART/MessageType.swift index 5622069..7ae0c4f 100644 --- a/Sources/CombustionBLE/UART/MessageType.swift +++ b/Sources/CombustionBLE/UART/MessageType.swift @@ -27,8 +27,9 @@ SOFTWARE. import Foundation enum MessageType: UInt8 { - case SetID = 1 - case SetColor = 2 - case SessionInfo = 3 - case Log = 4 + 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 aa404f5..8439f0b 100644 --- a/Sources/CombustionBLE/UART/Response.swift +++ b/Sources/CombustionBLE/UART/Response.swift @@ -118,14 +118,16 @@ 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: + 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/BleData/ProbeMode.swift b/Sources/CombustionBLE/UART/SetPrediction.swift similarity index 69% rename from Sources/CombustionBLE/BleData/ProbeMode.swift rename to Sources/CombustionBLE/UART/SetPrediction.swift index ddb0728..512910e 100644 --- a/Sources/CombustionBLE/BleData/ProbeMode.swift +++ b/Sources/CombustionBLE/UART/SetPrediction.swift @@ -1,4 +1,4 @@ -// ProbeMode.swift +// SetPrediction.swift /*-- MIT License @@ -26,20 +26,15 @@ 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 fromRawData(data: Data) -> ProbeMode { - let modeIdColorBytes = [UInt8](data) +class SetPredictionRequest: Request { + init(setPointCelsius: Double, 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)) - let rawProbeID = modeIdColorBytes[0] & (Constants.PRODE_MODE_MASK) - return ProbeMode(rawValue: rawProbeID) ?? .Normal + super.init(payload: payload, type: .setPrediction) } } + +class SetPredictionResponse : Response { } 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)} + } }