Read/Write/Notify

AppDelegate.swift
import UIKit
import CoreData
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let viewController = ViewController()
let navigationController = UINavigationController(rootViewController: viewController)
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()
return true
}
func applicationWillResignActive(_ application: UIApplication) {
}
func applicationDidEnterBackground(_ application: UIApplication) {
}
func applicationWillEnterForeground(_ application: UIApplication) {
}
func applicationDidBecomeActive(_ application: UIApplication) {
}
func applicationWillTerminate(_ application: UIApplication) {
self.saveContext()
}
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "SwiftBLE")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
ViewController.swift
import UIKit
import CoreBluetooth
class ViewController: UIViewController {
var tableView: UITableView!
var uuids = Array<UUID>()
var names = [UUID : String]()
var peripherals = [UUID : CBPeripheral]()
var targetPeripheral: CBPeripheral!
var centralManager: CBCentralManager!
let button = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
let barHeight = UIApplication.shared.statusBarFrame.size.height
let displayWidth = self.view.frame.width
let displayHeight = self.view.frame.height
tableView = UITableView(frame: CGRect(x: 0, y: barHeight, width: displayWidth, height: displayHeight - barHeight))
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
tableView.dataSource = self
tableView.delegate = self
self.view.addSubview(tableView)
button.frame = CGRect(x: 0, y: 0, width: 200, height: 40)
button.backgroundColor = UIColor.red
button.layer.masksToBounds = true
button.setTitle("検索", for: UIControlState.normal)
button.setTitleColor(UIColor.white, for: UIControlState.normal)
button.layer.cornerRadius = 20.0
button.layer.position = CGPoint(x: self.view.frame.width/2, y:self.view.frame.height-50)
button.tag = 1
button.addTarget(self, action: #selector(onClickMyButton(sender:)), for: .touchUpInside)
self.view.addSubview(button);
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
@objc func onClickMyButton(sender: UIButton){
self.uuids = []
self.names = [:]
self.peripherals = [:]
centralManager = CBCentralManager(delegate: self, queue: nil, options: nil)
}
}
extension ViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.names.count
}
}
extension ViewController: UITableViewDelegate{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let uuid = self.uuids[indexPath.row]
print("Num: \(indexPath.row)")
print("uuid: \(uuid.description)")
print("Name: \(String(describing: self.names[uuid]?.description))")
self.targetPeripheral = self.peripherals[uuid]
self.centralManager.connect(self.targetPeripheral, options: nil)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier:"MyCell" )
let uuid = self.uuids[indexPath.row]
cell.textLabel!.sizeToFit()
cell.textLabel!.textColor = UIColor.red
cell.textLabel!.text = self.names[uuid]
cell.textLabel!.font = UIFont.systemFont(ofSize: 20)
cell.detailTextLabel!.text = uuid.description
cell.detailTextLabel!.font = UIFont.systemFont(ofSize: 12)
return cell
}
}
extension ViewController: CBCentralManagerDelegate{
func centralManagerDidUpdateState(_ central: CBCentralManager) {
print("state \(central.state)")
switch central.state {
case .poweredOff:
print("Bluetoothの電源がOff")
case .poweredOn:
print("Bluetoothの電源はOn")
centralManager.scanForPeripherals(withServices: nil)
case .resetting:
print("レスティング状態")
case .unauthorized:
print("非認証状態")
case .unknown:
print("不明")
case .unsupported:
print("非対応")
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral,
advertisementData: [String: Any], rssi RSSI: NSNumber) {
print("pheripheral.name: \(String(describing: peripheral.name))")
print("advertisementData:\(advertisementData)")
print("RSSI: \(RSSI)")
print("peripheral.identifier.uuidString: \(peripheral.identifier.uuidString)")
let uuid = UUID(uuid: peripheral.identifier.uuid)
self.uuids.append(uuid)
let kCBAdvDataLocalName = advertisementData["kCBAdvDataLocalName"] as? String
if let name = kCBAdvDataLocalName {
self.names[uuid] = name.description
} else {
self.names[uuid] = "no name"
}
self.peripherals[uuid] = peripheral
tableView.reloadData()
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("connect")
let secondViewController: SecondViewController = SecondViewController()
secondViewController.setPeripheral(target: self.targetPeripheral)
secondViewController.setCentralManager(manager: self.centralManager)
secondViewController.searchService()
secondViewController.modalTransitionStyle = UIModalTransitionStyle.partialCurl
self.navigationController?.pushViewController(secondViewController, animated: true)
self.centralManager.stopScan()
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
if let e = error {
print("Error: \(e.localizedDescription)")
return
}
print("not connnect")
}
}
SecondViewController.swift
import Foundation
import UIKit
import CoreBluetooth
class SecondViewController: UIViewController {
var tableView: UITableView!
var serviceUuids: [String] = []
var services: [CBService] = []
var buttonBefore: UIButton!
var targetPeriperal: CBPeripheral!
var centralManager: CBCentralManager!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.blue
let barHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height
let displayWidth: CGFloat = self.view.frame.width
let displayHeight: CGFloat = self.view.frame.height
tableView = UITableView(frame: CGRect(x: 0, y: barHeight, width: displayWidth, height: displayHeight - barHeight))
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
tableView.dataSource = self
tableView.delegate = self
self.view.addSubview(tableView)
}
override func didMove(toParentViewController parent: UIViewController?) {
if parent == nil {
self.centralManager.cancelPeripheralConnection(self.targetPeriperal)
}
}
}
extension SecondViewController: UITableViewDelegate{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("ServiceUuid: \(serviceUuids[indexPath.row])")
let thirdViewController: ThirdViewController = ThirdViewController()
thirdViewController.setPeripheral(target: self.targetPeriperal)
thirdViewController.setService(service: self.services[indexPath.row])
thirdViewController.searchCharacteristics()
thirdViewController.modalTransitionStyle = UIModalTransitionStyle.partialCurl
self.navigationController?.pushViewController(thirdViewController, animated: true)
}
}
extension SecondViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return serviceUuids.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier:"MyCell" )
cell.textLabel!.sizeToFit()
cell.textLabel!.textColor = UIColor.red
cell.textLabel!.text = "\(serviceUuids[indexPath.row])"
cell.textLabel!.font = UIFont.systemFont(ofSize: 16)
cell.detailTextLabel!.text = "Service"
cell.detailTextLabel!.font = UIFont.systemFont(ofSize: 12)
return cell
}
}
extension SecondViewController: CBPeripheralDelegate{
func setPeripheral(target: CBPeripheral) {
self.targetPeriperal = target
}
func setCentralManager(manager: CBCentralManager) {
self.centralManager = manager
}
func searchService() {
print("searchService")
self.targetPeriperal.delegate = self
self.targetPeriperal.discoverServices(nil)
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if let e = error {
print("Error: \(e.localizedDescription)")
return
}
print("didDiscoverServices")
for service in peripheral.services! {
serviceUuids.append(service.uuid.uuidString)
services.append(service)
print("P: \(String(describing: peripheral.name)) - Discovered service S:'\(service.uuid)'")
}
tableView.reloadData()
}
}
ThirdViewController.swift
import Foundation
import UIKit
import CoreBluetooth
class ThirdViewController: UIViewController {
var tableView: UITableView!
var readTableView: UITableView!
var services: [CBService] = []
var characteristics: [CBCharacteristic] = []
var buttonBefore: UIButton!
var targetPeriperal: CBPeripheral!
var targetService: CBService!
var centralManager: CBCentralManager!
var targetCharacteristic: CBCharacteristic!
var readButton: UIButton!
var notifyLabel: UILabel!
var writeField: UITextField!
var writeButton: UIButton!
var readValues: [Data] = []
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.cyan
let barHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height
let displayWidth: CGFloat = self.view.frame.width
let displayHeight: CGFloat = self.view.frame.height
tableView = UITableView(frame: CGRect(x: 0, y: barHeight, width: displayWidth, height: displayHeight/2 - barHeight))
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
tableView.dataSource = self
tableView.delegate = self
self.view.addSubview(tableView)
readTableView = UITableView(frame: CGRect(x: 0, y: barHeight + displayHeight/2 + 100, width: displayWidth, height: 200))
readTableView.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
readTableView.dataSource = self
readTableView.delegate = self
self.view.addSubview(readTableView)
readButton = UIButton()
readButton.frame = CGRect(x: displayWidth/2 - 150, y: displayHeight/2+50, width: 100, height: 40)
readButton.backgroundColor = UIColor.red
readButton.layer.masksToBounds = true
readButton.setTitle("Read", for: UIControlState.normal)
readButton.layer.cornerRadius = 10.0
readButton.tag = 1
readButton.addTarget(self, action: #selector(ThirdViewController.onClickMyButton(sender:)), for: .touchUpInside)
notifyLabel = UILabel(frame: CGRect(x:displayWidth/2 + 50, y: displayHeight/2+55, width: 100, height: 30))
notifyLabel.textColor = UIColor.blue
notifyLabel.text = "Notify"
notifyLabel.tag = 2
notifyLabel.isUserInteractionEnabled = true
writeField = UITextField(frame: CGRect(x:10, y: displayHeight/2+15, width: displayWidth - 100 - 30, height: 30))
writeField.text = ""
writeField.delegate = self
writeField.borderStyle = .roundedRect
writeField.clearButtonMode = .whileEditing
writeButton = UIButton()
writeButton.frame = CGRect(x: displayWidth - 110, y: displayHeight/2+10, width: 100, height: 40)
writeButton.backgroundColor = UIColor.blue
writeButton.layer.masksToBounds = true
writeButton.setTitle("Write", for: UIControlState.normal)
writeButton.layer.cornerRadius = 10.0
writeButton.tag = 2
writeButton.addTarget(self, action: #selector(ThirdViewController.onClickMyButton(sender:)), for: .touchUpInside)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: UITouch in touches {
let tag = touch.view!.tag
if (tag == 2) {
if !self.targetCharacteristic.isNotifying {
self.targetPeriperal.setNotifyValue(true, for: self.targetCharacteristic)
notifyLabel.text = "Stop Notify"
} else {
self.targetPeriperal.setNotifyValue(false, for: self.targetCharacteristic)
notifyLabel.text = "Notify"
}
}
}
}
@objc func onClickMyButton(sender: UIButton){
print("onClickMyButton:")
print("sender.currentTitile: \(String(describing: sender.currentTitle))")
print("sender.tag:\(sender.tag)")
if let charasteristic = self.targetCharacteristic {
if(sender.tag == 1){
self.targetPeriperal.readValue(for: charasteristic)
}
else if(sender.tag == 2){
let data = writeField.text!.data(using: String.Encoding.utf8, allowLossyConversion:true)
self.targetPeriperal.writeValue(data!, for: targetCharacteristic, type: CBCharacteristicWriteType.withResponse)
}
}
}
func addButton(characteristic: CBCharacteristic) {
if (self.readButton.isDescendant(of: self.view)) {
self.readButton.removeFromSuperview()
}
if isRead(characteristic: characteristic) {
self.view.addSubview(self.readButton)
}
if (self.writeField.isDescendant(of: self.view)) {
self.writeField.removeFromSuperview()
}
if isWrite(characteristic: characteristic) {
self.view.addSubview(self.writeField)
}
if (self.writeButton.isDescendant(of: self.view)) {
self.writeButton.removeFromSuperview()
}
if isWrite(characteristic: characteristic) {
self.view.addSubview(self.writeButton)
}
if (self.notifyLabel.isDescendant(of: self.view)) {
self.notifyLabel.removeFromSuperview()
}
if isNotify(characteristic: characteristic) {
if !self.targetCharacteristic.isNotifying {
notifyLabel.text = "Notify"
} else {
notifyLabel.text = "Stop Notify"
}
self.view.addSubview(self.notifyLabel)
}
}
}
extension ThirdViewController: UITextFieldDelegate{
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
extension ThirdViewController: UITableViewDelegate{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView.isEqual(self.tableView) {
self.targetCharacteristic = characteristics[indexPath.row]
addButton(characteristic: self.targetCharacteristic)
}
}
}
extension ThirdViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView.isEqual(self.tableView) {
return characteristics.count
} else if tableView.isEqual(self.readTableView) {
return readValues.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.subtitle, reuseIdentifier:"MyCell" )
cell.textLabel!.sizeToFit()
cell.textLabel!.textColor = UIColor.red
cell.textLabel!.font = UIFont.systemFont(ofSize: 16)
cell.detailTextLabel!.font = UIFont.systemFont(ofSize: 12)
if tableView.isEqual(self.tableView) {
let characteristic = characteristics[indexPath.row]
cell.textLabel!.text = "\(characteristic.uuid)"
var strProp = ""
if isRead(characteristic: characteristic) {
strProp += "Read "
}
if isWrite(characteristic: characteristic) {
strProp += "Write "
}
if isNotify(characteristic: characteristic) {
strProp += "Notifiy"
}
cell.detailTextLabel!.text = "\(strProp)"
}
else if tableView.isEqual(self.readTableView) {
let value = self.readValues[indexPath.row]
cell.textLabel!.text = "\(value.base64EncodedString())"
let now = Date()
let locale = Locale(identifier: "ja_JP")
cell.detailTextLabel!.text = "\(now.description(with: locale))"
}
return cell
}
}
extension ThirdViewController: CBPeripheralDelegate{
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if let e = error {
print("Error: \(e.localizedDescription)")
return
}
readValues.insert(characteristic.value!, at: 0)
if readValues.count > 10 {
readValues.removeLast()
}
readTableView.reloadData()
}
func setPeripheral(target: CBPeripheral) {
self.targetPeriperal = target
}
func setService(service: CBService) {
self.targetService = service
}
func searchCharacteristics(){
print("searchService")
self.targetPeriperal.delegate = self
self.targetPeriperal.discoverCharacteristics(nil, for: self.targetService)
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService,
error: Error?) {
print("didDiscoverCharacteristicsForService")
for characteristic in service.characteristics! {
characteristics.append(characteristic)
}
tableView.reloadData()
}
func isRead(characteristic: CBCharacteristic) -> Bool{
if characteristic.properties.contains(.read) {
return true
}
return false
}
func isWrite(characteristic: CBCharacteristic) -> Bool{
if characteristic.properties.contains(.write) || characteristic.properties.contains(.writeWithoutResponse) {
return true
}
return false
}
func isNotify(characteristic: CBCharacteristic) -> Bool{
if characteristic.properties.contains(.notify) {
return true
}
return false
}
}
Reference
- CoreBluetooth
- UITableViewDataSource