IoT (사물인터넷)

Internet Of Things(IOT / 사물인터넷)
IOT (사물인터넷)는 각종 센서와 사물에 통신 기능을 내장하여 인터넷에 연결하는 기술을 의미합니다.
예를 들어, 스마트폰을 활용하여 집안이 아닐 때에 집안의 조명을 켜거나 끄고
추운 겨울날 퇴근 후 집에 오는 동안에 스마트폰으로 미리 집안의 보일러를 켜놓는 등이
대표적인 IOT의 활용 사례라고 볼 수 있습니다.
이번 아두이노 RC카 만들기 프로젝트 또한 IOT를 활용한 사례라고 볼 수 있습니다.

아두이노 RC카

아두이노 RC카는 다음과 같은 기능을 가지고 있습니다.

1. 언제 어디서든 전원과 인터넷만 된다면 작동 가능
RC카를 직접 인터넷에 연결시켜서 동작시키기 때문에 배터리가 충분하고 인터넷 접속이 가능하다면
언제 어디서든 원거리에서도 컨트롤 및 데이터를 받아올 수 있습니다.

2. 스마트폰 앱을 활용해 컨트롤
번거롭게 전용 리모트 컨트롤을 사용하지 않고 스마트폰 앱으로 쉽게 컨트롤할 수 있습니다.

3. RC카에 부착된 카메라의 영상을 앱으로 확인
스마트폰 앱에서 언제 어디서든 RC카에 부착된 카메라의 실시간 영상을 확인할 수 있습니다.

4. RC카에 부착된 센서의 데이터를 앱으로 확인
스마트폰 앱에서 언제 어디서든 RC카에 부착된 센서들(온도 및 습도, 조도, 초음파)의 데이터를 확인할 수 있습니다.

하드웨어 구성

아두이노 RC카 만들기 키트는 여러 하드웨어 부품으로 구성되어 있습니다.
각각 크기와 용도가 다르니 주의하세요.

[아두이노, 쉴드]



1. Arduino Uno / 아두이노 우노(USB케이블 포함)
Picture

여러 종류의 아두이노 보드 중에 가장 기본이 되는 보드입니다.
이 보드에 여러 쉴드를 부착하고 센서를 연결, 코드를 삽입하여
RC카가 작동하도록 하는 메인 보드 역할의 부품입니다.

2. Arduino YUN Shield / 아두이노 윤 쉴드

인터넷을 통해 RC카를 제어하기 위해서는 RC카가 인터넷에 연결되어야 합니다.
하지만 우리가 사용하는 아두이노 우노는 인터넷에 접속할 수 없어서
인터넷에 RC카를 연결, 인터넷을 통해 들어온 명령을 아두이노 우노로 전달하기 위해
아두이노 윤 쉴드를 사용합니다.

3. Arduino Motor Shield / 아두이노 모터 쉴드

우리가 사용하는 아두이노 우노만 가지고 모터를 돌리기에는 전력이 부족합니다.
따라서 RC카를 움직일만큼 충분한 힘을 발휘하는 모터를 위해 전력을
공급하고 모터를 동작시키는 역할을 하는 아두이노 모터 쉴드를 사용합니다.

[센서]

1. DHT11 / 온습도센서

센서 주변의 온도 및 습도를 측정하기 위해 사용되는 센서입니다.

2. HC-SR04 / 초음파센서

RC카 전방부분에 설치하여 전방에 물체가 있는지 여부를 탐지하는 센서입니다.

3. photocell / 조도 센서

RC카 주변의 빛 정도를 측정하기 위해 사용되는 센서입니다.

[입출력]



1. WebCam / 웹캠(Aoni 054WS)

RC카 전방의 영상을 촬영하기 위해 사용되는 웹캠입니다.

[전원]

br>
1. Battery / 배터리(18650)

RC카를 동작시키기 위한 배터리로, 3.7v 배터리를 2개 사용합니다.

2. Battery Holder / 배터리 홀더

배터리를 연결하여 아두이노에 연결하기 위한 홀더로, 배터리 2개를 직렬로 연결합니다.

