버스 정류장 전광판 Project/Easy Go2017. 9. 9. 23:03
<프로젝트 소개>
버스 정류장에 있는 전광판을 집에서 볼 수 있도록 구현.
외출 직전 굳이 스마트폰을 켜지 않아도 벽에 붙어있는 디스플레이만 보면 뛰어야 하는지, 걸어가도 되는지 알 수 있다.
-> 향후 시계, 날씨, 미세먼지 확인 기능을 추가하여 외출 도우미로 기능을 업그레이드 할 생각이고, 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 모델을 알게 됐다. 이 모델로 시제품을 다시 제작해 봐야겠다.