// 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);
}
Comments