RileyLinkDeviceTableViewController.swift 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899
  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. .emaLinkCommands
  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?[RileyLinkDevice.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. case emaLinkCommands
  313. }
  314. private var sections: [Section] = []
  315. private enum AlertRow: Int, CaseIterable {
  316. case battery
  317. }
  318. private enum DeviceRow: Int, CaseIterable {
  319. case customName
  320. case version
  321. case rssi
  322. case connection
  323. case uptime
  324. case frequency
  325. case battery
  326. case voltage
  327. }
  328. private var deviceRows: [DeviceRow] = []
  329. private enum RileyLinkCommandRow: Int, CaseIterable {
  330. case diagnosticLEDSMode
  331. case getStatistics
  332. }
  333. private enum EmaLinkCommandRow: Int, CaseIterable {
  334. case logicLEDSMode
  335. case getStatistics
  336. }
  337. private enum OrangeLinkCommandRow: Int, CaseIterable {
  338. case yellow
  339. case red
  340. case shake
  341. case findDevice
  342. }
  343. private enum OrangeConfigureCommandRow: Int, CaseIterable {
  344. case connectionLED
  345. case connectionVibrate
  346. }
  347. private func cellForRow(_ row: DeviceRow) -> UITableViewCell? {
  348. guard let rowIndex = deviceRows.firstIndex(of: row),
  349. let sectionIndex = sections.firstIndex(of: Section.device) else
  350. {
  351. return nil
  352. }
  353. return tableView.cellForRow(at: IndexPath(row: rowIndex, section: sectionIndex))
  354. }
  355. private func cellForRow(_ row: OrangeConfigureCommandRow) -> UITableViewCell? {
  356. guard let sectionIndex = sections.firstIndex(of: Section.orangeLinkCommands) else
  357. {
  358. return nil
  359. }
  360. return tableView.cellForRow(at: IndexPath(row: row.rawValue, section: sectionIndex))
  361. }
  362. private func cellForRow(_ row: OrangeLinkCommandRow) -> UITableViewCell? {
  363. guard let sectionIndex = sections.firstIndex(of: Section.orangeLinkCommands) else
  364. {
  365. return nil
  366. }
  367. return tableView.cellForRow(at: IndexPath(row: row.rawValue, section: sectionIndex))
  368. }
  369. private func cellForRow(_ row: RileyLinkCommandRow) -> UITableViewCell? {
  370. guard let sectionIndex = sections.firstIndex(of: Section.rileyLinkCommands) else
  371. {
  372. return nil
  373. }
  374. return tableView.cellForRow(at: IndexPath(row: row.rawValue, section: sectionIndex))
  375. }
  376. private func cellForRow(_ row: EmaLinkCommandRow) -> UITableViewCell? {
  377. guard let sectionIndex = sections.firstIndex(of: Section.emaLinkCommands) else
  378. {
  379. return nil
  380. }
  381. return tableView.cellForRow(at: IndexPath(row: row.rawValue, section: sectionIndex))
  382. }
  383. public override func numberOfSections(in tableView: UITableView) -> Int {
  384. return sections.count
  385. }
  386. public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  387. guard section < sections.count else {
  388. return 0
  389. }
  390. switch sections[section] {
  391. case .device:
  392. return deviceRows.count
  393. case .rileyLinkCommands:
  394. return RileyLinkCommandRow.allCases.count
  395. case .emaLinkCommands:
  396. return EmaLinkCommandRow.allCases.count
  397. case .configureCommand:
  398. return OrangeConfigureCommandRow.allCases.count
  399. case .orangeLinkCommands:
  400. let count = OrangeLinkCommandRow.allCases.count
  401. return hasPiezo ? count : count-1
  402. case .alert:
  403. return AlertRow.allCases.count
  404. }
  405. }
  406. @objc
  407. func switchAction(sender: RileyLinkSwitch) {
  408. switch sections[sender.section] {
  409. case .orangeLinkCommands:
  410. switch OrangeLinkCommandRow(rawValue: sender.index)! {
  411. case .yellow:
  412. if sender.isOn {
  413. device.orangeAction(.yellow)
  414. } else {
  415. device.orangeAction(.off)
  416. }
  417. yellowOn = sender.isOn
  418. redOn = false
  419. case .red:
  420. if sender.isOn {
  421. device.orangeAction(.red)
  422. } else {
  423. device.orangeAction(.off)
  424. }
  425. yellowOn = false
  426. redOn = sender.isOn
  427. case .shake:
  428. if sender.isOn {
  429. device.orangeAction(.shake)
  430. } else {
  431. device.orangeAction(.shakeOff)
  432. }
  433. shakeOn = sender.isOn
  434. default:
  435. break
  436. }
  437. case .configureCommand:
  438. switch OrangeConfigureCommandRow(rawValue: sender.index)! {
  439. case .connectionLED:
  440. device.setOrangeConfig(.connectionLED, isOn: sender.isOn)
  441. ledOn = sender.isOn
  442. case .connectionVibrate:
  443. device.setOrangeConfig(.connectionVibrate, isOn: sender.isOn)
  444. vibrationOn = sender.isOn
  445. }
  446. default:
  447. break
  448. }
  449. tableView.reloadData()
  450. }
  451. var yellowOn = false
  452. var redOn = false
  453. var shakeOn = false
  454. private var ledOn: Bool = false
  455. private var vibrationOn: Bool = false
  456. var voltage: Float?
  457. public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  458. let cell: RileyLinkCell
  459. if let reusableCell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier) as? RileyLinkCell {
  460. cell = reusableCell
  461. } else {
  462. cell = RileyLinkCell(style: .value1, reuseIdentifier: CellIdentifier)
  463. cell.switchView.addTarget(self, action: #selector(switchAction(sender:)), for: .valueChanged)
  464. }
  465. let switchView = cell.switchView
  466. switchView.isHidden = true
  467. switchView.index = indexPath.row
  468. switchView.section = indexPath.section
  469. cell.accessoryType = .none
  470. cell.detailTextLabel?.text = nil
  471. switch sections[indexPath.section] {
  472. case .device:
  473. switch deviceRows[indexPath.row] {
  474. case .customName:
  475. cell.textLabel?.text = LocalizedString("Name", comment: "The title of the cell showing device name")
  476. cell.detailTextLabel?.text = device.name
  477. cell.accessoryType = .disclosureIndicator
  478. case .version:
  479. cell.textLabel?.text = LocalizedString("Firmware", comment: "The title of the cell showing firmware version")
  480. cell.detailTextLabel?.text = firmwareVersion
  481. case .connection:
  482. cell.textLabel?.text = LocalizedString("Connection State", comment: "The title of the cell showing BLE connection state")
  483. cell.detailTextLabel?.text = device.peripheralState.description
  484. case .rssi:
  485. cell.textLabel?.text = LocalizedString("Signal Strength", comment: "The title of the cell showing BLE signal strength (RSSI)")
  486. cell.setDetailRSSI(bleRSSI, formatter: integerFormatter)
  487. case .uptime:
  488. cell.textLabel?.text = LocalizedString("Uptime", comment: "The title of the cell showing uptime")
  489. cell.setDetailAge(uptime)
  490. case .frequency:
  491. cell.textLabel?.text = LocalizedString("Frequency", comment: "The title of the cell showing current rileylink frequency")
  492. cell.setDetailFrequency(frequency, formatter: frequencyFormatter)
  493. case .battery:
  494. cell.textLabel?.text = LocalizedString("Battery level", comment: "The title of the cell showing battery level")
  495. cell.setDetailBatteryLevel(battery)
  496. case .voltage:
  497. cell.textLabel?.text = LocalizedString("Voltage", comment: "The title of the cell showing ORL")
  498. cell.setVoltage(voltage)
  499. }
  500. case .alert:
  501. switch AlertRow(rawValue: indexPath.row)! {
  502. case .battery:
  503. cell.accessoryType = .disclosureIndicator
  504. cell.textLabel?.text = LocalizedString("Low Battery Alert", comment: "The title of the cell showing battery level")
  505. cell.setBatteryAlert(batteryAlertLevel, formatter: integerFormatter)
  506. }
  507. case .rileyLinkCommands:
  508. switch RileyLinkCommandRow(rawValue: indexPath.row)! {
  509. case .diagnosticLEDSMode:
  510. cell.textLabel?.text = LocalizedString("Diagnostic LEDs", comment: "The title of the command to update diagnostic LEDs")
  511. cell.setLEDMode(ledMode)
  512. case .getStatistics:
  513. cell.textLabel?.text = LocalizedString("Get RileyLink Statistics", comment: "The title of the command to fetch RileyLink statistics")
  514. }
  515. case .emaLinkCommands:
  516. switch EmaLinkCommandRow(rawValue: indexPath.row)! {
  517. case .logicLEDSMode:
  518. cell.textLabel?.text = LocalizedString("Invert LED Logic", comment: "The title of the command to invert BLE connection LED logic")
  519. cell.setLEDMode(ledMode)
  520. case .getStatistics:
  521. cell.textLabel?.text = LocalizedString("Get RileyLink Statistics", comment: "The title of the command to fetch RileyLink statistics")
  522. }
  523. case .orangeLinkCommands:
  524. cell.accessoryType = .disclosureIndicator
  525. cell.detailTextLabel?.text = nil
  526. switch OrangeLinkCommandRow(rawValue: indexPath.row)! {
  527. case .yellow:
  528. switchView.isHidden = false
  529. cell.accessoryType = .none
  530. switchView.isOn = yellowOn
  531. cell.textLabel?.text = LocalizedString("Lighten Yellow LED", comment: "The title of the cell showing Lighten Yellow LED")
  532. case .red:
  533. switchView.isHidden = false
  534. cell.accessoryType = .none
  535. switchView.isOn = redOn
  536. cell.textLabel?.text = LocalizedString("Lighten Red LED", comment: "The title of the cell showing Lighten Red LED")
  537. case .shake:
  538. switchView.isHidden = false
  539. switchView.isOn = shakeOn
  540. cell.accessoryType = .none
  541. cell.textLabel?.text = LocalizedString("Test Vibration", comment: "The title of the cell showing Test Vibration")
  542. case .findDevice:
  543. cell.textLabel?.text = LocalizedString("Find Device", comment: "The title of the cell for sounding device finding piezo")
  544. cell.detailTextLabel?.text = nil
  545. }
  546. case .configureCommand:
  547. switch OrangeConfigureCommandRow(rawValue: indexPath.row)! {
  548. case .connectionLED:
  549. switchView.isHidden = false
  550. switchView.isOn = ledOn
  551. cell.accessoryType = .none
  552. cell.textLabel?.text = LocalizedString("Connection LED", comment: "The title of the cell for connection LED")
  553. case .connectionVibrate:
  554. switchView.isHidden = false
  555. switchView.isOn = vibrationOn
  556. cell.accessoryType = .none
  557. cell.textLabel?.text = LocalizedString("Connection Vibration", comment: "The title of the cell for connection vibration")
  558. }
  559. }
  560. return cell
  561. }
  562. public override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  563. switch sections[section] {
  564. case .device:
  565. return LocalizedString("Device", comment: "The title of the section describing the device")
  566. case .rileyLinkCommands:
  567. return LocalizedString("Test Commands", comment: "The title of the section for rileylink commands")
  568. case .emaLinkCommands:
  569. return LocalizedString("Test Commands", comment: "The title of the section for emalink commands")
  570. case .orangeLinkCommands:
  571. return LocalizedString("Test Commands", comment: "The title of the section for orangelink commands")
  572. case .configureCommand:
  573. return LocalizedString("Connection Monitoring", comment: "The title of the section for connection monitoring")
  574. case .alert:
  575. return LocalizedString("Alert", comment: "The title of the section for alerts")
  576. }
  577. }
  578. // MARK: - UITableViewDelegate
  579. public override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
  580. switch sections[indexPath.section] {
  581. case .device:
  582. switch deviceRows[indexPath.row] {
  583. case .customName:
  584. return true
  585. default:
  586. return false
  587. }
  588. case .configureCommand:
  589. return false
  590. case .orangeLinkCommands:
  591. switch OrangeLinkCommandRow(rawValue: indexPath.row)! {
  592. case .findDevice:
  593. return true
  594. default:
  595. return false
  596. }
  597. case .rileyLinkCommands:
  598. return device.peripheralState == .connected
  599. case .emaLinkCommands:
  600. return device.peripheralState == .connected
  601. case .alert:
  602. return true
  603. }
  604. }
  605. public override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  606. switch sections[indexPath.section] {
  607. case .device:
  608. switch deviceRows[indexPath.row] {
  609. case .customName:
  610. let vc = TextFieldTableViewController()
  611. if let cell = tableView.cellForRow(at: indexPath) {
  612. vc.title = cell.textLabel?.text
  613. vc.value = device.name
  614. vc.delegate = self
  615. vc.keyboardType = .default
  616. }
  617. show(vc, sender: indexPath)
  618. default:
  619. break
  620. }
  621. case .rileyLinkCommands:
  622. var vc: CommandResponseViewController?
  623. switch RileyLinkCommandRow(rawValue: indexPath.row)! {
  624. case .diagnosticLEDSMode:
  625. let nextMode: RileyLinkLEDMode
  626. switch ledMode {
  627. case.on:
  628. nextMode = .off
  629. default:
  630. nextMode = .on
  631. }
  632. vc = .setDiagnosticLEDMode(device: device, mode: nextMode)
  633. case .getStatistics:
  634. vc = .getStatistics(device: device)
  635. }
  636. if let cell = tableView.cellForRow(at: indexPath) {
  637. vc?.title = cell.textLabel?.text
  638. }
  639. if let vc = vc {
  640. show(vc, sender: indexPath)
  641. }
  642. case .emaLinkCommands:
  643. var vc: CommandResponseViewController?
  644. switch EmaLinkCommandRow(rawValue: indexPath.row)! {
  645. case .logicLEDSMode:
  646. let nextMode: RileyLinkLEDMode
  647. switch ledMode {
  648. case.on:
  649. nextMode = .off
  650. default:
  651. nextMode = .on
  652. }
  653. vc = .setDiagnosticLEDMode(device: device, mode: nextMode)
  654. case .getStatistics:
  655. vc = .getStatistics(device: device)
  656. }
  657. if let cell = tableView.cellForRow(at: indexPath) {
  658. vc?.title = cell.textLabel?.text
  659. }
  660. if let vc = vc {
  661. show(vc, sender: indexPath)
  662. }
  663. case .orangeLinkCommands:
  664. switch OrangeLinkCommandRow(rawValue: indexPath.row)! {
  665. case .findDevice:
  666. device.findDevice()
  667. tableView.deselectRow(at: indexPath, animated: true)
  668. default:
  669. break
  670. }
  671. case .configureCommand:
  672. break
  673. case .alert:
  674. switch AlertRow(rawValue: indexPath.row)! {
  675. case .battery:
  676. let alert = UIAlertController.init(title: LocalizedString("Battery level Alert", comment: "Header of list showing battery level alert options"), message: nil, preferredStyle: .actionSheet)
  677. let action = UIAlertAction.init(title: LocalizedString("OFF", comment: "Battery level alert OFF in list of options"), style: .default) { _ in
  678. self.batteryAlertLevel = nil
  679. self.tableView.reloadData()
  680. }
  681. alert.addAction(action)
  682. for value in [20,30,40,50] {
  683. let action = UIAlertAction.init(title: "\(value)%", style: .default) { _ in
  684. self.batteryAlertLevel = value
  685. self.tableView.reloadData()
  686. }
  687. alert.addAction(action)
  688. }
  689. present(alert, animated: true, completion: nil)
  690. }
  691. }
  692. }
  693. }
  694. extension RileyLinkDeviceTableViewController: TextFieldTableViewControllerDelegate {
  695. public func textFieldTableViewControllerDidReturn(_ controller: TextFieldTableViewController) {
  696. _ = navigationController?.popViewController(animated: true)
  697. }
  698. public func textFieldTableViewControllerDidEndEditing(_ controller: TextFieldTableViewController) {
  699. if let indexPath = tableView.indexPathForSelectedRow {
  700. switch sections[indexPath.section] {
  701. case .device:
  702. switch deviceRows[indexPath.row] {
  703. case .customName:
  704. device.setCustomName(controller.value!)
  705. default:
  706. break
  707. }
  708. default:
  709. break
  710. }
  711. }
  712. }
  713. }
  714. private extension TimeInterval {
  715. func format(using units: NSCalendar.Unit) -> String? {
  716. let formatter = DateComponentsFormatter()
  717. formatter.allowedUnits = units
  718. formatter.unitsStyle = .full
  719. formatter.zeroFormattingBehavior = .dropLeading
  720. formatter.maximumUnitCount = 2
  721. return formatter.string(from: self)
  722. }
  723. }
  724. private extension UITableViewCell {
  725. func setDetailDate(_ date: Date?, formatter: DateFormatter) {
  726. if let date = date {
  727. detailTextLabel?.text = formatter.string(from: date)
  728. } else {
  729. detailTextLabel?.text = "-"
  730. }
  731. }
  732. func setDetailRSSI(_ decibles: Int?, formatter: NumberFormatter) {
  733. detailTextLabel?.text = formatter.decibleString(from: decibles) ?? "-"
  734. }
  735. func setDetailAge(_ age: TimeInterval?) {
  736. if let age = age {
  737. detailTextLabel?.text = age.format(using: [.day, .hour, .minute])
  738. } else {
  739. detailTextLabel?.text = ""
  740. }
  741. }
  742. func setDetailBatteryLevel(_ batteryLevel: Int?) {
  743. if let batteryLevel = batteryLevel {
  744. detailTextLabel?.text = "\(batteryLevel)" + " %"
  745. } else {
  746. detailTextLabel?.text = ""
  747. }
  748. }
  749. func setDetailFrequency(_ frequency: Measurement<UnitFrequency>?, formatter: MeasurementFormatter) {
  750. if let frequency = frequency {
  751. detailTextLabel?.text = formatter.string(from: frequency)
  752. } else {
  753. detailTextLabel?.text = ""
  754. }
  755. }
  756. func setLEDMode(_ mode: RileyLinkLEDMode?) {
  757. switch mode {
  758. case .on:
  759. detailTextLabel?.text = LocalizedString("On", comment: "Text indicating LED Mode is on")
  760. case .off:
  761. detailTextLabel?.text = LocalizedString("Off", comment: "Text indicating LED Mode is off")
  762. case .auto:
  763. detailTextLabel?.text = LocalizedString("Auto", comment: "Text indicating LED Mode is auto")
  764. case .none:
  765. detailTextLabel?.text = ""
  766. }
  767. }
  768. func setVoltage(_ voltage: Float?) {
  769. if let voltage = voltage {
  770. detailTextLabel?.text = String(format: "%.1f%", voltage)
  771. } else {
  772. detailTextLabel?.text = ""
  773. }
  774. }
  775. func setBatteryAlert(_ level: Int?, formatter: NumberFormatter) {
  776. detailTextLabel?.text = formatter.percentString(from: level) ?? LocalizedString("Off", comment: "Detail text when battery alert disabled.")
  777. }
  778. }