Christopher StapelsHans Scharler
Published © MIT

ThingSpeak TV: Display Real-Time Values on an OLED Screen

Monitor your ThingSpeak channels and be happy.

IntermediateFull instructions provided6 hours2,173
ThingSpeak TV: Display Real-Time Values on an OLED Screen

Things used in this project

Hardware components

Wemos D1 Mini
Espressif Wemos D1 Mini
×1
Momentary Push Button Switch
×2
Male/Female Jumper Wires
Male/Female Jumper Wires
×1
OLED Display Module SSD1306
×1
Resistor 1k ohm
Resistor 1k ohm
×2

Software apps and online services

Arduino IDE
Arduino IDE
ThingSpeak API
ThingSpeak API

Hand tools and fabrication machines

Dremel
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

Front of TV Case

This is the front of the box that holds all the components.

TV Box Back

This is the back of the box that holds the TV.

Schematics

ThingSpeak TV Schematic

Code

Channel Display code

C/C++
#include <PubSubClient.h>
#include <ESP8266WiFi.h>

#include "SSD1306Wire.h"
#include "OLEDDisplayUi.h"
#include "fonts.h"
#include <ArduinoJson.h>

#define PIN 2

// Display Settings
const int I2C_DISPLAY_ADDRESS = 0x3c;
const int SDA_PIN = D2;
const int SDC_PIN = D3;

//button stuff
int oldChan=0;
long startTime=millis();
const int interrupt1Pin=12;
const int interrupt2Pin=14;

char ssid[] = "ssid"; // Change this to your network SSID (name).
char pass[] = "PWD"; // Change this your network password.
#define THING_SPEAK_ADDRESS "api.thingspeak.com"
char mqttUserName[] = "Channel_Display"; // Can be any name.
char mqttPass[] = "XXXXXXXXXXXXXXXXX"; // Change to your MQTT API Key from Account > MyProfile.
char userAPIKey[]="YYYYYYYYYYYYYYYYY"; // Change to your User API Key from Account > MyProfile.
const char* server = "mqtt.thingspeak.com";

const char* fieldChar[9];
int subField=0; //zero for subscribe to all fields
int channelCount=0;
long channelList[100];
String aPIKey[100];
int currentChannel=1; // Swhich channel number is shown, starts at one, not channelID
int updateScreen=0;  // Boolean to drive screen refresh
long channelNumber; // Keep track of the ThingSpeak channel number
int fieldNum=1;   // Loop number for fields on the display
int numFields=8;  // In case I want to show a subset or seperset of the available fields.
long updateInterval=2000;  // How often to I switch to a new field
long lastUpdate=0;  // Keep track of when I last updated the field
bool chChange=true;  // Boolean for tracking need to update MQTT subscriptions
int lastChannel=0;  // Placeholder for unsubscribe command.


SSD1306Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SDC_PIN);
OLEDDisplayUi ui ( &display );
WiFiClient client; // Initialize the Wifi client library.
PubSubClient mqttClient(client); // Initialize the PuBSubClient library.

//prototypes
int callback(char* topic, byte* payload, unsigned int length);
void mqttConnect();
int mqttSubscribe(long subChannelID,int field,char* readKey,int unsubscribeFlag);
int mqttUnSubscribe(long subChannelID,int field,char* readKey);
int connectWifi();
void newData(OLEDDisplay *display);
void updateData(OLEDDisplay *display,int fieldLoop);
void getChannelList(char* API_Key);
bool getChannel();
void upButton();
void downButton();

//functions

