Scru-fe BlueTooth 장착기

개요

씽기버스Thingiverse에 공된 Scru-Fe 꼬마자동차 마치 2008년 픽사가 제작한 컴퓨터 그래픽 SF 애니메이션 영화 주인공 월E를 닮았다. 를 가져와, 전체 모양을 훼손하지 않는 범위에서 몸체를 수정하여 3D 프린팅하였고, 스마트폰 제어를 위해 블루투스 모듈JMOD-BT-1을 추가 장착하였다.

메인 콘트롤러인 아두이노 우노 R3에 블루투스 모듈이 장착된 것을 볼 수 있다. 이 모듈은 기본적으로 슬레이브모드, 115200 baudrate, 8 데이터비트, 1 스톱비트, No 패리티, 흐름제어 OFF 형태로 출시된다. VCC(+)와 GND(-)는 아두이노 5V 전원에, RXD와 TXD핀은 A0, A1에 가상 시리얼 SoftwareSerial로 연결했다.

완성된 옆 모습이다. 기존 Scru-Fe보다 성능이 업그레이드된 명확한 증거는 서보 모터 iTec의 HS-311 다. 나의 고객(?)인 어느 꼬마 아가씨의 안전을 위해 누름 스위치를 더했고, 바퀴엔 옛날 기저귀 고무줄을 덧대 어느 정도의 마찰력과 쿠션을 창조했다.

스마트폰과 통신하는 앱은 Bluetooth serial controller구글 Play 스토어에서 검색로서, 사진에서 보는 것처럼 매우 직관적이다. 물론 세팅 과정이 별도로 필요하다.
Preference로 들어간다. 먼저 Visibilty를 선택하여 없앨 버튼을 언체크하고, Name에서는 버튼 이름을 바꾼다. Command에서는 각각의 버튼에 명령어를 할당하는데 여기서는 그것을 숫자로 정했다. 예를 들어, 2번 버튼의 Name을 전진으로 정하고 2를 명령값으로 할당한다. 아래 소스를 참고하자. 여러 기능이 더 있지만 여기서는 기본적인 것 세 가지만 사용했다.

혹시 부품은 'Scru-Fe'로 구글링하길.



메인 코드 src in here




#include "Servo.h"
#include "SoftwareSerial.h"

SoftwareSerial mySerial(A0, A1); // tX, rX

#define motorBdirection 13
#define motorAdirection 12
#define motorBpower 11
#define motorApower 10
#define servo 9
#define Buzzer 4

void setup() {
  Serial.begin(9600); 
  mySerial.begin(115200);

  pinMode(servo, OUTPUT);
  pinMode(motorAdirection, OUTPUT);
  pinMode(motorBdirection, OUTPUT);
  pinMode(motorApower, OUTPUT);
  pinMode(motorBpower, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, INPUT);
  pinMode(Buzzer, OUTPUT);

  //Turn Head to Center
  analogWrite(servo, 90);

  //소리
  buzz();
}

int speedy = 180;
int distance1 = 0;
int distance2 = 0;
int distance3 = 0;
char data;

void loop() {

  if (mySerial.available()) {
    data = mySerial.read();
    Serial.write(data);   
  }  

  if (data == '2') {//전진
    go(speedy+20, 100);
  } 
  else if (data == '6') {//우회전
    right(speedy-20, 450);
  } 
  else if (data == '8') {//후진
    back(speedy+20, 500); 
  } 
  else if (data == '4') {//좌회전
    left(speedy-20, 450);
  } 
  else if (data == '5') {//정지
    halt(1);
  } 
  else if (data == '9') {//auto moving . . .
    auto_driving();
  }
  else {
    //autonomous moving
    auto_driving();
  }
} 

Copter 조종 연습장치 src in here

초보 시절, 멀티콥터(또는 멀티로터 Multirotor, 드론으로 불리기도 한다.) 를 만들 때마다 시험 비행을 해야 했다. 혹시 모를 안전 문제로, 근처 학교 운동장에서 조심조심 비행 연습을 하곤 했는데, 운동장 구석에서 노는 아이들이 한 둘 있어도 여간 신경쓰이는 게 아니었다. 만들어지는 MultiCopter가 무거워지고 커지면서, 더이상 운동장은 안전한 장소가 되지 못했다. 제천 의림지 아래 솔방죽 근처나, 고암동 옛 비행 활주로 . . . 펌웨어를 수정할 때마다 차를 타고 10여분을 달려야 했다.

필요는 Maker를 낳는다.

구글 스케치업으로 구성 부품을 설계하고 각 부품별로 stl파일을 출력 export하였다. 연결부위는 MF104ZZ 플렌지 베어링베어링샵에서 구매을 사용하였는데, 오히려 크기와 무게를 줄일 수 있는 F684ZZ을 권한다. 이 때는 첨부하는 skp파일을 수정해야한다. 사용한 플렌지 베어링 내경이 M4이므로 사용된 볼트 역시 M4다. QuadCopter에 장착된 모습은 소스 파일에 첨부한다.

원래 바닥에 고정시키기 보다는, 상승 및 하강 그리고 정지 비행을 할 수 있는 장치를 만들 계획이었다. 시간이 되면 추가 제작

MS5611 자동고도 알고리즘

GY-86의 MS5611 센서값 동기화 with loop 타임

GY-86은 MPU6050(자이로 3축과 가속도 3축), HMC5883L(지자계 3축), MS5611(기압계와 온도계) 총 10 DOF(Degrees Of Freedom)를 지원하는 센서다. MS5611 센서값을 얻어 밖으로 내보내는 데 약 8~10ms(8000~10000 micro sec) 소요된다. 멀티콥터 Flight controller의 펌웨어 loop 타임이 2800 micro secMultiWii에서 4000 micro sec 가량인 것에 비해 2 ~ 3배 늦다. 따라서 한 loop 타임에 MS5611 센서값 하나를 받을 수 없다는 얘기다. 다시 말해 둘은 동기화가 되지 않는다.
이 문제를 어떻게 해결해야 하나? (한동안 고민하는 척 하다)
loop 타임 2 ~ 3회에 한번씩 MS5611에서 기압값을 가져오면 된다! 어떤 이들은 이것을 동기화를 위한 Sampling이라고도 하는데, 마치 이런 식이다.


loop 타임 1 : 센서값 요청
loop 타임 2 :  
loop 타임 3 : 
loop 타임 4 : 센서값 받기 & 센서값 요청 
loop 타임 5 :  
loop 타임 6 : 
loop 타임 7 : 센서값 받기 & 센서값 요청  
loop 타임 8 :  
loop 타임 9 : 

PID 제어src in here

loop 타임과 동기화되어 (느리지만) 지속적으로 나오는 기압값(MS5611-> Barometer 기압계)을 altitude_input, 기준 고도값을 pid_altitude_setpoint로 하여 pid_output_altitude에 조작량을 산출해낸다. PID 조작량 공식은 다음과 같다.

조작량 = Kp×편차(비례항) + Ki×편차의 누적값(적분항) + Kd×전회 편차와의 차(미분항)


pid_error_temp = altitude_input - pid_altitude_setpoint; //편차

pid_i_mem_altitude += pid_i_gain_altitude * pid_error_temp; //편차의 누적값


if (pid_i_mem_altitude > pid_max_altitude) pid_i_mem_altitude = pid_max_altitude;
else if (pid_i_mem_altitude < pid_max_altitude * -1) pid_i_mem_altitude = pid_max_altitude * -1;


//(pid_d_gain_altitude * (pid_error_temp - pid_last_altitude_d_error)) 전회 편차와의 차

pid_output_altitude = 
pid_p_gain_altitude * pid_error_temp + pid_i_mem_altitude + (pid_d_gain_altitude * (pid_error_temp - pid_last_altitude_d_error));

if (pid_output_altitude > pid_max_altitude) pid_output_altitude = pid_max_altitude;
else if (pid_output_altitude < pid_max_altitude * -1) pid_output_altitude = pid_max_altitude * -1;

pid_last_altitude_d_error = pid_error_temp;
참고로, 변수명으로 봐선 altitude_input이 고도값을 의미해야겠으나 실제로는 대기압값을 저장한다.

실험장치 꾸미기src in here

맨 아래 수신기가 보이고 두뇌 역할의 아두이노 나노가 바로 위에 있다. 모터 2개와 그들에게 전력을 제공하는 ESC가 양팔에 자리잡고 있다. 양팔 중앙에는 GY86이 있어, 수평을 유지하는 역할자이로와 가속도 센서의 합작으로을 한다.
무선조종기를 켜고 실험체에 전원(12V)을 연결한 다음, 쓰로틀(Throttle)을 서서히 올리면 양쪽 날개가 돌면서 양팔이 수평이 된다. 이 때, 한쪽 팔(날개)에 인위적인 힘을 가해도 자동으로 다시 수평을 이룬다.
무선 조종기의 좌우스틱 Roll Stick 으로 양팔의 기울기를 달리할 수 있다. Roll 스틱이 정중앙이면 양팔은 수평이 된다.

이제는 고도에 따라 모터 회전이 달라지는 코딩만 하면 된다. 일명 자동고도 알고리즘이다.
위에서 언급한 PID 제어를 이용했다. (장치를 인위적으로 올렸다 내렸다 하므로) 변하는 pid_output_altitude값을 현재 쓰로틀값에 더한다. 기준 고도보다 올라가면 기압이 내려가므로 pid_output_altitude는 -값이 되어 모터 회전이 느려지고, 기준 고도보다 낮으면 기압이 높아지므로 모터 회전이 빨라진다.
참고로, 기압계에는 BMP180 또는 BMP085 등이 있으나 정확도가 MS5611만 못하다.