3. Battery Charger / 배터리 충전기

배터리 2개를 동시에 충전 가능한 충전기로, 마이크로 5핀 충전기(스마트폰 충전기)를 사용해서 충전합니다.

4. VoltMeter / 볼트미터

배터리 잔량을 확인하기 위해 사용하는 볼트미터로, 현재 배터리 잔량이 표시됩니다.

[구동]



1. Servo Motor / 서보모터

RC카에서 웹캠의 각도(위,아래)를 조절하기 위해 사용되는 마이크로 서보 모터입니다.

2. Motor / 모터

RC카 바퀴를 움직이기 위한 모터입니다.

3. Wheel / 바퀴

RC카가 움직이는 데에 사용되는 바퀴입니다.

[차체]



1. 차체기판

RC카의 차체로, 아두이노와 쉴드, 센서와 모터 등 부품들을 장착하는 기판입니다.

2. 볼트, 너트, 지지대

여러 부품들을 고정시키는 데에 사용하는 부품입니다.

3. 카메라 거치대

카메라를 고정 및 위, 아래로 움직일 수 있도록 하는 부품입니다.

4. 모터 고정핀

모터들을 차체기판에 쉽게 고정시킬 수 있도록 하는 고정핀입니다.

[기타]



1. 점퍼 케이블

여러 센서와 아두이노, 및 쉴드간 데이터와 전력을 공급할 수 있도록 연결하는
케이블로써 암-수, 암-암 두가지로 구별됩니다.

소프트웨어 구성

아두이노 RC카 만들기 프로젝트에서 사용하는 플랫폼은 크게 2가지로 볼 수 있습니다.
하나는 RC카 자체를 프로그래밍 하기 위한 아두이노와,
나머지 하나는 RC카를 컨트롤하기 위한 안드로이드입니다.

1. 아두이노 프로그래밍


아두이노는 오픈소스 하드웨어로, 자유롭게 하드웨어를 제작하고 그 위에 프로그램을 작성할 수 있습니다.
아두이노 공식 홈페이지에서 아두이노 IDE를 다운로드 받아 설치할 수 있으며,
해당 IDE에 프로그램을 작성 및 아두이노 보드를 컴퓨터와 연결한 후 업로드가 가능합니다.

아두이노 IDE에서는 C++ 언어를 사용하여 프로그램을 작성할 수 있으며, 작성한 프로그램들을 "스케치" 라고 부릅니다.
이번 아두이노 RC카 만들기 프로젝트에서는 스케치를 따로 제공하며, 원하시는 분들은 코드를 분석 및 자유롭게 활용이 가능합니다.

* C++ 언어 강좌(두들낙서 님의 Youtube)
* 아두이노 공식 홈페이지(아두이노 IDE 설치)

2. 안드로이드 프로그래밍


안드로이드는 일반적으로 가장 많이 사용되는 스마트폰의 플랫폼으로, 누구나 안드로이드에서 작동하는 앱을 개발할 수 있습니다.
안드로이드 앱을 개발하기 위해서는 안드로이드 공식 개발자 사이트에 들어가서
안드로이드 스튜디오를 설치한 후 개발할 수 있으며, Java 언어 등을 활용하여 개발할 수 있습니다.

이번 아두이노 RC카 만들기 프로젝트에서는 안드로이드 앱 코드를 따로 제공하며, 원하시는 분들은 코드를 분석 및 자유롭게 활용이 가능하나
초보자가 접근하기는 쉽지 않은 영역이니 Java 언어와 안드로이드에 대한 충분한 학습을 하신 후 활용해보시기 바랍니다.

* 안드로이드 공식 개발자 사이트(안드로이드 스튜디오 설치)
* Java 언어 강의(생활코딩)

조립 방법

소프트웨어 적용

조립이 완료되었으니 작동을 시키기 위해 프로그램을 작성해보겠습니다.
소프트웨어 구성 항목에서 이야기한 바와 같이 이번 아두이노 RC카 만들기 프로젝트에서
프로그램은 크게 두가지로 나뉘어지며, 아두이노부터 살펴보도록 하겠습니다.