int callback(char* topic, byte* payload, unsigned int length) {
    
    StaticJsonBuffer<512> jsonBuffer;
    char jsonObject[512];
    char p[length + 1];
    
    Serial.print("Callback");
    updateScreen=1;
    memcpy(p, payload, length);
    p[length] = NULL;
    Serial.print("Answer: ");
    Serial.println(String(p));
    JsonObject& root=jsonBuffer.parseObject(p);
    if (!root.success()) {
        
        Serial.println( "parseObject() failed");
        return 0;
        
    }
    
    channelNumber=root["channel_id"];
    fieldChar[1]=root["field1"];
    fieldChar[2]=root["field2"];
    fieldChar[3]=root["field3"];
    fieldChar[4]=root["field4"];
    fieldChar[5]=root["field5"];
    fieldChar[6]=root["field6"];
    fieldChar[7]=root["field7"];
    fieldChar[8]=root["field8"];
    
}

void setup() {
    
    Serial.begin( 115200 );
    Serial.println( "Start" );
    int status = WL_IDLE_STATUS; // Set a temporary WiFi status.
    
    display.init();
    display.flipScreenVertically();
    display.clear();
    display.display();
    
    display.setFont( Crushed_Plain_36 );
    display.drawString( 1, 5, "Thing" );
    display.display();
    delay( 1000 );
    display.clear();
    display.drawString( 38, 5, "Speak" );
    display.display();
    delay( 1000 );
    display.clear();
    display.drawString( 1, 15, "TV!" );
    display.display();
    delay( 1000 );
    // Attempt to connect to WiFi network
    connectWifi();
    
    Serial.println( "Connected to wifi" );
    mqttClient.setServer( server, 1883 ); // Set the MQTT broker details.
    mqttClient.setCallback( callback );
    getChannelList( userAPIKey );
    
    pinMode( interrupt1Pin, INPUT_PULLUP );
    pinMode( interrupt2Pin, INPUT_PULLUP );
    attachInterrupt( interrupt1Pin, upButton, FALLING );
    attachInterrupt( interrupt2Pin, downButton, FALLING );
    
}


void loop() {
    
    long channelsList[140];

    // Maintain WiFi connection.
    if ( WiFi.status() != WL_CONNECTED ) {
        connectWifi();
    }

    // Maintain MQTT connection.
    if ( !mqttClient.connected() ){
        
        delay( 1000 );
        mqttConnect();// Reconnect if MQTT client is not connected.
        
    }
    
    mqttClient.loop(); // Call the loop continuously to establish connection to the server.

    // Update screen upon changes
    if ( updateScreen == 1 ){
        
        updateScreen = 0;
        newData( &display );
        fieldNum = 1;
        
    }
    
    long elapsedTime = millis() - lastUpdate;
    if ( elapsedTime > updateInterval ){
        
        lastUpdate=millis();
        
        if (( fieldChar[ fieldNum ]) != '\0' ){
            updateData( &display, fieldNum ); //moop
        }
        else{
            lastUpdate = 0;
            delay( 25 ); // Prevent WDT from triggering from looping on channels with no data
        }
        
        fieldNum++;
        
        if ( fieldNum > numFields ){
            fieldNum = 1;
            
        }
    }
    
    delay( 20 );
    
    if ( chChange ){
        
        char localKey[ 10 ];
        // Unsubscribe from last channel.
        aPIKey[ lastChannel ].toCharArray( localKey,17 );
        mqttSubscribe( channelList[ lastChannel ], subField, localKey, 1);
        
        if ( currentChannel < 0 ){
            currentChannel = channelCount - 2;
        }
        
        if ( currentChannel > channelCount-1 ){
            currentChannel = 1;
        }
        
        lastChannel = currentChannel;
        
        // Subscribe to the new channel.
        aPIKey[ currentChannel ].toCharArray( localKey, 17 );
        mqttSubscribe( channelList[ currentChannel ], subField, localKey, 0 );
        chChange = false;
        
    }
    
}

/** void mqttConnect()
 * Connect to the MQTT broker
 * clientID - make sure to terminate appropriately.
 */

