NodeJS 로 IOT 프로그래밍

BLE 를 IOT 장치로 사용하기

BLE 관련 프로그래밍을 조사해면, 대부분의 Central 장치에 대한 소스는 Android 와 iOS 중심으로 되어있다. 최근 Linux, MacOS, Windows 는 BLE 를 지원한다. 그래서 PC 를 BLE central 장치로 사용하려고 한다. 집안에서 서비스를 한다고 가정했을 때, PC 에서 BLE periperal 을 직접 제어할 수 있으면 좋겠다.

프로그래밍 도구로는 NodeJS 를 쓸 것이다. 파이선도 가능하지만 웹을 생각한다면 Node 가 좀 더 낫다.

Bluno 나 BLE Nano 를 peripheral 장치로 사용할 것이다. 이 것들은 Characteristic 이 고정되어 있지만, 데이터 통신을 가능하다.

아두이노 코드 - peripheral

우선 Bluno 로 시작하는데, BLE Nano 도 동일하다.

아래 코드를 보면 전혀 BLE 냄새가 안난다. Scan, response 의 일은 내장된 BLE chip 이 알아서 해주기 때문에 그냥 데이터 관련 코딩만 하면 된다. Serial 을 여는데 이 Serial 은 아두이노 UART 와 내장 BLE chip 사이의 Serial 이다. 기본 baud rate 은 AT 명령어로 바꿀 수 있다.

참고 : https://wiki.dfrobot.com/Bluno_SKU_DFR0267

loop 안을 보면 받은 데이터에 단순히 16 을 더해서 다시 보내는데, Central 장치가 [1,2,3,…,9,a] 10 byte 를 보내면 [11,12,13,…19,1a] 를 보낸다.

void setup() {
  Serial.begin(115200);   // bluno default baud rate
}
void loop() {
  if (Serial.available()) {
    byte data = Serial.read();
    data += 16;
    Serial.write(data);
  }
}

NodeJS 코드 - central

1 필요한 Node package 설치

# Requirement : Node & noble package
$ npm install noble

# Noble support node v8. Use @abandonware/noble with latest node ver
$ npm install @abandonware/noble

2 Periperal 찾기

Periperal BLE Service 와 Characteristics 를 알아야 하는데, 이를 찾는 예제 코드가 있다.

$ node node_modules/@abandonware/noble/examples/advertisement-discovery.js

# or copy the js file to current dir and modify the first line
# FROM var noble = require('../index');
# TO   var noble = require('@abandonware/noble');

$ node advertisement-discovery.js

BLE 장치의 name, id, address 를 찾을 수 있다. Service 와 Characteristic 은 UUID 형태로 제공되는데 아직은 알 수 없다.

Service UUID 는 https://www.bluetooth.com/specifications/gatt/services/ 를 참고하자.