1. 아두이노 IDE 설치하기
아두이노 스케치를 작성하고 업로드하기 위하여 아두이노 IDE를 설치해 보겠습니다.

우선, 아두이노 홈페이지에 접속하고, 상단 메뉴에서 "Download"를 클릭합니다.


그리고, 본인의 현재 운영체제에 맞는 버전을 클릭합니다.


그 다음 화면에서 기부 금액을 선택하는 화면이 나오는데,
"JUST DOWNLOAD"를 클릭하여 설치 프로그램을 다운받은 후 설치를 진행합니다.


설치가 끝나고 아두이노 프로그램을 실행시켰을 때 빈 스케치가 생성되면 정상적으로 설치된 것입니다.

2. 아두이노 스케치 적용하기
이제 RC카에 적용될 아두이노 스케치를 작성해보겠습니다.
아두이노 IDE를 실행하고, 빈 스케치를 생성합니다.

아두이노 RC카의 기능은 크게 4가지로 나눌 수 있습니다.

1) RC카 움직이기 (앞, 뒤, 좌, 우)
2) 카메라 움직이기 (상,중,하)
3) 센서로 데이터 측정하기(온습도, 초음파, 조도)
4) 카메라 영상 스트리밍

이 중 4)카메라 영상 스트리밍은 YUN쉴드에서 설정하여 구현하고,아두이노 스케치에서는 1 ~ 3번을 진행합니다.
본 자료에서는 전체 코드 중 특정 부분을 발췌하며, 전체 아두이노 스케치는 이곳에서 다운로드 받으세요.

1) 초기 설정하기

//YUN 쉴드를 통해 들어온 명령어 처리를 위한 변수
                byte serialIn = 0;
                byte commandAvailable = false;
                String strReceived = "";
                YunServer server;

                // RC카 구동을 위한 변수
                AF_DCMotor motor1(1, MOTOR12_64KHZ);// 1번모터 객체 생성,속도 64KHz pwm (L293D 에서 좌측 모터 2개 통제)
                AF_DCMotor motor2(2, MOTOR12_64KHZ);// 2번모터 객체 생성,속도 64KHz pwm (L293D 에서 우측 모터 2개 통제)
                Servo myservo1;  //카메라 상하 운동 서보 모터
                int cpos=90; // 서보모터 각도값

                // 센서들을 위한 변수
                int echoPin = 6; // 초음파센서 발신부
                int trigPin = 7; // 초음파센서 수신부

                int dhtpin = 11; //온습도센서
                DHT11 dht11(dhtpin);

                int lightPin = 0; // 조도센서

                void setup(){
                    server.begin(); // YUN 쉴드에서 명령을 대기함

                    motor1.setSpeed(200);// 초기 모터 속도 설정,200~255중 임의 조정(두개의 모터속도가 같으면 된다.)
                    motor2.setSpeed(200);
                    myservo1.attach(10,1000,2000); // 카메라 조정 서보모터 설정
                }


1) 명령 구분하기

            void loop(){
                YunClient client = server.accept(); // 인터넷을 통해 보내진 명령을 체크한다
                if (client) { // 명령이 있다면
                    process(client); //처리하는 함수로 전달
                    client.stop(); // 연결을 해제한다
                 }
                 delay(50); // 50ms마다 명령을 체크한다.
            }

            // loop()에서 명령 발생시 처리하는 함수
            void process(YunClient client) {
                String command = client.readStringUntil('@'); // 전달된 명령에서 "@"기호 전까지의 String을 읽어 저장한다.
                processCommand(command, client); // 명령을 실제로 처리하는 함수로 넘김.
            }
            