void mqttConnect()
{
    char clientID[ 9 ];
    
    // Loop until connected.
    while ( !mqttClient.connected() )
    {
        
        getID( clientID,8 ); // Createa random client ID
        
        // Connect to the MQTT broker.
        Serial.print( "Attempting MQTT connection..." );
        if ( mqttClient.connect( clientID, mqttUserName, mqttPass ) )
        {
            Serial.println( "Connected with Client ID: " + String( clientID ) + " User "+ String( mqttUserName ) + " Pwd "+String( mqttPass ) );
            
        } else
        {
            Serial.print( "failed, rc = " );
            // See http://pubsubclient.knolleary.net/api.html#state for the failure code explanation.
            Serial.print( mqttClient.state() );
            Serial.println( " Will try again in 5 seconds" );
            delay( 5000 );
        }
    }
}

/** int mqttSubscribe(long subChannelID,int field,char* readKey,int unsubscribeFlag)
 * Subscribe to the MQTT topic
 * String myTopic - string to hold the topic as its built
 */
int mqttSubscribe( long subChannelID,int field,char* readKey,int unsubscribeFlag ){
    
    String myTopic;
    // If unsubscribeFlag == 1 then unsubscribe.
    
    if (field==0){
        
        myTopic="channels/"+String(subChannelID)+"/subscribe/json/"+String(readKey);
        
    }
    
    else{
        
        myTopic = "channels/"+String( subChannelID )+"/subscribe/fields/field"+String( field )+"/"+String( readKey );
        
    }
    
    if (unsubscribeFlag == 0 ){
        
        Serial.println( "Subscribing to " +myTopic );
        Serial.println( "State= " + String(mqttClient.state()) );
        
    }
    
    char charBuf[ myTopic.length()+1 ];
    myTopic.toCharArray( charBuf, myTopic.length()+1 );
    // Serial.println(charBuf);
    if (unsubscribeFlag == 1){
        
        Serial.println( "UnSubscribing " + myTopic );
        return !mqttClient.unsubscribe( charBuf );
        
    }
    
    return mqttClient.subscribe( charBuf,0 );
    
    
}

/** int mqttUnSubscribe(long subChannelID,int field,char* readKey)
 *  Before subscribing to the next channel, you must unsubscribe from the last.
 *  String myTopic - string to hold the topic as its built
 */

int mqttUnSubscribe(long subChannelID,int field,char* readKey){
    
    String myTopic;
    
    if ( field == 0 ){
        
        myTopic = "channels/" + String( subChannelID ) + "/subscribe/json/" + String( readKey );
        
    }
    
    else{
        
        myTopic = "channels/"+String( subChannelID ) + "/subscribe/fields/field"+String( field ) + "/"+String( readKey );
        
    }
    
    char charBuf[ myTopic.length() + 1 ];
    myTopic.toCharArray( charBuf, myTopic.length() + 1 );
    return !mqttClient.unsubscribe( charBuf );
    
}

int connectWifi()
{
    
    while (WiFi.status() != WL_CONNECTED) {
        
        WiFi.begin( ssid, pass );
        delay( 2500 );
        
        Serial.println( "Connecting to WiFi" ); // Inform the serial output
        Serial.print( "." );
        display.clear();
        display.setFont( DejaVu_Sans_16 );
        display.drawString( 1, 5, "Connecting" );
        display.drawString( 1, 20, " to" );
        display.drawString( 1, 35, "WiFi" );
        display.display();
        
    }
    
    Serial.println( "Connected" );
    display.clear();
    
    display.drawString( 34, 5, "Connected!" );
    display.display();
    
}


void newData( OLEDDisplay *display ){
    
    display -> clear();
    
}

/**
 * void updateData(OLEDDisplay *display,int fieldLoop) 
 * Update the display
 */
