Swift 코드
struct ContentView: View {
@Environment(Bluetooth.self) var bluetooth
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "BluetoothAlert.BluetoothAlert")
request.earliestBeginDate = Calendar.current.date(byAdding: .second, value: 5, to: Date())
do {
try BGTaskScheduler.shared.submit(request)
print("Background Task Scheduled!")
} catch(let error) {
print("Scheduling Error \(error.localizedDescription)")
}
}
var body: some View {
Text("블루투스 알람 앱을 만들어보자")
.padding(50)
Button {
scheduleAppRefresh()
} label: {
Text("백그라운드로 불 켜기")
}
.padding(40)
Button(action: {
bluetooth.sendMessageToDevice("o")
}, label: {
Text("불켜기")
})
Button(action: {
bluetooth.sendMessageToDevice("f")
}, label: {
Text("불끄기")
})
List(bluetooth.peripherals.sorted(by: { $0.name ?? "" < $1.name ?? "" }), id: \.self) { ph in
Button {
print("연결")
bluetooth.centralManager.connect(ph)
} label: {
Text(ph.name ?? "Unknown Device")
}
}
}
}
import CoreBluetooth
import SwiftUI
@Observable class Bluetooth: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
var peripherals: Set<CBPeripheral> = Set<CBPeripheral>()
var values: String = .init()
var connected = false
weak var writeCharacteristic: CBCharacteristic? // 데이터를 주변기기에 보내기 위한 characcteristic을 저장하는 변수
// 데이터를 주변기기에 보내는 type을 설정한다. withResponse는 데이터를 보내면 이에 대한 답장이 오는 경우, withoutResponse는 데이터를 보내도 답장이 오지 않는 경우
private var writeType: CBCharacteristicWriteType = .withoutResponse
var centralManager : CBCentralManager! // centralManager 객체 만들기
var connectedPeripheral : CBPeripheral? // 연결된 아두이노 객체
var serviceUUID = CBUUID(string: "FFE0") // 아래에 있는 128비트짜리 uuid를 사용하면 모듈을 못찾는다.
//characteristicUUID는 serviceUUID에 포함되어 있다. 이를 이용하여 데이터를 송수신한다. FFE0 서비스가 갖고있는 FFE1로 설정한다.
var characteristicUUID = CBUUID(string: "FFE1")
public override init() {
super.init()
print("초기화")
self.centralManager = CBCentralManager.init(delegate: self, queue: nil, options: nil) // 1. centralManager 객체를 초기화 시킨다.
}
// central manager를 만들때 central manager는 이 메서드를 부른다.
// centralManagerDidUpdateState 함수는 delegate한테 central manager'의 상태가 변했다고 알려주는데
// 아래와 같은 경우는 init()에서 centralManager가 CBCentralManager.init(...)을 실행시켜서 초기화 시킴으로 상태가 변했으므로 이 함수를 실행시킨다.
// 저전력 블루투스가 지원되고 central 기기에서 쓰일수 있도록 반드시 구현해야한다.
func centralManagerDidUpdateState(_ central: CBCentralManager) {
print("만들어졌다")
switch central.state {
case .unknown:
print("unknown")
case .resetting:
print("resetting")
case .unsupported:
print("unsupported")
case .unauthorized:
print("unauthorized")
case .poweredOff:
print("powered off")
case .poweredOn:
print("powered on")
// self.centralManager.scanForPeripherals(withServices: nil, options: nil)
self.centralManager.scanForPeripherals(withServices: [serviceUUID])
@unknown default:
fatalError()
}
connectedPeripheral = nil
}
func startScan(){ // 2. 주변기기를 스캔
guard centralManager.state == .poweredOn else {return}
// [serviceUUID]만 갖고있는 기기만 검색
print("주변기기 스캔시작")
centralManager.scanForPeripherals(withServices: [serviceUUID], options: nil)
}
func stopScan() {
centralManager.stopScan()
}
// central manager가 peripheral를 발견할 때마다 delegate 객체의 메서드를 호출 // RSSI: 신호강도
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print("발견")
peripherals.insert(peripheral)
centralManager.connect(peripheral)
}
// 기기가 연결되면 호출되는 delegate 메서드다.
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.delegate = self // 5. 연결되면 주변기기 대리자 만들기
connectedPeripheral = peripheral
// peripheral의 Service들을 검색한다. 파라미터를 nil으로 설정하면 Peripheral의 모든 service를 검색한다.
peripheral.discoverServices([serviceUUID])
if peripheral.name == "YourNewName" {
connected = true
}
print([serviceUUID])
print("연결 성공")
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
if peripheral.name == "YourNewName" {
connected = false
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
for service in peripheral.services! {
peripheral.discoverCharacteristics([characteristicUUID], for: service)
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
for characteristic in service.characteristics!{
if characteristic.uuid == characteristicUUID {
peripheral.setNotifyValue(true, for: characteristic) // 10. 구독한다.
writeCharacteristic = characteristic // writeCharacteristic: 주변기기에 보내기 위한 특성을 저장하는 변수
writeType = characteristic.properties.contains(.write) ? .withResponse : .withoutResponse // 9.
connected = true
}
}
}
func sendMessageToDevice(_ message: String){ // -> Bool{
// 만약 블루투스가 연결되지 않았다면 보내면 안된다.
if connectedPeripheral?.state == .connected {
if let data = message.data(using: String.Encoding.utf8), let charater = writeCharacteristic {
connectedPeripheral!.writeValue(data, for: charater, type: writeType) // writeCharacteristic은 주변기기에 보내기 위한 특성
}
}
}
}
아두이노 코드
#include <SoftwareSerial.h>
#define LED 13
SoftwareSerial HM10(BT_RXD, BT_TXD); // RX핀(4번)은 HM10의 TX에 연결
// TX핀(5번)은 HM10의 RX에 연결
void setup(){
Serial.begin(9600);
pinMode(LED, OUTPUT); // LED를 출력으로 설정
HM10.begin(9600);
}
void loop(){
// Bluetooth 모듈에서 데이터를 읽어오기
if (HM10.available()){
char h = (char)HM10.read();
Serial.println(h);
if(h == 'o'){ // 알파벳 소문자 'o'를 입력하면
digitalWrite(LED, HIGH); // LED가 점등됨
}
if(h == 'f'){ // 알파벳 소문자 'f'를 입력하면
digitalWrite(LED, LOW); // LED가 소등됨
}
}
// 시리얼 모니터를 통해 데이터를 읽어서 Bluetooth 모듈로 전송함
if(Serial.available()){
char h = (char)Serial.read();
HM10.println(h);
}
}
BGAppRefreshTaskRequest 함정
BGAppRefreshTaskRequest 는 시스템이 여유가 있을 때 실행되기 때문에 시간을 아래처럼 5초 뒤부터 시작하도록 맞춰도 바로 실행되는게 아니라는 것을 알 수 있었습니다. 그렇기 때문에 즉각적인 동작을 수행하기 위해서는 타이머를 설정하여 불을 켜야하 합니다.
'SwiftUI' 카테고리의 다른 글
Typelias (0) | 2024.04.08 |
---|---|
Weak Self (0) | 2024.04.07 |
Background Threads, Queues (0) | 2024.04.07 |
버튼 커스텀화하기, Custom ButtonStyle (0) | 2024.04.07 |
Custom ViewModifiers (0) | 2024.04.07 |