<프로젝트 소개>

버스 정류장에 있는 전광판을 집에서 볼 수 있도록 구현.

외출 직전 굳이 스마트폰을 켜지 않아도 벽에 붙어있는 디스플레이만 보면 뛰어야 하는지, 걸어가도 되는지 알 수 있다.

 -> 향후 시계, 날씨, 미세먼지 확인 기능을 추가하여 외출 도우미로 기능을 업그레이드 할 생각이고, 3D프린터로 외형 제작까지 해볼까 한다.


<간단한 도식화>

그림에선 우노로 써놨는데, 메모리 부족으로 아두이노 메가 2560 모델로 교체했다.




<개발 환경>

Arduino Mega 2560 : 제어 보드

20x4 clcd : 4줄짜리 캐릭터 디스플레이. 배선을 줄이기 위해 I2C가 장착된 모델을 구입.

esp8266 esp-01 : 와이파이 연결을 위해 사용함.

그 외 브레드보드 및 점퍼 와이어







<동작 사진>

20초에 한 번씩 갱신하도록 구현함.

3개 노선의 도착시간을 알 수 있다.


<소스코드>

하루정도 돌려보니 잘 동작한다.

원래는 리퀘스트가 여러개라 GET요청 후 OK사인을 전달받고 그에 맞춰 동작하도록 프로그램을 짜야 프로그램이 견고하겠지만, 파싱의 부담이 커져서 성능이 떨어진다.

그래서 테스트를 해보며 각 리퀘스트 간 시간 간격을 조절했다.

가끔 오류가 뜨기도 한다.

다음맵이나 네이버맵과 비교하면 차이가 날 때도 종종 있는데, 이유는 여러가지가 있는 것 같다.

첫째는 갱신 주기가 20초로 길다는 것.

둘째는 아예 받아오는 API가 다를 수도 있겠다는 것. 실시간으로 요청을 했는데, 포털과 내가 받은 결과값이 다르다는 건 좀 이상하다.

#include <Wire.h> 

#include <LiquidCrystal_I2C.h>


LiquidCrystal_I2C lcd(0x3f,20,4);


#define _SS_MAX_RX_BUFF 512

#define ssid "????????????" // ssid

#define serviceKey String("??????????") // serviceKey

#define password "??????????" // password

#define interval 20000


int cmdSize = 0;

String rcvbuf;

long currentMillis;

long previousMillis = 0;

boolean requestLocker = false;

boolean requestLocker1 = false;

boolean requestLocker2 = true;

String result10_1;

String result111;

String result3;





void setup(void)

{

  Serial.begin(9600);

  Serial1.begin(9600);

  pinMode(7, INPUT_PULLUP);

  pinMode(6, INPUT_PULLUP);

  lcd.init();

  lcd.backlight();

  lcd.setCursor(1,0);

  connectWifi();

}






void loop(void)

{

  while(Serial1.available())

  {

    char c = Serial1.read();

    if(c != NULL){

      if(rcvbuf.length() > 1300)

        rcvbuf = "";

      rcvbuf += c;

      //Serial.write(c);

    }

  }


  if(millis() - previousMillis > interval){

    result111 = parseArrivalTime("111");

    displayArrivalTime(result10_1, 0);

    displayArrivalTime(result111, 1);

    displayArrivalTime(result3, 2);

    requestLocker2 = true;

  }

  else if(millis() - previousMillis > interval-4000 && requestLocker)

  {

    result10_1 = parseArrivalTime("10-1");

    requestArrivalTime("207000012", "207000125");

    requestLocker = false;

  }

  else if(millis() - previousMillis > interval-8000 && requestLocker1)

  {

    result3 = parseArrivalTime("3");

    requestArrivalTime("207000004", "207000125");

    requestLocker1 = false;

    requestLocker = true;

  }

  else if(millis() - previousMillis > interval-12000 && requestLocker2)

  {

    requestArrivalTime("207000019", "207000125");

    requestLocker2 = false;

    requestLocker1 = true;

  }

}





void displayArrivalTime(String result, int displayLine)