무선 조종기신호 제어 with 아두이노

송수신기의 채널 1~5를 아두이노 우노 디지털 핀 D8~12에 연결한다. 물론 작동 전 송신기와 수신기의 바인딩bind 작업은 필수다.

소스 코드


unsigned long timer_1, timer_2, timer_3, timer_4, timer_5, current_time;
byte LC_1, LC_2, LC_3, LC_4, LC_5;
int RC[6];
int RC_channel_1, RC_channel_2, RC_channel_3, RC_channel_4, RC_channel_5;

void setup(){
  Serial.begin(57600);  
  /*
    Set PCIE0 to enable PCMSK0 scan.
    Set PCINT0~4 (digital pin 8~12) to trigger an interrupt when the state of transmitter's signal is changed.
  */  
  PCICR |= (1 << PCIE0);                                                   
  PCMSK0 |= (1 << PCINT0);                                                  
  PCMSK0 |= (1 << PCINT1);                                                  
  PCMSK0 |= (1 << PCINT2);                                                
  PCMSK0 |= (1 << PCINT3);                                                 
  PCMSK0 |= (1 << PCINT4);                                          
}

void loop(){
  Serial.print(RC[1]);
  Serial.print("\t");
  Serial.print(RC[2]);
  Serial.print("\t");
  Serial.print(RC[3]);
  Serial.print("\t");
  Serial.print(RC[4]);
  Serial.print("\t");
  Serial.println(RC[5]);
}

ISR(PCINT0_vect){
  current_time = micros();
  //Channel 1:D8
  if(PINB & B00000001){                                        
    if(LC_1 == 0){                                               
      LC_1 = 1;                                                  
      timer_1 = current_time;                                   
    }
  }
  else if(LC_1 == 1){                                             
    LC_1 = 0;                                                     
    RC[1] = current_time - timer_1;                              
  }
  //Channel 2:D9
  if(PINB & B00000010 ){                                          
    if(LC_2 == 0){                                               
      LC_2 = 1;                                                   
      timer_2 = current_time;                                         
    }
  }
  else if(LC_2 == 1){                                            
    LC_2 = 0;                                                     
    RC[2] = current_time - timer_2;                          
  }
  //Channel 3:D10
  if(PINB & B00000100 ){                                       
    if(LC_3 == 0){                                           
      LC_3 = 1;                                                   
      timer_3 = current_time;                                 
    }
  }
  else if(LC_3 == 1){                                            
    LC_3 = 0;                                                    
    RC[3] = current_time - timer_3;                             

  }
  //Channel 4:D11
  if(PINB & B00001000 ){                                        
    if(LC_4 == 0){                                               
      LC_4 = 1;                                                   
      timer_4 = current_time;                                     
    }
  }
  else if(LC_4 == 1){                                            
    LC_4 = 0;                                                     
    RC[4] = current_time - timer_4;                             
  }

  //Channel 5:D12
  if(PINB & B00010000 ){                                          
    if(LC_5 == 0){                                               
      LC_5 = 1;                                                  
      timer_5 = current_time;                                    
    }
  }
  else if(LC_5 == 1){                                            
    LC_5 = 0;                                                     
    RC[5] = current_time - timer_5;                               
  }
}


고정익 드론 제작

MultiWii2.4(Mega2560, 기본형) src in here

멀티위가 요구하는 메가2560의 연결도

[왼쪽 그림]은 MultiWii2.4에서 아두이노 메가 2560에게 요구하는 연결도다.
MultiWii 2.4는 멀티콥터 전문 프로그래머들이 미리 짜 놓은 아두이노 스케치로서,
송신기 신호를 수신하는 '수신부', Gy86과 같은 센서로부터 기체 균형과 고도 및 방향을 유지하는데 필요한 데이타를 받는 '센서부', 센서부와 수신부에서 받은 데이타를 분석 및 결합시켜 기체의 안정적 비행을 유지하도록 동력부에 명령을 보내는 '제어부', 제어부의 명령에 따라 날개를 회전시키는 '동력부'와 관련된 코드를 포함한다.

송신기 세팅

Hobbyking 인터넷 RC전문상점에서 송신기와 수신기 세트를 $59.992017.3월 현재에 살 수 있다. 가성비가 좋은 편으로, 송신기 9채널과 수신기 8채널을 갖는다. 전원으로 AA배터리 8개를 사용하면, 무겁기도 하거니와 비용이 만만치 않으므로, 송신기 전용 Lipo 3s1s:3.7v, 2s:7.4v, 3s:11.1v 배터리를 구매하여 장착하는 것이 좋다.
송신기 1 ~ 4채널은 공장 초기치 그대로 사용하지만 채널 5, 6 윗 연결도에서 Auxilary 1, 2에 해당하는 것으로 공장초기치가 null로 되어 있다.은 각각 Gear와 Thro Hold로 세팅하면 편리하다. (조종기 세팅창 AUX-CH 메뉴에서) 세팅방법은 구글링하라.
참고로, 이글은 stick set, 다시말해 비행 조종 방법을 Mode 2로 전제하고 썼다. 모드 2는 미국과 유럽에서 주로 사용하는 것으로 일반 항공기의 조종 방법과 같다. 일본에서 많이 사용하는 모드 1보다는 모드 2가 현실적이다. 그래도 항공여행 중 비행기를 직접 몰아야하는 상황이 오지 않길 바란다. 특히 기장이 아닌 여행자 신분으로!

송신기와 수신기 바인딩

제품에 딸려오는 바인딩선 오른쪽 검은선으로 BIND핀의 -와 s(신호)를 연결하는 역할사진처럼 꼽고, 5V 전원을 BAT핀 극성에 맞게 꼽는다. 여기서, 빨강(+)과 검정(-)를 반대로 연결하면 $9짜리 수신기와는 영영 이별이다. 나는 이미 2개와 그랬다.
제대로 했다면 수신기에서 빨강 LED가 깜빡일 것이다. 그러면 송신기 뒷면 Bind range test 단추를 누른 상태에서 (송신기) 전원을 켠다. 삐~ 소리와 함께 빨강 LED 깜빡임이 정지되면 끝이다. Bind선과 5V 전원선을 빼면된다. 물론 송신기 전원도 꺼야지.

수신기와 메가2560 수신부 연결하기

다음과 같이 수신기 채널과 메가2560 을 연결하라.

  • 채널 1 : Aileron (핀8)
  • 채널 2 : Elivator (핀9)
  • 채널 3 : Throttle (핀10)
  • 채널 4 : Rudder (핀11)
  • 채널 5 : Auxilary 1 (핀12)
  • 채널 6 : Auxilary 2 (핀13)

GY86과 메가2560 센서부 연결하기

GY86는 IMU의 한 종류다. IMU는 inertial measurement unit 관성측정장치의 약자로, 기체의 기울어짐, 움직임, 속도 등을 감지하여 그것을 제어부에 전달한다. 마치 우리 몸의 평형 감각 기관인 반고리관과 유사하지 않은가!
IMU는 I2C 데이터 전송법을 쓴다. 이 방법은 데이터 전송선(SDA)와 데이터 전송을 위한 타이머선(SCL) 2가닥만으로, IMU에서 생산한 정보를 제어부에 보낸다. 제어부가 Master I2c가 되고 GY86은 Slave가 되어, Slave는 정보 생산 노예, Master는 머슴이 보내는 정보를 갖는다.

그냥 GY86의 SDA와 SCL를 메가2560의 SDA와 SCL에 각각 연결하면 된다.

동력부 연결하기

멀티위 2.4 스케치 업로드

결과물


시험비행 동영상

자작 델타형 프린터

[ 옛 글을 찾아 그대로 옮긴다. ]

3D 프린터, 처음 제작하다!

진정 3D 프린터 매니아라면 멘델보다는 델타를 만들란다. 멘델은 키트지만 이미 2개 조립하여 그 결과물에 놀라봤다. (이젠) 스스로 만들기에 도전한다. 인터넷 구글링에 7일정도 소요되었는데 여러 소스중 Richrap http://richrap.blogspot.kr이 가장 마음에 들었다. 부품 모두 PLA로 출력하고 연마봉과 프로파일을 구매하여 뼈대를 구성 . . . 그 모습이 제법 원본에 가까웠다.2014년 12월 첫 작품. 부품 부족으로 분해하느라 아, 지금은 존재하지 않는다.

둘째 태어나다!

첫 모방 작품은 크기가 작았다. 출력물의 크기는 물론 endstop의 위치마저도 세밀하지 못했다. 그렇다고 실패작이라고 하기엔 출력물 결과가 그리 나쁘지 않았었다.
몸체가 더 크면 오차율도 적어지리라. 결과지만 그 생각이 조금은 맞았다. 세로 기둥 평행 상태 유지, 세로 기둥 높이 통일, endstop의 위치 균등, Delta diagonal rod(이걸 그냥 '팔'이라 부르면 어떨까?)의 길이 통일이 무엇보다 중요한 요소다. 그러면 Hotend의 끝점이 바닥면(Plate)의 중심과 일치하게 된다. 2015년 1월 작품. 역시 부품 부족으로 해체.

셋째

