Adrian Smith
Published © CC BY-NC

YouTube subscriber and view counter using NodeMCU

A YouTube statistics display which shows subscriber and view counts on a 4 digit and 8 digit 7 segment display for that retro look.

IntermediateFull instructions provided2 hours660
YouTube subscriber and view counter using NodeMCU

Things used in this project

Hardware components

MAX7219/MAX7221 LED Display Drivers
Maxim Integrated MAX7219/MAX7221 LED Display Drivers
×2
NodeMCU V3 ESP8266-12E
×1
Texas Instruments 74AHCT125
×1
Resistor 10k ohm
Resistor 10k ohm
×2
Capacitor 100 nF
Capacitor 100 nF
×4
33uf 10V capacitor (generic)
×1
YouTube counter custom made PCB
×1
onsemi MAN74A Common Cathode LED 0.3" (or equivalent)
×12

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Custom parts and enclosures

PCB design files (KiCad)

PCB design files in KiCad with exported gerber files so you can make your own PCB

Schematics

Schematic

Schematic for the YouTube counter

PCB CAD files (KiCad)

PCB design files in KiCad with exported gerber files so you can make your own PCB.

Code

Firmware

C/C++
Compile and upload firmware using Arduino IDE and ESP8266 support libraries V2.6.0
// This program will create an access point to configure wifi. SSID "YTcounter", password is "password"
// Connect to this AP with an Android or IOS phone and sign in to the network. Config box will appear.
// Once wifi is established go to IP address displayed at boot and enter youtube channel ID and API key.

// If re-flashing this code please see below.
// use ESP8266 community V2.6.0 and change stack size to #define _stackSize (6748/4) 
// in stackthunk.cpp in the ESP8266 core files otherwise it just crashes with later 
// versions of the libraries. Stackthunk.cpp is in local appdata/arduinoxx where xx version no

// Subscriber counts above 999 will be rounded down to lowest 10 e.g 1003 subs will show 1000 subscribers.
// this is a change Youtube implemented and nothing can be done about this. Realistically only for small channels.

// Written for NodeMCU version 1.0 on ESP-12E V3 module. Should work with others, pin assignments may need changing.
// Settings: CPU clock 80Mhz, flash size 4Mb, V2 lower memory, Vtables=flash, erase flash: only sketch, legacy

// Troubleshooting; use a terminal program such as terraterm to monitor serial output. Any error messages
// will be shown such as HTTP status codes plus any debug messages from the NodeMCU RTOS and / or debugger.
// Serial settings: 115200 baud, 8 bits, no parity, one stop bit. First line will show garbage, that's normal.
// If no stats are shown error 403 is generally incorrect / expired API key. 400 or 404 is wrong channel ID.

// V1.02 - fixed HELP message not displaying if it can't connect to network

#include <WiFiManager.h> // https://github.com/tzapu/WiFiManager
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h> // for connecting to SSL sites
#include <EEPROM.h>
#include <YoutubeApi.h>
#include <ArduinoJson.h>
#include <stackthunk.h>
#include <SPI.h>
#include <LedControl.h>

// MAX7219 SPI pins
// data GPIO 13 (D7)
// clock GPIO 14 (D5)
// CS GPIO 15 (D8)

////Webpage start////

const char MAIN_page[] PROGMEM = R"=====(
<!DOCTYPE html>
<html>
<body>

<h2>Config Settings<h2>
<h3>Youtube Counter V1.0</h3>

<form action="/action_page">
  Channel ID:<br>
  <input type="text" name="Channel ID" value="">
  <br>
  API key:<br>
  <input type="text" name="API Key" value="">
  <br><br>
  <input type="submit" value="Submit">
</form> 

</body>
</html>
)=====";

////Webpage finish////

ESP8266WebServer server(80); //Server on port 80

uint IDaddr = 0; // eeprom starting address for channel ID
uint APIaddr = 32; // eeprom starting address for API Key

char API_KEY[48] = ""; // leave blank - data will be copied from eeprom later
WiFiClientSecure client;
YoutubeApi *api;

unsigned long timeBetweenRequests = 300000; // poll Youtube every 5 mins (300,000 milliseconds)
unsigned long nextRunTime;

byte  subsdigits[] = {0, 0, 0, 0};
byte  viewsdigits[] = {0, 0, 0, 0, 0, 0, 0, 0};
unsigned long subs;
unsigned long views;

// nodeMCU ESP8266 boards use D followed by pin number printed on the module PCB.
// others such as the wemos D1 use the GPIO pin numbers to reference I/O pins e.g D7 is GPIO 13.
LedControl lc=LedControl(D7,D5,D8,2); // data,clock,load,no of chips

