Hardware:
ESP8266-01
LED Belt Buckle
Voltage Regulator Module
Description:
So a friend of mine gave me an LED Belt Buckle.
It was always an intention of mine to WiFi enable it so that maybe people could send a message to it and it would display that message.
I decided to instead have it display Tweets where it can display tweets of a particular search term, which still allows people to send it a custom message, albeit through Twitter.
Since its WiFi and gets its power from batteries it makes a nice stand alone Twitter message display system, or even a small unit that can be used for events, performances and exhibitions providing audience interaction.
The circuit is built using an ESP8266-01 WiFi module. These are very inexpensive and can be programmed using the Arduino IDE.
The ESP-01 is the smallest and cheapest of the ESP range and only has minimal input and output options. Luckily we only require two outputs to control the buttons on the Belt Buckle so it will service for this project. The Digital Outputs simply go LOW to simulate a press of the two program buttons which are ENTER and PID on my Buckle. Using these buttons manually to program in messages takes a lot of time since each letter of the alphabet has to be scrolled through one by one however when programming using the ESP, messages can be entered much faster and more importantly automatically.
The ESP chips contain a Web Server and Access Point so it can both make Web requests and act as an Access Point to allow configuration via WiFi.
The Access Point Name and Password are pre programmed into the device. When you connect you need to browse to its IP Address and you will then be shown a Configuration Page where you enter in the Twitter Search Term. This can be your Twitter account @name which will result in Tweets directed at you to be displayed or it can be a trending topic or hashtag.
Since the unit is designed to be portable it made sense to include a configuration page where you enter the WiFi Internet Settings. You can then change these easily when you move it to other locations.
There are many versions of these buckles and while most work in a similar way they often have different character sets and programming patterns, thus my code may not be transferable to other buckles.
Unfortunately my Belt Buckle did not have an @ symbol and since its only four characters wide it makes reading messages a little awkward. It is however a cheap and fun project to make since it made use of items I already had.
It forms the backbone of an enhanced device that could utilise a larger LED Display.
It also gave me valuable experience using these very cheap, small and versatile ESP WiFi units.
Circuit:
The circuit consists of the WiFi module, the Belt Buckle and a variable voltage regulator module.
Power
Since the Belt Buckle was originally powered by two 3V Coin Cells it required a 6V Supply which is fed direct from the batteries. A variable voltage regulator module was used to step down the 6V to 3.3V for the ESP.
The ESP draws about 800mA so C Cells were used to ensure the unit could run for many hours on a full charge.
Control
My Belt Buckle program buttons simply shorted the program input pins to 0V so by directly connecting a single wire to one side of each of the two buttons and taking this line LOW simulates a button press as all circuits use a common GND.
Program Port
Foreseeing the requirement to modify the ESP program I decided to make it possible to connect a programming lead. This was achieved by splitting in half a Telephone lead joining connector. This produces two sockets, one is used to connect to the ESP Program pins and the other half has three wires that connect it to my USB ESP-01 Programmer. I can then simply program it by connecting a standard telephone lead between programmer and unit.
Programs:
The programs consist of two parts, the Arduino Code which is uploaded to the ESP8266 and the Server Code that is placed on a web server.
You will obviously need some webspace where you can upload the PHP script.
This script or webpage is accessed by the ESP with a simple web request.
The script then connects to the Twitter API to retrieve the Tweets. You will thus also require a Twitter App API Key.
This method is required since the Twitter API requires authentication by oAuth and the ESP-01 is unable to make web connections of this type directly. The PHP code thus conducts these calls when requested to do so and then passes the Tweet back to the ESP.
I won’t go into how to program the ESP units as this information can already be found from a number of sources on the web. All I will say is they can be a little awkward and temperamental since they do not have an onboard USB program port.
I should add that the server script dose not check for any kind of authentication from the ESP device and thus if the URL was known to others then they could use it to get Tweets themselves from any device they choose. This could result in you hitting Twitter API request limits.
Arduino Code
// LED BELT BUCKLE PROGRAMMEER V5
// FOR ESP8266-01
// BUGS
// FIND A WAY TO DETECT WHEN WIFI DISSCONNECTS AS WL_CONNECTED NOT CHANGING
// Show when not connected
// checking Wi-Fi Config page shows connected to last connected AP
// Display too small
// API URL – This will be where you place this code
// yourdirectoryname/_espGetTweets.php?q=searchterm
// default search term = London
// TCP Client
#include
WiFiClient client;
// ESP Webserver
#include
ESP8266WebServer webServer(80);
#include
// ESP AP Server
// password can be blank
// must be min 8 chars or will not work
const char *APName = "ESPTWIT1";
const char *APPass = "ED8F5GA2";
// Timer
#include
// enter a default message
String defaultMsg = "TWEET ME AT BRIANMOREAU ";
String searchTerm = "London";
// domain name of server where this twitter API script is
char server[] = "yourwebdomainname.com";
uint8_t httpPort = 80;
// wi-fi config
// ESP Auto stores last connection
// so can be blank if it’s previously connected
// as won’t call connect unless not connected
// will need a not connected LED MAYBE
// wifi config vars
char ssid[32];
char pass[32];
String SSIDX;
String PASSX;
// http://www.esp8266.com/wiki/doku.php?id=esp8266_gpio_pin_allocations
int ENTER = 2; // GPIO2
int PID = 0; // GPIO0
// GPIO1 1 Oboard Blue LED + TX
int faultLED = 1;
int faultState = 0;
// in code programs are num 0 - 5
uint8_t programNum = 0;
String text;
uint8_t x;
bool connectFlag = false;
uint8_t giveup = 1;
Timer getTweetsTimer;
uint8_t getTweetsTimerID;
Timer cycleDisplayTimer;
uint8_t cycleDisplayTimerID;
uint8_t numberOfTweets = 0;
uint8_t currentDisplayID = 0;
void setup() {
delay(3000);
// get stored wifi data
SSIDX = WiFi.SSID();
PASSX = WiFi.psk();
// store in char array
SSIDX.toCharArray(ssid,SSIDX.length()+1);
PASSX.toCharArray(pass,PASSX.length()+1);
// pin config
pinMode(ENTER, OUTPUT);
pinMode(PID, OUTPUT);
pinMode(faultLED, OUTPUT);
digitalWrite(ENTER, HIGH);
digitalWrite(PID, HIGH);
// turn fault led off
digitalWrite(faultLED, HIGH);
// start the access point server
WiFi.softAP(APName, APPass);
// define web link functions
webServer.on("/", doRoot);
webServer.on("/setup", doSetup);
webServer.on("/save", doSave);
// start server
webServer.begin();
// program in default msg
text = defaultMsg;
//programNum = 1;
programTweet();
numberOfTweets = 0;
currentDisplayID = 1;
delay(1000);
// CHANGE MESSAGE EVERY 1 MIN
cycleDisplayTimerID = cycleDisplayTimer.every(60000, cycleDisplay);
// GET TWEETS EVERY 10 MIN
getTweetsTimerID = getTweetsTimer.every(600000, conTCP);
}
void loop() {
// check if connected to wifi
// exp8266 will automatically connect using last settings as they are automatically stored
// flash fault LED for visual when wifi is bust
// if wi-fi turns off WL_CONNECTED state doesent appear to change
if (giveup <= 4) { // try 5 times
if (WiFi.status() != WL_CONNECTED) {
while (WiFi.status() != WL_CONNECTED) {
//Serial.println("Connecting to wifi ");
//Serial.println(ssid);
WiFi.begin(ssid, pass);
delay(5000);
giveup++;
if (giveup > 5) break;
} // end while not connected
} else {
// reset the giveup count as connected
giveup = 1;
} // end if not connected to wifi
} // end giveup
// keep checking to see if someone connects to device
webServer.handleClient();
// update timers
getTweetsTimer.update();
cycleDisplayTimer.update();
// get data from buffer if connected
if (connectFlag == true) {
if (client.available() > 0) {
if (client.find("BEGINTWEET")) {
String thisTweetTxt = client.readStringUntil('\n');
thisTweetTxt.toUpperCase();
text = thisTweetTxt;
//Serial.println(text);
programTweet();
//client.stop();
connectFlag = false;
} // end if find
} // end if available
} // end if connect = true
// flash fault LED if cant connect
if (giveup > 5) {
if (faultState == 0) {
digitalWrite(faultLED, LOW); // ON
faultState = 1;
delay(500);
} else {
digitalWrite(faultLED, HIGH); // OFF
faultState = 0;
delay(500);
}
}
delay(100);
} // end loop
void cycleDisplay() {
if (numberOfTweets == 0) {
// do nothing
// keep displaying intro message
} else if (numberOfTweets >= currentDisplayID) {
// advance display message by 1
digitalWrite(PID, LOW);
delay(500);
digitalWrite(PID, HIGH);
delay(500);
if (currentDisplayID <= 5) {
currentDisplayID++;
} else {
// back to main intro message
currentDisplayID = 1;
}
} else if (currentDisplayID > numberOfTweets) {
// advance back to start intro message when all spacews not programmed
int advance = 6 - numberOfTweets;
for (int x = 0; x < advance; x++) {
delay(200);
digitalWrite(PID, LOW);
delay(200);
digitalWrite(PID, HIGH);
if (currentDisplayID <= 5) {
currentDisplayID++;
} else {
// back to main intro message
currentDisplayID = 1;
}
}
}
} // end cycle display
// CONNECT TO SERVER TO GET TWEETS
// will keep trying forever
void conTCP() {
if (client.connect(server, httpPort)) {
// connected to resource
// send data
client.print("GET /apps/esp/_espGetTweets.php?q=");
client.print(searchTerm);
//client.print("&n=");
//client.print(thisTweetID);
client.println(" HTTP/1.1");
client.println("Host: www.brianmoreau.com");
//client.println("Content-Length: " + String(searchTerm.length()));
client.println("User-Agent: ESP8266");
client.println("Connection: close");
client.println();
connectFlag = true;
} else {
// No connection to server
// should maybe keep retrying else waits till next call
} // end if connect
} // end get tweets
void programTweet() {
// select program mode
// disable message change function timer
//cycleDisplayProgramTimer.stop(changeProgramTimerID);
// enter display program mode
program();
delay(500);
selectProgramNum();
// advance program number 1 to 5
if (programNum <= 4) {
programNum++;
} else {
programNum = 1;
}
// update current display id
currentDisplayID = programNum;
// and number of tweets
if (numberOfTweets <= 4) {
numberOfTweets++;
} else {
numberOfTweets = 5;
}
delay(500);
// loop through tweet text and program in each letter
// need to know display max message length
// - 2 to get rid of ending in HTML
for (int letters = 0; letters < text.length()-2; letters++) {
// keep checking for server connection
// while programming letters, works OK
webServer.handleClient();
char c = text.charAt(letters);
// if space
if (c == 32) {
selectLetter(26);
}
// if letter A - Z , A 65 - Z 90
else if (c > 64 && c < 91) {
int l = c - 65;
selectLetter(l);
}
// if number 0 -9 0 48 - 9 57
else if (c > 47 && c < 58) {
selectLetter(c);
}
} // end for
// fin selecting letters
program();
// force the message to be displayed for at least 30 seconds
delay(30000);
} // end program tweet
// select program mode , also acts to deselect program mode or fin entering text
void program() {
digitalWrite(ENTER, LOW);
digitalWrite(PID, LOW);
delay(1000);
digitalWrite(PID, HIGH);
delay(1000);
digitalWrite(ENTER, HIGH);
delay(1000);
}
// program numbers 0 to 5
void selectProgramNum() {
for (x = 0; x <= programNum - 1; x++) {
digitalWrite(PID, LOW);
delay(500);
digitalWrite(PID, HIGH);
delay(500);
}
// select it
select();
}
// select letter
void selectLetter(int lt) {
for (x = 0; x < lt; x++) {
delay(100);
digitalWrite(PID, LOW);
delay(100); // 100 ok
digitalWrite(PID, HIGH);
}
// select it
delay(200);
select();
}
void select() {
digitalWrite(ENTER, LOW);
delay(200); // 200 OK
digitalWrite(ENTER, HIGH);
delay(200); // 200 OK
}
// triggers when ROOT is browsed
void doRoot() {
// get variables from connection and make it the twitter search term
String var = webServer.arg (0);
if (var != "") {
searchTerm = var;
}
// write HTML
String html;
// make it mobile friendly
html = "<meta name='viewport' content='width=device-width, initial-scale=1'>";
html += "<h2>Twitter LED Display</h2>";
html += "<p>Search Term: " + searchTerm + "<BR>";
html += "<FORM ACTION='/'>"; // ROOT
html += "<INPUT TYPE='TEXT' NAME='searchText'>";
html += "<INPUT TYPE='SUBMIT'></p>";
html += "</FORM>";
html += "<BR><BR>";
html += "<a href='/setup'>WiFi Setup</a>";
webServer.send(200, "text/html", html);
}
void doSetup() {
// get number of access points detected
int n = WiFi.scanNetworks();
String html;
html = "<meta name='viewport' content='width=device-width, initial-scale=1'>";
html += "<h2>Wi-Fi Config</h2>";
html += "<p>Connected to: " + String(ssid) + "</p>";
html += "<p>STATUS: " + String(WiFi.status()) + "</p>";
html += "<p>Select Access Point to connect to:</p>";
// Print SSID for each network found
html += "<FORM ACTION='/save'>"; //
html += "<SELECT NAME='SSID'>";
for (int i = 0; i < n; ++i) {
html += "<option value='" + WiFi.SSID(i) + "'>" + WiFi.SSID(i) + "</option>";
} // end for
html += "</SELECT><BR><BR>";
html += "Password:<br>";
html += "<INPUT TYPE='TEXT' NAME='PASS'>";
html += "<INPUT TYPE='SUBMIT'>";
html += "</FORM>";
html += "<BR><BR>";
html += "<a href='/'>Back to index</a>";
// need page refresh
webServer.send(200, "text/html", html);
} // end doSetup
void doSave() {
// saves new wi-fi config
// get variables from connection
SSIDX = webServer.arg (0);
PASSX = webServer.arg (1);
// set valuse as ssid and pass
SSIDX.toCharArray(ssid,SSIDX.length()+1);
PASSX.toCharArray(pass,PASSX.length()+1);
// output status
String html;
html = "<meta name='viewport' content='width=device-width, initial-scale=1'>";
html += "<h2>Wi-Fi Service Updated</h2>";
html += "<p>SSID = " + String(ssid) + "<br></p>";
// not nessary to reset but may be best
html += "<p>Please cycle power</p>";
html += "<BR><BR>";
html += "<a href='/'>Back to index</a>";
webServer.send(200, "text/html", html); // stop current connection
// kicks user off access point also
WiFi.disconnect();
// reset giveup counter
giveup = 1;
// turn fault led off
digitalWrite(faultLED, HIGH);
// try make new connection 5 times
if (giveup <= 4) {
if (WiFi.status() != WL_CONNECTED) {
while (WiFi.status() != WL_CONNECTED) {
WiFi.begin(ssid, pass);
delay(5000);
giveup++;
if (giveup > 5) break;
} // end while not connected
} else {
// reset the giveup count as connected
giveup = 1;
} // end if not connected to wifi
} // end if giveup
} // end do save
|
Website Server Code
<?PHP
# _espGetTweets.php
# for ESP8266 LED Message Display of Tweets
# Arduino Code LED_Belt_Buckle_Controler_IOT_TwitterV5
# http://www.brianmoreau.com/
# https://dev.twitter.com/docs/api/1.1/get/search/tweets
# set YOUR twitter app keys
$conskey = "YOURAPPKEYS";
$conssec = "YOURAPPKEYS";
# search API URL
$search_url = 'https://api.twitter.com/1.1/search/tweets.json';
$userToken = "YOURUSERTOKEN";
$userSecret = "YOURUSERKEY";
# connect to api
$oauth = new OAuth($conskey,$conssec,OAUTH_SIG_METHOD_HMACSHA1,OAUTH_AUTH_TYPE_URI);
$oauth->setToken($userToken,$userSecret);
$q = urlencode($_GET[q]); // serach query
$c = urlencode($_GET[num]); // number of results
$c = "1";
# build search query
$lang = urlencode("en");
$args = array('q'=>$q,
'count'=>$c);
$oauth->fetch("$search_url",$args,"GET");
$json = json_decode($oauth->getLastResponse(),true);
# decode result
foreach($json['statuses'] as $tweet) {
//$id = $tweet['id'];
$text = $tweet['text'];
// remove URL's
// get pos of start of url
// case-insensitive search
$posLINK = stripos($text, "HTTPS://");
// find how many characters remaining
if ($posLINK) {
$r = strlen($text) - $posLINK;
// if not end of text
if ($r + $posLINK != strlen($text)) {
} else {
// extract link
$text = substr($text, 0, $posLINK);
}
}
echo("BEGINTWEET" . $text . "<br>");
}
?>
|
|