델타형 3D 프린터 둘째의 모양을 조금 다듬었다. sketchup의 소스를 변형시켜 둘째의 투박에 매끄러움을 더했다. Endstop의 위치도 상하로 작게 이동시킬 수 있는 장치도 자체 개발했다. 기둥을 오고가는 전선들이 자취를 감추었으며 아두이노 윤(Yun)과의 결합으로 웹상에서 작동 및 감시, 정지시킬 수 있는 기능을 추가시키고 있다. 물론 세밀한 교정 Calibration, 이 과정이 델타형 3D 프린터의 높은 산이다.은 필수다.

바닥의 기본 형태를 sketchup했다. 골격이 되는 알루미늄 프로파일은 녹색부분, 활처럼 감싸는 부위는 PLA출력물이다. 갈색은 Carrage 이동을 담당하는 연마봉 지지대, 노랑은 스테핑 모터 지지대다. 오른쪽은 스테빙 모터 지지대 PLA 출력물이다.

바닥과 상단부 모양을 만들었다. 전선을 프로파일 속으로 넣어 깔끔히 처리했다. 실감개를 장착하고 캐리지(Carriage)에 실을 꿰었으며 플렌지 베어링에 걸었다. 엔드스탑을 RAMPS 1.4와 연결(xMax, yMax, zMax / +, -, s)하여 호밍(Homing)에 성공하였다. 펌웨어 소스는 richRep 개정판을 이용하였는데, 일부분 나에게 맞게 손 봐야했다. 역시 PLA 부품 출력은 둘째를 이용했다.
다음 단계는, 먼저 메인콘트롤러RAMPS 1.4 : AtMega2560과 스텝모터 드라이버가 장착된 확장쉴드의 결합체를 고정시키는 작업을 하고 바로 바닥면(Build plate)을 만들어야 한다. 유리를 가공하여 자작 델타 I처럼 세 군데 구멍을 뚫어야한다. 그리고는 압출기를 달고 회로를 연결(Wiring)해야 한다. 시운전을 하여 엔드스탑 교정, 스케일 교정, 압출기 속도 교정을 해야한다. 모든 과정이 이전 자작품과 동일하다. 2015년 1월말 작품. 역시 해체. 완성된 모습 사진도 남지 않았다.

넷째, 새로운 시작 현역


다섯째 현역


여섯째 예비역


기타s

노즐 막힘 해결방법

  • [핵심 1] 노즐 막힘의 주범은 핫엔드 몸통부의 온도 상승이다. 따라서 냉각팬으로 몸통부Barrel의 온도를 최대한 낮춰야한다.

      * 프린트가 잘 되다가도 어느 시점이 지나면 그것이 당연한 듯 노즐 막힘이 찾아온다. 끝없는 구글링.
    • B급 필라멘트 문제인가?
    • my 핫엔드 자체 결함인가?
    • 핫엔드 온도가 너무 높거나 낮아서인가?
    • 베드 레블링에 문제가 있는가?
    • 제어 프로그램 Repertier Host의 설정, 특히 retraction 수치가 틀린가?
    • 좌절. 사물인터넷, 로봇, 드론 같은 겻길로의 외도 수 개월 그리고 일상으로의 회귀 몇 개월. 그러다 찾은 최종 결론,
      다른 조건들이 모두 충족되더라도 몸통부 온도 상승을 막지 못하면 노즐 막힘 Jamming 은 지속된다.
  • [핵심2] 가끔 필라멘트 끝을 해바라기씨유(발연점이 250℃)에 담갔다가 출력하라. 몸통 내부의 코팅효과로 필라멘트가 달라붙지 않는다. 그래도 몸통부가 냉각되지 않으면 몸통부 내부 PLA의 체적 상승을 막을 수 없다.
    노즐팁 안에서는 코팅효과와 더불어 PLA 찌꺼기를 제거하는 역할도 한다.

      * 모든 기름을 가열하면 발연점에 달하며 연기를 내기 시작한다.
    • 올리브유 : 180℃
    • 포도씨유 : 220~230℃
    • 옥수수유 : 230~240℃
    • 콩기름 : 230~240℃
    • 카놀라유 : 240~250℃
    • 해바라기씨유 : 250℃
  • FLA가 Hotend 안에서 타지 않도록 프린팅 후 바로 식혀라.
  • 노즐 외부에 붙은 PLA 찌꺼기를 물티슈나 아세톤으로 제거하라.
  • 매번 새로운 출력시 시험압출 5mm로 노즐 내부를 말끔하게 비워라.
  • 압출물이 수직으로 말끔하게 떨어지는 상태가 가장 좋은 상태다.
  • 베드 레블링(Bed Leveling)을 하여 빌드 플레이트가 수평이 되도록하라.
  • 압출 모터의 텐션을 적절히 유지하라.

쿼드콥터 제작

DIY 작품들 스케치업 src in here

헥사콥터 제작

DIY 작품들 스케치업 src in here

나의 작업실, 아파트 베란다

하지만 겨울엔 추워서 안방 화장실에서 작업한다.두번 째 사진

전동드릴 안전 장치 src in here

2014년 5학년 실과(목재다루기) 수업할 때 활용했다. 보라색은 손잡이고 바닥에는 미끄럼 방지용 가죽을 붙였다. (손잡이를 잡은 아이들 왼손이 두려움에 조금씩 떨렸다. 아이들 손 위를 교사의 손으로 겹쳐 눌러야만 했다.(-:))
메이커마다 전동드릴 규격이 다르므로 드릴과의 연결부위 skp 파일은 수정해야한다. 연마봉은 직경 8mm를 사용하는데 길이는 자유다. '베어링샵'에서 구매해 사용했다.

IP카메라 뷰어 제작기

1. 제작 가능성 찾기

우리 집은 제천이다. 동해안 가족 여행하면서 우리집 내부를 볼 수 있는 IP 카메라? 5,6만원이면 그럭저럭 쓸만한 중국제품을 구매할 수 있다. 100만 화소에 쌍방향 음성 통신도 가능하다.

이 IP 카메라의 원리를 추정해보자. Maker의 본능이다.

  1. 외부 선이 없으니 무선네트워크를 위한 AP access point 또는 라우터가 주변에 존재한다.
  2. AP는 외부 네트워크와 연결되어야한다.
  3. 그러면 AP는 와이파이 모뎀이다.
  4. 마치 스마트폰처럼, 이것 역시 와이파이 모뎀에 상시 연결되어 있다.
  5. 따라서, 외부네트워크에서 언제든 IP 카메라를 제어할 수 있다.
  6. [추가과제] 외부에서 집에 놓고 온 스마트폰도 제어할 수 있다?
그럼 다음과 같은 논리도 가능하다.
  1. 후면 800만, 전면 200만 화소의 (안쓰는) 갤럭시 S2가 있다.
  2. 우리 집 AP(wifi모뎀)과 상시 연결될 수 있다.
  3. 갤럭시 S2에 IP카메라 앱을 만들어 운영한다.
  4. 외부네트워크에서 언제든 앱이 제공하는 영상에 접근할 수 있다.
  5. AP 모뎀의 포트포워딩 내부 포트의 외부 개방을 하여 외부 접근이 가능하게 한다.