2) 명령 처리하기

             // process(YunClient client)에서 명령을 받아 실제로 처리하는 함수
             void processCommand(String input,YunClient client)
             {
                String command = input; //넘어온 명령을 command라는 String 변수에 저장한다.

                if (command == "MF")  //명령이 MF라면(전진)
                {
                    motor1.run(FORWARD); // 모터 1을 앞으로
                    motor2.run(FORWARD); // 모터 2를 앞으로
                }
                else if (command == "MB") //명령이 MB라면(후진)
                {
                    motor1.run(BACKWARD);// 모터 1을 뒤로
                    motor2.run(BACKWARD);// 모터 2를 뒤로
                }
                else if (command == "ML")  //명령이 ML이라면(좌측)
                {
                    motor1.run(BACKWARD);// 모터 1을 뒤로
                    motor2.run(FORWARD);// 모터 2를 앞으로
                }
                else if (command == "MR") //명령이 MR이라면(우측)
                {
                    motor1.run(FORWARD);// 모터 1을 앞으로
                    motor2.run(BACKWARD);// 모터 2를 뒤로
                }
                else if (command == "MS")  //명령이 MS라면(정지)
                {
                    motor1.run(RELEASE); //모터 1 정지
                    motor2.run(RELEASE); //모터 2 정지
                }
                else if (command == "C_U") // 명령이 C_U라면(카메라 위로)
                {
                    cpos=cpos+5; // 현재 각도값 + 5
                    myservo1.write(cpos); //서보모터 조정
                }
                else if (command == "C_D") // 명령이 C_D라면(카메라 아래로)
                {
                    cpos=cpos-5; // 현재 각도값 - 5
                    myservo1.write(cpos); // 서보모터 조정
                }
                else if (command == "C_C") // 명령이 C_C라면(카메라 중앙)
                {
                    cpos=90; // 현재 각도값을 90으로 지정
                    myservo1.write(cpos); // 서보모터 지정
                }
                else if(command == "status") // 명령이 status라면(센서값)
                {
                    float duration, distance; // 초음파 거리값과 시간값을 저장할 변수
                    digitalWrite(trigPin, HIGH); // 초음파 신호 전송
                    delay(10); // 10ms 간 전송
                    digitalWrite(trigPin, LOW); // 전송 중지

                    duration = pulseIn(echoPin, HIGH);  // 초음파 신호 수신까지 걸린 시간 저장
                    distance = ((float)(340 * duration) / 1000000) / 2;  // 시간을 활용해서 실제 거리 계산(m)

                    int err; // 온습도 계산시 에러 발생한 경우 사용
                    float temp, humi; // 온도, 습도값 저장할 변수
                    if((err=dht11.read(humi, temp))==0){}  // 온도, 습도값을 측정해서 저장, 만약 값이 0이라면 넘어감

                    int lightVal; // 조도값을 저장할 변수
                    lightVal = analogRead(lightPin); // 조도값 측정 후 저장

                    // 지금까지 얻은 데이터를 json 형식으로 가공해서 웹에 표시한다.
                    client.print("[{\'distance\':\'");
                    client.print(distance);
                    client.print("m\',\'temperature\':\'");
                    client.print(temp);
                    client.print("\',\'humidity\':\'");
                    client.print(humi);
                    client.print("\',\'light\':\'");
                    client.print(lightVal,DEC);
                    client.print("\'}]");
                }
             }
            


본 코드 외에도 위의 링크를 통해 전체 코드를 복사, 혹은 다운로드 받은 후에
보드로 업로드하시면 이 코드들이 보드에 적용됩니다.

업로드 하는 방법은 여기에 "보드에 스케치 업로드 하기"부분을 확인하세요.

3. 인터넷 연결 세팅하기
조립된 RC카에 배터리를 연결하면, YUN 쉴드 우측에 주황색 LED가 들어오게 됩니다.
2개의 주황색 LED가 들어온 후 잠시 기다리면, 와이파이 목록에 "iduino-xxxxxx"라는 와이파이 존이 생기는 것을 확인할 수 있습니다.

노트북 혹은 스마트폰 등으로 그 와이파이에 접속한 후, 주소창에 192.168.240.1을 입력하여 와이파이 관리용 웹페이지로 접속합니다.
최초 패스워드는 iduino입니다.