{

  lcd.setCursor(0,displayLine);

  lcd.print("                    ");

  lcd.setCursor(0,displayLine);

  lcd.print(result);

  Serial.println(result);

}





String parseArrivalTime(String busNum)

{

  previousMillis = millis();

  int startIndex = rcvbuf.indexOf("<predictTime1>");

  if(startIndex == -1){

    return busNum + " : no bus";

  }

  int strLength = strlen("<predictTime1>");

  int endIndex = rcvbuf.indexOf("<", startIndex + strLength);

  String predictTime1 = rcvbuf.substring(startIndex+strLength,endIndex);


  startIndex = rcvbuf.indexOf("<predictTime2>");

  strLength = strlen("<predictTime2>");

  endIndex = rcvbuf.indexOf("<", startIndex + strLength);

  String predictTime2 = rcvbuf.substring(startIndex+strLength,endIndex);

  if(predictTime2.equals("")){

    return busNum + " : " + predictTime1 + "min";

  }

  Serial.println("===========");

  Serial.println(predictTime1);

  Serial.println(predictTime2);

  Serial.println("===========");


  rcvbuf = "";


  return busNum + " : " + predictTime1 + "min, " + predictTime2 + "min";

}





void requestArrivalTime(String routeId, String stationId)

{

  String str = "GET /ws/rest/busarrivalservice?serviceKey=" + serviceKey + "&routeId=";

  str.concat(routeId);

  str.concat("&stationId=");

  str.concat(stationId);

  str.concat(" HTTP/1.1\r\nHost:openapi.gbis.go.kr\r\nConnection: close\r\n\r\n");

  

  cmdSize = str.length();

  Serial1.println("AT+CIPSTART=\"TCP\",\"openapi.gbis.go.kr\",80");

  delay(500);

  Serial1.print("AT+CIPSEND=");

  delay(500);

  Serial1.println(cmdSize);

  delay(500);

  Serial1.println(str);

}






void connectWifi()

{

  Serial1.println("AT+CWMODE=1");

  delay(500);

  while(Serial1.available()){

    Serial.write(Serial1.read());

  }

  Serial1.println("AT+CIPMUX=0");

  delay(500);

  while(Serial1.available()){

    Serial.write(Serial1.read());

  }

  Serial1.print("AT+CWJAP=\"");

  Serial1.print(ssid);

  Serial1.print("\",\"");

  Serial1.print(password);

  Serial1.println("\"");

  delay(6000);

  while(Serial1.available()){

    Serial.write(Serial1.read());

  }

  previousMillis = millis();

}



<향후 계획>

테스트를 해보며 알게 됐는데, ESP8266이 의외로 성능이 좋은 것 같다.

제어의 중심이 되는 아두이노 우노로는 처리가 잘 안되는 작업들도 처리가 가능할 만큼 빠르다. 스펙을 찾아보니 클럭도 빠르고, 메모리도 크다. 굿굿...

하지만 ESP8266시리즈의 ESP-01모델은 제어할 수 있는 핀 자체가 없다. 그래서 제어의 메인으로 활약하지는 못하고, 통신 기능만을 수행하는 모듈로밖에 사용할 수 없었다.


그런데 단독으로 사용할 수 있는 방법도 있다.

ESP8266이 장착된 NodeMCU 보드를 사용하거나, ESP8266시리즈의 ESP-12E(또는 F)모델을 사용하면 아두이노 없이도 독립적으로 동작하게 만들 수 있다.

문제는 NodeMCU는 익숙하지 않은 Lua를 사용하고, ESP-12E(F)는 핀 사이즈가 작아서 확장 기판을 써야 하는데, 납땜을 해야 한다.

어차피 인두기도 사야 하는데, 얼른 인두기부터 알아봐야겠다.


2017.09.18 추가

Wemos D1 모델을 알게 됐다. 이 모델로 시제품을 다시 제작해 봐야겠다.

'Project > Easy Go' 카테고리의 다른 글

버스 정류장 전광판  (0) 2017.09.09