void setup() 
{
    // brightness 12-15 best for MAN74A LED modules. If using modern high efficency LED's reduce to 6-8.
    lc.shutdown(0,false); // MAX7219 always starts in shutdown so have to wake it up
    lc.setIntensity(0,12); // device address 0-7, brightness value range 0-15 (min-max)
    lc.shutdown(1,false); // MAX7219 always starts in shutdown so have to wake it up
    lc.setIntensity(1,12); // device address 0-7, brightness value range 0-15 (min-max)
    WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP
    Serial.begin(115200);
    EEPROM.begin(512); // start eeprom emulation and allocate 512 bytes. That is plenty.
    WiFiManager wm;
    wm.setAPCallback(configModeCallback);

    //reset settings - wipe credentials for testing
    //wm.resetSettings();

    // Automatically connect using saved credentials,
    // if connection fails, it starts an access point with the specified name ( "YTcounterAP"),
    // if empty will auto generate SSID, if password is blank it will be anonymous AP (wm.autoConnect())
    // then goes into a blocking loop awaiting configuration and will return success result

    bool res;
    res = wm.autoConnect("YTCounterAP","password"); // password protected ap
  
    Serial.print("*NET: IP address: "); 
    Serial.println(WiFi.localIP());
    Serial.print("*YTC: Channel ID:");
    String IDstr;   
    for(int i=0;i<32;i++) // channel ID is 24 characters long, allow up to 32 characters
     {
       IDstr = IDstr + char(EEPROM.read(IDaddr+i));     
     }  
    Serial.println(IDstr);  
    Serial.print("*YTC: API Key:");
    String APIstr;   
    for(int i=0;i<48;i++) // API key is 40 characters long, allow up to 48 characters
     {
       APIstr = APIstr + char(EEPROM.read(APIaddr+i));     
     }  
    Serial.println(APIstr);  
    Serial.println("*NET: Waiting for data...");

    server.on("/", handleRoot);      //Which routine to handle at root location
    server.on("/action_page", handleForm); //form action is handled here
    server.begin();                  //Start server
    client.setInsecure(); // required for version 2.5 and above
    strcpy(API_KEY, APIstr.c_str());
    api = new YoutubeApi(API_KEY, client);

}

void loop() 
{
      server.handleClient(); // code for webserver config menu to set youtube parameters 
      getstats(); 
      displayDigits();
}

// functions

//===============================================================
// This routine is executed when you open webpage in a browser
//===============================================================
void handleRoot() 
{
 delay(10); // delay required otherwise it crashes and reboots when entering data.
 String s = MAIN_page; //Read HTML contents
 server.send(200, "text/html", s); //Send web page
}
//===============================================================
// This routine is executed when you press submit
//===============================================================
void handleForm() 
{
 String ChannelID = server.arg("Channel ID"); 
 String APIkey = server.arg("API Key"); 

 for (int i = 0; i < 512; i++) 
    {
      EEPROM.write(i, 0); // erase eeprom before writing new data
    }
    
    EEPROM.commit();
    delay(500);

 Serial.print("Channel ID:");
 Serial.println(ChannelID);
 
   for(int i=0;i<ChannelID.length();i++)
  {
    EEPROM.write(IDaddr+i, ChannelID[i]); 
  }
   
 Serial.print("API Key:");
 Serial.println(APIkey);
 
  for(int i=0;i<APIkey.length();i++)
  {
    EEPROM.write(APIaddr+i, APIkey[i]); 
  }

 EEPROM.commit();
 String s = "<a href='/'> Stored! You can close your browser tab or click on this text if you made a mistake. </a>";
 server.send(200, "text/html", s); //Send web page
}

