Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
Mike Heavers
Published © MIT

Making a Bus Schedule Sign w/ ESP8266, Arduino & NeoPixels

A simple prototype to query a web API with ESP8266, get the time until the next bus arrives, and display it on a battery powered LED sign.

IntermediateFull instructions provided20 hours2,070
Making a Bus Schedule Sign w/ ESP8266, Arduino & NeoPixels

Things used in this project

Hardware components

SparkFun ESP8266 Thing - Dev Board
SparkFun ESP8266 Thing - Dev Board
×1
Adafruit Neopixel White LED Strip
×1
Li-Ion Battery 1000mAh
Li-Ion Battery 1000mAh
×1
Adafruit 3XAA Battery Holder
×1

Software apps and online services

Arduino IDE
Arduino IDE
Portland Trimet API
Adobe Illustrator

Hand tools and fabrication machines

Crescent Collage Board Black 8x10
Balsa Wood
Laser cutter (generic)
Laser cutter (generic)

Story

Read more

Schematics

LED Hookup Diagram

How to connect the Neopixel LEDs to the ESP8266

Code

LED Test Code

Arduino
Tests the lighting for the LED strip
#include <Adafruit_NeoPixel.h>
 
//#define PIN      15
#define PIN      5
#define N_LEDS 9

int ledsToShow = 5;
int brightness = 100;
bool showSingleLight = false;
 
Adafruit_NeoPixel strip = Adafruit_NeoPixel(N_LEDS, PIN, NEO_GRB + NEO_KHZ800);
 
void setup() {
  strip.begin();
  strip.setBrightness(brightness);
  for (uint16_t i = 0; i < N_LEDS; i++) {
    if (showSingleLight){
      if (i==ledsToShow){
        strip.setPixelColor(i, strip.Color(255, 255, 255));
        strip.show();
      } else {
        strip.setPixelColor(i, strip.Color(0, 0, 0));
        strip.show();
      }
    } else {
      if (i<ledsToShow){
        strip.setPixelColor(i, strip.Color(255, 255, 255));
        strip.show();
      } else {
        strip.setPixelColor(i, strip.Color(0, 0, 0));
        strip.show();
      }
    }
    
  }
}

void loop(){
  
}

Full code

Arduino
The final code with WiFi connection, data retrieval, and LED lighting logic
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <TimeLib.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <Adafruit_NeoPixel.h>


//WIFI
const char* ssid = "[YOUR_NETWORK_NAME]";
const char* password = "[YOUR_NETWORK_PASSWORD]";


//API
const char* host = "developer.trimet.org";
String url = "/ws/V1/arrivals/locIDs/5901/appID/[YOUR_APP_ID]";
const int httpsPort = 443;
WiFiClientSecure client;


//TIME
#define NTP_OFFSET   -8 * 60 * 60 //Portland is UTC - 8
#define NTP_ADDRESS  "pool.ntp.org" //Default NTP Address
#define NTP_INTERVAL 30 * 1000    // how often to update in miliseconds
WiFiUDP ntpUDP;
//NTPClient timeClient(ntpUDP,NTP_ADDRESS,NTP_OFFSET, NTP_INTERVAL); //use the custom ntp params
NTPClient timeClient(ntpUDP); //use the ntp defaults

//LIGHTS
#define PIN      15
//#define PIN 5
#define N_LEDS 9
Adafruit_NeoPixel strip = Adafruit_NeoPixel(N_LEDS, PIN, NEO_GRB + NEO_KHZ800);
int stripBrightness = 20;
bool showSingleLight = false;

void lightsOnImmediate() { //turn lights on with no delay
  for (uint16_t i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, strip.Color(255, 255, 255));
    strip.show();
  }
}

void lightsOffImmediate() {
  for (uint16_t i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, strip.Color(0, 0, 0));
    strip.show();
  }
}

void activateLights(int numLights) {
  Serial.println("activating lights");
  Serial.println(numLights);

  if (showSingleLight){
    for (uint16_t i = 0; i < numLights; i++) {
      if (i==numLights){
        strip.setPixelColor(i, strip.Color(255, 255, 255));
        strip.show();
      } else {
        strip.setPixelColor(i, strip.Color(0, 0, 0));
        strip.show();
      }
      
    }
  } else {
    for (uint16_t i = 0; i < numLights; i++) {
      strip.setPixelColor(i, strip.Color(255, 255, 255));
      strip.show();
    }
  }
  
}

//STATE
time_t timeEstimated; //when the next bus will come
time_t timeNow; //what time it is now
time_t timeDiff; //difference between now and estimated arrival

