[사물 인터넷 네트워크와 서비스 구축 강좌] #3-9 센서장치-서버 BLE 통신
강좌 전체보기
.
이번 파트는 라즈베리파이 – 센서장치간 BLE 통신을 다룹니다.
이제까지 실습해온 [3-7 센서장치-모바일 BLE 통신], [3-8 센서장치-센서장치 BLE 통신] 과 과정은 거의 유사합니다. 다만 이번에는 라즈베리파이를 사용해서 연결할 뿐입니다. 이번에도 파이썬을 이용해 센서장치와 BLE 연결을 하고, 채팅을 하도록 만들어 보겠습니다.
라즈베리파이 설정
앞선 실습에서 만든 ESP32 – GATT server 센서장치를 그대로 사용합니다. 라즈베리파이를 위해 아래에서 파이썬 코드를 받습니다.
소스코드에서 수정할 부분들이 있습니다.
TARGET_UUID = "4d6fc88bbe756698da486866a36ec78e" target_dev = None UART_SERVICE_UUID = UUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b") uart_service = None UART_WRITE_UUID = UUID("beb5483e-36e1-4688-b7f5-ea07361b26a8") write_char = None UART_READ_UUID = UUID("beb5483e-36e1-4688-b7f5-ea07361b26fa") read_char = None
Beacon/Service/Read characteristic/Write characteristic UUID 값을 ESP32 센서장치에 설정한 값과 똑같이 맞춰줍니다.
그리고 라즈베리파이에 업로드 후 실행하세요.
- sudo python3 BleConnect.py
ESP32 – GATT server 센서장치를 PC에 연결 후 시리얼 모니터를 켭니다.
테스트
라즈베리파이에 올린 Python 코드는 연결이 완료되면 주기적으로 “Hello~” 메시지를 보내도록 만들어져 있습니다. 따라서 주기적으로 시리얼 모니터에서 Hello~ 메시지가 보여야 합니다. 시리얼 모니터에서 메시지를 전송하면 라즈베리파이에도 보여야합니다.
여기까지 확인되면 정상동작 하는겁니다!!
소스코드
파이썬 소스코드를 확인해 보겠습니다. 파이썬 코드를 실행하면 먼저 BLE 스캔 작업부터 합니다.
class ScanDelegate(DefaultDelegate): def __init__(self): DefaultDelegate.__init__(self) def handleDiscovery(self, dev, isNewDev, isNewData): if isNewDev: print("Discovered device %s" % dev.addr) elif isNewData: print("Received new data from %s", dev.addr) ...... scanner = Scanner().withDelegate(ScanDelegate()) devices = scanner.scan(10.0) for dev in devices: print("Device %s (%s), RSSI=%d dB" % (dev.addr, dev.addrType, dev.rssi)) for (adtype, desc, value) in dev.getScanData(): # Check iBeacon UUID # 255 is manufacturer data (1 is Flags, 9 is Name) if adtype is 255 and TARGET_UUID in value: target_dev = dev print(" +--- found target device!!") print(" (AD Type=%d) %s = %s" % (adtype, desc, value))
scanner.scan(10.0) 을 호출하면 10초가 스캔이 진행됩니다. 이때 스캔 결과를 받기위해 Scanner().withDelegate(ScanDelegate()) 를 먼저 호출해서 콜백함수를 등록했습니다.
BLE 장치가 발견되면 ScanDelegate 클래스의 handleDiscovery() 함수가 호출되지만, 연결할 BLE 장치를 찾는 작업은 스캔이 모두 끝나고나서 합니다. (즉 ScanDelegate 인스턴스를 콜백으로 등록한건 이 코드에서는 별 의미가 없습니다. 단순히 로그 메시지만 찍습니다.)
10초간 스캔 작업이 모두 끝나면 for 루프를 돌면서 그동안 찾은 BLE 장치를 하나씩 검사합니다. 특히 우리가 찾는 iBeacon UUID 를 패킷에 포함하고 있는지 확인합니다.
우리가 원하는 BLE 장치를 찾으면 이제 연결 작업을 합니다.
if target_dev is not None: print("Connecting...") print(" ") p = Peripheral(target_dev.addr, target_dev.addrType) try: # Set notify callback p.setDelegate( NotifyDelegate(p) ) …… ############################################# # Set up characteristics ############################################# uart_service = p.getServiceByUUID(UART_SERVICE_UUID) write_char = uart_service.getCharacteristics(UART_WRITE_UUID)[0] read_char = uart_service.getCharacteristics(UART_READ_UUID)[0] read_handle = read_char.getHandle() # Search and get the read-Characteristics "property" # (UUID-0x2902 CCC-Client Characteristic Configuration)) # which is located in a handle in the range defined by the boundries of the Service for desriptor in p.getDescriptors(read_handle, 0xFFFF): if (desriptor.uuid == 0x2902): print("Client Char found at handle 0x"+ format(desriptor.handle, "02X")) read_cccd = desriptor.handle p.writeCharacteristic(read_cccd, struct.pack('<bb', 0x01, 0x00)) ############################################# # BLE message loop ############################################# while 1: if p.waitForNotifications(5.0): # handleNotification() was called continue # p.writeCharacteristic(hButtonCCC, struct.pack('<bb', 0x01, 0x00)) write_char.write(str.encode("hello~")) finally: p.disconnect()
Peripheral(target_dev.addr, target_dev.addrType) 을 호출하면 연결이 시작됩니다. 연결이 완료되면 우리가 찾는 Service/Read characteristic/Write characteristic 이 있는지 확인합니다.
그리고 Read characteristic 에 notify 기능을 on 시켜야 채팅 메시지를 라즈베리파이가 받을 수 있겠죠? 이를 위해 Read characteristic 에 달린 CCCD 를 활성화 시켜줍니다. 아래 코드가 이 작업을 하는 코드입니다.
read_char = uart_service.getCharacteristics(UART_READ_UUID)[0] read_handle = read_char.getHandle() # Search and get the read-Characteristics "property" # (UUID-0x2902 CCC-Client Characteristic Configuration)) # which is located in a handle in the range defined by the boundries of the Service for desriptor in p.getDescriptors(read_handle, 0xFFFF): if (desriptor.uuid == 0x2902): print("Client Char found at handle 0x"+ format(desriptor.handle, "02X")) read_cccd = desriptor.handle p.writeCharacteristic(read_cccd, struct.pack('<bb', 0x01, 0x00))
이제 characteristic 설정이 끝났으니 메인 루프를 돌리면서 notify 오는지 p.waitForNotification(5.0) 함수로 확인합니다. 이 함수는 블로킹 함수이기 때문에 notify 를 받지 않는다면 5초간 코드가 여기서 멈춥니다. 5초 후에는 hello~ 메시지를 센서장치에 전송하고 다시 같은 작업을 반복합니다.
############################################# # BLE message loop ############################################# while 1: if p.waitForNotifications(5.0): # handleNotification() was called continue # p.writeCharacteristic(hButtonCCC, struct.pack('<bb', 0x01, 0x00)) write_char.write(str.encode("hello~"))
활용
사실 BLE 를 사용하는 주된 목적은 모바일 장치와 센서장치를 연동하기 위해서입니다. 하지만 홈 서버 장치에 BLE 기능을 추가하면, 홈 서버 주변에 있는 다양한 BLE 기기들과 동적으로 연동해서 데이터를 주고 받거나 컨트롤 할 수 있습니다.
때에 따라서는 홈 서버의 BLE 가 강력한 컨트롤 수단이 될 수 있습니다. 특히 저전력으로 동작해야하는 센서장치가 곧곧에 있다거나, 홈 서버 또는 이와 유사한 장치가 이동하면서 주변의 센서장치를 검색하고 연결하는 시나리오에서 유용할 수 있습니다.
참고자료 :
- BluePy APIs
- BluePy examples
- BluePy examples 2
- BluePy – set notify
- iBeacon prefix details
- Adafruit_BluefruitLE
- BluefruitLE python – GitHub
- GAP address – public address, random address
주의!!! [사물 인터넷 네트워크와 서비스 구축 강좌] 시리즈 관련 문서들은 무단으로 내용의 일부 또는 전체를 게시하여서는 안됩니다. 계속 내용이 업데이트 되는 문서이며, 문서에 인용된 자료의 경우 원작자의 라이센스 문제가 있을 수 있습니다.
강좌 전체보기
.