일반적인 가정용 공유기 관리 페이지와 거의 비슷한 역할을 하는 페이지로, 우측 상단의 "SYSTEM"을 눌러 시스템 관리 페이지로 접속합니다.
이 페이지에서는 와이파이 이름과 비밀번호, Rest API Acess 등을 수정할 수 있습니다.
페이지의 중간정도에 WIRELESS PARAMETERS 부분에서
CONFIGURE A WIRELESS NETWORK 옵션을 선택하고, DETECTED WIRELESS NETWORKS 부분에서 RC카를 접속시킬 와이파이를 선택합니다.
선택 후 보안 방식(SECURITY)과 비밀번호(PASSWORD)를 입력하고,
하단 REST API ACCESS 부분을 OPEN으로 설정하고 CONFIGURE & RESTART 버튼을 눌러 적용하면 YUN 쉴드가 자동으로 재부팅되며
방금 선택한 네트워크에 자동으로 접속합니다.

4. RC카의 공유기 내부 IP 확인하기
이전 단계에서 RC카의 YUN 쉴드를 자신이 선택한 와이파이에 접속시켰다면, 이제 본인의 와이파이에서 RC카에 할당된 IP를 확인해야 합니다.
각 공유기 회사별로 관리자 페이지에 접속해서 RC카에 부여된 IP를 확인합니다.
RC카에 부여된 IP를 확인하고 나면 해당 IP를 그 공유기에 접속된 다른 기기에서 브라우저를 통해 열어보고,
3번에서 진행한 설정 페이지가 나온다면 정상적으로 확인한 것입니다.

* 공유기 내부ip 확인법

5. 웹캠 스트리밍 설정하기
YUN 쉴드는 OpenWRT라는 무선랜 라우터를 위한 리눅스 기반의 오픈소스 운영 체제를 가지고 있습니다.
따라서 리눅스에 ssh 등으로 접속하여 여러 설정, 패키지 설치 등의 작업이 가능합니다.

웹캠 스트리밍을 사용하기 위해서는 스트리밍이 가능하게 하는 여러 패키지들을 리눅스에 설치하고 설정을 해주어야 합니다.
우선 YUN 쉴드가 공유기에 잘 접속되있는 것을 확인하고, 해당 공유기에 접속된 다른 PC에서 리눅스에 접속을 해보도록 하겠습니다.

1) Windows 운영체제인 경우
원격으로 리눅스에 접속하기 위하여 PUTTY라는 프로그램을 사용합니다.



PUTTY 홈페이지에 들어가면 다음과 같이 여러 종류가 있는데,
그 중 목록에서 가장 상단에 있는 putty.exe를 눌러 프로그램을 다운로드 받고 실행합니다. (따로 설치가 진행되지 않습니다.)



실행된 프로그램에서 상단 Host Name(or IP address) 부분에 4단계에서 확인한 RC카 IP 주소를 적고, Port는 22로,
Connection type을 SSH로 설정한 후에 하단 우측에 Open 버튼을 클릭합니다.

그림과 같이 dos창이 하나 뜨고 잠시 기다리면 "login as : " 라는 메시지가 표시됩니다.
root를 적고 엔터를 누르면, 패스워드를 입력하라는 메시지가 나오고 iduino를 입력하고 엔터를 누릅니다.
(패스워드는 화면에 표시되지 않는것이 정상입니다.)



다음과 같은 화면이 나오면 정상적으로 접속된 것입니다.

이제 웹캠 스트리밍을 위한 패키지를 설치해보도록 하겠습니다.
우선, 패키지 설치에 앞서 이미 설치된 패키지들의 업데이트를 진행하고, 스트리밍을 위한 패키지들을 설치하겠습니다.
                root@iduino:~# opkg update
                root@iduino:~# opkg install kmod-video-uvc
                root@iduino:~# opkg install fswebcam
                


여기까지 kmod-video-uvc라는 패키지와 fswebcam이라는 패키지가 설치되었고,
이번에는 폴더를 제작하고 해당 폴더에 mjpg-streamer이라는 패키지를 설치해 보도록 하겠습니다.

                root@iduino:~# cd /www
                root@iduino:~# mkdir webcam
                root@iduino:~# cd /www/webcam
                root@iduino:~# opkg install mjpg-streamer
                


