BlueZ를 이용한 블루투스 프로그래밍 샘플코드입니다. 2012년에 기록된 걸로 봐서 H사에 있을 때 썼던거 같긴한데요. BLE를 사용하지 않는경우 일반적으로 이러한 프로그래밍 방식은 크게 바뀌지 않았기에 embeddedclub.net에서 가져와 백업해 둡니다. 아마 어디에 있는 영문문서였던거 같은데 출처는 확실하게 기억이 나지 않습니다. 

     

    1. BlueZ 라이브러리 소개

      리눅스 상에서 블루투스 장비를 프로그래밍 하기위해서는 보통 BlueZ 라이브러리를 이용합니다. (씨리얼 방식으로 통신하기도 하는 듯합니다.) BlueZ 라이브러리는 리눅스 환경에서 Bluetooth 무선 표준 스펙을 구현한 구현물입니다. 공식적으로 이 프로젝트는 Kernel 2.4, 2.6 을 지원합니다.

     

       http://www.bluez.org/

     

    BlueZ

    19th March 2022, 06:18 am by Tedd Ho-Jeong An This is another release mostly with the bug fixes on HOG, GATT, A2DP, Media, AVDTP, AVRCP, and scanning failure. Also, this release includes a fix for building with old glibc (< 2.25), and other minor issues fo

    www.bluez.org

     

    2. BlueZ 라이브러리 설치 및 설정
    리눅스 상에서 BlueZ 를 동작하도록 하기 위해서는 우선 라이브러리를 설치해야합니다.

     

      http://www.bluez.org/download.html

     

      상기의 페이지에서 BlueZ 라이브러리의 최신버전을 받으시고, 컴파일한뒤 구동 환경으로 복사하시기 바랍니다. 이때, BlueZ 라이브러리를 구동하는 환경이 커널 2.4 시리즈인 경우 커널상에 Bluetooth 스택이 올라가있지 않은 경우가 있습니다. 이 경우 패치 커널에 적용하고 다시 설치해야만 정상적으로 블루투스를 이용할 수 있습니다.

      각 커널 별 블루투스 스택 지원 사항에 대한 설명과 패치 파일 및 패치 과정에 관련된 설명은 아래 사이트를 참고하시기 바랍니다.

     

      http://www.holtmann.org/linux/kernel/

     

      일단 커널 스택과 라이브러리가 정상적으로 설치되었다면 블루투스 동글을 삽입한뒤 다음과 명령어를 통해서 삽입된 블루투스 장비가 있다는 사실을 확인하시기 바랍니다. (제 경우에는 USB 형태의 동글을 이용했습니다.)

     

    # hciconfig

    만약, 리눅스 커널이 삽입된 동글을 인식했다면 hci0 라는 디바이스가 잡힐 것 입니다.

    # hciconfig hci0 up

    명령어를 이용해서 해당 디바이스를 활성화시키시면 모든 준비작업이 끝납니다.

     

    3. BlueZ 예제
    ※ 소스에 대한 상세한 설명을 원하시는 분은 참고자료 섹션의 MIT 대학의 사이트를 참고하시기 바랍니다.

    3.1 통신 장비 스캐닝 / simplescan.c

     시스템에 연결된 블루투스 장치를 이용하여 주변의 연결가능한 블루투스 장치를 스캔하는 코드 입니다. 

     

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <bluetooth/bluetooth.h>
    #include <bluetooth/hci.h>
    #include <bluetooth/hci_lib.h>
    
    int main(int argc, char **argv)
    {
        inquiry_info *ii = NULL;
        int max_rsp, num_rsp;
        int dev_id, sock, len, flags;
        int i;
        char addr[19] = { 0 };
        char name[248] = { 0 };
    
        dev_id = hci_get_route(NULL);
        sock = hci_open_dev( dev_id );
        if (dev_id < 0 || sock < 0) {
            perror("opening socket");
            exit(1);
        }
    
        len = 8;
        max_rsp = 255;
        flags = IREQ_CACHE_FLUSH;
        ii = (inquiry_info*)malloc(max_rsp * sizeof(inquiry_info));
    
        num_rsp = hci_inquiry(dev_id, len, max_rsp, NULL, &ii, flags);
        if( num_rsp < 0 ) perror("hci_inquiry");
    
        for (i = 0; i < num_rsp; i++) {
            ba2str(&(ii+i)->bdaddr, addr);
            memset(name, 0, sizeof(name));
            if (hci_read_remote_name(sock, &(ii+i)->bdaddr, sizeof(name),name, 0) < 0)
                strcpy(name, "[unknown]");
            printf("%s %s\n", addr, name);
        }
    
        free( ii );
        close( sock );
        return 0;
    }

     

        블루투스 프로그래밍은 주변에 블루투스 장비를 찾는 것에서 시작합니다. 따라서 우선 주변에 존재하는 블루투스 장비를 스캐닝하는 작업을 해야합니다.  상기의 코드가 주변에 존재한느 블루투스 장비의 이름과 주소를 얻어오는 코드입니다.

     

      블루투스의 주소를 표현하는데 사용하는 구조체는 아래의 형태를 가지고 있습니다.

    typedef struct {
        uint8_t b[6];
    } __attribute__((packed)) bdaddr_t;

      주소를 문자열로 변환하거나, 문자열을 주소로 변환하는데에는 아래의 함수를 이용합니다.

       int str2ba( const char *str, bdaddr_t *ba );
       int ba2str( const bdaddr_t *ba, char *str );

      블루투스 주소는 ``XX:XX:XX:XX:XX:XX"의 형태로 X는 16진수 표현을 이용합니다. (자세한 설명은 관련 자료를 참조)
    해동소스의 결과는 리눅스 콘솔상에서 하단의 명령어를 입력함으로써 동일한 역할을 하게 됩니다.

    # hcitool scan

     

     

    3.2. 블루투스 서비스 등록 / sdpregister.c

      이 예제를 동작시키기 위해서는 로컬 리눅스 시스템에 sdpd 이 실행 중이어야합니다. 만약 데몬이 실행중이 아니거나 sdpd 가 없다면 BlueZ 라이브러리를 컴파일해서 다시 설치해 주셔야합니다. sdpd 가 정상적으로 실행 중이라면 블루투스 서비스가 등록되고, 아래의 명령을 통해서 현재 로컬 시스템에 등록된 서비스를 확인할 수 있습니다.

    # sdptool browse local

      해당 명령어의 local 부분에 주소를 입력하면 해당 주소상의 sdpd 에 등록된 서비스 목록을 확인할 수 있습니다.

     

    #include <bluetooth/bluetooth.h>
    #include <bluetooth/sdp.h>
    #include <bluetooth/sdp_lib.h>
    
    sdp_session_t *register_service()
    {
        uint32_t service_uuid_int[] = { 0, 0, 0, 0xABCD };
        uint8_t rfcomm_channel = 11;
        const char *service_name = "Roto-Rooter Data Router";
        const char *service_dsc = "An experimental plumbing router";
        const char *service_prov = "Roto-Rooter";
    
        uuid_t root_uuid, l2cap_uuid, rfcomm_uuid, svc_uuid;
        sdp_list_t *l2cap_list = 0,
        *rfcomm_list = 0,
        *root_list = 0,
        *proto_list = 0,
        *access_proto_list = 0;
        sdp_data_t *channel = 0, *psm = 0;
    
        sdp_record_t *record = sdp_record_alloc();
    
        // set the general service ID
        sdp_uuid128_create( &svc_uuid, &service_uuid_int );
        sdp_set_service_id( record, svc_uuid );
    
        // make the service record publicly browsable
        sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
        root_list = sdp_list_append(0, &root_uuid);
        sdp_set_browse_groups( record, root_list );
    
        // set l2cap information
        sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
        l2cap_list = sdp_list_append( 0, &l2cap_uuid );
        proto_list = sdp_list_append( 0, l2cap_list );
    
        // set rfcomm information
        sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
        channel = sdp_data_alloc(SDP_UINT8, &rfcomm_channel);
        rfcomm_list = sdp_list_append( 0, &rfcomm_uuid );
        sdp_list_append( rfcomm_list, channel );
        sdp_list_append( proto_list, rfcomm_list );
    
        // attach protocol information to service record
        access_proto_list = sdp_list_append( 0, proto_list );
        sdp_set_access_protos( record, access_proto_list );
    
        // set the name, provider, and description
        sdp_set_info_attr(record, service_name, service_prov, service_dsc);
    
        int err = 0;
        sdp_session_t *session = 0;
    
        // connect to the local SDP server, register the service record, and
        // disconnect
        session = sdp_connect( BDADDR_ANY, BDADDR_LOCAL, SDP_RETRY_IF_BUSY );
        err = sdp_record_register(session, record, 0);
    
        // cleanup
        sdp_data_free( channel );
        sdp_list_free( l2cap_list, 0 );
        sdp_list_free( rfcomm_list, 0 );
        sdp_list_free( root_list, 0 );
        sdp_list_free( access_proto_list, 0 );
    
        return session;
    }



    서비스 등록시에 rfcomm 만 등록하는 경우 서비스를 받아오는 측에서 서비스 목록을 제대로 가져오지 못하니, RFCOMM 만 이용하여 서비스를 운용중이더라도 L2CAP 을 같이 등록해야합니다.
    (이는 SDP 자체가 L2CAP 프로토콜을 이용해서 서비스 내역을 교환하기 때문으로 보입니다.)

     

     

    3.3. 블루투스 서비스 파싱 / sdpparser.c

      sdpd 에 등록된 연결 대상의 지원하는 서비스 내역을 가져오는 부분입니다. 우리가 찾고자하는 서비스의 UUID 를 지정해주면 해당 서비스 내역만을 가져오게 됩니다.

    #include <bluetooth/bluetooth.h>
    #include <bluetooth/sdp.h>
    #include <bluetooth/sdp_lib.h>
    
    int main(int argc, char **argv)
    {
        uint32_t svc_uuid_int[] = { 0x0, 0x0, 0x0, 0xABCD };
        uuid_t svc_uuid;
        int err;
        bdaddr_t target;
        sdp_list_t *response_list = NULL, *search_list, *attrid_list;
        sdp_session_t *session = 0;
    
        str2ba( "01:23:45:67:89:AB", &target );
    
        // connect to the SDP server running on the remote machine
        session = sdp_connect( BDADDR_ANY, &target, SDP_RETRY_IF_BUSY );
    
        // specify the UUID of the application we're searching for
        sdp_uuid128_create( &svc_uuid, &svc_uuid_int );
        search_list = sdp_list_append( NULL, &svc_uuid );
    
        // specify that we want a list of all the matching applications' attributes
        uint32_t range = 0x0000ffff;
        attrid_list = sdp_list_append( NULL, &range );
    
        // get a list of service records that have UUID 0xabcd
        err = sdp_service_search_attr_req( session, search_list, \
        SDP_ATTR_REQ_RANGE, attrid_list, &response_list);
    
        sdp_list_t *r = response_list;
    
        // go through each of the service records
        for (; r; r = r->next ) {
            sdp_record_t *rec = (sdp_record_t*) r->data;
            sdp_list_t *proto_list;
    
            // get a list of the protocol sequences
            if( sdp_get_access_protos( rec, &proto_list ) == 0 ) {
                sdp_list_t *p = proto_list;
    
                // go through each protocol sequence
                for( ; p ; p = p->next ) {
                    sdp_list_t *pds = (sdp_list_t*)p->data;
    
                    // go through each protocol list of the protocol sequence
                    for( ; pds ; pds = pds->next ) {
    
                        // check the protocol attributes
                        sdp_data_t *d = (sdp_data_t*)pds->data;
                        int proto = 0;
                        for( ; d; d = d->next ) {
                            switch( d->dtd ) {
                                case SDP_UUID16:
                                case SDP_UUID32:
                                case SDP_UUID128:
                                    proto = sdp_uuid_to_proto( &d->val.uuid );
                                    break;
                                case SDP_UINT8:
                                    if( proto == RFCOMM_UUID ) {
                                        printf("rfcomm channel: %d\n",d->val.int8);
                                    }
                                    break;
                              }
                         }
                    }
                    sdp_list_free( (sdp_list_t*)p->data, 0 );
                }
                sdp_list_free( proto_list, 0 );
            }
            printf("found service record 0x%x\n", rec->handle);
            sdp_record_free( rec );
        }
        sdp_close(session);
    }

     

    3.4. RFCOMM 예제

      리눅스의 통신이 항상 그렇듯 BSD 소켓을 이용해서 통신합니다. 단지 소켓 통신과 주소설정 과정이 기존의 TCP, UDP 소켓과 약간 차이가 있으니 그 부분만을 주의해서 보면 큰 문제 없이 통신이 가능합니다. 블루투스에서는 RFCOMM 이 인터넷의 TCP와 유사하며, L2CAP 이 인터넷의 UDP 와 유사한 프로토콜입니다. 이를 유의하시기 바랍니다.

     

    1) rfcomm-server

      RFCOMM 의 채널은 TCP 상의 포트와 유사한 demux 키의 열할을 합니다. 채널은 1~31번까지 할당가능합니다.

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <bluetooth/bluetooth.h>
    #include <bluetooth/rfcomm.h>
    
    int main(int argc, char **argv)
    {
        struct sockaddr_rc loc_addr = { 0 }, rem_addr = { 0 };
        char buf[1024] = { 0 };
        int s, client, bytes_read;
        int opt = sizeof(rem_addr);
    
        // allocate socket
        s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
    
        // bind socket to port 1 of the first available
        // local bluetooth adapter
        loc_addr.rc_family = AF_BLUETOOTH;
        loc_addr.rc_bdaddr = *BDADDR_ANY;
        loc_addr.rc_channel = (uint8_t) 1;
        bind(s, (struct sockaddr *)&loc_addr, sizeof(loc_addr));
    
        // put socket into listening mode
        listen(s, 1);
    
        // accept one connection
        client = accept(s, (struct sockaddr *)&rem_addr, &opt);
    
        ba2str( &rem_addr.rc_bdaddr, buf );
        fprintf(stderr, "accepted connection from %s\n", buf);
        memset(buf, 0, sizeof(buf));
    
        // read data from the client
        bytes_read = read(client, buf, sizeof(buf));
        if( bytes_read > 0 ) {
            printf("received [%s]\n", buf);
        }
    
        // close connection
        close(client);
        close(s);
        return 0;
    }

     

     

    2) rfcomm-client.c

    다음은 클라이언트 역할을 하는 샘플코드입니다. 

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <bluetooth/bluetooth.h>
    #include <bluetooth/rfcomm.h>
    
    int main(int argc, char **argv)
    {
        struct sockaddr_rc addr = { 0 };
        int s, status;
        char dest[18] = "01:23:45:67:89:AB";
    
        // allocate a socket
        s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
    
        // set the connection parameters (who to connect to)
        addr.rc_family = AF_BLUETOOTH;
        addr.rc_channel = (uint8_t) 1;
        str2ba( dest, &addr.rc_bdaddr );
    
        // connect to server
        status = connect(s, (struct sockaddr *)&addr, sizeof(addr));
    
        // send a message
        if( status == 0 ) {
            status = write(s, "hello!", 6);
        }
    
        if( status < 0 ) perror("uh oh");
    
        close(s);
        return 0;
    }

     

    3.5. L2CAP 예제

    1) l2cap-server.c

      L2CAP의 포트 번호는 0x1001 - 0x7FFF 번위 중 홀수만 할당 가능합니다.

    #include <stdio.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <bluetooth/bluetooth.h>
    #include <bluetooth/l2cap.h>
    
    int main(int argc, char **argv)
    {
        struct sockaddr_l2 loc_addr = { 0 }, rem_addr = { 0 };
        char buf[1024] = { 0 };
        int s, client, bytes_read;
        int opt = sizeof(rem_addr);
    
        // allocate socket
        s = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
    
        // bind socket to port 0x1001 of the first available
        // bluetooth adapter
        loc_addr.l2_family = AF_BLUETOOTH;
        loc_addr.l2_bdaddr = *BDADDR_ANY;
        loc_addr.l2_psm = htobs(0x1001);
    
        bind(s, (struct sockaddr *)&loc_addr, sizeof(loc_addr));
    
        // put socket into listening mode
        listen(s, 1);
    
        // accept one connection
        client = accept(s, (struct sockaddr *)&rem_addr, &opt);
    
        ba2str( &rem_addr.l2_bdaddr, buf );
        fprintf(stderr, "accepted connection from %s\n", buf);
    
        memset(buf, 0, sizeof(buf));
    
        // read data from the client
        bytes_read = read(client, buf, sizeof(buf));
        if( bytes_read > 0 ) {
            printf("received [%s]\n", buf);
        }
    
        // close connection
        close(client);
        close(s);
    }

     

    2) l2cap-client.c

    #include <stdio.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <bluetooth/bluetooth.h>
    #include <bluetooth/l2cap.h>
    
    int main(int argc, char **argv)
    {
        struct sockaddr_l2 addr = { 0 };
        int s, status;
        char *message = "hello!";
        char dest[18] = "01:23:45:67:89:AB";
    
        if(argc < 2)
        {
            fprintf(stderr, "usage: %s <bt_addr>\n", argv[0]);
            exit(2);
        }
    
        strncpy(dest, argv[1], 18);
    
        // allocate a socket
        s = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
    
        // set the connection parameters (who to connect to)
        addr.l2_family = AF_BLUETOOTH;
        addr.l2_psm = htobs(0x1001);
        str2ba( dest, &addr.l2_bdaddr );
    
        // connect to server
        status = connect(s, (struct sockaddr *)&addr, sizeof(addr));
    
        // send a message
        if( status == 0 ) {
            status = write(s, "hello!", 6);
        }
    
        if( status < 0 ) perror("uh oh");
    
        close(s);
    }

     

    4. 참고사이트 

      아래는 참고사이트링크이다. 세월이 지난만큼 주소나 관련 정보가 다른곳에 위치하거나 또는 사라졌을 수 있다. 

     - http://people.csail.mit.edu/albert/bluez-intro/

     - http://www.holtmann.org/linux/kernel/

     - http://www.bluez.org/download.html

     

     

    반응형
    • 네이버 블러그 공유하기
    • 네이버 밴드에 공유하기
    • 페이스북 공유하기
    • 카카오스토리 공유하기