1-1. 부품 및 SW

  • 안쓰는 갤럭시 S2
  • 안드로이드 버전 4.12로 API 레벨이 16이다.

  • 안드로이드 스튜디오
  • AndroidManifest.java, activity_main.xml, MainActivity.java, build.gradle이 꼭 다루어져야 한다. 다른 파일들은 관심밖이어도 무관하다.

  • (나중에 선택된) 서버 앱으로서의 IP Webcam
  • Play 스토어에서 무료로 내려받을 수 있다. 클라이언트에서 접근할 주소를 앱 화면에 제시한다.

    갤럭시S2
    안드로이드 스튜디오
    IP Webcam

    2. 서버와 클라이언트 관계

    IP카메라 역할인 갤럭시S2는 영상 정보를 제공해야 하므로 서버, 나의 스마트폰은 클라이언트다.
    상식적으로 서버에는 서버 애플리케이션(php,asp 등등)이 있어야 하고, 클라이언트에는 클라이언트 애플리케이션(익스플로러, 크롬 등등)이 있어야 한다. 마찬가지로, 나의 IP카메라에는 서버 앱이, 나의 스마트폰에는 클라이언트 앱이 존재해야 한다. 따라서 갤럭시S2에는 영상 전송용 서버 앱을, 나의 스마트폰에는 영상 수신용 클라이언트 앱을 설치해야 한다.
    그림처럼, 주인(서버)은 손님(클라이언트)의 주문(Request)을 수행(Response)만 할 뿐 손님에게 주문할 수 없다. 역으로, 손님(클라이언트)은 주인(서버)에게 주문만 할뿐 주인 행세를 하지 못한다. 이 말은 쌍방향 통신이 아니라는 얘기다.

    3. 기존 IP카메라용 서버 앱과 웹 클라이언트 이용

    IP카메라 서버와 클라이언트 앱 둘 다 만들어볼까 싶었다가, 그럴 시간이 부족하다는 생각에 둘 중에서 서버용 앱을 물색했다. 구형 스마트폰을 IP카메라로 사용했고, 그 앱으로 IP Webcam을 선택했다는 글이 매우 많았다.
    앱을 설치하니 마치 스마트폰 내장 카메라가 작동된 것과 같은 화면이 연출된다. 이 서버에 접근할 IP 주소를 화면 하단에 친절히 안내하는 것만 다르다. 내부적으로야 다음과 같은 과정을 거쳤겠지만. 전제가 하나 있다. 서버와 클라이언트가 서로 연결되어야 한다. * 사진 1

    • 서버 : 나는 서버다. 따라서 클라이언트의 요청을 기다린다.
    • 서버 : 정보를 요청할 때는 화면 하단에 제시된 http://192.168.0.X:8080으로 하라. 포트포워딩과 DDNS 설정으로 달리 접근할 수도 있다.(이 사이트에 방법이 있음)
    • (지문) 클라이언트는 제시된 주소로 서버에 접속한다.
    • 서버 : (클라이언트에서 접속한 주소가 내가 제시한 것과 같으면 Socket연동 알고리즘 사용 내가 보는 화면을 클라이언트로 전송하라.
    • 클라언트 : 서버가 보내주는 영상스트림을 Play하라.
    웹페이지를 열어 서버에서 보내주는 영상 스트림을 받아냈다.사진 1 아래 두 대의 폰에 두 개의 IP카메라 서버가 실행되고, 컴퓨터에서는 IP카메라 서버에서 보내는 영상스트림을 2개의 웹 클라이언트로 받고 있다. 서버가 여럿이어도 그 수만큼 웹페이지를 열면 된다. (이론상으론) 무한 갯수의 서버-클라이언트 조합을 만들 수 있겠다.

    4. IP카메라 클라이언트용 앱개발

    웹클라이언트를 대신할 클라이언트 앱이 Play 스토어에 존재한다. 대부분 IP Webcam과 더불어 개발되어 배포된 IP Webcam Viwer를 선호했다. 하지만 나는 따로 개발하려 한다. 서버에서 클라이언트의 httpRequest를 기다린다면, 그저 httpRequest만 코딩하면 되겠다. 스마트폰 앱이어야 하므로 안드로이드 스튜디오에서 프로그램해야 하겠고, http요청을 되받는 것이 WebView 콤포넌트이므로 그것을 이용해 Videofeed해야겠다. 참고로, VLC 미디어 재생기왼쪽 사진에서도 비슷한 방식으로 네트워크 스트림을 Videofeed한다.

    안드로이드 스튜디오 활용 및 코딩 방법은 이 글의 범주를 벗어나므로 구글링에 맡긴다.
    안드로이드 앱을 구성하는 3요소가 있다면 다음과 같을 것이다.

  • AndroidManifest.java src in here
  • 앱의 권한(예를 들어, 인터넷 연결 허용, 스마트폰 하드웨어 가속 장치 사용 등)을 맡는다.
    [핵심] uses-permission android:name="android.permission.INTERNET"

  • activity_main.xml src in here
  • 앱의 GUI를 구성한다.

  • MainActivity.java src in here
  • GUI구성 요소를 제어하여 앱 목적을 달성한다.

    apk를 만들 때 살펴야 할 것이 하나 더 있다. build.gradle 파일에서, targetSdkVersion을 자신의 폰 API 레벨과 같게 해야한다. 참고로, minSdkVersion의 값이 낮을수록 저사양 기종에서 동작할 수 있으며, 그것은 targetSdkVersion 값 이하여야 한다.
    
    android {
        compileSdkVersion 25
        buildToolsVersion "25.0.2"
        defaultConfig {
            applicationId "com.example.jeegs.ipcamv"
            minSdkVersion 9
            targetSdkVersion 16 //  gallexy s2 안드로이드 버전 : 4.12, API 레벨 : 16
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    }
    
    나의 스마트 폰은 gallexy s2 안드로이드 버전 4.12, API 레벨 16이다. 그래서 targetSdkVersion을 16으로 정했다. 안드로이드 버전과 API레벨 도표

    안드로이드 스튜디오에서 나의 폰을 연동하여 실행시키니 폰 화면의 WebView 콤포넌트 부분이 하얗다.
    어라?
    Android 4.4 (API level 19)부터 WebView가 달라졌단다. Android Developers(https://developer.android.com/) 구형인 레벨 16에서 19로 레벨 업된 WebView를 실행시킬 수 없는 것이다. WebView API가 다르기 때문이다! 그래서 레벨 19 이상의 폰을 수소문한다.

    아내 폰을 빌렸다. 갤럭시 S5, 안드로이드 버전 6.01 . . . 레벨 23 현존 최고 레벨은 25!이다. 그런데 문제가 생겼다. usb인식이 인된다. 참고로, 나는 윈도우 7, 64bit, 8G 메모리인 시스템을 사용중이다.
    역시 구글링이 최고다. 갤럭시 S5의 [디바이스 정보]에서 빌드번호를 찾아 그것을 7회 탭탭하면 [개발자옵션]이 나온단다. 이전 버전과는 사뭇 달랐다. USB 디버깅을 선택하고, PC에서는 http://www.3366.co.kr 접속 → 빠른해결 → 다운로드 → 휴대폰 관련 프로그램 → USB 통합드라이버 내려받아 설치하니 인식 완료.

    안드로이드 스튜디오에서 갤럭시 S5와의 연동이 자동으로 이루어졌다. minSdkVersion을 16, targetSdkVersion을 23으로 바꾼 build.gradle 파일 src in here, AndroidManifest.java, activity_main.xml, MainActivity.java 파일을 재검토 후 실행시키니 IP카메라의 영상이 클라이언트 폰에 전송되었다. 에뮬레이터보다 훨씬 빠르고 생동감이 넘쳤다.

    5. 클라이언트 앱 실행 장면

    안드로이드 스튜디오에서 (느려터진) 에뮬레이터 대신 스마트폰에 직접 연결하여 Build했다.
    installing apk . . .
    앱이 내 스마트폰에 자동으로 설치되었다.
    IP카메라 서버를 실행시키고, 지금 만든 클라이언트 앱을 작동시키니 서버 영상이 그대로 옮겨졌다. 스트리밍 지연이 200ms 정도로 부드럽다.

    이젠 IP 서버 카메라를 싣고 움직일 로봇 팔을 만들 차례다. 구동장치로서 서보모터를 이용하지만 소음이 생기는 단점이 있다. 그래서 Adafruit에서 이미 구매했다가 방치해 놓았던 5선 스텝모터 2개를 사용할 작정이다.
    구동 장치뿐 아니라, 그것을 제어할 신호를 받을 서버용 콘트롤러Wifi 모듈가 필요하다. 와이파이 서버 모듈을 찾다 눈에 들어 온 것이 ESP8266 Nod MCD Lua다. ESP8266 Nod MCD Lua는 client 서버, 웹서버, Web Socket 기능을 갖는다. '엘레파츠'에서 개당 8600원에 팔고 있었다. 2017.03.10. 현재 (이런, 알고보니 기동전원이 3.3V란다. ㅠㅠ)

    기존 ip카메라 서버와는 별도로 새로운 서버를 구축해야하는 프로젝트다. 안드로이드 앱에서 보낸 신호를 서버에서 받아, 그것으로 2축 로봇 팔을 제어해야 한다. ESP8266 Nod MCD Lua 모듈 활용과 관련된 좋은 정보를 담은 사이트 HardCopyWorld가 있다!

    IP카메라 탑재용 로봇팔 제작기

    1. 부품s

  • ESP8266 Nod MCD Lua
  • client 서버, 웹서버, Web Socket 기능을 갖는다. 이 프로젝트에서는 웹서버나 웹소켓으로 이용할 것이다. 2017.3월, 엘레파츠에서 8600원에 판매한다.
    I2C 핀이 없고 3.3V(자가 구동 및 데이타 출력신호)로 운영된다는 단점이 있지만 다행히도 VIN은 5V를 지원한다. 그래도 데이타 출력신호 3.3V를 5V로 승압하는 장치를 추가 제작해야한다. (이 프로젝트에서 사용할) 스텝모터 드라이버가 입력신호로 5V를 필요로 하기때문이다.
    혹시 그런 장치가 이미 출시되었는가?구글링 . . .
    회로도가 존재한다.src in here
    로직 신호마다 자체 제작해야 한다면 배보다 배꼽이 더 커지는 격이다.(로직 신호 하나 구성에 저항 세 개, Tr 하나가 든다.) 다행히도 한 보드에 4개의 로직 신호를 처리할 수 있는 4채널 Logic Level Converter src in here 를 디바이스마트에서 구매할 수 있다. 2017.3월 현재, 개당 3600원이다.

  • NEMA-17, 200 steps/rev, 12V 350mA 또는 Stepper Motor - 5VDC 32-Step 1/16 Gearing
  • 둘 다 스텝모터로서 Adafruit에서 2014.10월 함께 구매했던 것으로 2017년 3월 현재 각각 $14와 $4.95에 판매한다.
    각기 다음과 같은 특성을 갖는다. Adafruit에서 인용

      NEMA-17, 200 steps/rev, 12V 350mA

    • 200 steps per revolution, 1.8 degrees
    • Coil #1: Red & Yellow wire pair. Coil #2 Green & Brown/Gray wire pair.
    • Bipolar stepper, requires 2 full H-bridges!
    • 12V rated voltage (you can drive it at a lower voltage, but the torque will drop) at 350mA max current
    • 28 oz*in, 20 N*cm, 2 Kg*cm holding torque per phase

      단상(Unipolar) Stepper Motor - 5VDC 32-Step 1/16 Gearing

    • Unipolar stepper with 0.1" spaced 5-pin cable connector
    • 513 steps per revolution
    • At 5VDC, 25 RPM and 50 RPM by 9VDC
    • Holding Torque: 150 gram-force*cm, 15 N*mm/ 2 oz-force*in
    • 28 oz*in, 20 N*cm, 2 Kg*cm holding torque per phase
  • 스텝모터 드라이버
  • Adafruit에서 2014.10월 구매했던 스텝모터쉴드를 사용할 것이다. 2017년 3월 현재 $19.95에 판매한다.
    다음과 같은 특성을 갖는다. Adafruit에서 인용

    • 4 H-Bridges: TB6612 chipset provides 1.2A per bridge (3A for brief 20ms peaks)
    • Up to 4 bi-directional DC motors with individual 8-bit speed
    • Up to 2 stepper motors (unipolar or bipolar)
    • Can run motors on 4.5VDC to 13.5VDC.
    • Polarity protected 2-pin terminal block and jumper to connect external power, for separate logic/motor supplies

    [부품목록]

    ESP8266 Nod MCD Lua
    NEMA-17, 200 steps/rev, 12V 350mA
    단상 Stepper Motor - 5VDC 32-Step
    스텝모터 드라이버
    4CH Logic Level Converter
    단상(Unipolar) Stepper Motor - 5VDC 32-Step
    esp8266 Nod MCD Lua 핀구조

    2. 전체 개념도

    개념도

    위 개념도를 보자.
    서버와 클라이언트의 조합이다. 손님은 주문(Request)하고 주인은 반응(Response)을 준다. 손님의 주문은 주인에게 속한 구동장치를 제어하라는 것이다. 주인은 손님이 요청한 주문을 실행(구동장치 구동)하고 그 결과를 손님에게 통보(Response)한다. 손님은 클라이언트앱을 통해서 그 결과를 확인한다.


    3. 회로도



    4. 서버 펌웨어 및 클라이언트앱 제작

    4-1. 서버 펌웨어 제작


    4-2. 클라이언트앱 제작



    5. 실행


    ipTIME 포트포워딩

    1. ipTIME 공장초기화

    후면 RST(리셋) 단추를 10초 가량 누르면 공유기 상단 CPU LED가 빠르게 깜빡인다. 초기화 완료!

    2. DDNS 설정

    공유기 엄밀히 말하면 라우터는 고정 IP를 갖지 못한다. 수시로 변하는 IP 주소를 일일이 파악해 주소창에 입력하는 일은 여간 번거로운 일이 아니다. 쉽지도 않다. 그래서 DDNS로 호스트 주소를 정하는 것이다.
    192.168.0.1로 접속하여 [DDNS설정]에서 호스트이름(예를들어, jeegsun.iptime.org)을 등록한다. 이제 공유기 내에서 IP주소가 어떻게 변하든 나와는 상관없다. 주소창에 호스트이름과 포트(예를들어, jeegsun.iptime.org:8081, 8081은 포트포워드할 포트다.)만 입력하면 된다. '나만의 도메인'을 가진 것이다. 이제 포트를 공개(OPEN)할 일만 남았다.

    3. 포트포워딩(포트 공개)

    192.168.0.1로 접속하여 [포트포워드 설정]에서 원하는 내부 IP를 포트포워드 내부와 외부에 해당 IP를 공개한다는 의미한다.
    이때 포트포워드 가능한 내부 아이피 범위는 192.168.0.2 - 192.168.0.254! 외부에서 접속할 외부포트와 내부에서 접속할 내부포트를 설정해주어야 한다. 외부에서 접근할 때와 내부에서 접근할 때 주소가 다를 수 있다.
    한 예로, 외부포트 8081, 내부포트 8080으로 설정했다면,
    같은 네트워크에서는 http://내부아이피:8080으로, 외부에서는 http://호스트이름:8081로 접속할 수 있다. (사실 http://호스트이름:8081만으로도 내부와 외부3G, LTE,4G 포함 모두 접속 가능하다.) DDNS에서 설정한 호스트이름과 open된 포트만 기억하면 된다. 단 그 내부IP 주소는 포트포워드에 추가되어 있어야한다.

    * ipTime이 순수 국산인 줄 착각했었다. 여러 이설이 가능하겠지만, 위키에서 관련 정보를 얻은 다음 나름 판단컨데, ipTime은 온전한 국산이라 말할 수 없다. 중국 zioncom에서 생산된 Hardware에 ipTime이 개발한 펌웨어을 장착한 것이다. ipTime 기기는 zioncom의 OEM 생산이 아니라 그 회사 고유 생산 모델이란다.
    도대체 두 회사 관계는 어떻게 설명해야 옳을까?

    기가 인터넷 구성 방법

    2014년도 후반기에 기가급 속도의 인터넷 상품이 출시되었다. 당시 제천 그린코아루 아파트라 말하고 신청 가능하냐 했더니,
    "아직 제천까지는 안됩니다."
    까마득히 잊고 있었다. ipTIME 포트포워딩과 VPN(사설가상서버)을 다루기 전까지는.

    1. 인터넷 서비스업체나의 경우 KT에 기가인터넷 서비스를 요청한다.
      기가용 허브 모뎀과 기가용 케이블규격 CAT5 이상이어야 한다.을 가져올 것이다.
    2. 기가 전용 공유기가 있어야 한다. 100/300Mbps급보다 가격이 조금 비싸다.
      이왕이면 기가 WIFI 규격인 802.11ac를 지원하는 것으로!
    3. 기가 지원 랜카드가 장착된 PC나의 경우 Realtek PCIe GBE Family Controller, 구글링하니 GigaBit 지원 Ethernet 랜카드란다. 인지 확인한다.
      노트북도 기가 WIFI 규격인 802.11ac를 지원하는 것이 좋다.
    2017년 3월 초,
    "제천 그린코아루 아파트입니다. 기가 인터넷이 가능가요?"
    기가 콤펙트1기가가 아닌 500MBps짜리만 된단다. 기존 네트워크 선들이 기가용 케이블이 아니라서 그렇단다.
    기가 인터넷이 2014년 말에 출시되었으니, 그 이후 지어진 아파트들은 진정한 기가인터넷을 접할 수 없다는 Fact가 생긴다.
    참고로, 허브용 모뎀과 공유기, 공유기와 PC 사이도 기가용 케이블로 연결되어야 '진정한 기가'다.
    관련 기사가 있어 공유한다. IT동아: 기가인터넷, PC와 공유기가 구형이면 무용지물

    나의 가족

    50대중반 초등교사의
    좌충우돌 Maker운동 동참기

    "늦었다 할 때 시작하는 기쁨도"

    나는 50대 중반을 넘어선 초등학교 교사다. 4년전 처음 Maker Movement에 접했다. 메이커 운동은 엘빈 토플러의 제 3의 물결 혹은 그 너머에 해당하는 흐름이다. 동참할 가능성을 타진하다 결국 일을 벌였다. 이젠 여행이나 다니라는 말이 잔뜩 들렸다. 그래도 인생은 50부터라는 말을 믿었다. '지금'이라는 낱말이 아직 늦지 않았음을 상기시켰다.
    2014년 즈음, 한창 인기몰이 중이던 3D 프린터. 나와 같은 초보자에게는 커다란 장벽일 수 있지만 가랑비에 옷 젖는다고, 지속적인 구글링 . . . PrintRbot에서 소형 프린터를 구매하여 델타형 프린터 부품을 설계 및 출력하여 조립 후 펌웨어를 분석, 내게 맞게 소스 수정하는 과정을 거치면서, 어림잡아 3년의 메이커운동Maker Movement이 시작되었다. 가끔 전자 서적O'reilly 또는 Amazon을 구매하여 깊이를 더했지만 아직도 구글링이 대부분이다.
    3D프린터의 매력에 빠져 1년 반, 사물인터넷The internet of things에 6개월, 나머지 1년은 드론에 매달려왔다. 물론 중간 중간 안드로이드C++ with Java와 Visual Studio 2016에 곁눈질도 했지만.

    늦었다 싶은 이에게 도움이 되길 바란다.
    지금은 드론 Flight Controller 자체 펌웨어를 프로그램중이다.

    ESP8266 Nod MCU Lua With Arduino Sketch

  • FTDI와 WIFI 통신 모듈
  • USB serial 통신을 담당하는 FTDI 모듈과 WIFI 통신을 담당하는 ESP-12E 모듈이 있어, USB 연결 잭만 연결하면 (아두이노 IDE를 통해) 바로 서버용 펌웨어 개발이 가능하다.
    참고로, WiFi 통신을 제대로 활용하기 위해서는 loop() 타임을 50m 이내로 제한하라. 매 루프마다 ESP8266 core(와이파이 관련)가 실행되기 때문이다. 만일 루프 타임이 길어질 경우, delay()로 지연시켜,ESP8266 core가 동작할 틈을 마련해 주어야 한다.

  • 개발환경
  • Arduino IDE와 Node MCU Lua 스크립트를 이용하는 두 가지 개발 환경이 있다. Node MCU Lua 스크립트 방식의 펌웨어가 탑재된 상태로 출시된다.

      Arduino IDE 여기에서는 이 환경을 사용할 것이다.

    • 일반적인 아두이노 스케치 업로드 방식과 같다.
      아두이노 스케치를 작성 및 업로드하여 ESP8266 Node MCU Lua 보드의 펌웨어를 갱신한다.
      일반 아두이노 보드와 비교하면 스케치(펌웨어) 업로드 속도가 3배 이상 느리다.

      Node MCU Lua 스크립트

    • 실시간으로 스크립트를 입력하고 결과를 보여주는 shell prompt 환경을 제공하여 초심자에게 유용할 수 있다.
  • LED Blink : ESP8266 세계에 첫발을!
  •  
    /*
      GPIO14인 D5에 Led +, GND에 Led -를 꼽는다.
      D5와 GND가 근접해 있어 Led 꼽기가 편하다. 
      참고로, HIGH 전압이 최대 2.4V이므로 Led에 저항을 달 필요가 없다. 
      * 실행결과 : Led가 3초간 켜졌다가 1초간 꺼짐을 반복한다.
    */
    
    #define Led 14  
    void setup() {
      pinMode(Led, OUTPUT);    
    }
    void loop() {
      digitalWrite(Led, HIGH);  // 3초간 켜기
      delay(3000);  
      digitalWrite(Led, LOW); // 1초간 끄기
      delay(1000);              
    }
    

  • 내 이웃 와이파이 공유기(SSID)들을 스캔하다
  •  
    /*
      연결할 부품없이 그저 usb케이블만 연결한다.
      그러면 내 이웃에 존재하는 와이파이 공유기 아이디(SSID)와 신호세기를 찾아 보여준다.
      ( ) 값이 클수록 신호강도가 좋다. 아래 1, 2위는 모두 우리 집에 있는 와이파이 공유기다.
      * 실행결과 : 
                scan start
                scan done
                6 networks found
    
                1: U+NetC74B (-81)*
                2: U+zone (-81)*
                3: KT_WLAN_A9D7 (-93)*
                4: jeetoilet (-35)* //신호세기 1위
                5: SSE (-91)*
                6: jgs (-69)* //신호세기 2위
    */
    
    #include "ESP8266WiFi.h"
    void setup() {
      Serial.begin(115200);
      // Set WiFi to station mode and disconnect from an AP if it was previously connected
      WiFi.mode(WIFI_STA);
      WiFi.disconnect();
      delay(100);
      Serial.println("Setup done");
    }
    void loop() {
      Serial.println("scan start");
      // WiFi.scanNetworks will return the number of networks found
      int n = WiFi.scanNetworks();
      Serial.println("scan done");
      if (n == 0)
        Serial.println("no networks found");
      else
      {
        Serial.print(n);
        Serial.println(" networks found");
        for (int i = 0; i < n; ++i)
        {
          // Print SSID and RSSI for each network found
          Serial.print(i + 1);
          Serial.print(": ");
          Serial.print(WiFi.SSID(i));
          Serial.print(" (");
          Serial.print(WiFi.RSSI(i));
          Serial.print(")");
          Serial.println((WiFi.encryptionType(i) == ENC_TYPE_NONE)?" ":"*");
          delay(10);
        }
      }
      Serial.println(""); 
      delay(5000);
    }
    

  • 새로운 와이파이 공유기로!
  •  
    /*
      Copyright (c) 2015, Majenko Technologies. All rights reserved.
    
      ESP8266에 내장된 WiFi 모듈이 새로운 와이파이 공유기(AP, 웹서버 기능)가 되어, 그 SSID를 주변에 뿌린다. 
      스마트폰을 열어 WIFI  기기 검색을 해보라.
      임의로 입력한 'kimchi'가 뜰 것이다.
      비밀번호 'bimilbeonho'를 입력해 연결할 수 있다.              
    
    */
    
    #include "ESP8266WiFi.h"
    #include "WiFiClient.h" 
    #include "ESP8266WebServer.h"
    
    const char *ssid = "kimchi"; //새롭게 만들 공유기 아이디
    const char *password = "bimilbeonho"; // 그 공유기 비밀번호
    
    ESP8266WebServer server(80);
    
    void handleRoot() {
      server.send(200, "text/html", "You are connected");
    }
    
    void setup() {
      delay(1000);
      Serial.begin(115200);
      Serial.println();
     
      //① ESP8266에 내장된 WiFi 모듈을 공유기(AP : Access Point)로 만들어 IP를 할당한다.(예, 192.168.x.x)
      WiFi.softAP(ssid, password);
      IPAddress myIP = WiFi.softAPIP();
    
      Serial.print("AP IP address: ");
      Serial.println(myIP);
     
      //② 공유기(AP : 웹서버 기능)를 활성화한다. 자신의 SSID를 주변에 뿌린다
      server.on("/", handleRoot);
      server.begin();
    
      Serial.println("HTTP server started");
    }
    void loop() {
      server.handleClient();
    }
    

  • Client로 이용하다
  •  
    /*
        basicHttpClient.ino :
        예제 스케치들과 인터넷에 떠도는 소스들을 참고해서 단순화시키고 주석을 달았다.
    */
    #include "ESP8266WiFi.h"
      
    //내 공유기의 SSID와 비밀번호
    const char *ssid = "jeetoilet";
    const char *password = "xxxxxxxx";
       
    //접속할 사이트 주소
    const char* host = "jeegs.cafe24.com";
    
    void setup() {
      Serial.begin(115200);
    
      /*
         1) ESP8266에 내장된 WiFi 모듈을 내 공유기(with ssid와 password)에 접속시킨다.
         2) 연결되었다면(WiFi.status()==WL_CONNECTED),
            내 공유기는 ESP8266에 내장된 WiFi 모듈에게 IP를 하나 건네준다.
            건네주는 IP는 랜덤(동적)으로 생성되는데 그 범위는 192.168.0.2 - 192.168.0.254이다.
            참고로, 스마트 폰으로 내 집 공유기에 접속하면 위 주소 범위 안에서 랜덤으로 IP를 할당받는다.
            따라서, ESP8266에 내장된 WiFi 모듈은 스마트폰에 내장된 와이파이 모듈과 같은 기능을 한다.
            (스마트폰의 와이파이연결->비밀번호 넣기, 연결 기다림 . . . 과 같은 과정!)
      */
    
      // ① ESP8266에 내장된 WiFi 모듈을 공유기(with ssid와 password)에 접속시키기
      WiFi.begin(ssid, password);
      while (WiFi.status() != WL_CONNECTED) {
        delay(1000);
      }
    
      //ESP8266에 내장된 WiFi 모듈의 IP 보여주기
      Serial.println("\nIP address: ");
      Serial.println(WiFi.localIP());
    }
    
    void loop() {
    
      //기본 루프타임 : 7초
      delay(7000); 
    
      //② (ESP8266에 내장된 WiFi 모듈에) 클라이언트 만들기
      WiFiClient client;
    
      //③ 클라이언트에서 특정 HTTP 서버에 접속하기
      const int httpPort = 80;
      if (!client.connect(host, httpPort)) {
        Serial.println("Not connected!");
        return;
      }
    
      /*
         가져오기(GET) 방식으로 자료를 요청한다.
         "jeegs.cafe24.com의 루트(/)에 있는 index.html 파일을 받고(GET) 싶어요."
      */
    
      //④ (접속함과 동시에) 가져오기(GET) 방식으로 자료 요청하기
      client.print(String("GET /") + " HTTP/1.1");
      client.print("\r\n");//CR+LF조합(줄의 처음으로 + 다음줄로)으로 그냥 줄바꿈 문법일 뿐이다. 
      client.print("Host:jeegs.cafe24.com");
      client.print("\r\n");
      client.print("Connection:close");
      client.print("\r\n\r\n");
    
      //클라이언트가 서버로부터 3초동안 자료를 받지 못하면 클라이언트 종료!
      int timeout = millis() + 3000;
      while (client.available() == 0) {
        if (timeout - millis() < 0) {
          Serial.println("Over time!");
          client.stop();
          return;
        }
      }
    
      //⑤ '클라이언트가 서버로부터 받는 자료가 있을 동안' 줄마다 읽어, 자료 끝까지 출력하기
      while (client.available()) {
        String line = client.readStringUntil('\r');
        Serial.print(line);
      }
      //⑥ 더 이상 받을 자료가 없다면 클라이언트 종료하기
      if (client.available() == 0)  {
        client.stop();
        Serial.println("\n Client closed!");
      }
    }
    
    
    

  • Web Server로 이용하다
  •  
    #include "ESP8266WiFi.h"
    #include "WiFiClient.h"
    #include "ESP8266WebServer.h"
    #include "ESP8266mDNS.h"
    
    const char *ssid = "jeetoilet";
    const char *password = "********";
    
    ESP8266WebServer server (8080);//포트포워딩한 포트번호
    

    웹서버 구성도

  • Web Socket으로 이용하다!
  •  
    
    

    WebSocket in html5

    웹소켓 WebSocket이란?

    WebSockets are a bi-directional, full-duplex, persistent connection from a web browser to a server. Once a WebSocket connection is established the connection stays open until the client or server decides to close this connection. With this open connection, the client or server can send a message at any given time to the other. This makes web programming entirely event driven, not (just) user initiated. It is stateful. As well, at this time, a single running server application is aware of all connections, allowing you to communicate with any number of open connections at any given time.
    It operates over a single socket and is exposed via a JavaScript interface in HTML 5 compliant browsers. And the WebSocket is a standard bidirectional TCP socket between the client and the server. The socket starts out as a HTTP connection and then "Upgrades" to a TCP socket after a HTTP handshake. After the handshake, either side can send data.

    (하, 재미있게 설명되고 있다. 말하자면) HTTP 웹서버와 잠시 악수만 하고는 TCP 웹소켓으로 바로 전환해서 재미있게 정보를 교환한단다. 더하자면, 웹소켓 서버를 통해 여러 사용자들 사이 정보교환(채팅)도 가능해진다. 추후 채팅 기능 추가하기

    WebSocket 작동원리

    Once you get a Web Socket connection with the web server, you can send data from browser to server by calling a send() method, and receive data from server to browser by an onmessage event handler.

    WebSocket 이벤트

      Socket.onopen

    • 웹소켓 연결시 작동한다.

      Socket.onmessage

    • 웹소켓 서버로부터 데이타를 받으면 작동한다.

      Socket.onerror

    • 웹소켓 통신에 문제가 생겼을 때 작동한다.

      Socket.onclose

    • 웹소켓 연결이 끊어졌을 때 작동한다.


    WebSocket 메소드

      Socket.send()

    • 웹소켓을 통해 데이타를 서버로 전송한다.

      Socket.close()

    • 웹소켓 연결을 끊는다.


    WebSocket 통신 웹 I 스크립트 만으로 이루어짐

    	
               
          

    서버에서 보낸 편지:

    WebSocket 실행

    WebSocket 통신 웹 II esp8266 서버 IP인 192.168.0.13:80xx를 포트포워드한 대표도메인을 이용, 웹서버 안에서 실행된 웹소켓에 접속했다.

    
    RGB LED Controler : 

    RGB:

    R:
    G:
    B:

    From esp8266 서버 :


    WebSocket 통신웹 소스 설명 src in here

    //ESP8266 서버에서 작동중인 Web Socket에 연결하라. ws는 MDNS로 정한 서비스 이름이다.
    var connection = new WebSocket('ws://jgs.iptime.org:80xx/', ['arduino']);
    
    //Web Socket에 연결되면, connection.send()에 의해 웹서버로 data가 전송된다.
    connection.onopen = function () {
    	connection.send('Message from Browser to ESP8266 : OK! ' + new Date());
    	connection.send('ping');
    	setInterval(function() {
    		connection.send('Time: ' + new Date());
    	}, 5000
    	           );
    };
    
    connection.onerror = function (error) {
       console.log('WebSocket Error ', error);
    };
    
    //웹소켓에서 보내준 data(webSocket.sendTXT(num, "data"))를 클라이언트에서 받는다.
    connection.onmessage = function (e) {
      console.log('Server: ', e.data);   
      document.getElementById("dataFromWebsocket").innerHTML 
                                   = "From esp8266 Server : " + e.data;
    };
    
    //위 웹 GUI에서 제어한 RGB값을 웹서버로 전송한다.
    function sendRGB() {
    	var r = parseInt(document.getElementById('r').value).toString(16);
    	var g = parseInt(document.getElementById('g').value).toString(16);
    	var b = parseInt(document.getElementById('b').value).toString(16);
    	if(r.length < 2) r = '0' + r;	
    	if(g.length < 2) g = '0' + g;	
    	if(b.length < 2) b = '0' + b;	
    	var rgb = '#'+r+g+b;
    	console.log('RGB: ' + rgb);
            document.getElementById("rgb").innerHTML = "RGB: " + rgb;
    	connection.send(rgb);
    }
    * p id="dataFromWebsocket" 웹소켓으로부터 전송되는 데이타는? /p
    * p id="rgb" rgb값은? /p
    
      
     
                
      
        
          

    HTML5 SVG Star

    원격제어 로봇 with 사물인터넷 ING

    프롤로그 prologue

    가칭 '홈키퍼로봇'은 평소 휴면상태(배터리 절약)이다 동작이 감지되면 주인에게 알람(진동 또는 email)을 보낸다. 주인의 앱은 Awake 기능이 실행되어 알람을 상시 수신할 수 있다.
    주인의 앱은 로봇을 이동시킬 수 있고 장착된 카메라를 회전시킬 수 있으며, 각종 센서들(온도, 습도, 광량, 가스감지, 먼지감지 등)의 상태값을 확인할 수 있다. 로봇에는 RF 송신장치가 있어, 그것과 미리 주파수를 연동시켜 놓은 전등과 같은 전기 장치들을 On/Off할 수 있다. 이 모든 것이 원격에서 가능하다.
    참고로, 내 폰은 아직 3G를 벗어나지 못해, 머리부에 장착된 IP 카메라의 영상 스트림에 1~2초의 시간 지연이 발생한다.
    로봇의 평소 모습은 어떠할까? I2C 방식의 OLED 디스플레이가 있어, 우리 집 온도, 습도, 먼지 밀도를 보여 준다. 그리고 장식장에 버젓이 자리 잡는다.
    비용은 얼마나? ESP8266 Nod Mcu Lua, 아두이노 우노 R3, 모터쉴드, DC모터 달린 바퀴 2개, 서보모터(HS311)가 기본으로 들어가는데, 각종 센서는 무시하기로 하고 . . . 약 4만원!
    IP카메라는 퇴역한 갤럭시S2를 분해하여, 디스플레이는 무거워 버리고 메인보드만 사용한다. 갤럭시 S2의 경우 3.7V 전원을 사용하는데, 아두이노의 5V 전원을 Voltage Dividing 회로( 출력전압 = 입력전압 * ( R2 / (R1+R2)) )를 구성하여 그것에 인가한다. 참, 로봇 본체는 구글 스케치업으로 설계하고 3D 프린터로 출력한다. (이미 3D 프린터를 자체 제작하여 5대 보유하고 있다. 자랑질이다.)

    주요 기능들

  • 동작감시 Email 알람
  • 특정사이트 가입이 필요하여 번거롭고 의존적이다.

  • 동작감시 진동 알람
  • 로봇에 부착된 PIR에서 동작감지 후 (ESP서버에서)클라이언트앱으로 신호를 전송하면 앱에서 그것을 진동(또는 소리)으로 바꾸고 IP카메라 영상을 활성화시킨다.
    AndroidManifest.xml파일에서 uses-permission android:name="android.permission.VIBRATE" 하는 것을 잊지 말자. permition을 통해 안드로이드 기기에 내재된 모듈(여기서는 '진동장치)에 대한 접근 권한을 얻는다고 생각하자.
    안드로이드 폰의 진동센서을 구동시키는 소스는 다음과 같다.

    package com.example.jeegs.sensor_;
    import android.app.Activity;
    import android.content.Context;
    import android.os.Bundle;
    import android.os.Vibrator;
    import android.view.View;
    import android.widget.Button;
    public class MainActivity extends Activity {
        Button one,two,three;
        Vibrator vibrator;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            start = (Button)findViewById(R.id.basicBtn);
            pattern = (Button)findViewById(R.id.patternBtn);
            cancel= (Button)findViewById(R.id.cancelBtn);
            vibrator = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
            start.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {       
                    vibrator.vibrate(5000);//1000 mili second, 5초
                }
            });
            pattern.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    long pattern[] = {60,120,180,240,300,360,420,480};
                    vibrator.vibrate(pattern, 1);
                }
            });
            cancel.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    vibrator.cancel();
                }
            });
        }
    }
    
  • 온도, 습도, 조도, 먼지 센싱
  • 온도, 습도, 조도는 사물인터넷의 단골 메뉴다. 여기에 먼지 센서를 추가하여 차별을 꾀한다.

  • OLED 디스플레이
  • 웹이나 앱을 실행시키지 않고도 집 안 온도, 습도, 조도, 먼지량을 볼 수 있게 한다.

  • IP 카메라
  • 퇴역해서 서랍장 속에 방치된 갤럭시S2를 IP카메라용 서버로 변신시고 Play스토어에서 IP WebCam을 내려받아 설치한다. 특별히 설정해줄 영역도 없다. 하단에 뜨는 IP 주소와 포트만 기억하자. 그것은 웹브라우저나 앱에서 접근할 주소다.

  • 전기 장치 원격 On/Off
  • 433Mhz RF transmitter and receiver kit를 구매한다. 뭐 키트 한 세트가 1000원 미만이니 부담없다. 이 중 RF receiver kit와 아두이노 우노를 연결하여 기존에 사용하던 RF 리모콘의 키값을 찾아낸다.
    이제는 RF transmitter모듈을 ESP8266 서버에 연결하고, 그 키값을 해당 장치에 쏘아주면 기존 사용하던 RF 리모콘과 같아진다. 단, 해당 장치와 RF transmitter는 같은 주파수(433Mhz)를 사용해야한다.

    * 참고 :
    일반적으로 적외선 리모콘은 TV용, RF 리모콘은 점등/소등용이다. RF 리모콘 대부분은 433Mhz의 주파수를 갖는다.

    아래 소스는 RF 리모콘 키값을 출력한다.

    #include "RCSwitch.h" //RC Switch library
    RCSwitch mySwitch = RCSwitch();
    void setup() {
      Serial.begin(9600);
      // Receiver on interrupt 0  : pin #2
      mySwitch.enableReceive(0);  
    }
    void loop() {
      if (mySwitch.available()) {
        output(mySwitch.getReceivedValue(), 
               mySwitch.getReceivedBitlength(), 
               mySwitch.getReceivedDelay(), 
               mySwitch.getReceivedRawdata(),
               mySwitch.getReceivedProtocol());
        mySwitch.resetAvailable();
      }
    }
    
  • 공유기 포트포워딩을 통한 원격제어(with 3G/LTE/4G, PC)
  • 홈키퍼 로봇은 서버 IP 2개를 사용하는데, 이것들을 포트포워드하여 외부네트워크에 개방하고, DDNS설정으로 IP 모두를 대표하는 도메인을 정한다. (Port Foward와 DDNS 설정법은 이 사이트에 자세히 설명되어 있다.) 192.168.0.24와 같은 IP 숫자보다는 kimch.iptime.org와 같은 도메인 이름이 유의미하여 기억하기 쉽지 않을까? 앞으로는 IP 주소가 아니라 그 도메인으로 접속할 수 있다.

    서버 운영

    (위에서도 말했듯) 홈키퍼 로봇엔 2대의 서버가 운영된다. IP 카메라 서버와 ESP8266 서버가 그것이다.

  • IP 카메라 서버
  • 타인이 만들어 무료로 공유한 IP WebCam을 이용한다. 서버 프로그램을 DIY하는 것이 Maker의 도리겠지만 기존 것을 자유롭게 '공유'하는 것도 합당하는 판단이다. 서버 프로그램 코딩과 관련된 문서들이 여기저기 산재했으니 필요하면 구글링하라.

    IP WebCam은 서버 프로그램이다. 안드로이드 기기에 설치 및 실행되면 바로 서버가 작동한다. 서버 주소는 192.168.0.24:8080과 같은 형식을 갖는다.
    서버는 주인이고 클라이언트는 손님이다. 서버는 구글 홈이고 클라이언트는 구글 크롬(또는 익스플로러)이다. 웹브라우저에서 구글 홈 주소(http://www.goole.com)에 접속하면 간단한 홈페이지를 보내 준다. IP WebCam 서버가 실행된 다음 (하단에 나타나는) IP 주소로 접속하면 서버 홈페이지를 나에게 보내준다. 손님이 어떤 음식을 주문하면 음식점 주인은 그것을 내어 줘야한다는 논리다. 거부하기 어렵다. (가끔 음식점이 문을 닫아 그렇게 하지 못하는 경우도 있지만)
    한번 시도해보라. IP WebCam 서버에서 보내주는 영상(스트림)을 쉽게 볼 수 있으리라.

  • ESP8266 Nod MCU Lua 서버
  • ESP8266 서버는 ESP8266 Nod MCU Lua(WIFI 통신 모듈)를 이용하는 것으로 실내 환경 상태(온도, 습도, 조도, 먼지)를 감지하고 로봇 바퀴를 구동하며, IP 카메라를 회전시키는 것과 전자기기를 On/Off하는 역할을 맡는다.

    ESP8266 Nod MCU Lua 서버 및 웹소켓 프로그래밍

  • ESP8266 Nod MCU Lua 모듈을 파헤치다
  • ESP8266 Nod MCU Lua를 웹서버로!
    • 시나리오

    • 아두이노 IDE 설정

    • Lua 세계의 Hello world : LED 켜기

    • 웹서버 프로그래밍
  • ESP8266 Nod MCU Lua 서버에 웹소켓(WebSocket)을 더하다
    • 시나리오

      웹서버에서 한걸음 더 진화된 개념이 웹소켓이다. 필요한 일부분만 갱신 요청하면 기존 웹서버의 '전체화면 갱신'에 따른 시간지연이란 단점이 보완된다.

    • 아두이노 IDE 설정

    • 핵심 코드

    • 웹소켓 프로그래밍

    웹서버에서 한걸음 더 진화된 개념이 웹소켓이다. 필요한 일부분만 갱신 요청하여 기존 웹서버의 '전체화면 갱신'에 따른 시간지연이란 단점을 보완했다.

  • ESP8266 Nod MCU Lua와 아두이노 사이에 시리얼로 통신하다
  • Lua 보드는 3.3V로 운영되기에 기존 모터쉴드 보드 등과의 연동이 번거롭다. 물론 Leve shift나 Voltage dividing을 이용하여 그 번거로움을 기꺼이 맛보기도 한다. 또한 Lua는 아두이노에 비해 GPIO핀 수가 적다. 아두이노와의 시리얼 통신이 필요한 이유들이다.

    • 아두이노 측 코드
    #include "SoftwareSerial.h"
    /* 
     * SoftwareSerial :
     * 하드웨어 시리얼(D0, D1)은 PC와 아두이노 사이 Usb 시리얼 케이블 연결을 위해 사용하므로
     * 핀 두 개를 새로운 시리얼 통신을 위해 할당한다. 이것은 새로운 시리얼 포트를 창조하는 것이다.
     * 아두이노 핀 수가 허락하는 한 더 많은 New 시리얼 라인을 창조해낼 수 있다. 
    */
    SoftwareSerial ESPserial(2, 3); // RX, TX
    void setup()
    {
      Serial.begin(9600); //PC와의 시리얼 통신(D0, D1)
      ESPserial.begin(9600); //ESP8266 웹소켓과의 시리얼 통신(D2, D3)
    }
    
    int received = 0;
    void loop()
    {
      // listen for communication from the ESP8266 and then write it to the serial monitor
    
      if (ESPserial.available()) {//ESP8266에서 아두이노에게 보내는 데이타가 있는가?
        received = ESPserial.read();//(있을 경우) 그것을 읽어 received에 저장
      }
      /*
       * ESP8266 서버에서 데이타를 받는 경우인데, ESP8266에서 처리할 수 있는 신호(로직)는 3.3V,
       * 아두이노는 5V로서 서로 수준이 다르다. 이에, 둘 사이 전위차를 없애기 위해 
       * Level shifter나 Voltage divider를 사용한다. 그러나 아두이노가 ESP8266로부터 데이타를 받는 경우는 
       * 이것이 필요없다. 아두이노에서 3.3V 신호를 받으면 HIGH 신호로 인식하기 때문이다. 
       * (아날로그 신호인 경우는 이야기가 달라지지만 여기서는 생략한다.)
      */
      if (Serial.available()) {//아두이노에서 ESP8266에게 보내는 데이타가 있는가?  
        ESPserial.write( Serial.read() ); //(있을 경우) 그것을 읽어 received에 저장
      }
    }
    
    • ESP8266 Nod MCU Lua 측 코드

    • Lua는 GPIO2핀을 TXD1으로 정의한다. 나는 그 핀을 esp8266_SERIAL로 재정의했다. (#define esp8266_SERIAL Serial1)
      참고로 Lua에는 3쌍(정확히는 2와 1/2쌍)의 시리얼 통신 라인이 정의되어 있는데, 그것이 외부 기기 제어를 편리하게 위함인지 나는 확신하지 못한다.
      다음은 esp8266_SERIAL 사용 예로, 구체적인 이해는 구글링에 맡긴다.

    void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght) {
     switch (type) {
        case WStype_DISCONNECTED:
          USE_SERIAL.printf("[%u] Disconnected!\n", num);
          break;
        case WStype_CONNECTED: {
            IPAddress ip = webSocket.remoteIP(num);
            USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
            webSocket.sendTXT(num, "Connected");
          }
          break;
        case WStype_TEXT:
          USE_SERIAL.printf("[%u] get Text: %s\n", num, payload);
          if (payload[0] == '5') { //홈키퍼 로봇 정지
            esp8266_SERIAL.write("0");
          } else if (payload[0] == '2') { //홈키퍼 로봇 전진
            esp8266_SERIAL.write("1");
          } else if (payload[0] == '8') { //홈키퍼 로봇 후진
            esp8266_SERIAL.write("2");
          }  else if (payload[0] == '4') { //홈키퍼 로봇 좌회전
            esp8266_SERIAL.write("3");
          }  else if (payload[0] == '6') { //홈키퍼 로봇 우회전
            esp8266_SERIAL.write("4");
          }
          else if (payload[0] == 'l') { //홈키퍼 로봇 서보 좌
            esp8266_SERIAL.write("5");
          }
          else if (payload[0] == 'c') { //홈키퍼 로봇 서보 중앙
            esp8266_SERIAL.write("6");
          }
          else if (payload[0] == 'r') { //홈키퍼 로봇 서보 우
             esp8266_SERIAL.write("7");
          }
          if (payload[0] == '@') { //온도, 습도 only
            float h = dht.readHumidity();
            float t = dht.readTemperature();
            webSocket.sendTXT(num, "온도와 습도 : " + String(h) + " " + String(t));
          } else webSocket.sendTXT(num, payload);
          break;
      }
    }
    

    클라이언트 앱 프로그래밍

  • 시나리오
  • 서버에게 영상을 요청하는 기능을 포함시킨다. 단지 http://192.168.0.24:8080과 같은 주소로 영상을 주문(여기서는 videofeed로)하면 된다. 그러면 IP WebCam 서버가 (자신이 숙주로 있는) 기기에서 확보한 영상 스트림을 안드로이드 앱으로 전송한다.

  • 아, 안드로이드 스튜디오
  • 핵심 API : WebView
  • 앱 프로그래밍
  • public class MainActivity extends AppCompatActivity {
        public String url;
        WebView cameraWebview;
        WebView webView;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            cameraWebview = (WebView)findViewById(R.id.cameraWebview);
            cameraWebview.getSettings().setBuiltInZoomControls(true);       
            url = "http://192.168.0.24:8080/videofeed";
            cameraWebview.loadUrl(url);       
        }
    }
    

    SoftwareSerial 사용법

    
    #include 
    SoftwareSerial mySerial(A1, A0); // RX, TX
    
     . . .
    
    if (mySerial.available()) {
        data = mySerial.read();
        Serial.write(data);
    }