void displayDigits() 
{
 
  // separate individual digits using mod math
  subsdigits[3] = (subs % 10); // ones
  subsdigits[2] = (subs / 10) % 10; // tens
  subsdigits[1] = (subs / 100) % 10; // hundreds
  subsdigits[0] = (subs / 1000) % 10; // thousands

  if (subs <10)

  {
    subsdigits[0] = 0x20; // print a space (blank character)
    subsdigits[1] = 0x20;
    subsdigits[2] = 0x20;
  }

  else if (subs <100)

  {
    subsdigits[0] = 0x20; // print a space (blank character) 
    subsdigits[1] = 0x20;
  }

  else if (subs <1000)

  {
    subsdigits[0] = 0x20; // print a space (blank character)
  }

  // write the digits to MAX7219 at address 1
  // (digit 0 on the left unlike the cheap Chinese modules where it's on the right)
  for (int i = 0; i <= 4; i++)
  {
    lc.setChar(1, i, subsdigits[i], false); 
  }

    // Seperate digits out using mod math

    viewsdigits[7] = (views % 10); // ones
    viewsdigits[6] = (views / 10) % 10; // tens
    viewsdigits[5] = (views / 100) % 10; // hundreds
    viewsdigits[4] = (views / 1000) % 10; // thousands
    viewsdigits[3] = (views / 10000) % 10; // 10 thousands
    viewsdigits[2] = (views / 100000) % 10; // 100 thousands
    viewsdigits[1] = (views / 1000000) % 10; // 1 millions
    viewsdigits[0] = (views / 10000000) % 10; // 10 millions

  if (views <10)

  {
    viewsdigits[0] = 0x20; // print a space (blank character)
    viewsdigits[1] = 0x20;
    viewsdigits[2] = 0x20;
    viewsdigits[3] = 0x20;
    viewsdigits[4] = 0x20;
    viewsdigits[5] = 0x20;
    viewsdigits[6] = 0x20;
  }

  else if (views <100)

  {
    viewsdigits[0] = 0x20; // print a space (blank character) 
    viewsdigits[1] = 0x20;
    viewsdigits[2] = 0x20;
    viewsdigits[3] = 0x20;
    viewsdigits[4] = 0x20;
    viewsdigits[5] = 0x20;
  }

  else if (views <1000)

  {
    viewsdigits[0] = 0x20; // print a space (blank character)
    viewsdigits[1] = 0x20;
    viewsdigits[2] = 0x20;
    viewsdigits[3] = 0x20;
    viewsdigits[4] = 0x20;
  }

  else if (views <10000)
  {
    viewsdigits[0] = 0x20; // print a space (blank character)
    viewsdigits[1] = 0x20;
    viewsdigits[2] = 0x20;
    viewsdigits[3] = 0x20;
  }

  else if (views <100000)
  {
    viewsdigits[0] = 0x20; // print a space (blank character)
    viewsdigits[1] = 0x20;
    viewsdigits[2] = 0x20;
  }

  else if (views <1000000)
  {
    viewsdigits[0] = 0x20; // print a space (blank character)
    viewsdigits[1] = 0x20;
  }

  else if (views <10000000)
  {
    viewsdigits[0] = 0x20; // print a space (blank character)
  }

 // write digits to MAX7219 at address 0

     for (int i = 0; i <= 8; i++)
     {
      lc.setChar(0, i, viewsdigits[i], false);
     }


}  

void getstats()

{
  String IDstr;   
    for(int i=0;i<32;i++) // channel ID is 24 characters long, allow up to 32 characters
     {
       IDstr = IDstr + char(EEPROM.read(IDaddr+i));     
     }  
  
   if (millis() > nextRunTime)  {
    if(api->getChannelStatistics(IDstr))
    {
      Serial.println(F("---------Stats---------"));
      Serial.print(F("Subscriber Count: "));
      Serial.println(api->channelStats.subscriberCount);
      Serial.print(F("View Count: "));
      Serial.println(api->channelStats.viewCount);
      Serial.print(F("Comment Count: "));
      Serial.println(api->channelStats.commentCount);
      Serial.print(F("Video Count: "));
      Serial.println(api->channelStats.videoCount);
      Serial.println(F("------------------------"));
           
      Serial.printf("Free heap size: %u\n", ESP.getFreeHeap());
      Serial.printf("Free cont stack size: %u\n", ESP.getFreeContStack());

      subs = api->channelStats.subscriberCount;
      views = api->channelStats.viewCount;
    
    }
    nextRunTime = millis() + timeBetweenRequests;
  }
}

void configModeCallback (WiFiManager *myWiFiManager) 
    {
    Serial.println("Entered config mode");
    Serial.println(WiFi.softAPIP());
    Serial.println(myWiFiManager->getConfigPortalSSID());
     lc.setChar(1,0,'H',false); // display HELP on subs display to indicate can't connect to wifi
     lc.setChar(1,1,'E',false);
     lc.setChar(1,2,'L',false); 
     lc.setChar(1,3,'P',false);
    }

Credits

Adrian Smith

Adrian Smith

6 projects • 3 followers
Electronics engineer for around 10 years, repair electronic LED signs and other old electronics from the 1980 / 1990's.
Thanks to Brian Lough and Tzapu.

Comments