이제 웹캠 스트리밍을 위한 패키지는 모두 설치하였고,
공유기 부팅시 자동으로 웹캠 스트리밍이 시작되도록 설정해 보겠습니다.

                root@iduino:~# vi /etc/rc.local
                // 부팅시 자동으로 실행되게 하는 설정파일인 rc.local file을 엽니다.
                


                # Put your custom commands here that should be executed once
                # the system init finished. By default this file does nothing.

                mjpg_streamer -i "input_uvc.so -d /dev/video0 -y -f 20 -r 320x480" -o "output_http.so"
                // 이 줄을 추가합니다.
                exit 0

                //esc를 누르고, :wq 를 눌러서 저장하고 나옵니다.
                


공유기 부팅시 자동으로 웹캠 스트리밍이 시작되도록 설정하였습니다.

방금 입력했던 mjpg_streamer -i "input_uvc.so -d /dev/video0 -y -f 20 -r 320x480" -o "output_http.so" 에서,
-f는 프레임을 의미하고 -r은 해상도를 의미합니다.
각자가 원하는 사이즈와 해상도로 설정해도 무관하나 너무 고해상도, 혹은 높은 프레임의 경우에는 끊김 등의 문제가 발생할 수 있으니 주의하세요.

이제 RC카의 전원을 잠시 끄고, 다시 배터리를 연결한 후 잠시 부팅을 기다리고 난 후부터는
http://RC카의 공유기 내부 아이피:8080/?action=stream 을 브라우저로 들어가면 카메라에서 나오는 영상의 스트리밍을 확인할 수 있습니다.

6. 포트 포워딩 설정하기
지금까지 진행한 작업은 내부 네트워크(공유기)를 통해서만 명령 전달, 처리, 웹캠 스트리밍 등이 가능하지만
언제 어디서든 인터넷이 연결된다면 RC카를 제어하고 웹캠 스트리밍을 확인할 수 있도록 하기 위하여
외부에서 네트워크를 통해 RC카에 접속할 수 있도록 설정해야 합니다.

공유기는 하나의 외부 IP를 가지고 있고, 공유기에 접속된 기기들에게 내부 IP를 할당시켜주는 방식으로 작동되고 있습니다.
외부에서 특정한 기기에 네트워크로 접속하려고 한다면, 공유기가 가지고 있는 외부 IP에 접근 후 공유기를 통해 기기의 내부 IP를 찾아 접속해야 합니다.
고정 IP에 접근을 할 때 접속하고자 하는 특정 기기에 대한 정보를 담아서 접속을 해야 공유기에서 최종적으로 원하는 기기로 접속을 시켜 주는데,
이때 특정 기기를 판별하는 조건이 "포트"입니다.

각 공유기 관리 페이지에는 "포트 포워딩"이라는 기능이 있으며,
포트 포워딩이란 외부 IP와 함께 들어온 특정한 포트 번호를 각 특정 내부 IP로 매핑하는 기능을 의미합니다.
따라서 언제 어디서든 RC카를 제어하고 웹캠 스트리밍을 보려면 RC카의 IP에 포트 포워딩을 적용하고,
접속시 외부 IP와 설정한 포트번호를 함께 사용해서 언제 어디서든 외부 IP를 통해 접속시킬 수 있도록 합니다.

포트 포워딩은 "외부 포트"와 "내부 포트", 그리고 "내부 IP"를 설정하게 되어 있습니다.
여기서 "외부 포트"란 외부 IP와 함께 사용될 포트 번호를,
"내부 포트"란 특정 기기 내부에서 사용하는 포트를 의미합니다.
그리고 "내부 IP"란 공유기에서 특정 기기에 할당한 IP를 의미합니다.

