[사물 인터넷 네트워크와 서비스 구축 강좌] #2-3 모바일과 센서장치의 시리얼 통신
강좌 전체보기
.
.
시리얼 통신을 이용한 센서장치 – 안드로이드 폰 연결
앞서 소개했듯이 시리얼-USB 통신을 이용해서 센서장치를 모바일 폰에도 연결할 수 있습니다. 이 방식을 사용하면 센서장치에 구성에 여러가지 이익이 있습니다.
- 배터리에서 해방
시리얼-USB 연결을 하면 센서장치는 모바일 폰에서 전원을 공급받을 수 있습니다. 따라서 센서장치의 구성이 간단해지고 소형화 할 수 있습니다. 센서장치를 모바일 형태로 구현할 때 가장 큰 문제 중 하나가 배터리라는 점을 감안하면 이건 굉장한 장점입니다. - 간단한 연결 설정, 안정적인 데이터 통신
유선 연결이기 때문에 별도로 접속 비번 등을 설정, 확인할 필요가 없습니다. 사용자가 필요할 때 연결만 하면 됩니다. 연결 후에는 안정적인 통신이 가능합니다. - 서비스 구현의 용이성
센서장치가 수집한 데이터를 직접 모바일 폰으로 전송하기 때문에 서버를 거칠 필요가 없습니다. 서버에 저장해야 할 데이터라면 모바일 폰에서 안정적으로 보낼 수 있습니다. 따라서 서버와 모바일 폰 구현이 보다 간단해질 수 있습니다. 센서장치가 가진 데이터를 무선을 통해 서버로 전송하는 일은 보안, 네트워크 상태 등 감안해야 할 잠재적 문제가 많습니다. - 실시간성
센서장치가 수집한 데이터를 모바일 폰에서 딜레이 없이 실시간 모니터링이 가능한 장점이 있습니다.
다만 이 방식은 적용할 수 있는 서비스 시나리오, 센서장치의 역할에 제약이 있습니다.
- 모바일 폰과의 연동없이 상시적으로 동작하면서 정보를 수집하는 센서장치라면 적합하지 않습니다.
- 동작을 위해서는 사용자가 폰에 직접 연결해야 하므로, 이런 사용자 개입을 원치 않는 시나리오에도 적합치 않습니다.
- 안드로이드의 경우 근래에 출시된 폰은 문제가 별로 없는듯 하지만 구형 폰에서는 제조사에 따라 USB 연결이 원활치 않을 수 있습니다. 호환성에 문제가 발생할 수 있습니다.
- iOS의 경우 센서장치 USB 연결 및 개발에 제약사항이 많은것으로 알려져 있습니다. 프로토타이핑이나 데모버전을 구현할 때 iOS 연동은 힘든 일입니다.
.
그럼 본격적으로 센서장치와 안드로이드 폰의 통신을 구현해 보겠습니다.
센서장치는 아두이노를 이용해서 구현합니다. 다만, 앞선 2-2 강좌와는 달리 이번에는 아두이노의 D0, D1 시리얼 통신용 핀을 사용합니다. (앞선 강좌에서는 이 핀들을 PC와의 통신에 사용했습니다.) D0, D1 시리얼 핀에 연결된 USB 케이블이 OTG 케이블을 거쳐 안드로이드 폰에 연결됩니다.
일반적으로 안드로이드 폰을 PC에 꽂으면 PC가 USB host 가 되고, 안드로이드 폰은 USB client 가 됩니다. 하지만 이 예제에서는 안드로이드 폰이 USB host가 되어서 연결된 장치를 관리해줘야 합니다. OTG 케이블이 안드로이드 폰을 USB host 로 동작하도록 만들어줍니다.
아두이노에는 DHT-11 온습도 센서를 연결해서 데이터를 수집하도록 하겠습니다. 그리고 온습도 값을 주기적으로 폰으로 전송하겠습니다. 안드로이드 폰에서는 문자 ‘1’, ‘0’ 을 보내서 온습도 데이터 송신 기능이 on, off 되도록 구현하겠습니다.
아두이노에 DHT-11 센서를 아래와 같이 연결해줍니다. 아래 링크의 내용을 참고하면 됩니다.
링크 내용을 참고해서 DHT11 센서를 연결하고 라이브러리도 설치해주세요. 만약 사진과는 달리 interface board에 부탁된 모듈을 쓰신다면 핀이 3개만 있을겁니다. 그런 경우는 저항을 쓸 필요가 없으며, Signal 핀을 2번 핀에 연결만 해주면 됩니다.
아래 링크에서 소스코드를 받아 아두이노에 올립니다.
#include <DHT11.h> int pin=2; // 연결한 아두이노 디지털 핀 번호 DHT11 dht11(pin); unsigned long prev_time = 0L; const int READ_INTERVAL = 2000; boolean is_on = true; void setup() { Serial.begin(9600); } void loop() { unsigned long current_time = millis(); // Read sensor every 2 sec. if(current_time > prev_time + READ_INTERVAL && is_on) { int err; float temp, humi; if((err=dht11.read(humi, temp))==0) { Serial.print("temperature:"); Serial.print(temp); Serial.print(", humidity:"); Serial.print(humi); Serial.println(); } else { Serial.println(); Serial.print("Error No :"); Serial.print(err); Serial.println(); } prev_time = current_time; } if(Serial.available()) { char received = Serial.read(); if(received == '1') { is_on = true; Serial.println("Sensor on"); } else if(received == '0') { is_on = false; Serial.println("Sensor off"); } } }
.
아두이노 초기화 함수 setup()에서 시리얼 통신 초기화(Baudrate=9600) 해줬습니다. 그리고 무한반복 loop() 함수 안에서 2초 간격으로 DHT11 센서값을 읽고 시리얼통신으로 출력하도록 했습니다.
.
void loop() { unsigned long current_time = millis(); // Read sensor every 2 sec. if(current_time > prev_time + READ_INTERVAL && is_on) { int err; float temp, humi; if((err=dht11.read(humi, temp))==0) { Serial.print("temperature:"); Serial.print(temp); Serial.print(", humidity:"); Serial.print(humi); Serial.println(); } else { Serial.println(); Serial.print("Error No :"); Serial.print(err); Serial.println(); } prev_time = current_time; } ...... }
.
그리고 이어지는 코드에서 시리얼 통신으로 들어온 데이터가 있으면 1바이트씩 읽습니다. 수신한 데이터가 문자 ‘1’ 이면 온습도 데이터 출력을 유지하고 (is_on = true), 문자 ‘0’을 수신하면 데이터 출력을 멈춥니다. (is_on = false)
.
void loop() { ...... if(Serial.available()) { char received = Serial.read(); if(received == '1') { is_on = true; Serial.println("Sensor on"); } else if(received == '0') { is_on = false; Serial.println("Sensor off"); } } }
.
아두이노 준비는 모두 끝났으니 이제 핸드폰에 시리얼 통신 앱을 설치하면 됩니다. 아래 링크에서 Kotlin으로 작성된 안드로이드 앱 소스를 받아 확인하시고, 같이 들어있는 APK 파일을 안드로이드 폰에 올려 설치하세요.
안드로이드 앱에는 두 개의 소스 파일이 있습니다. (SerialConnector/app/src/main/java/com/hardcopy/serialconnector 경로 확인)
- MainActivity.kt
앱 시작하면 진입하는 메인 화면입니다. 여기서 시리얼-USB 통신으로 받은 데이터를 출력하고, 사용자가 입력한 데이터를 전송합니다. - UsbService.java
USB 연결 상태를 관리하고, 기기가 연결되면 통신할 준비를 하는 소스입니다. Serial 통신용 라이브러리를 참고해서 동작하도록 작성되어 있습니다.
핵심이 되는 UsbService.java 소스를 확인해 보겠습니다.
.
public class UsbService extends Service { // 시리얼 통신으로 받은 데이터를 처리하는 콜백. UI 로 받은 데이터를 전달 private UsbSerialInterface.UsbReadCallback mCallback = new UsbSerialInterface.UsbReadCallback() { @Override public void onReceivedData(byte[] arg0) { ...... } }; // USB 연결 상태 변경시 호출되는 콜백 private UsbSerialInterface.UsbCTSCallback ctsCallback = new UsbSerialInterface.UsbCTSCallback() { @Override public void onCTSChanged(boolean state) { ...... } }; private UsbSerialInterface.UsbDSRCallback dsrCallback = new UsbSerialInterface.UsbDSRCallback() { @Override public void onDSRChanged(boolean state) { ...... } }; // USB 연결, 해제, 권한 요청 결과 등을 받는 BroadcastReceiver private final BroadcastReceiver usbReceiver = new BroadcastReceiver() { @Override public void onReceive(Context arg0, Intent arg1) { if (arg1.getAction().equals(ACTION_USB_PERMISSION)) { ...... } else if (arg1.getAction().equals(ACTION_USB_ATTACHED)) { ...... } else if (arg1.getAction().equals(ACTION_USB_DETACHED)) { ...... } } }; @Override public void onCreate() { ...... } @Override public IBinder onBind(Intent intent) { ...... } @Override public int onStartCommand(Intent intent, int flags, int startId) { ...... } @Override public void onDestroy() { ...... } // 시리얼 포트로 데이터를 전송하는 메서드 public void write(byte[] data) { ...... } public void setHandler(Handler mHandler) { ...... } // BaudRate 설정하는 메서드 public void setBaudRate(int baudrate) { ...... } // USB 연결된 장치의 종류에 따라 시리얼 포트 오픈 private void findSerialPortDevice() { ...... } private void setFilter() { ...... } // 사용자 퍼미션 승락 요청. 결과는 BroadcastReceiver 로 전달 private void requestUserPermission() { ...... } public class UsbBinder extends Binder { ...... } // 시리얼 포트를 열고 관련 콜백 함수를 등록하는 루틴. 주요 이벤트는 등록된 콜백으로 받음. private class ConnectionThread extends Thread { @Override public void run() { ...... } } }
.
USB 장치가 안드로이드 폰에 연결되면 사용자에게 연결 승낙 요청을 받고, 이후 USB 시리얼 포트를 오픈하는 작업이 자동으로 진행됩니다. 호출되는 순서는 아래와 같습니다.
.
BroadcastReceiver usbReceiver.onReceive() ACTION_USB_ATTACHED findSerialPortDevice() requestUserPermission() BroadcastReceiver usbReceiver.onReceive() ACTION_USB_PERMISSION_GRANTED ConnectionThread().start() ConnectionThread.run() UsbSerialDevice.createUsbSerialDevice(device, connection) serialPort.open()
.
데이터를 USB 장치로 전송할 때는 write() 함수를 호출만 하면 됩니다. 데이터를 수신하면 미리 등록한 Handler 를 통해 MESSAGE_FROM_SERIAL_PORT 메시지를 받게 됩니다. 여기에 같이 담겨오는 데이터를 가지고 처리하면 됩니다.
MainActivity.kt 파일은 UI를 처리하는 Activity 클래스를 담고 있습니다. UsbService 형태를 굳이 수정하지 않는다면 MainActivity의 코드만 수정해서 입맛에 맞게 다양한 작업을 처리할 수 있습니다.
아두이노 – USB 케이블 – OTG 케이블 – 안드로이드 폰을 연결하고, 설치한 앱을 실행하면 아래처럼 동작하는 화면을 확인할 수 있습니다. 온습도 센서값이 주기적으로 표시되고, 사용자가 0 또는 1을 입력하면 측정을 stop/start 하게됩니다. 어렵지 않게 나만의 휴대용 온습도계를 만들 수 있습니다!
여기까지 Serial-USB 통신을 활용하는 방법을 확인해 봤습니다. 3장 부터는 본격적으로 무선 통신 기법에 대해 다룹니다.
.
강좌 전체보기
.