Arduino :: Lecture & TIPs

[Arduino] 버스 도착 알리미 제작기

프로젝트 소개

집 앞의 버스 정류장에 도착하는 버스 정보를 집에서 확인 할 수 있도록 구현했습니다.

외출 직전 스마트폰을 켜고, 잠금해제를 하지 않더라도 탁상 시계로 만들어 책상 위의 시계를 통해 도착 시간이 얼마나 되는지를 확인하고, 준비를 어느 정도 해야 하는지, 뛰어가야 하는지, 걸어가도 되는지 여부를 알 수 있습니다.

준비물

NodeMCU ESP8266-12E
0.96인치 SSD1306 I2C OLED 디스플레이

간단한 도식화

개발 환경

Arduino IDE
NodeMCU ESP8266-12E : 제어 보드
0.96인치 SSD1306 I2C OLED 디스플레이

완성 데모

공공데이터포털에서 제공하는 XML 형식의 실시간 버스 조회 데이터를 20초에 한번씩 내려받아 정제한 후 디스플레이에 표시되도록 구현했습니다. 버스 정류소에 경유하는 버스 중 주로 이용하는 세 가지 버스 노선 번호의 도착시간을 알 수 있도록 했습니다. 이를 응용하여 특정 시간이 경과하면 화면이 넘어가도록 해서 전체 버스 노선의 정보를 알 수 있도록 구현해보아도 좋을 것 같습니다.

소스 코드

며칠간 구동시켜서 결과값을 비교해보니 잘 동작하는 것을 확인할 수 있었습니다.

원칙적으로는 송신하는 Request 가 여러 개인 만큼, 지정된 리소스를 요청 한 후 결과값을 전달 받고 그에 맞추어 동작하도록 구현하는 것이 원칙입니다만, 아직까지 성능이 좋지 않은 Arduino 의 경우 Parsing 까지 구현하기에는 성능의 부담이 커지는 것이 사실입니다.

그래서 직접 테스트를 해보며 리소스 요청에 대한 시간 간격을 조절했습니다.

Kakao Map 데이터와 경기도버스정보 (http://www.gbis.go.kr) 웹 사이트의 데이터와도 비교해보았는데, 상당히 유사한 결과를 보여주며 어느 정도 신뢰할 수 있는 결과 값이 표시됨을 확인할 수 있었습니다.

BusInfoSystem.ino

#define serviceKey String ("[GBIS Read API Key]")

//API Key

char* ssid = "[Wi-Fi SSID]";
char* password = "[Wi-Fi Password]";


const int GBISUPD_INTERVAL = 20000;
const char* host = "openapi.gbis.go.kr";

const int httpPort = 80;

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
WiFiClient client;

int cmdSize = 0;
String busstopID = "224000481"; // Siheung City Hall

String rcvbuf;
long currentMillis;
long previousMillis = 0;
boolean wifi_ready;
boolean requestLocker = false;
boolean requestLocker1 = false;
boolean requestLocker2 = true;
String result_26_1;
String result_63;
String result_11_3;

void setup() {
  Serial.begin(9600);
  setup_oled();
  wifi_ready = connect_ap(ssid, password);
  if (!wifi_ready){
    nowifi_oled();
  }
}


void loop(){
  while(client.available())
  {
    char c = client.read();
    if(c != NULL){
      if(rcvbuf.length() > 1300)
        rcvbuf = "";
      rcvbuf += c;
      //Serial.write(c);
      Serial.print(c);
    }
  }

  if(millis() - previousMillis > GBISUPD_INTERVAL){
    result_26_1 = parseArrivalTime("26-1");
    do_oled(0, 11, result_26_1);
    do_oled(0, 22, result_63);
    do_oled(0, 33, result_11_3);
    requestLocker2 = true;
  }

  else if(millis() - previousMillis > GBISUPD_INTERVAL - 4000 && requestLocker)
  {
    result_63 = parseArrivalTime("63");
    
    // 26-1
    requestArrivalTime("224000027", "224000481");  
    requestLocker = false;
  }
  
  else if(millis() - previousMillis > GBISUPD_INTERVAL - 8000 && requestLocker1)
  {
    result_11_3 = parseArrivalTime("11-3");

    // 63
    requestArrivalTime("224000010", "224000481");  
    requestLocker1 = false;
    requestLocker = true;
  }

  else if(millis() - previousMillis > GBISUPD_INTERVAL - 12000 && requestLocker2)
  {

    // 11-3
    requestArrivalTime("213000014", "224000481");
    requestLocker2 = false;
    requestLocker1 = true;
  }
 }

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");

  if(client.connect(host, httpPort)){
  Serial.println("connected");
  Serial.print(str);
  client.print(str);
  client.println();

  cmdSize = str.length();

  client.println("AT+CIPSTART=\"TCP\",\"openapi.gbis.go.kr\",80");
  delay(500);
  client.print("AT+CIPSEND=");
  delay(500);
  client.println(cmdSize);
  delay(500);
  client.println(str);
  }
  else {
    Serial.println("Connection Failed: ");
    return;
  }
}