예를 들어, 5번 항목에서 웹캠 스트리밍을 확인하기 위한 링크는
http://RC카의 공유기 내부 아이피:8080/?action=stream 였고,
외부 IP가 1.1.1.1이며, 원하는 외부 포트 번호가 1111이라고 가정하고
웹캠 스트리밍에 포트 포워딩을 적용한다면

여기서 외부 포트는 1111, 내부 포트는 8080, 내부 IP는 1.1.1.1로,
적용하게 되면 http://1.1.1.1:1111/?action=stream 을 통해 언제 어디서든 웹캠 스트리밍을 확인할 수 있습니다.

RC카를 위해 적용해야 할 포트 포워딩은 2가지로,
첫번째는 RC카 명령을 전달할 포트, 이 경우 내부 포트는 80, 내부 IP는 3번 항목에서 확인한 IP입니다.
두번째는 웹캠 스트리밍을 확인할 포트, 이 경우 내부 포트는 위와 같이 8080, 내부 IP는 3번 항목에서 확인한 IP입니다.

* 공유기에서 포트포워딩 설정하는 방법
* 외부 IP 확인하는 방법



두번째로, 지금까지 RC카를 제작하고 아두이노에 들어갈 스케치와 기타 처리를 해 주었으니
RC카를 제어할 안드로이드 앱에 대해서 살펴보겠습니다.

1. 안드로이드 스튜디오 설치
아두이노에서 스케치를 위해 아두이노 IDE를 설치했던 것처럼 안드로이드에서도 안드로이드 앱을 개발하기 위해 안드로이드 스튜디오를 설치해야 합니다.
하단의 링크를 참조해서 안드로이드 스튜디오 설치를 진행해주세요.
* 안드로이드 스튜디오 설치 링크
* 안드로이드 스튜디오 설치 방법

2. 안드로이드 앱 분석하기
안드로이드 스튜디오 설치가 완료되었다면, 안드로이드 스튜디오를 실행하고, 프로젝트를 불러옵니다.
(불러오는 방법)

RC카 앱의 기능은 크게 4가지로 나눌 수 있습니다.

1) RC카 움직이기 (앞, 뒤, 좌, 우)
2) 카메라 움직이기 (상,중,하)
3) 측정된 센서 데이터 확인(온습도, 초음파, 조도)
4) 카메라 영상 스트리밍 보기

본 자료에서는 전체 코드 중 특정 부분을 발췌하며, 전체 아두이노 스케치는 이곳 에서 다운로드 받으세요.
코드상에 http://1.1.1.1:포트번호 부분은 위에서 본인이 확인하고 지정한 IP와 포트번호로 변경해주세요.

1) UI 구성하기

                ...
                
                ...

                

                    

                        
                    

                    

                        
                    

                    

                        
                    

                    

                        
                    

                
                

                ...

                  
                    
                    

                        

                        
                        

                            
                        

                        

                    

                    

                        
                        

                            
                        

                        
                        

                            
                        

                        
                        

                            
                        

                    
                

                ...

                    
                    

                        
                    

                        
                    

                        
                    

                        
                    

                        
                    

                        
                    

                


            


2) 초기 설정하기

 private ImageButton btnForward;//앞
    private ImageButton btnBack;//뒤
    private ImageButton btnLeft;//좌
    private ImageButton btnRight;//우

    //카메라 서보 모터
    private ImageButton cbtnU;//위로
    private ImageButton cbtnD;//아래로
    private ImageButton cbtnM;//중앙

    private WebView videoview;
    private WebView commandView;

    private TextView temp_tv, humi_tv, distance_tv, light_tv;

                ...

               videoview = (WebView) findViewById(R.id.yun_webview);
        videoview.getSettings().setLoadWithOverviewMode(true);
        videoview.getSettings().setUseWideViewPort(true);
        videoview.getSettings().setJavaScriptEnabled(true);

        commandView = (WebView) findViewById(R.id.commandView);

        btnForward = (ImageButton) findViewById(R.id.f_b);
        btnBack = (ImageButton) findViewById(R.id.b_b);
        btnLeft = (ImageButton) findViewById(R.id.l_b);
        btnRight = (ImageButton) findViewById(R.id.r_b);

        cbtnU = (ImageButton) findViewById(R.id.u_b);
        cbtnD = (ImageButton) findViewById(R.id.d_b);
        cbtnM = (ImageButton) findViewById(R.id.m_b);

        temp_tv = (TextView) findViewById(R.id.temperature);
        humi_tv = (TextView) findViewById(R.id.humidity);
        light_tv = (TextView) findViewById(R.id.light);
        distance_tv = (TextView) findViewById(R.id.distance);
            


