CommandSession.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. //
  2. // RileyLinkCmdSession.swift
  3. // RileyLinkBLEKit
  4. //
  5. // Created by Pete Schwamb on 10/8/17.
  6. // Copyright © 2017 Pete Schwamb. All rights reserved.
  7. //
  8. import Foundation
  9. public enum RXFilterMode: UInt8 {
  10. case wide = 0x50 // 300KHz
  11. case narrow = 0x90 // 150KHz
  12. }
  13. public enum SoftwareEncodingType: UInt8 {
  14. case none = 0x00
  15. case manchester = 0x01
  16. case fourbsixb = 0x02
  17. }
  18. public enum CC111XRegister: UInt8 {
  19. case sync1 = 0x00
  20. case sync0 = 0x01
  21. case pktlen = 0x02
  22. case pktctrl1 = 0x03
  23. case pktctrl0 = 0x04
  24. case fsctrl1 = 0x07
  25. case freq2 = 0x09
  26. case freq1 = 0x0a
  27. case freq0 = 0x0b
  28. case mdmcfg4 = 0x0c
  29. case mdmcfg3 = 0x0d
  30. case mdmcfg2 = 0x0e
  31. case mdmcfg1 = 0x0f
  32. case mdmcfg0 = 0x10
  33. case deviatn = 0x11
  34. case mcsm0 = 0x14
  35. case foccfg = 0x15
  36. case agcctrl2 = 0x17
  37. case agcctrl1 = 0x18
  38. case agcctrl0 = 0x19
  39. case frend1 = 0x1a
  40. case frend0 = 0x1b
  41. case fscal3 = 0x1c
  42. case fscal2 = 0x1d
  43. case fscal1 = 0x1e
  44. case fscal0 = 0x1f
  45. case test1 = 0x24
  46. case test0 = 0x25
  47. case paTable0 = 0x2e
  48. }
  49. public struct RileyLinkStatistics {
  50. public let uptime: TimeInterval
  51. public let radioRxOverflowCount: UInt16
  52. public let radioRxFifoOverflowCount: UInt16
  53. public let packetRxCount: UInt16
  54. public let packetTxCount: UInt16
  55. public let crcFailureCount: UInt16
  56. public let spiSyncFailureCount: UInt16
  57. public init(uptime: TimeInterval, radioRxOverflowCount: UInt16, radioRxFifoOverflowCount: UInt16, packetRxCount: UInt16, packetTxCount: UInt16, crcFailureCount: UInt16, spiSyncFailureCount: UInt16) {
  58. self.uptime = uptime
  59. self.radioRxOverflowCount = radioRxOverflowCount
  60. self.radioRxFifoOverflowCount = radioRxFifoOverflowCount
  61. self.packetRxCount = packetRxCount
  62. self.packetTxCount = packetTxCount
  63. self.crcFailureCount = crcFailureCount
  64. self.spiSyncFailureCount = spiSyncFailureCount
  65. }
  66. }
  67. public struct CommandSession {
  68. let manager: PeripheralManager
  69. let responseType: PeripheralManager.ResponseType
  70. public let firmwareVersion: RadioFirmwareVersion
  71. /// Invokes a command expecting a response
  72. ///
  73. /// Unsuccessful responses are thrown as errors.
  74. ///
  75. /// - Parameters:
  76. /// - command: The command
  77. /// - timeout: The amount of time to wait for the pump to respond before throwing a timeout error. This should not include any expected BLE latency.
  78. /// - Returns: The successful response
  79. /// - Throws: RileyLinkDeviceError
  80. private func writeCommand<C: Command>(_ command: C, timeout: TimeInterval) throws -> C.ResponseType {
  81. let response = try manager.writeCommand(command,
  82. timeout: timeout + PeripheralManager.expectedMaxBLELatency,
  83. responseType: responseType
  84. )
  85. switch response.code {
  86. case .rxTimeout:
  87. throw RileyLinkDeviceError.responseTimeout
  88. case .commandInterrupted:
  89. throw RileyLinkDeviceError.responseTimeout
  90. case .zeroData:
  91. throw RileyLinkDeviceError.invalidResponse(Data())
  92. case .invalidParam, .unknownCommand:
  93. throw RileyLinkDeviceError.invalidInput(command.data.hexadecimalString)
  94. case .success:
  95. return response
  96. }
  97. }
  98. /// Invokes a command expecting an RF packet response
  99. ///
  100. /// - Parameters:
  101. /// - command: The command
  102. /// - timeout: The amount of time to wait for the pump to respond before throwing a timeout error. This should not include any expected BLE latency.
  103. /// - Returns: The successful packet response
  104. /// - Throws: RileyLinkDeviceError
  105. private func writeCommand<C: Command>(_ command: C, timeout: TimeInterval) throws -> RFPacket where C.ResponseType == PacketResponse {
  106. let response: C.ResponseType = try writeCommand(command, timeout: timeout)
  107. guard let packet = response.packet else {
  108. throw RileyLinkDeviceError.invalidResponse(Data())
  109. }
  110. return packet
  111. }
  112. /// - Throws: RileyLinkDeviceError
  113. public func updateRegister(_ address: CC111XRegister, value: UInt8) throws {
  114. let command = UpdateRegister(address, value: value, firmwareVersion: firmwareVersion)
  115. _ = try writeCommand(command, timeout: 0)
  116. }
  117. /// - Throws: RileyLinkDeviceError
  118. public func readRegister(_ address: CC111XRegister) throws -> UInt8 {
  119. guard firmwareVersion.supportsReadRegister else {
  120. throw RileyLinkDeviceError.unsupportedCommand(String(describing: RileyLinkCommand.readRegister))
  121. }
  122. let command = ReadRegister(address, firmwareVersion: firmwareVersion)
  123. let response: ReadRegisterResponse = try writeCommand(command, timeout: 0)
  124. guard response.code == .success else {
  125. throw RileyLinkDeviceError.invalidInput("Unsupported register: \(String(describing: address))")
  126. }
  127. return response.value
  128. }
  129. /// - Throws: RileyLinkDeviceError
  130. public func enableCCLEDs() throws {
  131. let enableBlue = SetLEDMode(.blue, mode: .auto)
  132. _ = try writeCommand(enableBlue, timeout: 0)
  133. let enableGreen = SetLEDMode(.green, mode: .auto)
  134. _ = try writeCommand(enableGreen, timeout: 0)
  135. }
  136. private static let xtalFrequency = Measurement<UnitFrequency>(value: 24, unit: .megahertz)
  137. /// - Throws: RileyLinkDeviceError
  138. public func setBaseFrequency(_ frequency: Measurement<UnitFrequency>) throws {
  139. let val = Int(
  140. frequency.converted(to: .hertz).value /
  141. (CommandSession.xtalFrequency / pow(2, 16)).converted(to: .hertz).value)
  142. try updateRegister(.freq0, value: UInt8(val & 0xff))
  143. try updateRegister(.freq1, value: UInt8((val >> 8) & 0xff))
  144. try updateRegister(.freq2, value: UInt8((val >> 16) & 0xff))
  145. }
  146. public func readBaseFrequency() throws -> Measurement<UnitFrequency> {
  147. let freq0 = try readRegister(.freq0)
  148. let freq1 = try readRegister(.freq1)
  149. let freq2 = try readRegister(.freq2)
  150. let value = Double(UInt32(freq0) + (UInt32(freq1) << 8) + (UInt32(freq2) << 16))
  151. let frequency = value * (CommandSession.xtalFrequency / pow(2, 16)).converted(to: .hertz).value
  152. return Measurement<UnitFrequency>(value: frequency, unit: .hertz).converted(to: .megahertz)
  153. }
  154. /// Sends data to the pump, listening for a reply
  155. ///
  156. /// - Parameters:
  157. /// - data: The data to send
  158. /// - repeatCount: The number of times to repeat the message before listening begins
  159. /// - timeout: The length of time to listen for a response before timing out
  160. /// - retryCount: The number of times to repeat the send & listen sequence
  161. /// - preambleExtension: If set, the length of time to continuously send preamble data. Overrides the register based preamble settings.
  162. /// - Returns: The packet reply
  163. /// - Throws: RileyLinkDeviceError
  164. public func sendAndListen(_ data: Data, repeatCount: Int, timeout: TimeInterval, retryCount: Int, preambleExtension: TimeInterval?) throws -> RFPacket {
  165. let delayBetweenPackets: TimeInterval = 0
  166. let command = SendAndListen(
  167. outgoing: data,
  168. sendChannel: 0,
  169. repeatCount: UInt8(clamping: repeatCount),
  170. delayBetweenPacketsMS: UInt16(clamping: Int(delayBetweenPackets)),
  171. listenChannel: 0,
  172. timeoutMS: UInt32(clamping: Int(timeout.milliseconds)),
  173. retryCount: UInt8(clamping: retryCount),
  174. preambleExtensionMS: UInt16(clamping: Int(preambleExtension?.milliseconds ?? 0)),
  175. firmwareVersion: firmwareVersion
  176. )
  177. // At least 17 ms between packets for radio to stop/start
  178. let radioTimeBetweenPackets = TimeInterval(milliseconds: 17)
  179. let timeBetweenPackets = delayBetweenPackets + radioTimeBetweenPackets
  180. // 16384 = bitrate, 8 = bits per byte
  181. let singlePacketSendTime: TimeInterval = (Double(data.count * 8) / 16_384)
  182. let preambleTime = preambleExtension ?? 0
  183. let totalRepeatSendTime: TimeInterval = (singlePacketSendTime + timeBetweenPackets + preambleTime) * Double(repeatCount+1)
  184. let totalTimeout = (totalRepeatSendTime + timeout) * Double(retryCount + 1)
  185. return try writeCommand(command, timeout: totalTimeout)
  186. }
  187. /// Sends data to the pump, listening for a reply
  188. ///
  189. /// - Parameters:
  190. /// - data: The data to send
  191. /// - repeatCount: The number of times to repeat the message before listening begins
  192. /// - timeout: The length of time to listen for a response before timing out
  193. /// - retryCount: The number of times to repeat the send & listen sequence if no response is heard
  194. /// - Returns: The packet reply
  195. /// - Throws: RileyLinkDeviceError
  196. public func sendAndListen(_ data: Data, repeatCount: Int, timeout: TimeInterval, retryCount: Int) throws -> RFPacket {
  197. return try sendAndListen(data, repeatCount: repeatCount, timeout: timeout, retryCount: retryCount, preambleExtension: nil)
  198. }
  199. /// - Throws: RileyLinkDeviceError
  200. public func listen(onChannel channel: Int, timeout: TimeInterval) throws -> RFPacket? {
  201. let command = GetPacket(
  202. listenChannel: 0,
  203. timeoutMS: UInt32(clamping: Int(timeout.milliseconds))
  204. )
  205. return try writeCommand(command, timeout: timeout)
  206. }
  207. /// - Throws: RileyLinkDeviceError
  208. public func send(_ data: Data, onChannel channel: Int, timeout: TimeInterval) throws {
  209. let command = SendPacket(
  210. outgoing: data,
  211. sendChannel: UInt8(clamping: channel),
  212. repeatCount: 0,
  213. delayBetweenPacketsMS: 0,
  214. preambleExtensionMS: 0,
  215. firmwareVersion: firmwareVersion
  216. )
  217. _ = try writeCommand(command, timeout: timeout)
  218. }
  219. /// - Throws: RileyLinkDeviceError
  220. public func setSoftwareEncoding(_ swEncodingType: SoftwareEncodingType) throws {
  221. guard firmwareVersion.supportsSoftwareEncoding else {
  222. throw RileyLinkDeviceError.unsupportedCommand(String(describing: RileyLinkCommand.setSWEncoding))
  223. }
  224. let command = SetSoftwareEncoding(swEncodingType)
  225. let response = try writeCommand(command, timeout: 0)
  226. guard response.code == .success else {
  227. throw RileyLinkDeviceError.invalidInput(String(describing: swEncodingType))
  228. }
  229. }
  230. public func resetRadioConfig() throws {
  231. guard firmwareVersion.supportsResetRadioConfig else {
  232. return
  233. }
  234. let command = ResetRadioConfig()
  235. _ = try writeCommand(command, timeout: 0)
  236. }
  237. public func getRileyLinkStatistics() throws -> RileyLinkStatistics {
  238. guard firmwareVersion.supportsRileyLinkStatistics else {
  239. throw RileyLinkDeviceError.unsupportedCommand(String(describing: RileyLinkCommand.getStatistics))
  240. }
  241. let command = GetStatistics()
  242. let response: GetStatisticsResponse = try writeCommand(command, timeout: 0)
  243. return response.statistics
  244. }
  245. public func setPreamble(_ preambleValue: UInt16) throws {
  246. guard firmwareVersion.supportsCustomPreamble else {
  247. throw RileyLinkDeviceError.unsupportedCommand(String(describing: RileyLinkCommand.setPreamble))
  248. }
  249. let command = SetPreamble(preambleValue)
  250. _ = try writeCommand(command, timeout: 0)
  251. }
  252. /// Asserts that the caller is currently on the session's queue
  253. public func assertOnSessionQueue() {
  254. dispatchPrecondition(condition: .onQueue(manager.queue))
  255. }
  256. }