bool awaitingArrivals = true;
bool awaitingCurrentTime = true;
bool arrivalsRequested = false;

//CONFIG
int pollDelay = 30000; //time between each retrieval
int retryWifiDelay = 1000;
int noEstimatedTimeDelay = 30000;

void setup() {
  Serial.begin(115200);
  strip.begin();
  strip.setBrightness(stripBrightness);
  lightsOffImmediate();

  //start time client to get real time
  timeClient.begin(); //start the thing that tells us the current time
  connectToWifi();
}

void connectToWifi() {

  //connect to wifi
  Serial.print("connecting to ");
  Serial.println(ssid);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) { //WAITING FOR WIFI
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

}

void getArrivals() {

  // Use WiFiClientSecure class to create TLS connection

  Serial.print("connecting to ");
  Serial.println(host);
  if (!client.connect(host, httpsPort)) {
    Serial.println("connection failed");
    delay(retryWifiDelay);
    //return;
    getArrivals();
    return;
  }


  //Query the API
  Serial.print("requesting URL: ");
  Serial.println(url);

  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" +
               "User-Agent: BuildFailureDetectorESP8266\r\n" +
               "Connection: close\r\n\r\n");

  Serial.println("request sent");
  while (client.connected()) {
    String line = client.readStringUntil('\n');
    if (line == "\r") {
      Serial.println("headers received");
      break;
    }
  }


  String matchString = "";
  while (client.available()) {
    String line = client.readStringUntil('\r');
    if (line.endsWith("</resultSet>")) { //this signals the end of the response from XML API
      matchString = line;
    }
  }

  Serial.println(matchString); //log result xml response from server
  int lineEstimated = matchString.indexOf("estimated=");

  if (lineEstimated == -1) {
    Serial.println("NO ESTIMATED TIME!");
    turnSignOff();
    delay(noEstimatedTimeDelay);
    getArrivals();
    return;
  }

  int lineStart = lineEstimated + 11; //exclude `estimated="`
  int lineEnd = matchString.indexOf(" fullSign") - 4; //exclude ending zeroes and quote ( `000"` )
  String timeString = matchString.substring(lineStart, lineEnd);
  int timeInt = timeString.toInt();
  Serial.println("estimated arrival timestamp: ");
  Serial.println(timeInt);
  timeEstimated = timeInt; //assign this to a timestamp

  awaitingArrivals = false;

  client.stop();

}



void loop() {

  if (awaitingArrivals) {
    if (!arrivalsRequested) {
      arrivalsRequested = true;
      getArrivals();
    }
  } else {
    if (awaitingCurrentTime) {
      awaitingCurrentTime = false;
      getCurrentTime();
    }
  }
}

void getCurrentTime() {
  //get current time
  timeClient.update();
  timeNow = timeClient.getEpochTime();
  Serial.println("timeclient epoch time: ");
  Serial.println(timeNow);

  //get epoch time to next bus arrival
  timeDiff = timeEstimated - timeNow;
  showDuration(timeDiff);
}

void showDuration(time_t duration)
{

  int numMinutes;
  int numSeconds;

  // prints the duration in days, hours, minutes and seconds
  if (duration >= SECS_PER_DAY) {
    Serial.print(duration / SECS_PER_DAY);
    Serial.print(" day(s) ");
    duration = duration % SECS_PER_DAY;
  }
  if (duration >= SECS_PER_HOUR) {
    Serial.print(duration / SECS_PER_HOUR);
    Serial.print(" hour(s) ");
    duration = duration % SECS_PER_HOUR;
  }
  if (duration >= SECS_PER_MIN) {
    numMinutes = duration / SECS_PER_MIN;
    Serial.print(numMinutes);
    Serial.print(" minute(s) ");
    duration = duration % SECS_PER_MIN;
    numSeconds = duration;
  }
  Serial.print(duration);
  Serial.print(" second(s) ");

  if (numMinutes < 1) {
    turnSignOff();
  } else {
    showNumber(numMinutes);
  }

  resetCycle();

}

void turnSignOff() {
  lightsOffImmediate();
}

void showNumber(int numMinutes) {
  if (numMinutes > 9){
    numMinutes = 9;
  }
  activateLights(numMinutes);
}

void resetCycle() {
  awaitingArrivals = true;
  awaitingCurrentTime = true;
  arrivalsRequested = false;

  Serial.println("hold display...");
  delay(pollDelay);
  Serial.println("poll again");
}

Credits

Mike Heavers

Mike Heavers

2 projects • 4 followers

Comments