3) 버튼 이벤트 작성하기

   btnForward.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                touchHandle(event, "MF@", "전");
                return true;
            }
        });
        btnBack.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                touchHandle(event, "MB@", "후");
                return true;
            }
        });

        btnLeft.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                touchHandle(event, "ML@", "좌");
                return true;
            }
        });
        btnRight.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                touchHandle(event, "MR@", "우");
                return true;
            }
        });

        //---------------카메라-----------------------------------//
        cbtnU.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                touchHandle(event, "C_D@", "하");

                return true;

            }
        });
        cbtnD.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                touchHandle(event, "C_U@", "상");

                return true;
            }
        });

        cbtnM.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                touchHandle(event, "C_C@", "중앙");
                return true;
            }
        });

            


4) 실제 명령 전송하기

  private void touchHandle(MotionEvent event, String orderStr, String tips) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                MainActivityOrder(orderStr, tips);
                break;

            case MotionEvent.ACTION_UP:
                MainActivityOrder("MS@", "정지");
                break;

            default:
                break;
        }
    }

    private void MainActivityOrder(String orderStr, String tips) {
        commandView.loadUrl("http://1.1.1.1:1235/arduino/" + orderStr);
        Log.w("command", tips + "/" + orderStr);
    }
            


5) 상태 가져오기

        new GetStatusTask().execute("http://1.1.1.1:1235/arduino/status@");
...

                private class GetStatusTask extends AsyncTask {
        private String Content;
        ProgressDialog asyncDialog;

        protected void onPreExecute() {
            if (asyncDialog == null) {
                asyncDialog = new ProgressDialog(MainActivity.this);
            }
            asyncDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
            asyncDialog.setCancelable(false);
            asyncDialog.setMessage("센서정보를 가져오고 있습니다..");
            asyncDialog.show();
        }

        protected Void doInBackground(String... urls) {

            BufferedReader reader = null;
            try {
                URL url = new URL(urls[0]);

                URLConnection conn = url.openConnection();
                conn.setDoOutput(true);

                reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                StringBuilder sb = new StringBuilder();
                String line = null;

                while ((line = reader.readLine()) != null) {
                    sb.append(line);
                }
                Content = sb.toString();
                Log.w("content", Content);
            } catch (Exception ex) {
            } finally {
                try {
                    reader.close();
                } catch (Exception ex) {
                }
            }
            return null;
        }

        protected void onPostExecute(Void unused) {
            try {
                if (Content == null) {
                    Toast.makeText(MainActivity.this, "센서정보를 가져올 수 없습니다.\nRC카 재부팅을 해주세요.", Toast.LENGTH_SHORT).show();
                    finish();
                } else {
                    JSONArray jarray = new JSONArray(Content);
                    for (int i = 0; i < jarray.length(); i++) {
                        JSONObject jObject = jarray.getJSONObject(i);
                        String distance = jObject.getString("distance");
                        String temperature = jObject.getString("temperature");
                        String humidity = jObject.getString("humidity");
                        String light = jObject.getString("light");

                        distance_tv.setText(distance + "");
                        temp_tv.setText(temperature + "");
                        humi_tv.setText(humidity + "");
                        light_tv.setText(light + "");
                    }
                    asyncDialog.dismiss();
                }
            } catch (JSONException e) {
                Toast.makeText(MainActivity.this, "RC카가 꺼져 있습니다.\n전원을 켜주세요.", Toast.LENGTH_SHORT).show();
            }

        }
            


6) 카메라 영상 확인하기

        videoview.loadUrl("http://1.1.1.1:1234/?action=stream");