RileyLinkDeviceTableViewController.swift 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847
  1. //
  2. // RileyLinkDeviceTableViewController.swift
  3. // Naterade
  4. //
  5. // Created by Nathan Racklyeft on 3/5/16.
  6. // Copyright © 2016 Nathan Racklyeft. All rights reserved.
  7. //
  8. import UIKit
  9. import LoopKitUI
  10. import RileyLinkBLEKit
  11. import RileyLinkKit
  12. import os.log
  13. let CellIdentifier = "Cell"
  14. public class RileyLinkSwitch: UISwitch {
  15. public var index: Int = 0
  16. public var section: Int = 0
  17. }
  18. public class RileyLinkCell: UITableViewCell {
  19. public let switchView = RileyLinkSwitch()
  20. public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
  21. super.init(style: style, reuseIdentifier: reuseIdentifier)
  22. contentView.addSubview(switchView)
  23. }
  24. public required init?(coder aDecoder: NSCoder) {
  25. super.init(coder: aDecoder)
  26. }
  27. public override func layoutSubviews() {
  28. super.layoutSubviews()
  29. switchView.frame = CGRect(x: frame.width - 51 - 20, y: (frame.height - 31) / 2, width: 51, height: 31)
  30. }
  31. }
  32. public class RileyLinkDeviceTableViewController: UITableViewController {
  33. private let log = OSLog(category: "RileyLinkDeviceTableViewController")
  34. public let device: RileyLinkDevice
  35. private var bleRSSI: Int?
  36. private var firmwareVersion: String? {
  37. didSet {
  38. guard isViewLoaded else {
  39. return
  40. }
  41. cellForRow(.version)?.detailTextLabel?.text = firmwareVersion
  42. }
  43. }
  44. private var uptime: TimeInterval? {
  45. didSet {
  46. guard isViewLoaded else {
  47. return
  48. }
  49. cellForRow(.uptime)?.setDetailAge(uptime)
  50. }
  51. }
  52. private var battery: Int? {
  53. didSet {
  54. guard isViewLoaded else {
  55. return
  56. }
  57. cellForRow(.battery)?.setDetailBatteryLevel(battery)
  58. }
  59. }
  60. private var frequency: Measurement<UnitFrequency>? {
  61. didSet {
  62. guard isViewLoaded else {
  63. return
  64. }
  65. cellForRow(.frequency)?.setDetailFrequency(frequency, formatter: frequencyFormatter)
  66. }
  67. }
  68. private var ledMode: RileyLinkLEDMode? {
  69. didSet {
  70. guard isViewLoaded else {
  71. return
  72. }
  73. cellForRow(.diagnosticLEDSMode)?.setLEDMode(ledMode)
  74. }
  75. }
  76. var rssiFetchTimer: Timer? {
  77. willSet {
  78. rssiFetchTimer?.invalidate()
  79. }
  80. }
  81. private var hasPiezo: Bool = false
  82. private var appeared = false
  83. private var batteryAlertLevel: Int? {
  84. didSet {
  85. batteryAlertLevelChanged?(batteryAlertLevel)
  86. }
  87. }
  88. private var batteryAlertLevelChanged: ((Int?) -> Void)?
  89. public init(device: RileyLinkDevice, batteryAlertLevel: Int?, batteryAlertLevelChanged: ((Int?) -> Void)? ) {
  90. self.device = device
  91. self.batteryAlertLevel = batteryAlertLevel
  92. self.batteryAlertLevelChanged = batteryAlertLevelChanged
  93. super.init(style: .grouped)
  94. updateDeviceStatus()
  95. NotificationCenter.default.addObserver(forName: .DeviceStatusUpdated, object: device, queue: .main)
  96. { (notification) in
  97. self.updateDeviceStatus()
  98. }
  99. }
  100. required public init?(coder aDecoder: NSCoder) {
  101. fatalError("init(coder:) has not been implemented")
  102. }
  103. public override func viewDidLoad() {
  104. super.viewDidLoad()
  105. title = device.name
  106. switch device.hardwareType {
  107. case .riley, .none:
  108. deviceRows = [
  109. .customName,
  110. .version,
  111. .rssi,
  112. .connection,
  113. .uptime,
  114. .frequency
  115. ]
  116. sections = [
  117. .device,
  118. .rileyLinkCommands
  119. ]
  120. case .ema:
  121. deviceRows = [
  122. .customName,
  123. .version,
  124. .rssi,
  125. .connection,
  126. .uptime,
  127. .frequency,
  128. .battery
  129. ]
  130. sections = [
  131. .device,
  132. .alert,
  133. .rileyLinkCommands
  134. ]
  135. case .orange:
  136. deviceRows = [
  137. .customName,
  138. .version,
  139. .rssi,
  140. .connection,
  141. .uptime,
  142. .battery,
  143. .voltage
  144. ]
  145. if device.hasOrangeLinkService {
  146. sections = [
  147. .device,
  148. .alert,
  149. .configureCommand,
  150. .orangeLinkCommands
  151. ]
  152. } else {
  153. sections = [
  154. .device
  155. ]
  156. }
  157. }
  158. self.observe()
  159. }
  160. @objc func updateRSSI() {
  161. device.readRSSI()
  162. }
  163. // This does not trigger any BLE reads; it just gets status from the device in a safe manner, and reloads the table
  164. func updateDeviceStatus() {
  165. device.getStatus { (status) in
  166. DispatchQueue.main.async {
  167. self.firmwareVersion = status.version
  168. self.ledOn = status.ledOn
  169. self.vibrationOn = status.vibrationOn
  170. self.voltage = status.voltage
  171. self.battery = status.battery
  172. self.hasPiezo = status.hasPiezo
  173. self.tableView.reloadData()
  174. }
  175. }
  176. }
  177. func updateUptime() {
  178. device.runSession(withName: "Get stats for uptime") { (session) in
  179. do {
  180. let statistics = try session.getRileyLinkStatistics()
  181. DispatchQueue.main.async {
  182. self.uptime = statistics.uptime
  183. }
  184. } catch let error {
  185. self.log.error("Failed to get stats for uptime: %{public}@", String(describing: error))
  186. }
  187. }
  188. }
  189. func updateFrequency() {
  190. device.runSession(withName: "Get base frequency") { (session) in
  191. do {
  192. let frequency = try session.readBaseFrequency()
  193. DispatchQueue.main.async {
  194. self.frequency = frequency
  195. }
  196. } catch let error {
  197. self.log.error("Failed to get base frequency: %{public}@", String(describing: error))
  198. }
  199. }
  200. }
  201. func readDiagnosticLEDMode() {
  202. device.readDiagnosticLEDModeForBLEChip(completion: { ledMode in
  203. DispatchQueue.main.async {
  204. self.ledMode = ledMode
  205. }
  206. })
  207. }
  208. // References to registered notification center observers
  209. private var notificationObservers: [Any] = []
  210. deinit {
  211. for observer in notificationObservers {
  212. NotificationCenter.default.removeObserver(observer)
  213. }
  214. }
  215. private func observe() {
  216. let center = NotificationCenter.default
  217. let mainQueue = OperationQueue.main
  218. notificationObservers = [
  219. center.addObserver(forName: .DeviceNameDidChange, object: device, queue: mainQueue) { [weak self] (note) -> Void in
  220. if let cell = self?.cellForRow(.customName) {
  221. cell.detailTextLabel?.text = self?.device.name
  222. }
  223. self?.title = self?.device.name
  224. self?.tableView.reloadData()
  225. },
  226. center.addObserver(forName: .DeviceConnectionStateDidChange, object: device, queue: mainQueue) { [weak self] (note) -> Void in
  227. if let cell = self?.cellForRow(.connection) {
  228. cell.detailTextLabel?.text = self?.device.peripheralState.description
  229. }
  230. },
  231. center.addObserver(forName: .DeviceRSSIDidChange, object: device, queue: mainQueue) { [weak self] (note) -> Void in
  232. self?.bleRSSI = note.userInfo?[RileyLinkBluetoothDevice.notificationRSSIKey] as? Int
  233. if let cell = self?.cellForRow(.rssi), let formatter = self?.integerFormatter {
  234. cell.setDetailRSSI(self?.bleRSSI, formatter: formatter)
  235. }
  236. },
  237. center.addObserver(forName: .DeviceDidStartIdle, object: device, queue: mainQueue) { [weak self] (note) in
  238. self?.updateDeviceStatus()
  239. },
  240. center.addObserver(forName: .DeviceStatusUpdated, object: device, queue: mainQueue) { [weak self] (note) in
  241. self?.updateDeviceStatus()
  242. },
  243. ]
  244. }
  245. public override func viewWillAppear(_ animated: Bool) {
  246. super.viewWillAppear(animated)
  247. if appeared {
  248. tableView.reloadData()
  249. }
  250. rssiFetchTimer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(updateRSSI), userInfo: nil, repeats: true)
  251. appeared = true
  252. updateRSSI()
  253. if deviceRows.contains(.frequency) {
  254. updateFrequency()
  255. }
  256. updateUptime()
  257. switch device.hardwareType {
  258. case .riley:
  259. readDiagnosticLEDMode()
  260. case .ema:
  261. device.updateBatteryLevel()
  262. readDiagnosticLEDMode()
  263. case .orange:
  264. device.updateBatteryLevel()
  265. device.orangeWritePwd()
  266. device.orangeReadSet()
  267. device.orangeReadVDC()
  268. device.orangeAction(.fw_hw)
  269. default:
  270. break
  271. }
  272. }
  273. public override func viewDidDisappear(_ animated: Bool) {
  274. super.viewDidDisappear(animated)
  275. if redOn || yellowOn {
  276. device.orangeAction(.off)
  277. }
  278. if shakeOn {
  279. device.orangeAction(.shakeOff)
  280. }
  281. }
  282. public override func viewWillDisappear(_ animated: Bool) {
  283. super.viewWillDisappear(animated)
  284. rssiFetchTimer = nil
  285. }
  286. // MARK: - Formatters
  287. private lazy var dateFormatter: DateFormatter = {
  288. let dateFormatter = DateFormatter()
  289. dateFormatter.dateStyle = .none
  290. dateFormatter.timeStyle = .medium
  291. return dateFormatter
  292. }()
  293. private lazy var integerFormatter = NumberFormatter()
  294. private lazy var decimalFormatter: NumberFormatter = {
  295. let decimalFormatter = NumberFormatter()
  296. decimalFormatter.numberStyle = .decimal
  297. decimalFormatter.maximumFractionDigits = 2
  298. return decimalFormatter
  299. }()
  300. private lazy var frequencyFormatter: MeasurementFormatter = {
  301. let formatter = MeasurementFormatter()
  302. formatter.numberFormatter = decimalFormatter
  303. return formatter
  304. }()
  305. // MARK: - Table view data source
  306. private enum Section: Int, CaseIterable {
  307. case device
  308. case alert
  309. case configureCommand
  310. case orangeLinkCommands
  311. case rileyLinkCommands
  312. }
  313. private var sections: [Section] = []
  314. private enum AlertRow: Int, CaseIterable {
  315. case battery
  316. }
  317. private enum DeviceRow: Int, CaseIterable {
  318. case customName
  319. case version
  320. case rssi
  321. case connection
  322. case uptime
  323. case frequency
  324. case battery
  325. case voltage
  326. }
  327. private var deviceRows: [DeviceRow] = []
  328. private enum RileyLinkCommandRow: Int, CaseIterable {
  329. case diagnosticLEDSMode
  330. case getStatistics
  331. }
  332. private enum OrangeLinkCommandRow: Int, CaseIterable {
  333. case yellow
  334. case red
  335. case shake
  336. case findDevice
  337. }
  338. private enum OrangeConfigureCommandRow: Int, CaseIterable {
  339. case connectionLED
  340. case connectionVibrate
  341. }
  342. private func cellForRow(_ row: DeviceRow) -> UITableViewCell? {
  343. guard let rowIndex = deviceRows.firstIndex(of: row),
  344. let sectionIndex = sections.firstIndex(of: Section.device) else
  345. {
  346. return nil
  347. }
  348. return tableView.cellForRow(at: IndexPath(row: rowIndex, section: sectionIndex))
  349. }
  350. private func cellForRow(_ row: OrangeConfigureCommandRow) -> UITableViewCell? {
  351. guard let sectionIndex = sections.firstIndex(of: Section.orangeLinkCommands) else
  352. {
  353. return nil
  354. }
  355. return tableView.cellForRow(at: IndexPath(row: row.rawValue, section: sectionIndex))
  356. }
  357. private func cellForRow(_ row: OrangeLinkCommandRow) -> UITableViewCell? {
  358. guard let sectionIndex = sections.firstIndex(of: Section.orangeLinkCommands) else
  359. {
  360. return nil
  361. }
  362. return tableView.cellForRow(at: IndexPath(row: row.rawValue, section: sectionIndex))
  363. }
  364. private func cellForRow(_ row: RileyLinkCommandRow) -> UITableViewCell? {
  365. guard let sectionIndex = sections.firstIndex(of: Section.rileyLinkCommands) else
  366. {
  367. return nil
  368. }
  369. return tableView.cellForRow(at: IndexPath(row: row.rawValue, section: sectionIndex))
  370. }
  371. public override func numberOfSections(in tableView: UITableView) -> Int {
  372. return sections.count
  373. }
  374. public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  375. guard section < sections.count else {
  376. return 0
  377. }
  378. switch sections[section] {
  379. case .device:
  380. return deviceRows.count
  381. case .rileyLinkCommands:
  382. return RileyLinkCommandRow.allCases.count
  383. case .configureCommand:
  384. return OrangeConfigureCommandRow.allCases.count
  385. case .orangeLinkCommands:
  386. let count = OrangeLinkCommandRow.allCases.count
  387. return hasPiezo ? count : count-1
  388. case .alert:
  389. return AlertRow.allCases.count
  390. }
  391. }
  392. @objc
  393. func switchAction(sender: RileyLinkSwitch) {
  394. switch sections[sender.section] {
  395. case .orangeLinkCommands:
  396. switch OrangeLinkCommandRow(rawValue: sender.index)! {
  397. case .yellow:
  398. if sender.isOn {
  399. device.orangeAction(.yellow)
  400. } else {
  401. device.orangeAction(.off)
  402. }
  403. yellowOn = sender.isOn
  404. redOn = false
  405. case .red:
  406. if sender.isOn {
  407. device.orangeAction(.red)
  408. } else {
  409. device.orangeAction(.off)
  410. }
  411. yellowOn = false
  412. redOn = sender.isOn
  413. case .shake:
  414. if sender.isOn {
  415. device.orangeAction(.shake)
  416. } else {
  417. device.orangeAction(.shakeOff)
  418. }
  419. shakeOn = sender.isOn
  420. default:
  421. break
  422. }
  423. case .configureCommand:
  424. switch OrangeConfigureCommandRow(rawValue: sender.index)! {
  425. case .connectionLED:
  426. device.setOrangeConfig(.connectionLED, isOn: sender.isOn)
  427. ledOn = sender.isOn
  428. case .connectionVibrate:
  429. device.setOrangeConfig(.connectionVibrate, isOn: sender.isOn)
  430. vibrationOn = sender.isOn
  431. }
  432. default:
  433. break
  434. }
  435. tableView.reloadData()
  436. }
  437. var yellowOn = false
  438. var redOn = false
  439. var shakeOn = false
  440. private var ledOn: Bool = false
  441. private var vibrationOn: Bool = false
  442. var voltage: Float?
  443. public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  444. let cell: RileyLinkCell
  445. if let reusableCell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier) as? RileyLinkCell {
  446. cell = reusableCell
  447. } else {
  448. cell = RileyLinkCell(style: .value1, reuseIdentifier: CellIdentifier)
  449. cell.switchView.addTarget(self, action: #selector(switchAction(sender:)), for: .valueChanged)
  450. }
  451. let switchView = cell.switchView
  452. switchView.isHidden = true
  453. switchView.index = indexPath.row
  454. switchView.section = indexPath.section
  455. cell.accessoryType = .none
  456. cell.detailTextLabel?.text = nil
  457. switch sections[indexPath.section] {
  458. case .device:
  459. switch deviceRows[indexPath.row] {
  460. case .customName:
  461. cell.textLabel?.text = LocalizedString("Name", comment: "The title of the cell showing device name")
  462. cell.detailTextLabel?.text = device.name
  463. cell.accessoryType = .disclosureIndicator
  464. case .version:
  465. cell.textLabel?.text = LocalizedString("Firmware", comment: "The title of the cell showing firmware version")
  466. cell.detailTextLabel?.text = firmwareVersion
  467. case .connection:
  468. cell.textLabel?.text = LocalizedString("Connection State", comment: "The title of the cell showing BLE connection state")
  469. cell.detailTextLabel?.text = device.peripheralState.description
  470. case .rssi:
  471. cell.textLabel?.text = LocalizedString("Signal Strength", comment: "The title of the cell showing BLE signal strength (RSSI)")
  472. cell.setDetailRSSI(bleRSSI, formatter: integerFormatter)
  473. case .uptime:
  474. cell.textLabel?.text = LocalizedString("Uptime", comment: "The title of the cell showing uptime")
  475. cell.setDetailAge(uptime)
  476. case .frequency:
  477. cell.textLabel?.text = LocalizedString("Frequency", comment: "The title of the cell showing current rileylink frequency")
  478. cell.setDetailFrequency(frequency, formatter: frequencyFormatter)
  479. case .battery:
  480. cell.textLabel?.text = NSLocalizedString("Battery level", comment: "The title of the cell showing battery level")
  481. cell.setDetailBatteryLevel(battery)
  482. case .voltage:
  483. cell.textLabel?.text = NSLocalizedString("Voltage", comment: "The title of the cell showing ORL")
  484. cell.setVoltage(voltage)
  485. }
  486. case .alert:
  487. switch AlertRow(rawValue: indexPath.row)! {
  488. case .battery:
  489. cell.accessoryType = .disclosureIndicator
  490. cell.textLabel?.text = NSLocalizedString("Low Battery Alert", comment: "The title of the cell showing battery level")
  491. cell.setBatteryAlert(batteryAlertLevel, formatter: integerFormatter)
  492. }
  493. case .rileyLinkCommands:
  494. switch RileyLinkCommandRow(rawValue: indexPath.row)! {
  495. case .diagnosticLEDSMode:
  496. cell.textLabel?.text = LocalizedString("Toggle Diagnostic LEDs", comment: "The title of the command to update diagnostic LEDs")
  497. cell.setLEDMode(ledMode)
  498. case .getStatistics:
  499. cell.textLabel?.text = LocalizedString("Get RileyLink Statistics", comment: "The title of the command to fetch RileyLink statistics")
  500. }
  501. case .orangeLinkCommands:
  502. cell.accessoryType = .disclosureIndicator
  503. cell.detailTextLabel?.text = nil
  504. switch OrangeLinkCommandRow(rawValue: indexPath.row)! {
  505. case .yellow:
  506. switchView.isHidden = false
  507. cell.accessoryType = .none
  508. switchView.isOn = yellowOn
  509. cell.textLabel?.text = NSLocalizedString("Lighten Yellow LED", comment: "The title of the cell showing Lighten Yellow LED")
  510. case .red:
  511. switchView.isHidden = false
  512. cell.accessoryType = .none
  513. switchView.isOn = redOn
  514. cell.textLabel?.text = NSLocalizedString("Lighten Red LED", comment: "The title of the cell showing Lighten Red LED")
  515. case .shake:
  516. switchView.isHidden = false
  517. switchView.isOn = shakeOn
  518. cell.accessoryType = .none
  519. cell.textLabel?.text = NSLocalizedString("Test Vibration", comment: "The title of the cell showing Test Vibration")
  520. case .findDevice:
  521. cell.textLabel?.text = NSLocalizedString("Find Device", comment: "The title of the cell for sounding device finding piezo")
  522. cell.detailTextLabel?.text = nil
  523. }
  524. case .configureCommand:
  525. switch OrangeConfigureCommandRow(rawValue: indexPath.row)! {
  526. case .connectionLED:
  527. switchView.isHidden = false
  528. switchView.isOn = ledOn
  529. cell.accessoryType = .none
  530. cell.textLabel?.text = NSLocalizedString("Connection LED", comment: "The title of the cell for connection LED")
  531. case .connectionVibrate:
  532. switchView.isHidden = false
  533. switchView.isOn = vibrationOn
  534. cell.accessoryType = .none
  535. cell.textLabel?.text = NSLocalizedString("Connection Vibration", comment: "The title of the cell for connection vibration")
  536. }
  537. }
  538. return cell
  539. }
  540. public override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  541. switch sections[section] {
  542. case .device:
  543. return LocalizedString("Device", comment: "The title of the section describing the device")
  544. case .rileyLinkCommands:
  545. return LocalizedString("Test Commands", comment: "The title of the section for rileylink commands")
  546. case .orangeLinkCommands:
  547. return LocalizedString("Test Commands", comment: "The title of the section for orangelink commands")
  548. case .configureCommand:
  549. return LocalizedString("Connection Monitoring", comment: "The title of the section for connection monitoring")
  550. case .alert:
  551. return LocalizedString("Alert", comment: "The title of the section for alerts")
  552. }
  553. }
  554. // MARK: - UITableViewDelegate
  555. public override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
  556. switch sections[indexPath.section] {
  557. case .device:
  558. switch deviceRows[indexPath.row] {
  559. case .customName:
  560. return true
  561. default:
  562. return false
  563. }
  564. case .configureCommand:
  565. return false
  566. case .orangeLinkCommands:
  567. switch OrangeLinkCommandRow(rawValue: indexPath.row)! {
  568. case .findDevice:
  569. return true
  570. default:
  571. return false
  572. }
  573. case .rileyLinkCommands:
  574. return device.isConnected
  575. case .alert:
  576. return true
  577. }
  578. }
  579. public override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  580. switch sections[indexPath.section] {
  581. case .device:
  582. switch deviceRows[indexPath.row] {
  583. case .customName:
  584. let vc = TextFieldTableViewController()
  585. if let cell = tableView.cellForRow(at: indexPath) {
  586. vc.title = cell.textLabel?.text
  587. vc.value = device.name
  588. vc.delegate = self
  589. vc.keyboardType = .default
  590. }
  591. show(vc, sender: indexPath)
  592. default:
  593. break
  594. }
  595. case .rileyLinkCommands:
  596. var vc: CommandResponseViewController?
  597. switch RileyLinkCommandRow(rawValue: indexPath.row)! {
  598. case .diagnosticLEDSMode:
  599. let nextMode: RileyLinkLEDMode
  600. switch ledMode {
  601. case.on:
  602. nextMode = .off
  603. default:
  604. nextMode = .on
  605. }
  606. vc = .setDiagnosticLEDMode(device: device, mode: nextMode)
  607. case .getStatistics:
  608. vc = .getStatistics(device: device)
  609. }
  610. if let cell = tableView.cellForRow(at: indexPath) {
  611. vc?.title = cell.textLabel?.text
  612. }
  613. if let vc = vc {
  614. show(vc, sender: indexPath)
  615. }
  616. case .orangeLinkCommands:
  617. switch OrangeLinkCommandRow(rawValue: indexPath.row)! {
  618. case .findDevice:
  619. device.findDevice()
  620. tableView.deselectRow(at: indexPath, animated: true)
  621. default:
  622. break
  623. }
  624. case .configureCommand:
  625. break
  626. case .alert:
  627. switch AlertRow(rawValue: indexPath.row)! {
  628. case .battery:
  629. let alert = UIAlertController.init(title: "Battery level Alert", message: nil, preferredStyle: .actionSheet)
  630. let action = UIAlertAction.init(title: "OFF", style: .default) { _ in
  631. self.batteryAlertLevel = nil
  632. self.tableView.reloadData()
  633. }
  634. alert.addAction(action)
  635. for value in [20,30,40,50] {
  636. let action = UIAlertAction.init(title: "\(value)%", style: .default) { _ in
  637. self.batteryAlertLevel = value
  638. self.tableView.reloadData()
  639. }
  640. alert.addAction(action)
  641. }
  642. present(alert, animated: true, completion: nil)
  643. }
  644. }
  645. }
  646. }
  647. extension RileyLinkDeviceTableViewController: TextFieldTableViewControllerDelegate {
  648. public func textFieldTableViewControllerDidReturn(_ controller: TextFieldTableViewController) {
  649. _ = navigationController?.popViewController(animated: true)
  650. }
  651. public func textFieldTableViewControllerDidEndEditing(_ controller: TextFieldTableViewController) {
  652. if let indexPath = tableView.indexPathForSelectedRow {
  653. switch sections[indexPath.section] {
  654. case .device:
  655. switch deviceRows[indexPath.row] {
  656. case .customName:
  657. device.setCustomName(controller.value!)
  658. default:
  659. break
  660. }
  661. default:
  662. break
  663. }
  664. }
  665. }
  666. }
  667. private extension TimeInterval {
  668. func format(using units: NSCalendar.Unit) -> String? {
  669. let formatter = DateComponentsFormatter()
  670. formatter.allowedUnits = units
  671. formatter.unitsStyle = .full
  672. formatter.zeroFormattingBehavior = .dropLeading
  673. formatter.maximumUnitCount = 2
  674. return formatter.string(from: self)
  675. }
  676. }
  677. private extension UITableViewCell {
  678. func setDetailDate(_ date: Date?, formatter: DateFormatter) {
  679. if let date = date {
  680. detailTextLabel?.text = formatter.string(from: date)
  681. } else {
  682. detailTextLabel?.text = "-"
  683. }
  684. }
  685. func setDetailRSSI(_ decibles: Int?, formatter: NumberFormatter) {
  686. detailTextLabel?.text = formatter.decibleString(from: decibles) ?? "-"
  687. }
  688. func setDetailAge(_ age: TimeInterval?) {
  689. if let age = age {
  690. detailTextLabel?.text = age.format(using: [.day, .hour, .minute])
  691. } else {
  692. detailTextLabel?.text = ""
  693. }
  694. }
  695. func setDetailBatteryLevel(_ batteryLevel: Int?) {
  696. if let batteryLevel = batteryLevel {
  697. detailTextLabel?.text = "\(batteryLevel)" + " %"
  698. } else {
  699. detailTextLabel?.text = ""
  700. }
  701. }
  702. func setDetailFrequency(_ frequency: Measurement<UnitFrequency>?, formatter: MeasurementFormatter) {
  703. if let frequency = frequency {
  704. detailTextLabel?.text = formatter.string(from: frequency)
  705. } else {
  706. detailTextLabel?.text = ""
  707. }
  708. }
  709. func setLEDMode(_ mode: RileyLinkLEDMode?) {
  710. switch mode {
  711. case .on:
  712. detailTextLabel?.text = LocalizedString("On", comment: "Text indicating LED Mode is on")
  713. case .off:
  714. detailTextLabel?.text = LocalizedString("Off", comment: "Text indicating LED Mode is off")
  715. case .auto:
  716. detailTextLabel?.text = LocalizedString("Auto", comment: "Text indicating LED Mode is auto")
  717. case .none:
  718. detailTextLabel?.text = ""
  719. }
  720. }
  721. func setVoltage(_ voltage: Float?) {
  722. if let voltage = voltage {
  723. detailTextLabel?.text = String(format: "%.1f%", voltage)
  724. } else {
  725. detailTextLabel?.text = ""
  726. }
  727. }
  728. func setBatteryAlert(_ level: Int?, formatter: NumberFormatter) {
  729. detailTextLabel?.text = formatter.percentString(from: level) ?? NSLocalizedString("Off", comment: "Detail text when battery alert disabled.")
  730. }
  731. }