#include <WiFi.h>
#include "time.h"
#include <Adafruit_NeoPixel.h>
#include "SPI.h"
#include <HTTPClient.h>
#include <PubSubClient.h>
#include <WiFiClientSecure.h>
//#include <WiFiManager.h>
#define NEO_PIN 21 // Which pin on the Arduino is connected to the NeoPixels?
#define NUMPIXELS 12 // Popular NeoPixel ring size
#define MQTTmsgBufSize 50 //MQTT bufffer
#define sitekey "1234567890abc"
#define debugmode 1 //are we sending data out of the Serial Port
//Vars that will be updated from the database
boolean showPegSecs=0; //are we showing the seconds on the pegs?
byte pegs[7][3]={{0,0,0}, //0 -OFF
{0,0,255}, //1 - mins BLUE colour //these will be updated from the database
{0,255,0}, //2 - secs WHITE colour
{255,0,0} //3 - hrs RED colour
{255,0,0} //4 - mix of secs and mins colour
{255,0,0} //5 - mix of secs and hrs colour
{255,0,0} //6 - mix of mins and hrs colour
{255,0,0}}; //7 - mix of secs, mins and hrs colour
int NTPrefresh=60; //how many mins before we request the NTP time again. // refreshed from database
uint32_t clrHrs;
uint32_t clrMins;
uint32_t clrSecs;
uint32_t clrAll;
clrHrs=0; //red
clrMins=43690; //blue
clrSecs=43690; //blue but will have saturation=0
unsigned long miliStart;
byte STATUS_bootsequence=0;
byte TMRsecs;
byte TMRmins;
byte TMRhrs;
byte doNTPRefresh; //this is just a flag
const char * ssid="Wokwi-GUEST";
const char * wifipw="";
int RGBleds [4][3]={{12,13,14}, //these are the GPIO numbers for the 4 pegs
{25,26,27},
{32,33,22},
{4,5,18}};
// ------------------Neopixel Driver---------------
Adafruit_NeoPixel pixels(NUMPIXELS, NEO_PIN, NEO_GRB + NEO_KHZ800);
//---- MQTT Broker settings and variables
const char* mqtt_server = "57fbd47785c14295ad3b1983a8deaaec.s2.eu.hivemq.cloud";
const char* mqtt_username = "RSalvage";
const char* mqtt_password = "MyLREC3em2vs6bj";
const int mqtt_port =8883;
bool MQTT_fail=0;
WiFiClientSecure espClient; // for no secure connection use WiFiClient instead of WiFiClientSecure
PubSubClient client(espClient);
unsigned long lastMsg = 0;
#define MSG_BUFFER_SIZE (50)
char msg[MSG_BUFFER_SIZE];
const char* in_topic= "Vortex";
const char* out_topic= "Vortex";
static const char *root_ca PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----
)EOF";
//===================================== MQTT SECTION ===============================
void reconnect() {
// Loop until we're reconnected
#if (debugmode)
Serial.println("In MQTT Reconnect");
#endif
while (!client.connected()) {
//change the display style depending on the boot status
Serial.println("Try MQTT Connection");
String clientId = "vortex-"; // Create a random client ID
clientId += String(random(0xffff), HEX);
if (client.connect(clientId.c_str(), mqtt_username, mqtt_password)) {
Serial.println("MQTT connected");
client.subscribe(out_topic); // subscribe the topics here
} else {
Serial.println("MQTT failed...rc="+String(client.state()));
Serial.println("Retry 5 Secs");
delay(5000);
}
}
}
//=======================================
// This void is called every time we have a message from the broker
void MQTTcallback(char* topic, byte* payload, unsigned int length) {
String incomingMessage = "";
String outmsg="";
String temp;
for (int i = 0; i < length; i++) {
incomingMessage+=(char)payload[i];
}
temp=incomingMessage;
Serial.println("Message arrived ["+String(topic)+"]"+incomingMessage);
//--- check the incoming message
if (incomingMessage=="UPDATE") { //The database has changed.
getVortexDatabase();
}
}
//===================================END OF MQTT===========================
void setTimezone(String timezone){
Serial.printf(" Setting Timezone to %s\n",timezone.c_str());
setenv("TZ",timezone.c_str(),1); // Now adjust the TZ. Clock settings are adjusted to show the new local time
tzset();
}
void initTime(String timezone){
struct tm timeinfo;
Serial.println("Setting up time");
configTime(0, 0, "pool.ntp.org"); // First connect to NTP server, with 0 TZ offset
if(!getLocalTime(&timeinfo)){
Serial.println(" Failed to obtain time");
return;
}
Serial.println(" Got the time from NTP");
// Now we can set the real timezone
setTimezone(timezone);
}
void getNTPTime(){
struct tm timeinfo;
if(!getLocalTime(&timeinfo)){
Serial.println("Failed to obtain time 1");
return;
}
Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S zone %Z %z FROM NTP");
TMRhrs=timeinfo.tm_hour; // Set time
TMRmins=timeinfo.tm_min;
TMRsecs=timeinfo.tm_sec;
}
void startWifi(){
WiFi.begin(ssid, wifipw);
Serial.println("Connecting Wifi");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
Serial.print("Wifi RSSI=");
Serial.println(WiFi.RSSI());
}
void setTime(int yr, int month, int mday, int hr, int minute, int sec, int isDst){
struct tm tm;
tm.tm_year = yr - 1900; // Set date
tm.tm_mon = month-1;
tm.tm_mday = mday;
tm.tm_hour = hr; // Set time
tm.tm_min = minute;
tm.tm_sec = sec;
tm.tm_isdst = isDst; // 1 or 0
time_t t = mktime(&tm);
Serial.printf("Setting time: %s", asctime(&tm));
struct timeval now = { .tv_sec = t };
settimeofday(&now, NULL);
TMRhrs=tm.tm_hour; // Set time
TMRmins=tm.tm_min;
TMRsecs=tm.tm_sec;
}
void setup(){
int lp;
int ilp;
STATUS_bootsequence=1; //we are in the boot sequence
Serial.begin(115200);
Serial.setDebugOutput(true);
startWifi();
for (lp=0;lp<4;lp++) {
for (ilp=0;ilp<3;ilp++) {
pinMode(RGBleds[lp][ilp],OUTPUT);
}
}
/*
WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP
// it is a good practice to make sure your code sets wifi mode how you want it.
//WiFiManager, Local intialization. Once its business is done, there is no need to keep it around
WiFiManager wm;
// reset settings - wipe stored credentials for testing
// these are stored by the esp library
// wm.resetSettings();
// Automatically connect using saved credentials,
// if connection fails, it starts an access point with the specified name ( "AutoConnectAP"),
// 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(); // auto generated AP name from chipid
// res = wm.autoConnect("AutoConnectAP"); // anonymous ap
res = wm.autoConnect("Vortex_Clock","password"); // password protected ap
if(!res) {
Serial.println("Failed to connect");
// ESP.restart();
}
else {
//if you get here you have connected to the WiFi
Serial.println("connected...yeey :)");
}
*/
initTime("GMT0BST,M3.5.0/1,M10.5.0"); // Set for London UK
getNTPTime(); //Setup the time
miliStart=millis(); //Flag for screen / LED refresh
doNTPRefresh=false;
pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
pixels.clear(); // Set all pixel colors to 'off'
//setTime(2023, 7, 4, 20, 40, 35, 1); //For testing only
// ------------------Startup MQTT ---------------
espClient.setCACert(root_ca);
client.setServer(mqtt_server, mqtt_port);
client.setCallback(MQTTcallback);
}
void loop() {
int hour_d;
int min_d;
int min_r;
int secs_d;
int secs_r;
int h_sat;
int m_sat;
int s_sat;
boolean showSecs;
boolean showMins;
hour_d=HEX("FF");
Serial.print(hour_d);
while (1==1) {}
//the first thing that we do is refresh the MQTT
if ((STATUS_bootsequence==1) && (!MQTT_fail)) {reconnect();}
if (MQTT_fail==0) { client.loop(); }
h_sat=255;
m_sat=255;
s_sat=255;
//lets say that saturation is 255 (eg full), when the seconds are merged also, it is halved.
if (millis() > miliStart+1000) {
TMRsecs++;
if (TMRsecs>59) {
TMRsecs=0;
TMRmins++;
doNTPRefresh=(TMRmins>=NTPrefresh);
if (TMRmins>59) {
TMRmins=0;
TMRhrs++;
if (TMRhrs>12) {
TMRhrs=0;
}
}
}
Serial.println(String(TMRhrs)+":"+String(TMRmins)+":"+String(TMRsecs));
hour_d = TMRhrs%12;
min_d = TMRmins/5;
min_r = TMRmins%5;
secs_d=TMRsecs/5;
secs_r=TMRsecs%5;
showSecs=true;
showMins=true;
//Set the default colours
if (secs_d==min_d) {
m_sat=128;
showSecs=false;
}
if (secs_d==hour_d) {
h_sat=128;
showSecs=false;
}
if (min_d==hour_d) {
clrHrs=54613; //Purple
showMins=false;
}
pixels.clear();
clrAll=pixels.ColorHSV(clrHrs, h_sat, 255);
pixels.setPixelColor(hour_d, clrAll); //we always show the hours
if (showMins==true) {
clrAll=pixels.ColorHSV(clrMins, m_sat, 255);
pixels.setPixelColor(min_d, clrAll); //mins in blue
}
if (showSecs==true) {
clrAll=pixels.ColorHSV(clrSecs, 0, 255);
pixels.setPixelColor(secs_d, clrAll);
}
showModMins(min_r, secs_r);
pixels.show();
miliStart=millis();
}
STATUS_bootsequence=2; //we have done a loop at least once.
if (doNTPRefresh) {
getNTPTime(); //this just tightens up the timers every hour (or so)
}
}
void showModMins(int mins, int secs) {
int lp;
int minsFlag=0;
int secsFlag=0;
int colIndex;
Serial.print("m="+String(mins)+" s="+String(secs)+" ... ");
for(lp=1;lp<5;lp++) {
minsFlag=(mins>=lp);
secsFlag=(secs==lp);
//deal with the minutes first.
colIndex=0;
if ((secsFlag==true) && (showPegSecs==true)) {
colIndex=2;
if (minsFlag) { //set it to mix
colIndex=3;
}
} else if (minsFlag) {
colIndex=1; //we know it is only the mins
}
setcolour(lp-1,pegs[colIndex][0],pegs[colIndex][1],pegs[colIndex][2]);
Serial.print(" "+String(minsFlag));
}
Serial.println();
}
void setcolour(int which, int R, int G, int B) {
analogWrite(RGBleds[which][0],R);
analogWrite(RGBleds[which][1],G);
analogWrite(RGBleds[which][2],B);
}
void getVortexDatabase() {
Serial.println("GETTING VORTEX DATABASE");
char serverName[150];
String tempst = "";
int httpCode;
String payload;
int lp;
byte trycount;
String HC;
String MC;
String SC;
byte sync;
byte bright;
byte pegs;
HTTPClient http;
returncode=0;
tempst = databasename + "sendvars&sitekey="+sitekey;
tempst.toCharArray(serverName, 150);
http.begin(serverName);
trycount=0;
do {
httpCode = http.GET();
Serial.println("Attempt " + String(trycount)+" to get the vars - code:"+String(httpCode));
trycount++;
delay(phpDelay);
} while (httpCode!=200 && trycount<5); //Try 5 times to get the data
if (httpCode==200) { //Check for the returning code
payload = http.getString();
Serial.println("Return Code " + String(httpCode));
Serial.println(payload);
if (payload=="Unauthorised Access") {
} else {
HC = getWebString("hcol", payload);
MC = getWebString("mcol", payload);
SC = getWebString("scol", payload);
sync=getWebNumber("sync", payload);
bright=getWebNumber("bright", payload);
peg=getWebNumber("showpegs", payload);
//SET THE COLOURS
temp=StrToHex(RED.substring(4,6)); //0,2 //2,4 //4,6
}
} else {
#if (debugmode)
Serial.println("Error on HTTP request VARS:" + String(httpCode));
#endif
returncode = -1;
OLEDprintMsg(0); //0=FAIL, 1=OK, 2=DONE, 3=UNAUTH, 4=SUCCESS
}
http.end(); //Free the resources
return returncode;
}
int strToHex(const String src)
{
char hexValue[3] ;
src.toCharArray(hexValue,3);
Serial.println(hexValue);
byte tens = (hexValue[0] < '9') ? hexValue[0] - '0' : hexValue[0] - '7';
byte ones = (hexValue[1] < '9') ? hexValue[1] - '0' : hexValue[1] - '7';
byte number = (16 * tens) + ones;
return(number);