CommandSession.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  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 setCCLEDMode(_ mode: RileyLinkLEDMode) throws {
  131. let ccMode: RileyLinkLEDMode
  132. switch mode {
  133. case .on:
  134. ccMode = .auto
  135. default:
  136. ccMode = .off
  137. }
  138. let enableBlue = SetLEDMode(.blue, mode: ccMode)
  139. _ = try writeCommand(enableBlue, timeout: 0)
  140. let enableGreen = SetLEDMode(.green, mode: ccMode)
  141. _ = try writeCommand(enableGreen, timeout: 0)
  142. }
  143. private static let xtalFrequency = Measurement<UnitFrequency>(value: 24, unit: .megahertz)
  144. /// - Throws: RileyLinkDeviceError
  145. public func setBaseFrequency(_ frequency: Measurement<UnitFrequency>) throws {
  146. let val = Int(
  147. frequency.converted(to: .hertz).value /
  148. (CommandSession.xtalFrequency / pow(2, 16)).converted(to: .hertz).value)
  149. try updateRegister(.freq0, value: UInt8(val & 0xff))
  150. try updateRegister(.freq1, value: UInt8((val >> 8) & 0xff))
  151. try updateRegister(.freq2, value: UInt8((val >> 16) & 0xff))
  152. }
  153. public func readBaseFrequency() throws -> Measurement<UnitFrequency> {
  154. let freq0 = try readRegister(.freq0)
  155. let freq1 = try readRegister(.freq1)
  156. let freq2 = try readRegister(.freq2)
  157. let value = Double(UInt32(freq0) + (UInt32(freq1) << 8) + (UInt32(freq2) << 16))
  158. let frequency = value * (CommandSession.xtalFrequency / pow(2, 16)).converted(to: .hertz).value
  159. return Measurement<UnitFrequency>(value: frequency, unit: .hertz).converted(to: .megahertz)
  160. }
  161. /// Sends data to the pump, listening for a reply
  162. ///
  163. /// - Parameters:
  164. /// - data: The data to send
  165. /// - repeatCount: The number of times to repeat the message before listening begins
  166. /// - timeout: The length of time to listen for a response before timing out
  167. /// - retryCount: The number of times to repeat the send & listen sequence
  168. /// - preambleExtension: If set, the length of time to continuously send preamble data. Overrides the register based preamble settings.
  169. /// - Returns: The packet reply
  170. /// - Throws: RileyLinkDeviceError
  171. public func sendAndListen(_ data: Data, repeatCount: Int, timeout: TimeInterval, retryCount: Int, preambleExtension: TimeInterval?) throws -> RFPacket {
  172. let delayBetweenPackets: TimeInterval = 0
  173. let command = SendAndListen(
  174. outgoing: data,
  175. sendChannel: 0,
  176. repeatCount: UInt8(clamping: repeatCount),
  177. delayBetweenPacketsMS: UInt16(clamping: Int(delayBetweenPackets)),
  178. listenChannel: 0,
  179. timeoutMS: UInt32(clamping: Int(timeout.milliseconds)),
  180. retryCount: UInt8(clamping: retryCount),
  181. preambleExtensionMS: UInt16(clamping: Int(preambleExtension?.milliseconds ?? 0)),
  182. firmwareVersion: firmwareVersion
  183. )
  184. // At least 17 ms between packets for radio to stop/start
  185. let radioTimeBetweenPackets = TimeInterval(milliseconds: 17)
  186. let timeBetweenPackets = delayBetweenPackets + radioTimeBetweenPackets
  187. // 16384 = bitrate, 8 = bits per byte
  188. let singlePacketSendTime: TimeInterval = (Double(data.count * 8) / 16_384)
  189. let preambleTime = preambleExtension ?? 0
  190. let totalRepeatSendTime: TimeInterval = (singlePacketSendTime + timeBetweenPackets + preambleTime) * Double(repeatCount+1)
  191. let totalTimeout = (totalRepeatSendTime + timeout) * Double(retryCount + 1)
  192. return try writeCommand(command, timeout: totalTimeout)
  193. }
  194. /// Sends data to the pump, listening for a reply
  195. ///
  196. /// - Parameters:
  197. /// - data: The data to send
  198. /// - repeatCount: The number of times to repeat the message before listening begins
  199. /// - timeout: The length of time to listen for a response before timing out
  200. /// - retryCount: The number of times to repeat the send & listen sequence if no response is heard
  201. /// - Returns: The packet reply
  202. /// - Throws: RileyLinkDeviceError
  203. public func sendAndListen(_ data: Data, repeatCount: Int, timeout: TimeInterval, retryCount: Int) throws -> RFPacket {
  204. return try sendAndListen(data, repeatCount: repeatCount, timeout: timeout, retryCount: retryCount, preambleExtension: nil)
  205. }
  206. /// - Throws: RileyLinkDeviceError
  207. public func listen(onChannel channel: Int, timeout: TimeInterval) throws -> RFPacket? {
  208. let command = GetPacket(
  209. listenChannel: 0,
  210. timeoutMS: UInt32(clamping: Int(timeout.milliseconds))
  211. )
  212. return try writeCommand(command, timeout: timeout)
  213. }
  214. /// - Throws: RileyLinkDeviceError
  215. public func send(_ data: Data, onChannel channel: Int, timeout: TimeInterval) throws {
  216. let command = SendPacket(
  217. outgoing: data,
  218. sendChannel: UInt8(clamping: channel),
  219. repeatCount: 0,
  220. delayBetweenPacketsMS: 0,
  221. preambleExtensionMS: 0,
  222. firmwareVersion: firmwareVersion
  223. )
  224. _ = try writeCommand(command, timeout: timeout)
  225. }
  226. /// - Throws: RileyLinkDeviceError
  227. public func setSoftwareEncoding(_ swEncodingType: SoftwareEncodingType) throws {
  228. guard firmwareVersion.supportsSoftwareEncoding else {
  229. throw RileyLinkDeviceError.unsupportedCommand(String(describing: RileyLinkCommand.setSWEncoding))
  230. }
  231. let command = SetSoftwareEncoding(swEncodingType)
  232. let response = try writeCommand(command, timeout: 0)
  233. guard response.code == .success else {
  234. throw RileyLinkDeviceError.invalidInput(String(describing: swEncodingType))
  235. }
  236. }
  237. public func resetRadioConfig() throws {
  238. guard firmwareVersion.supportsResetRadioConfig else {
  239. return
  240. }
  241. let command = ResetRadioConfig()
  242. _ = try writeCommand(command, timeout: 0)
  243. }
  244. public func getRileyLinkStatistics() throws -> RileyLinkStatistics {
  245. guard firmwareVersion.supportsRileyLinkStatistics else {
  246. throw RileyLinkDeviceError.unsupportedCommand(String(describing: RileyLinkCommand.getStatistics))
  247. }
  248. let command = GetStatistics()
  249. let response: GetStatisticsResponse = try writeCommand(command, timeout: 0)
  250. return response.statistics
  251. }
  252. public func setPreamble(_ preambleValue: UInt16) throws {
  253. guard firmwareVersion.supportsCustomPreamble else {
  254. throw RileyLinkDeviceError.unsupportedCommand(String(describing: RileyLinkCommand.setPreamble))
  255. }
  256. let command = SetPreamble(preambleValue)
  257. _ = try writeCommand(command, timeout: 0)
  258. }
  259. /// Asserts that the caller is currently on the session's queue
  260. public func assertOnSessionQueue() {
  261. dispatchPrecondition(condition: .onQueue(manager.queue))
  262. }
  263. }