peripheral discovered (874b3456cc63486aa6d178ebfc20a859 with address <20-cd-39-xx-xx-xx, public>, connectable true, RSSI -76:
    hello my local name is:
        Bluno
    can I interest you in any of the following advertised services:
        undefined

3 Service/Characteristic 얻어오기

장치의 이름, 주소를 알았으니 이제 어떤 Service 가 있는지 알아봐야 한다. peripheral_explorer.js 를 가져와서 실행하기 전에 async 패키지를 설치한다.

$ npm install async

async 버전 차이 때문에, 코드 수정이 좀 필요하다.

# Find async.whilst and add “async” to the next function. (two times)
async.whilst(
    async function () {   // <- function () {
# Second, show descriptors
async.series([
  function(callback) {
    characteristic.discoverDescriptors(function(error, descriptors){
      if (descriptors.length > 0)
        characteristicInfo += '\n    descriptor  ' + descriptors;
      callback();
    });
  },

아래와 같이 실행한다.

$ node peripheral-explorer.js 874b3456cc63486aa6d178ebfc20a859
or
$ node peripheral-explorer.js 20-cd-39-xx-xx-xx

결과는 다음과 같다.

peripheral with ID 874b3456cc63486aa6d178ebfc20a859 found
  Local Name        = Bluno
  Manufacturer Data = 4c000215e2c56db5dffb48xxx...
services and characteristics:
180a (Device Information)
  2a23 (System ID)
    properties  read
    value       babc93000039cd20 | ':<9M '
  2a24 (Model Number String)
    properties  read
    value       444620426c756e6f | 'DF Bluno'
  2a25 (Serial Number String)
    properties  read
    value       30313233343536373839 | '0123456789'
  2a26 (Firmware Revision String)
    properties  read
    value       46572056312e3937 | 'FW V1.97'
  2a27 (Hardware Revision String)
    properties  read
    value       48572056312e37 | 'HW V1.7'
  2a28 (Software Revision String)
    properties  read
    value       53572056312e3937 | 'SW V1.97'
  2a29 (Manufacturer Name String)
    properties  read
    value       4446526f626f74 | 'DFRobot'
  2a2a (IEEE 11073-20601 Regulatory Certification Data List)
    properties  read
    value       fe006578706572696d656e74616c | '~experimental'
  2a50 (PnP ID)
    properties  read
'   value       010d0000001001 | '
dfb0
  dfb1
    descriptor  {"uuid":"2901","name":"Characteristic User Description","type":"org.bluetooth.descriptor.gatt.characteristic_user_description"}
    properties  read, writeWithoutResponse, write, notify
    value       01 | ''
  dfb2
    descriptor  {"uuid":"2901","name":"Characteristic User Description","type":"org.bluetooth.descriptor.gatt.characteristic_user_description"}
    properties  read, writeWithoutResponse, write, notify
    value       02 | ''

Bluno 는 UUID “180a” and “dfb0” 두개의 서비스를 갖고 있다.

UUID “180a” 는 Device 정보를 나타내는 서비스인데, 대부분의 BLE 장치에는 이 서비스를 가지고 있어 자신의 정보를 알려준다.

UUID “dbf0” 는 Bluno 가 제공하는 서비스이며, 두개의 Characteristic 을 가지고 있다. (“dbf1”, “dbf2”) “dbf1” 은 Serial, “dbf2” 는 Command 역할을 한다. (시리얼 통신 채널과 AT Command 채널)

Characteristic 를 읽고 쓰려면 get/put 을 사용한다. Notify 는 Periperal 에서 Central 로 push 를 가능하게 하는데 이는 “2902” descriptor 를 이용한다. (CCCD : Client Characteristic Configuration Descriptor)

참고 : https://www.bluetooth.com/specifications/gatt/descriptors/

Bluno 는 이 CCCD 를 제공하지 않는다. 그래서 MacOS 에서는 Subscription (Notification request) 기능을 사용할 수 없다. Linux 나 Window 는 CCCD 없이도 잘 된다. Bluno 가 스펙에 안맞긴 하지만, 다소 의아하다. 이 문제에 관심이 있으면 아래 링크를 참고한다.

참고 : https://www.dfrobot.com/forum/viewtopic.php?f=18&t=2035&start=20

Keywish BLE Nano 는 Serial 통신을 위한 하나의 characteristic 만을 제공한다(“ffe0”), CCCD 도 있고 MacOS 에서 잘 작동한다.

최종 코드

최종 Javascript 코드 : https://gist.github.com/doojinkang/f825bf4f8f1883802d15bbfec2bf4173

1~10 을 buffer 에 넣어서 periperal 로 보낸다. Periperal (아두이노) 쪽에서는 16을 더해서 0x11, 0x12, … 0x1a 를 PC 로 보낸다.

# For Bluno (on Linux)
$ node ble_arduino.js dfb0 dfb1

# For BLE Nano (on Both Linux or MacOS)
$ node ble_arduino.js ffe0 ffe1

# Discover(Scan) and get ID/Address/ Name --->
        # Connect and get Services/Characteristics --->
                 # Subscribe / Write Characteristics (Serial)

기본적인 통신이 되었으니, 이제 IOT 환경을 꾸밀 수 있을 것이다.