[본 강좌는 서울 IoT 센터에서 진행할 교육을 위해 작성된 한글 문서입니다.]
[This document is currently written in Korean only for the purpose of being presented in the Seoul IoT tutorial.]
Tizen IoT 보드에서 Infrared Motion Sensor를 통해 주변의 움직임을 감지한 후, 상황에 따라 LED Light를 켜고 끄는 간단한 앱을 만들어보는 강좌입니다.
# 준비물
실습을 위하여 아래 물품들을 준비해주세요.
:::hw
Board|Raspberry Pi 3 B+|1|
Sensor|Motion Sensor|1|HC-SR501
Sensor|LED Light|1|
Chip|Resistor|1|330 ohm
:::
:::sw
OS|Tizen IoT | Headless / Headed 5.5|
SDK|Tizen Studio|
:::
## Raspberry Pi 3 B+ Board

Raspberry Pi 3 B+ 보드의 핀은 아래와 같이 구성되어있습니다.

## LED Light & Resistor
* 5파이 LED 전구 (전압 2.2V / 전류 50mA)

* 330~1kΩ 저항

## Infrared Motion Sensor (HC-SR501)
Infrared Motion Sensor는 물체의 적외선 정보를 감지하여 주변의 움직임을 파악하는 센서입니다.



[HC-SR501 사용자 매뉴얼](https://www.mpja.com/download/31227sc.pdf)
우리가 사용할 HC-SR501은 아래와 같이 구성되어 있습니다.

### Sensitivity Adjust
HC-SR501은 정해진 측정 거리 내의 적외선을 감지할 수 있으며, 3~7m의 범위 내에서 측정 거리를 설정할 수 있습니다.
측정 거리가 증가하면 멀리 있는 물체의 움직임도 감지할 수 있기 때문에 움직임에 더 민감하게 반응합니다.
* Clockwise: 시계 방향으로 돌리면 측정 거리를 감소시켜 센서의 민감도가 줄어듭니다. 즉, 시계 방향 끝까지 돌리면 3m 범위 내에서만 적외선을 감지합니다.
* Counter-Clockwise: 시계 반대 방향으로 돌리면 측정 거리를 증가시켜 센서의 민감도 또한 높아집니다. 즉, 시계 반대 방향 끝까지 돌리면 7m 범위 내에서 적외선을 감지합니다.
### Time Delay Adjust
움직임이 감지된 후, 얼마의 시간 동안 다음 움직임 감지를 지연시킬 지 결정합니다. 3초~5분의 범위 내에서 지연 시간을 설정할 수 있습니다.
* Clockwise: 시계 방향으로 돌리면 지연 시간이 증가합니다. 즉, 시계 방향 끝까지 돌리면 5분동안 센서 값을 유지합니다.
* Counter-Clockwise: 시계 반대 방향으로 돌리면 지연 시간이 감소합니다. 즉, 시계 반대 방향 끝까지 돌리면 3초동안만 센서 값을 유지합니다.
### Trigger Setting
움직임을 감지하는 방식을 설정합니다.
* Single Trigger: 처음 움직임이 감지된 후로부터 지연 시간동안은 새로운 움직임이 감지되어도 무시합니다. 즉, 움직임이 있는 상태(ON)가 처음부터 정해진 지연 시간만큼만 유지된 후, 움직임이 없는 상태(OFF)가 또 지연 시간만큼 유지된 다음, 새로운 움직임을 감지할 준비를 합니다.
* Repeat Trigger: 처음 움직임이 감지된 후로부터 지연 시간동안 또 새로운 움직임이 감지된다면 지연 시간을 갱신시킵니다. 즉, 움직임이 있는 상태(ON)가 움직임이 멈추지 않는 동안 계속해서 이어질 수 있으며 마지막 움직임이 멈춘 후, 움직임이 없는 상태(OFF)가 또 지연 시간만큼 유지된 다음, 새로운 움직임을 감지할 준비를 합니다.
본 강좌를 위해서는 아래와 같이 설정을 해주세요.
* 지연 시간: 약 5초 / Single Trigger 모드
* 감지 거리: 약 3m

## Motion Sensor / LED Light 연결

***
# General Purpose Input/Output (GPIO)
Binary input peripheral 상태 읽기/쓰기 가능한 Interface를 지원합니다.
## GPIO Pins
아래 Pin map에서 초록색으로 표시된 Pin들이 GPIO 입력 또는 출력을 위해 사용 가능합니다.

코딩 시 Pin number는 GPIO XX에 해당하는 숫자를 넣어주시면 됩니다.
즉, 'Motion Sensor / LED Light 연결' 부분에서 설명된대로 센서들을 꽂았다면
* Motion Sensor는 18번 Pin을 통해 데이터를 전달할 것이고
* LED Light는 24번 Pin을 통해 데이터를 전달받을 것입니다.
## GPIO APIs
[Tizen Peripheral IO Native APIs - GPIO 가이드](https://developer.tizen.org/development/iot-extension-sdk/api-guides/tizen-peripheral-io-native-api/gpio)
Tizen 에서 제공되는 Peripheral IO API를 사용하여 GPIO Pin 관련 작업을 수행할 수 있습니다.
### Opening and Closing a Handle
GPIO 핀에 연결된 센서를 제어하기 위해서는 먼저 GPIO 핸들을 열어 접근해야합니다.
**peripheral_gpio_open()**
특정 pin 넘버의 Peripheral GPIO 핸들을 열어줍니다.
```c
int peripheral_gpio_open(int gpio_pin, peripheral_gpio_h *gpio);
```
**peripheral_gpio_close()**
사용이 끝난 후, Peripheral GPIO 핸들을 닫아줍니다.
```c
int peripheral_gpio_close(peripheral_gpio_h gpio);
```
### Setting the Data Direction
GPIO 핀은 데이터를 쓸 때와 읽을 때 구분 없이 같은 핀을 쓸 수 있기 때문에 API를 통해 하려는 작업이 데이터를 쓰는 것인지 데이터를 읽는 것인지 설정해주어야 합니다.
**peripheral_gpio_set_direction()**
데이터 전송 방향을 설정합니다.
* PERIPHERAL_GPIO_DIRECTION_IN : 데이터 읽기 모드
* PERIPHERAL_GPIO_DIRECTION_OUT_INITIALLY_HIGH : 데이터 쓰기 모드, 초기값을 high(1)로 설정
* PERIPHERAL_GPIO_DIRECTION_OUT_INITIALLY_LOW : 데이터 쓰기 모드, 초기값을 low(0)로 설정
```c
int peripheral_gpio_set_direction(peripheral_gpio_h gpio, peripheral_gpio_direction_e direction);
```
### Reading and Writing Binary Data
방금 정해준 데이터 전송 방향에 따라 데이터를 읽거나 쓸 수 있습니다.
**peripheral_gpio_read()**
GPIO 핸들로부터 binary 데이터 값을 읽어옵니다.
```c
int peripheral_gpio_read(peripheral_gpio_h gpio, uint32_t *value);
```
**peripheral_gpio_write()**
GPIO 핸들에 binary 데이터 값을 입력합니다.
```c
int peripheral_gpio_write(peripheral_gpio_h gpio, uint32_t value);
```
### Setting the Interrupted Callback
특정 GPIO 핸들의 데이터 값에 변화가 생기면 특정 작업을 수행하도록 callback 함수를 등록할 수 있습니다.
**peripheral_gpio_set_edge_mode()**
peripheral_gpio_set_interrupted_cb()를 부르기 전, 항상 어떤 종류의 변화에 대해 callback을 등록할 지 정해야합니다.
GPIO 데이터 값의 변화는 아래와 같이 3가지로 분류됩니다.

```c
int peripheral_gpio_set_edge_mode(peripheral_gpio_h gpio, peripheral_gpio_edge_e edge);
```
**peripheral_gpio_interrupted_cb**
변화가 감지되면 어떤 작업을 수행할 지 callback 함수 안에서 정의해줍니다.
```c
typedef void(*peripheral_gpio_interrupted_cb)(peripheral_gpio_h gpio, peripheral_error_e error, void *user_data);
```
**peripheral_gpio_set_interrupted_cb()**
interrupted callback 함수를 set 해줍니다.
```c
int peripheral_gpio_set_interrupted_cb(peripheral_gpio_h gpio, peripheral_gpio_interrupted_cb callback, void *user_data);
```
**peripheral_gpio_unset_interrupted_cb()**
사용이 끝난 후, interrupted callback 함수를 unset 해줍니다.
```c
int peripheral_gpio_unset_interrupted_cb(peripheral_gpio_h gpio);
```
# Code Implementation
## 샘플 코드 준비
**1. Git Bash에서 rcc/smart-motion-light 코드 clone 받기**
```C
git clone https://git.tizen.org/cgit/apps/native/st-things-light -b basic st-things-light-basic
```
**2. Tizen Studio에서 smart-light 프로젝트 불러오기**
* File > Import >> Tizen > TIzen Project >> Root directory > Browse

* clone 받은 st-things-light-basic 폴더 선택

* Project Name: smart-light / Profile: iot-headless / Version: 5.0 선택 (이미 열려있는 프로젝트와 프로젝트명이 동일하면 안 열리니 주의)

## Privilege 추가
Peripheral IO API 사용을 위해 peripheralio privilege 추가해주어야합니다.
미리 준비된 smart-light 코드에는 privilege가 추가되어 있지만, 새로운 프로젝트부터 시작하는 상황이라면 아래와 같이 privilege를 등록해주세요.
* tizen-manifest.xml 파일 선택
* Privileges 탭 > 추가(+) 버튼 >

* Custom Privileges 추가
```
http://tizen.org/privilege/peripheralio
```

* 파일 저장 후 Source 탭에서 privilege 추가된 것을 재확인
> tizen-manifest.xml
```xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<manifest xmlns="http://tizen.org/ns/packages" api-version="4.0" package="org.tizen.motionlight" version="1.0.0">
<profile name="iot-headless"/>
<service-application appid="org.tizen.motionlight" auto-restart="false" exec="motionlight" multiple="false" nodisplay="true" on-boot="false" taskmanage="false" type="capp">
<label>motionlight</label>
<icon>motion-light.png</icon>
</service-application>
<privileges>
<privilege>http://tizen.org/privilege/peripheralio</privilege>
</privileges>
</manifest>
```
## Resource 코드 내 GPIO 설정 확인
> src/resource/resource_infrared_motion_sensor.c
```c
#include <peripheral_io.h>
#include "log.h"
static peripheral_gpio_h g_sensor_h = NULL;
static int g_pin_num = -1;
void resource_close_infrared_motion_sensor(void)
{
if (!g_sensor_h) return;
_I("Infrared Motion Sensor is finishing...");
peripheral_gpio_close(g_sensor_h);
g_sensor_h = NULL;
g_pin_num = -1;
}
int resource_read_infrared_motion_sensor(int pin_num, uint32_t *out_value)
{
int ret = PERIPHERAL_ERROR_NONE;
if (!g_sensor_h) {
peripheral_gpio_h temp = NULL;
ret = peripheral_gpio_open(pin_num, &temp);
retv_if(ret, -1);
ret = peripheral_gpio_set_direction(temp, PERIPHERAL_GPIO_DIRECTION_IN);
if (ret) {
peripheral_gpio_close(temp);
_E("peripheral_gpio_set_direction failed.");
return -1;
}
g_sensor_h = temp;
g_pin_num = pin_num;
}
if (g_pin_num != pin_num) {
_E("Invalid pin number.");
return -1;
}
ret = peripheral_gpio_read(g_sensor_h, out_value);
retv_if(ret < 0, -1);
return 0;
}
```
> src/resource/resource_led.c
```c
#include <peripheral_io.h>
#include "log.h"
static peripheral_gpio_h g_sensor_h = NULL;
static int g_pin_num = -1;
void resource_close_led(void)
{
if (!g_sensor_h) return;
_I("LED is finishing...");
peripheral_gpio_close(g_sensor_h);
g_sensor_h = NULL;
g_pin_num = -1;
}
int resource_write_led(int pin_num, int write_value)
{
int ret = PERIPHERAL_ERROR_NONE;
if (!g_sensor_h) {
peripheral_gpio_h temp = NULL;
ret = peripheral_gpio_open(pin_num, &temp);
retv_if(ret, -1);
ret = peripheral_gpio_set_direction(temp, PERIPHERAL_GPIO_DIRECTION_OUT_INITIALLY_LOW);
if (ret) {
peripheral_gpio_close(temp);
_E("peripheral_gpio_set_direction failed.");
return -1;
}
g_sensor_h = temp;
g_pin_num = pin_num;
}
if (g_pin_num != pin_num) {
_E("Invalid pin number.");
return -1;
}
ret = peripheral_gpio_write(g_sensor_h, write_value);
retv_if(ret < 0, -1);
_I("LED Value : %s", write_value ? "ON":"OFF");
return 0;
}
```
## Motion Sensor 값 읽어오기
Motion Sensor가 연결된 핀에 접근하여 Motion Sensor 값을 읽어옴
> src/controller.c
```c
#include <unistd.h>
#include <Ecore.h>
#include <tizen.h>
#include <service_app.h>
#include "log.h"
#include "resource/resource_infrared_motion_sensor.h"
#include "resource/resource_led.h"
static Eina_Bool _get_motion_sensor_data()
{
int ret = 0;
uint32_t value = 0;
if (!ad) {
_E("failed to get app_data");
return ECORE_CALLBACK_CANCEL;
}
// Get value from motion sensor
ret = resource_read_infrared_motion_sensor(SENSOR_MOTION_GPIO_NUMBER, &value);
if (ret != 0) {
_E("cannot read data from the infrared motion sensor");
return ECORE_CALLBACK_CANCEL;
}
_D("Detected motion value is: %u", value);
return ECORE_CALLBACK_RENEW;
}
```
## LED 값 변경하기
LED가 연결된 핀에 접근하여 LED Light 값을 변경함
> src/controller.c
```c
static int _set_led_data(int state) {
int ret = 0;
// Write state to LED light
ret = resource_write_led(SENSOR_LED_GPIO_NUMBER, state);
if (ret != 0) {
_E("cannot write led data");
return -1;
}
_I("LED : %d",state);
return 0;
}
```
```c
static Eina_Bool _get_motion_sensor_data()
{
int ret = 0;
uint32_t value = 0;
if (!ad) {
_E("failed to get app_data");
return ECORE_CALLBACK_CANCEL;
}
// Get value from motion sensor
ret = resource_read_infrared_motion_sensor(SENSOR_MOTION_GPIO_NUMBER, &value);
if (ret != 0) {
_E("cannot read data from the infrared motion sensor");
return ECORE_CALLBACK_CANCEL;
}
_D("Detected motion value is: %u", value);
// Set LED light with value
_set_led_data(value);
return ECORE_CALLBACK_RENEW;
}
```
## 타이머 설정하기 (app_control)
특정 주기(TIMER_GATHER_INTERVAL)마다 모션 센서 값을 불러오고 LED 값을 설정해주는 __read_motion_write_led 함수 호출
> src/controller.c
```c
typedef struct app_data_s {
Ecore_Timer *getter_timer;
} app_data;
static void service_app_control(app_control_h app_control, void *user_data)
{
app_data *ad = user_data;
// Delete old timer if there is one
if (ad->getter_timer)
ecore_timer_del(ad->getter_timer);
// Create a timer to call the given function in given period of time
ad->getter_timer = ecore_timer_add(TIMER_GATHER_INTERVAL, _get_motion_sensor_data, ad);
if (!ad->getter_timer) {
_E("Failed to add getter timer");
return;
}
}
```
## interrupt_cb 설정하기 (app_control)
> src/controller.c
```c
static void service_app_control(app_control_h app_control, void *user_data)
{
int ret = 0;
// Set an interrupted callback to be invoked when interrupt is triggered on motion sensor
ret = resource_set_interrupted_cb_infrared_motion_sensor(SENSOR_MOTION_GPIO_NUMBER, _motion_interrupted_cb);
if (ret != 0) {
_E("cannot set interrupted callback for motion sensor");
return;
}
}
```
## 리소스 정리하기 (app_terminate)
App 종료 시 App에서 사용하던 리소스를 전부 정리해주어야 합니다.
> src/controller.c
```c
static void service_app_terminate(void *user_data)
{
app_data *ad = user_data;
// Delete timer
if (ad->getter_timer)
ecore_timer_del(ad->getter_timer);
// Turn off LED light with __set_led()
_set_led_data(0);
// Close Motion and LED resources
resource_close_infrared_motion_sensor();
resource_close_led();
// Free app data
free(ad);
FN_END;
}
```
## Tizen App 실행
Project Explorer 내 프로젝트 우클릭 > Run As > Tizen Native App

***
# 최종 확인
Motion sensor 값에 따른 LED Light 변화를 확인할 수 있습니다.


Notice
Are you sure to delete this post?
Basic Smart Light App
1
0
|
Last modified on August 23, 2019
Craft info. | |
Maker |
|
Status | In Progress |
Period | 2019-08-22 ~ 2030-10-12 |
About This Craft | |
Tutorial for creating a Basic Smart Light App | |
Making Note