CommandSession.swift 12 KB

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