String parseArrivalTime(String busNum)
{
  previousMillis = millis();
  int startIndex = rcvbuf.indexOf("<predictTime1>");
  if(startIndex == -1){ 
    rcvbuf = "";
    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("") && predictTime1 == "1"){
    return busNum + " : " + predictTime1 + "min";
  }
  else if(predictTime2.equals("") && predictTime1 != "1"){
  return busNum + " : " + predictTime1 + "mins";
  }
 
  Serial.println("===========");
  Serial.println(predictTime1);
  Serial.println(predictTime2);
  Serial.println("===========");
  rcvbuf = "";
  
  if (predictTime1 != "1" && predictTime2 != "1") { 
  return busNum + " :     " + predictTime1 + "mins, " + predictTime2 + "mins";
  }
  else if (predictTime1 == "1" && predictTime2 != "1"){
  return busNum + " :     " + predictTime1 + "min, " + predictTime2 + "mins";
  }
  else if (predictTime1 != "1" && predictTime2 == "1"){
  return busNum + " :     " + predictTime1 + "mins, " + predictTime2 + "min";
  }  
}

OLED.ino 

#include "SSD1306.h"
SSD1306 display(0x3c, D3, D4);

void setup_oled(){
  display.init();
  display.clear();  
  display.flipScreenVertically();
  display.setFont(ArialMT_Plain_10);
  display.drawString(0,13, "initialize OLED...");  
  display.display();
}

void wifi_oled(int cnt) {
  display.clear();  
  display.setFont(ArialMT_Plain_10);
  display.drawString(0,13, "waiting wifi..."); 
  display.drawString(0,24, String("ssid : ")+String(ssid));
  display.drawString(0,35, String("password : ")+ String(password)); 
  display.drawString(0,53, String(cnt));
  display.display();  
}

void nowifi_oled() {
  display.clear();  
  display.setFont(ArialMT_Plain_10);
  display.drawString(0,0, "NO WIFI: skip wifi..."); 
  display.display();  
}

void do_oled(int16_t x, int16_t y, String result) {
  display.clear();
  display.setFont(ArialMT_Plain_10);
  display.drawString(0,0, "Bus No.");
  display.drawString(64,0, "Time Left");
  display.setFont(ArialMT_Plain_10);
  display.drawString(0,13, result_26_1);
  display.drawString(0,29, result_63);
  display.drawString(0,45, result_11_3);
  display.display();
  Serial.println(result);
}

Wifi.ino

#include <ESP8266WiFi.h>

boolean connect_ap(char* ssid, char* password) {
  int count = 60;                                 // 최대 60 회 연결 시도 중 wifi 연결하면 성공, 아니면 실패
  Serial.println();
  Serial.print("connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    wifi_oled(count);
    if (!count--) {
      Serial.print("\nNO WIFI");
      return(false);
    }
  }
  previousMillis = millis();
  Serial.print("\n Got WiFi, IP address: ");
  Serial.println(WiFi.localIP()); 
  Serial.write(Serial.read());
  return(true);
}

ESP8266 시리즈의 ESP-12E 모델을 사용하여 Arduino 없이 독립적으로 동작하게 만들어 보았습니다. 생각보다 성능도 좋고 Arduino Uno 에 비해 처리가 잘 안되는 작업들도 처리가 가능할 만큼 빠릅니다.

최종 결과값 비교 및 검증

버스 시간 안내 화면에 표시된 버스 도착 까지의 잔여 시간
직접 만든 탁상 시계에 표시된 버스 도착 까지의 잔여 시간 결과 값.

비교해보시면 상당히 신뢰도 있는 결과 값을 보여주는 것을 확인하실 수 있습니다.
감사합니다.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: