강좌 전체보기

.

이번 파트는 라즈베리파이 – 센서장치간 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 가 강력한 컨트롤 수단이 될 수 있습니다. 특히 저전력으로 동작해야하는 센서장치가 곧곧에 있다거나, 홈 서버 또는 이와 유사한 장치가 이동하면서 주변의 센서장치를 검색하고 연결하는 시나리오에서 유용할 수 있습니다.

참고자료 :

주의!!! [사물 인터넷 네트워크와 서비스 구축 강좌] 시리즈 관련 문서들은 무단으로 내용의 일부 또는 전체를 게시하여서는 안됩니다. 계속 내용이 업데이트 되는 문서이며, 문서에 인용된 자료의 경우 원작자의 라이센스 문제가 있을 수 있습니다.

강좌 전체보기

.