void updateData( OLEDDisplay *display,int fieldLoop ) {
    
    display -> clear();
    display -> display();
    display -> setFont(DejaVu_Sans_16);
    String buffer1 = "Channel:" +String( channelNumber );
    display -> drawString( 1,1,buffer1 );
    delay( 10 );
    display -> display();
    buffer1 = "Field# " + String( fieldLoop );
    display -> drawString( 1,16,buffer1 );
    delay( 20 );
    display -> display();
    display -> drawString( 1, 39, fieldChar[ fieldLoop ] );
    delay( 50 );
    display -> display();
    
}

/** 
*  void getChannelList(char* API_Key)
*  Make API call to channels list
*  startTime - keep track of how long we have been readung
*  TIMOUT - as the name implies
*/

void getChannelList( char* API_Key ){
    
    long startTime = millis();
    long TIMEOUT = 4000;
    
    display.clear();
    display.setFont( DejaVu_Sans_16 );
    display.drawString( 4, 1, "Getting" );
    display.drawString( 4, 15, "Channel" );
    display.drawString( 4, 30, "List" );
    display.display();
    
    channelCount = 0;
    
    if ( client.connect( THING_SPEAK_ADDRESS , 80 )){
        
        // GET data via HTTP
        Serial.println( "Connecting to ThingSpeak for channel list..." );
        Serial.println();
        
        client.print( "GET /channels.json?api_key=" );
        client.println(userAPIKey);
        client.println();
        while ( client.available() < 1 && (( millis() - startTime ) < TIMEOUT ) ){
            
            delay( 1 );
            
        }
        
        while(getChannel()){
            
            channelCount++;
            
        }
    }
    
    else
    {
        
        Serial.println ( "Connection Failed" );
        
    }
    
    client.stop();
    
}

/**
 * getChannel()
* Processes one line of channel list and adds to array.
*  charCount - keep track of the size of the labels
*  channelResponse - data from serial input
*/

bool getChannel(){
    
    int braces = 0;
    int charCount = 0;
       
    if( client.available() > 0 ){
        // Get response from server
        char charIn;
        String channelResponse;
        
        channelResponse = client.readStringUntil( ']' );
        channelResponse += client.readStringUntil( ']' );
        
        // Parse before returning.
        int spot= channelResponse.indexOf( "id" );
        Serial.print( String( channelCount )+ " " );
        channelList[ channelCount ] = channelResponse.substring( spot+4,spot+10 ).toInt();
        Serial.print( String( channelList[channelCount])+ " ");
        spot=channelResponse.indexOf( "\"api_key\":\"" );
        aPIKey[ channelCount ] = channelResponse.substring( spot+11, spot+27 );
        Serial.println( aPIKey[ channelCount ] );
        if ( spot == 0 ){
            
            return false; // If there is no length to the data read.
            
        }
        
        return true; // When reading is complete.
        
    }
    
    return false;  // If the connection failed.
    
}


void upButton(){
    
    // noInterrupts();
    currentChannel++;
    long Milliseconds = millis() + 250; //debounce delay
    while ( Milliseconds > millis() ) ;
    chChange = true; // Trigger MQTT subscription change.
    
}


void downButton(){
    
    currentChannel--;
    long Milliseconds = millis() + 250; //debounce delay
    while ( Milliseconds > millis() ) ;
    chChange=true; // Trigger MQTT subscription change
    
}

/**
* Build a random client ID
*  clientID - char array for output
*  idLength - length of clientID (actual length will be one longer for NULL)
*/

void getID( char clientID[], int idLength ){
    static const char alphanum[] = "0123456789"
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    "abcdefghijklmnopqrstuvwxyz"; // For random generation of client ID.
    
    // Generate ClientID
    for (int i = 0; i < idLength ; i++) {
        clientID[ i ] = alphanum[ random( 51 ) ];
    }
    clientID[ idLength ] = '\0';
    
}

Credits

Christopher Stapels

Christopher Stapels

3 projects • 13 followers
Hans Scharler

Hans Scharler

15 projects • 86 followers
IoT Engineer, Maker - I have a toaster that has been tweeting since 2008.

Comments