Showing posts with label ESP8266. Show all posts
Showing posts with label ESP8266. Show all posts

Sunday, February 20, 2022

Simpson 260 VOM answers, "Is the internet up?"

-----

It is a pretty commonly uttered question (sometimes loudly) around the home or office; "Is the internet up?"  As the go to IT Support Manager around the house this can get a little tiresome.  Most of the time the internet is up and it's a user or personal device problem that can be solved with a re-boot, re-load, re-etc.  Rarely is the internet really down requiring a router and cable modem reboot or a call to the ISP.  Wouldn't a simple visual check that anyone could quickly understand be helpful? 

-----

I recently found my father's old Simpson 260 meter.  He let me borrow it anytime I wanted with the warning of "DON'T COOK IT!".   My memory recalls only using it for continuity and batteries which is good because I did not have a clue as to what would "cook it".  I decided to put this heirloom to use as an internet monitor.  This project is amazingly useful and simple to duplicate.

-----

The rig uses an ESP8266 to ping different servers.   The ping time (in mS) is displayed on the Simpson 260 and 'percent of full scale'.   In other words a ping of 73mS would be 73% of full scale on the Simpson 260.    We set the source code (see below) to cycle through ten servers and show the ping result every 15 seconds.

-----

If the LAN is down or a ping error is detected the meter "wags" back and forth 5 times and tries another server.   The message to the house is, "If the needle ain't wagging back and forth then your problem ain't with the internet connection!"

-----

Upload the Arduino IDE based source code below to the ESP8266.  Connection to the Simpson 260 is easy. 
 

-----

/*
 *  A vintage Simpson 260 meter to shows network PING times
 *  and if there is a connection to the internet.
 *  
 *  Deflect the Simpson meter from 0-100% of the 2.5V full scall
 *  based on ping times of servers.
 *  
 *  10mS = 10% of full scale.   
 *  45mS = 55% of full scale.
 *  73mS = 73% if full scale.
 *  XXmS = XX% of full scale, etc....
 *  Anything over 100ms is consider terrible and just maxes to 100%
 *  
 *  A bad ping (site not found, network down, etc) will 'wag' the meter
 *  back and forth from 0% to 100% five times then try next ping site.
 *
 *  ESP8266 NodeMCU Board BAUD 115200
 *  HiLetgo 1PC ESP8266 NodeMCU CP2102 ESP-12E Development Board from Amazon
 *  
 *  Full documetation at:
 *  WhiskyTangoHotel.Com    
 *  
 *  FEB2022
 *  
 */

#include <ESP8266WiFi.h>
#include <Pinger.h>

const char* ssid     = "Virus-2.4";      // These vars are your private WIFI
const char* password = "zenakelsocats";  // connection information

// Define some 'ping friendly' sites.   ARRAY starts a 0
String PingSite[] = {  
  "whiskeytangohotel.com",
  "google.com",
  "yahoo.com",
  "bing.com",
  "abc.com",
  "cbs.com",
  "cnn.com",
  "apple.com",
  "pingler.com",
  "mailinator.com"
};  // end ping array define

int Number_of_Ping_Sites = 10;   // counted from the list above
int Secs_Between_Pings = 15;
float Min_Ping_Result = 999;
const int PINGOUT = 2;   // Drives the S260. Blue onboard LED and ~D4
Pinger pinger;

void setup() {
  analogWrite(PINGOUT, 0);
  delay(50);
 
  Serial.begin(115200);
  delay(100);

  // Connect to the WiFi network
  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");  
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

// For the ping dot h features https://github.com/bluemurder/esp8266-ping
  pinger.OnEnd([](const PingerResponse& response)
  {
    // Print time information
    if(response.TotalReceivedResponses > 0)
    {
      Serial.printf("Approximate round trip times in milli-seconds:\n");
      Serial.printf(
        "    Minimum = %lums, Maximum = %lums, Average = %.2fms\n",
        response.MinResponseTime,
        response.MaxResponseTime,
        response.AvgResponseTime);
        Min_Ping_Result = response.MinResponseTime;
    }
    
    // Print host data
    Serial.printf("Destination host data:\n");
    Serial.printf(
      "    IP address: %s\n",
      response.DestIPAddress.toString().c_str());
    if(response.DestMacAddress != nullptr)
    {
      Serial.printf(
        "    MAC address: " MACSTR "\n",
        MAC2STR(response.DestMacAddress->addr));
    }
    if(response.DestHostname != "")
    {
      Serial.printf(
        "    DNS name: %s\n",
        response.DestHostname.c_str());
        
        Serial.println("Minimum ping was: " + String(Min_Ping_Result) + "mS." + " Meter to " + String(int(Min_Ping_Result)) + "% of full scale.");
        Serial.println("Delay to next ping is " + String(Secs_Between_Pings) + " seconds...");
        Serial.println("---------------------");
    }
    return true;        
  });  // end ping features

  //Self Test the meter by moving the meter full scale
  // Increase meter value and on board LED brightness
  for(int dutyCycle = 0; dutyCycle < 200; dutyCycle++){   
    // changing the LED brightness with PWM;
    analogWrite(PINGOUT, dutyCycle);
    Serial.println(String(dutyCycle) + " increasing meter self test...");
    //analogWrite(PINGOUT, testval);
    delay(10);
  }   // end meter increase
 
  // Decrease meter value and on board LED brightness
  for(int dutyCycle = 200; dutyCycle > 0; dutyCycle--){
    // changing the LED brightness with PWM
    analogWrite(PINGOUT, dutyCycle);
    Serial.println(String(dutyCycle) + " decreasing meter self test...");
    //analogWrite(PINGOUT, testval);
    delay(10);
  }  // end meter decrease  
  Serial.println("Self test complete!!!");
  Serial.println("---------------------");
 
}   // end void setup

// dutytCycle/2 = ~ the % of 2.5V scale on S260
//    0 dutytCycle =
//   50 dutytCycle = 28%
//  100 dutytCycle = 53%
//  150 dutytCycle = 77%
//  200 dutytCycle = 100%

// Set S260 to +DC.  The + lead to D4.  Neg lead to GND

void loop() {    // loop until the Cowboys win a Super Bowl

  for (int i = 0; i <= (Number_of_Ping_Sites - 1); i++) {  // don't always use the same PingSite; cycle them.
      Serial.println("PingSite[" + String(i) + "]: " + PingSite[i]);
            
      if(pinger.Ping(PingSite[i]) == false)
      {    
         Serial.println("Error during ping command.  Walk the meter 5 times.");
         // Walk the meter back and forth to symbol 'ping error' or network down    
          for (int walk = 0; walk<=4; walk++) {
              // Increase meter value and on board LED brightness
              for(int dutyCycle = 0; dutyCycle < 200; dutyCycle++){   
                // changing the LED brightness with PWM;
                analogWrite(PINGOUT, dutyCycle);
                Serial.println(String(dutyCycle) + " Showing FAIL increasing for: " + PingSite[i]);
                delay(10);
              }   // end fail meter increase
            
              // Decrease meter value and on board LED brightness
              for(int dutyCycle = 200; dutyCycle > 0; dutyCycle--){
                // changing the LED brightness with PWM
                analogWrite(PINGOUT, dutyCycle);
                Serial.println(String(dutyCycle) + " Showing FAIL decreasing for: " + PingSite[i]);
                delay(10);
              }  // end fail meter decrease
          } // end for fail meter back forth walk          
      }  // end if pinger.Ping       

      // Write Ping value to the meter.  Low is better.
      // We basically make percent of full scale equal the ping in mS, ie; 45mS = 45%...
      // Anything over a 100mS is a crappy ping so we make 100mS (100% of scale)
      if (Min_Ping_Result > 100) { Min_Ping_Result = 100; }
      
      analogWrite(PINGOUT, Min_Ping_Result * 2);  // move to meter to display the ping value
      delay(Secs_Between_Pings * 1000);   // delay for next ping    
  }  // end for/next to cycle the PingSites             
}  // end loop until the Cowboys win the Super Bowl
-----

Monday, February 7, 2022

Retirement Clock Goes Digital

 

-----

We overheard in a conversation recently something like: "Now that I'm retired I really only focus on what day of the week it is and the general time of day."  We then discovered this awful looking Day of the Week Clock and the vision of a more modern version came to mind.

-----

The rig is based around this MakerFocus ESP32 Development Board mainly because it works with the Arduino IDE and has WiFi with a display on board.   The code (source below) gets it's time/date from a NTP server and then simply figures out and displays the day of the week and what percentage of the day has past.  Evidently this is all the critical information needed to guide the non-working through their day.

-----

To dress up the look a case was 3D printed.  After seeing the result we needed to celebrate with a beer.   As shown in the video below it was a bit early in the day for a drink, but as the ol' saying goes "It's 71% somewhere!".

-----

Retirement Clock Source Code:

/*
 * HelTec Automation(TM) ESP32 Series Dev boards OLED "Retirement Clock"
 * Adruino IDE Board Setting: WiFi Kit32, Disabled, 240MHz, 921600, None
 *
 * "Retirement Clock" shows only Day of Week and percentage time left in the day.
 *  
 * FEB 2022
 * Search whiskeytangohotel.com for project details.
 *
*/

#include "Arduino.h"
#include "heltec.h"

#include <TimeLib.h>
#include <WiFi.h>
#include <WiFiUdp.h>

const char ssid[] = "xxxxxxx";  //  your network SSID (name)
const char pass[] = "xxxxxxx";       // your network password
long Sync_Delay = 60000;  //  60000 is sync every ten minutes,  We aren't going for precision here. LOL

int DOWNUM = 0;     //  DOWNUM from NTP (expect 0 - 6)
String DOW = "DOW";  // DOWNUM to a Day of Week String
int hourNUM = 0;    //  hour() value from NTP
float minuteNUM = 0;   // minute() value from NPT

// NTP Servers:
static const char ntpServerName[] = "pool.ntp.org";
const int timeZone = 0;     // leave as 0 for UTC.  We do the offset math in void setup

WiFiUDP Udp;
unsigned int localPort = 8888;  // local port to listen for UDP packets

time_t getNtpTime();
void digitalClockDisplay();
void printDigits(int digits);
void sendNTPpacket(IPAddress &address);

void setup() {
  //NTP setup
  Serial.begin(115200);
  while (!Serial) ; // Needed for Leonardo only
  delay(250);
  Serial.println("TimeNTP");
  Serial.print("Connecting to... ");
  Serial.println(ssid);
  WiFi.disconnect();
  WiFi.mode(WIFI_MODE_STA);
  WiFi.begin(ssid, pass);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.print("IP number assigned by DHCP is: ");
  Serial.println(WiFi.localIP());
  Serial.println("Starting UDP");
  Udp.begin(localPort);
  Serial.print("Local port: ");
//  Serial.println(Udp.localPort());
  Serial.println("waiting for sync... ");
  setSyncProvider(getNtpTime);
  setSyncInterval(Sync_Delay);  //   defines how often to check NTP time

  // Hetec setup
  Heltec.begin(true /*DisplayEnable Enable*/, false /*LoRa Disable*/, true /*Serial Enable*/);
  Heltec.display->flipScreenVertically();
  Heltec.display->setFont(ArialMT_Plain_24);
} // end setup link

time_t prevDisplay = 0; // when the digital clock was last displayed timer

void loop() {   // main program loop
  if (timeStatus() != timeNotSet) {
    if (now() != prevDisplay) { //update the display only if time has changed
      prevDisplay = now();
    }
  }

  // Parse time info and send info to serial monitor (for debug)
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.print(" ");
  Serial.print(day());
  Serial.print(".");
  Serial.print(month());
  Serial.print(".");
  Serial.print(year());
  Serial.print(" : ");

  if (int(year()) < 2022) {
     Serial.print(String(int(year())) + " is the NTP server year.  That aint right, so try again!!!");
     Serial.println();      
     setup();
  }   // the NTP server return something 'wierd' (probably 1970) so try again  

  //Covert Time in percent of day completed.  Midnight Local = 0%, Noon Local = 50%  
  minuteNUM = minute();
  hourNUM =   int(hour());  
  // manually adjust hourNUM and minuteNUM for debug
  // hourNUM =  5;    // valid values are INTERGERS 0 thru 24
  // minuteNUM = 49;    // valid values are INTEGERS 0 thru 60  

  minuteNUM = minuteNUM / 60;    // covert minutes to 'fraction'  
  hourNUM = hourNUM - 6;   // hourNUM is in UTC.   Adjust to local.   -6 = CST, -5 = CDT, 0 = UTC
  DOWNUM = int(weekday());
   
  // Calculate percentage of the day pasted into 'progress'
  int progress = round(   (  ((hourNUM + minuteNUM))   /  24   )  * 100    );
  if (progress < 0) {   // it's past UTC midnight and the timezone offset created a negative %
    progress = 100 + progress;
    DOWNUM = DOWNUM -  1;
  }  //end progress < 0

  // Convert DOWNUM into printable String.  Sunday is 1
  if (DOWNUM  == 1) { DOW = "SUN"; }
  if (DOWNUM  == 2) { DOW = "MON"; }
  if (DOWNUM  == 3) { DOW = "TUE"; }
  if (DOWNUM  == 4) { DOW = "WED"; }
  if (DOWNUM  == 5) { DOW = "THU"; }
  if (DOWNUM  == 6) { DOW = "FRI"; }
  if (DOWNUM  == 7) { DOW = "SAT"; }

  Serial.print(String(int(weekday())) + " is " + DOW);
  Serial.println();   
  Serial.print(String(hourNUM) + " + " + String(minuteNUM) + " = " + String(progress) + "%");
  Serial.println();
 
  // clear the display
  Heltec.display->clear();

  // Draw the Precent of Day pasted onto progress bar
  Heltec.display->drawProgressBar(0, 0, 127, 20, progress);

  // Label the Day Of Week and percentage as String
  Heltec.display->setTextAlignment(TEXT_ALIGN_CENTER);
  Heltec.display->drawString(60, 31, DOW + " " + String(progress) + "%");
  // Write buffer to Heltec display
  Heltec.display->display();
 
  delay(5000); // wait some.  we are in no hurry to update the screen
}  // end void loop main program


void printDigits(int digits)
{
  // utility for digital clock display: prints preceding colon and leading 0
  Serial.print(":");
  if (digits < 10)
    Serial.print('0');
  Serial.print(digits);
}

/*-------- NTP code ----------*/

const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets

time_t getNtpTime()
{
  IPAddress ntpServerIP; // NTP server's ip address

  while (Udp.parsePacket() > 0) ; // discard any previously received packets
  Serial.println("Transmit NTP Request");
  // get a random server from the pool
  WiFi.hostByName(ntpServerName, ntpServerIP);
  Serial.print(ntpServerName);
  Serial.print(": ");
  Serial.println(ntpServerIP);
  sendNTPpacket(ntpServerIP);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      Serial.println("Receive NTP Response");
      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }
  Serial.println("No NTP Response :-(");
  return 0; // return 0 if unable to get the time
}

// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12] = 49;
  packetBuffer[13] = 0x4E;
  packetBuffer[14] = 49;
  packetBuffer[15] = 52;
  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}

-----

Tuesday, May 11, 2021

2m / 70cm Ham Radio Traffic Logger

https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYmnOfpJVA2y6GyGkoIOYluMaY2jMoXwGHuCfwkxN8XPYeJUn95zCR6ENM9gJrmd-2vhqYp_0OzWCLDiUItELwP690bjO5wu2y8NSGTZy4fF-TfU84jffUY3ztjd0bSz5hzo35BTHrxpsR/s1600/BT_Logger_Schmatic-793823.JPG 

Is the frequency in use (or QRL? in Morse Code)?

-----

We had a curiosity about how often some of the local ham radio repeaters and the 2m calling frequency was being used and decided to put together a rig that could answer the question with real data.    Ham radio has a lot to do with experimentation and tinkering so we set out on a mission to answer the question with "stuff" we had laying around the shack.

-----

Pretty much any newcomer to ham radio has invested $35'ish on a Baofeng hand held.  The little unit is quickly out grown, but it has come in useful time and time again.   This project was just another example of it's unintended usefulness.  Also, like most hobbyist there's never a shortage of WiFi friendly ESP8266s laying around.   Combining the two for an RF activity logger became the objective.

-----

And..... it turned out to be pretty easy and didn't even require opening up the Baofeng.   Anytime the Baofeng hears a signal the LCD display light is activated.   For reasons unknown this presents a voltage spike on the speaker jack.   We suspected this could be the case from the loud "pop" that can be heard when using earphones.

-----

Here is a look at the signal generated on the speaker jack when the display light gets activated:

Weird, huh?   Oh well, we can use that to make the project dead simple.  Just tune the Baofeng to the frequency of interest and let the rig run and log any traffic that hit the antenna.

-----

The schematic at the top of the page shows how we feed the speaker signal into the analog input (A0) of the ESP8266.   To tame the 5 volt signal going into the ESP8266 two diodes are placed back to back.   That's it.   The ESP8266 is programmed to update a Google Drive Sheet via an IFTTT Webhook anytime a voltage is detected.   The activity log looks like this and the source code is below that.  If you duplicate the project, let us know.

-----

-----

/*
 *  Baofeng Traffic Logger
 *  MAY2021
 *  WhiskeyTangoHotel.Com
 *   
 *  Logs to Google Sheet is traffic is dectected on BF HT Radio    !!!  
 *  by monitoring a voltage on the speaker output.  On traffic the !!!
 *  BF display lights and creates a voltage on the speaker jack    !!!
 *  Since no transit, does not require a license, but if you don't !!!
 *  have a ham license, get one.  It's a fun hobby!!!              !!!  
 *  See: http://www.arrl.org/getting-licensed                      !!!
 *  
 *  uC setting for Ardunio IDE (ESP8266 with WiFI)
 *  NodeMCU 1.0 (ESP-12E Module), 80MHz, 921600, 4M (3M SPIFFS)
 *  
*/

// For the Wireless
#include <ESP8266WiFi.h>

// WiFi Connection Information
const char* ssid = "yourSSID";           // PRIVATE: Enter your personal setup information.
const char* password = "yourpassword";   // PRIVATE: Enter your personal setup information.

// IFTTT Information for WebHook widget
String MAKER_SECRET_KEY = "yourIFTTTkey";   // PRIVATE: Enter your personal setup information. Your IFTTT Webhook key here
String TRIGGER_NAME_google_drive = "yourIFTTTWebHookTriggerName";  // this is the Maker IFTTT trigger name for google drive spreadsheet logging
String url_google_drive;  // url that gets built for the IFTTT Webhook logging to google drive spreadsheet
String Status ="Program_Started.....";  // Status payload for Google Sheet.  We log all starts and reboots
const char* host = "maker.ifttt.com";

// Define ESP8266 pins
const int led = 2;  // Blue on board LED is on PIN-D4 (GPIO2) for this NoderMCU 1.0 ESP8266.  Blink it between reads
const int analogInPin = A0;  // ESP8266 Analog Pin ADC0 = A0

int sigValue = 0;  // value to determine if a signal is detected

// Program control variables
int logging = 1; // If 1 then send SMS.  Any other value (0) turns it off.  For debug, typically would be set = 1

void setup(void){ // This setup code is run once.
  pinMode(led, OUTPUT);     // set up the onboard Blue LED pin as an output.  
  Serial.begin(115200);     // turn on the serial monitor for debug

  // wait until serial port opens for native USB devices
  while (! Serial) {
    delay(10);
  }
 
  // Is the WiFi working?
  WiFi.begin(ssid, password);
  Serial.println("");
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print("Trying to connect to ");
    Serial.print(ssid);
    Serial.print(" on ");
    Serial.print(WiFi.localIP());
    Serial.println(" ");
    for (int x = 0; x < 20; x++) { //  
      digitalWrite(led, !digitalRead(led));  // toggle state of the on board blue LED. Shows program is trying to WiFi connect
      //Serial.println("Server Start blink loop....");
      delay(50);   
    } // endfor WiFi blink connect
  }
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.println(WiFi.localIP());

  for (int x = 0; x < 10; x++) { // 5 slow LED blinks to slow WIFI Connected.
  digitalWrite(led, !digitalRead(led));  // toggle state of the on board blue LED.
  Serial.println("WIFI is Connected....");
  delay(500);   } // endif for WIFI Connected blink  

  // Use WiFiClient class to create TCP connections for WiFi logging
  WiFiClient client;
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
    Serial.println("connection failed");  // Boo!!!
    return;
  }

  // Trigger the IFTTT Webhook Channel to update a Google sheet at program start.
  // This can help log power outs, etc.  For first run we defined String Status for identify a startup condition.
  // Create the request for IFTTT google drive
  url_google_drive = "https://maker.ifttt.com/trigger/" + TRIGGER_NAME_google_drive + "/with/key/" + MAKER_SECRET_KEY + "?value1=" + String(Status);
  Serial.println(" ");  
  Serial.println("Status: Google Sheet update done with trigger:");
  Serial.println (url_google_drive);
  Serial.println(" ");  
    
  // This sends the request to the IFTTT server
  client.print(String("POST ") + url_google_drive + " HTTP/1.1\r\n" +
  "Host: " + host + "\r\n" +
  "Connection: close\r\n\r\n");  
  delay(500);  // Delay for web traffic; maybe not required.  

  // Read all the lines of the reply from server and print them to Serial
  while(client.available()){
     String line = client.readStringUntil('\r');    }

}


void loop(void){    // Loop until the Dallas Cowboys win a Super Bowl
 
  sigValue = analogRead(analogInPin);
 
  // For Degug print SigVal readings in the Serial Monitor
  //Serial.println(sigValue);

  // The blue onboard LED will SLOW blink between to show prog is 'running'.
  // FAST blink when sig is dectected.
  digitalWrite(led, !digitalRead(led));  // toggle state of the on board blue LED.
    
  if (sigValue > 500) {  //  Threshold may need adjusting.  Voltage on speaker jack detected.  We have a signal
      Serial.println("!!!_Signal_Detected_!!!");   
      Serial.println(" ");
              
      if (logging == 1) { // is Google Sheet logging turned on?
          Serial.println("***** Logging is ON *****");
          String Status = "!!!_Signal_Detected_!!!";
          // Set up IFTTT Webhook Channel to update the Google Sheet.  
          // Use WiFiClient class to create TCP connections for IFTTT
          WiFiClient client;
          const int httpPort = 80;
          if (!client.connect(host, httpPort)) {
            Serial.println("connection failed");
            return;
          }
          
          // Create the request for IFTTT google drive
          url_google_drive = "https://maker.ifttt.com/trigger/" + TRIGGER_NAME_google_drive + "/with/key/" + MAKER_SECRET_KEY + "?value1=" + String(Status);
            
          // This sends the request to the IFTTT server
          client.print(String("POST ") + url_google_drive + " HTTP/1.1\r\n" +
          "Host: " + host + "\r\n" +
          "Connection: close\r\n\r\n");  
          delay(500);  // Delay for web traffic; maybe not required.  
        
          // Read all the lines of the reply from server and print them to Serial
          while(client.available()){
             String line = client.readStringUntil('\r');    }
                  
          Serial.println("Status: Google Sheet update done with trigger:");
          Serial.println(url_google_drive);
          Serial.println(" ");
          
          // Fast blink the blue onboard LED to show sig was dectected and delay for flood control
          for (int x = 0; x < 600; x++) { // x = 20 for ~ 1 sec, x = 100 for ~5 sec.....
              digitalWrite(led, !digitalRead(led));  // toggle state of the on board blue LED
              delay(50);   
          } // endfor delay/blink
          
          Serial.println("-------------------------------------");
          Serial.println("Waiting for signal... ");
           
      } else {
        
          Serial.println("Logging is OFF.");
          Serial.println(" ");
      } // endif/else logging  


            
  }
  delay(250); // Delay for onboard blue LED Blink shows program running
    
} // void loop until Cowboys win Super Bowl

-----

Wednesday, February 26, 2020

ESP8266 Doorbell Sends SMS and Updates Google Sheet

-----
On-line shopping has changed the way goods are acquired.  For most packages it is not necessary to be around for the dropoff.  But... for the important stuff it is.  Those packages always seem to show up right when you are in the backyard for mere seconds causing that frustrating "Personal Signature Required for Delivery" note on the door.  This is our DIY effort to solve that problem.
-----
Needed:
     - ESP8266 (microcontroller with WiFi)
     - 1M ohm resistor
     - reed switch
     - IFTTT account (using the WebHook applet)
     - wire and stuff
     - fundamental knowledge on Arduino sketches (simple code edits).
-----
The connections are simple:

 ----
And the rig will look something like this:
-----
A 1M ohm resister connected from 3.3V to the A0 [ADC] input on the ESP8266 keeps the A0 reading at maximum value.   The normally open reed switch is placed right on top of the coils that activate the doorbell.  These coils act like that electromagnet you build in grade school to ring the doorbell and cause the reed switch to close.  This drops the impedance to the 1M resistor and changes the ADC value on the A0 pin for detection of the doorbell.  Then the SMS is sent to your phone and a Google Drive Sheet is updated.
-----
-----
The sketch you need to upload to the the ESP8266 looks like this:
```````````````````````````````````````````````````````````````````````````````
/*
 *  FEB2020
 *  Door Bell Monitoring
 *  WhiskeyTangoHotel.Com
 * 
 *  Build details at:
 *  http://www.whiskeytangohotel.com/2020/02/esp8266-doorbell-sends-sms-and-updates.html
 * 
 *  On Doorbell ring:
 *    Logs to Google Drive (as an appending spreadsheet)
 *    Sends SMS to cell phone
 * 
 *  ESP8266 uC
*/

// For the Wireless
#include <ESP8266WiFi.h>

// WiFi Connection Information
const char* ssid = "yourssid";    // PRIVATE: Enter your personal setup information.
const char* password = "yourwifipassword"; // PRIVATE: Enter your personal setup information.

// IFTTT Information for WebHook widget
String MAKER_SECRET_KEY = "yourIFTTTprivatekey";  // PRIVATE: Enter your personal setup information. Your IFTTT Webhook key here
String TRIGGER_NAME_google_drive = "googlebell";    // this is the Maker IFTTT trigger name for google drive spreadsheet logging
String TRIGGER_NAME_SMS = "doorbell";              // this is the Maker IFTTT trigger name to send SMS.
const char* host = "maker.ifttt.com";
String url_SMS;                      // url that gets built for the IFTTT Webhook sending SMS
String url_google_drive;            // url that gets built for the IFTTT Webhook logging to google drive spreadsheet
String Status ="**_Starting_on:";  // Status payload for Google Sheet.  We log all starts and reboots

// Define and set up some variables
int sensorValue = 0;  // reading from A0.  This pin detects the doorbell (ADC reading between 0-1024)

// Define pins
const int led = 2;     // Blue on board LED is on PIN2.  Active LOW.  Blinks it between reads
const int bell = A0;  // analog input for the doorbell transducer. 1MOhm on board in // with 1MOhm from the reed switch pickup

// Program control variables
int Seconds_dwell_after_detect = 8;  // Prevents logging flood and debounce.   Sensor reads, LED flashing, etc.
int logging = 1; // If 1 then SMS and log to cloud.  Any other value (0) turns it off.

void setup(){  // This is run once.
  pinMode(led, OUTPUT);  // set up the onbaord LED pin as an output.    
  Serial.begin(115200);  // turn on the serial monitor for debug

  // wait until serial port opens for native USB devices
  while (! Serial) {
    delay(10);
  }
 
  // Is the WiFi working?
  WiFi.begin(ssid, password);
  Serial.println("");
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print("Trying to connect to ");
    Serial.print(ssid);
    Serial.print(" on ");
    Serial.println(WiFi.localIP());
    for (int x = 0; x < 20; x++) { // 
      digitalWrite(led, !digitalRead(led));  // toggle state of the on board blue LED. Shows program is trying to WiFi connect
      //Serial.println("Server Start blink loop....");
      delay(5);   // Delay so short the LED looks like it is always on
    } // endfor WiFi blink connect
  }
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.println(WiFi.localIP());

  for (int x = 0; x < 10; x++) { // 5 slow LED blinks to slow WIFI Connected.
  digitalWrite(led, !digitalRead(led));  // toggle state of the on board blue LED.
  Serial.println("WIFI is Connected....");
  delay(500);   } // endif for WIFI Connected blink


  // Use WiFiClient class to create TCP connections for WiFi logging
  WiFiClient client;
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
    Serial.println("connection failed");  // Boo!!!
    return;
  }

  // Trigger the IFTTT Webhook Channel to update a Google sheet with the activity of the server starting/restarting
  // This can help log power outs, etc.  For first run we defined String Status for identify a startup condition.
  // Create the request for IFTTT google drive
  url_google_drive = "https://maker.ifttt.com/trigger/" + TRIGGER_NAME_google_drive + "/with/key/" + MAKER_SECRET_KEY + "?value1=" + String(Status);
  Serial.println (url_google_drive);
  Serial.println(" "); 
   
  // This sends the request to the IFTTT server
  client.print(String("POST ") + url_google_drive + " HTTP/1.1\r\n" +
  "Host: " + host + "\r\n" +
  "Connection: close\r\n\r\n"); 
  delay(500);  // Delay for web traffic; maybe not required. 

  // Read all the lines of the reply from server and print them to Serial
  while(client.available()){
     String line = client.readStringUntil('\r');    }

  String Status = "Payload_String_for_Google_Sheet";
}
 
void loop(){   // Loop forever or until the Dallas Cowboys win a Super Bowl
  // Read the A0 pin and post based on the delay values set above.
  // The blue onboard LED will fast blink while polling
  sensorValue = analogRead(bell);
  //Serial.println (sensorValue);   //debug only

  // Fast Blink Blue LED while waiting for doorbell press
  digitalWrite(led, !digitalRead(led));  // toggle state of the on board blue LED. Shows program is running
  Serial.println("Waiting for DoorBell....");
  delay(50);   

  // Use WiFiClient class to create TCP connections for IFTT
  WiFiClient client;
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
    Serial.println("connection failed");
    return;
  }
 
  if (sensorValue < 200 || sensorValue > 300) {  //  A0 values between 200-300 are normal.  Increases (apprx doubles) when DB is pressed
      Serial.println("----------Doorbell Dectected----------");

      if (logging == 1) { // is logging turned on? Non "1" is for debug...  Typically would be set = 1
        Serial.println("***** Logging is ON *****");

        // Build the IFTTT Webhoo Channel for SMS url
        url_SMS = "https://maker.ifttt.com/trigger/" + TRIGGER_NAME_SMS + "/with/key/" + MAKER_SECRET_KEY;
        client.print(String("POST ") + url_SMS + " HTTP/1.1\r\n" +
        "Host: " + host + "\r\n" +
        "Connection: close\r\n\r\n");
        Serial.println(" ");
        Serial.println("SMS payload to IFTTT is:");
        Serial.println(url_SMS);   
        Serial.println(" ");  

        // This must be run before any IFTTT webhook
        const int httpPort = 80;
        if (!client.connect(host, httpPort)) {
          Serial.println("connection failed");
          return;
        }
           
        // Set up IFTTT Webhook Channel to update a Google sheet with the activity. 
        Status ="__Doorbell_on:";
        url_google_drive = "https://maker.ifttt.com/trigger/" + TRIGGER_NAME_google_drive + "/with/key/" + MAKER_SECRET_KEY + "?value1=" + String(Status);
        client.print(String("POST ") + url_google_drive + " HTTP/1.1\r\n" +
        "Host: " + host + "\r\n" +
        "Connection: close\r\n\r\n");
        Serial.println(" ");
        Serial.println("IFTTT url payload to Google Drive is:");
        Serial.println(url_google_drive);
        Serial.println(" ");
   
      } else {   // We are not sending to IFTTT.  Debug mode
         Serial.println("Logging is OFF.");
         Serial.println(" ");
      } // endif/else logging 
   
    for (int x = 0; x < Seconds_dwell_after_detect*2; x++) { // DoorBell dectected. Pause and LED flash for visual acknowledgement
      digitalWrite(led, !digitalRead(led));  // toggle state of the on board blue LED
      Serial.println("Doorbell dectected,  Now in BLINK Loop....");
      delay(500);   } // endfor DoorBell Dwell loop  
       
  }   // end doorbell A0 pressed
}  // end of endless loop
-----
That's it and thanks for stopping by!!!

Monday, December 3, 2018

Internet Time Morse Code Clock

-----
There are plenty of ways to irritate your XYL with an obsession of ham radio; but if you feel you need just one more then keep reading.   The black box above is actually a clock.  It's a cool clock if you know Morse Code (or CW), but it is worthless if you don't.
-----
The brains of the Morse Code clock is an ESP8266 with on board WiFi.  At power up, the rig connects to WiFi and fetches the time from the internet (so always accurate).  On first run the 'clock' sends out some Self Test tones after getting the time.  These Self Test tones can be something like a callsign, but it could be anything or even nothing.  The pace (or WPM) of the Morse Code is adjustable by a variable in the source code.  In the example videos it is 10WPM as calculated by "The Paris Standard".

From that point on the time of day (localized to your time zone) is output to a speaker and LED tower in Morse Code (24 hour format) as HHMM.  A calm quite house is filled with dit-dah tones and flashing light.
----
The build is not that difficult as shown in the schematic below.  On the bench it looks something like this:
-----
A metal box found around the shack was repurposed to clean up the look.  This vid shows the end result.  At power up the LED tower flashes until it gets the time from the internet.  Then the Self Test tones and LED flashes happen.   After that, only the time is "announced" when the red button is pressed.
It all came out pretty nice.
-----
If you decide to duplicate the build here is the simple schematic:
 -----
And the Arduino/ESP8266 IDE Source Code:
 _______________________________________________________________
/*
 *  NOV2018
 *  CW "Talking Clock"
 *  WhiskeyTangoHotel.Com
 *
 * Get Internet time.
 * Upon a button press, convert the Internet time to defined local time zone.
 * Parse the Internet time to HHMM (24 hr format)
 * Convert the HHMM time to Morse Code (CW) dits and dahs
 * Play the Morse Code converted times over a speaker and a LED.
 *
 * Hold the button down for over a second and get a random letter or number played.
 *
 * At startup a self test sends a callsign (or user define msg) to
 * the speaker in CW.
 *
 * BTW, CW is fun to learn.
 *  --... / ...--   //  --... / ...--
 *
*/

#include <time.h>

// Set up or the Wireless.  We wil need this to get the internet time
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>

// WiFi Connection Information
const char* ssid = "Virus-2.4";         // PRIVATE: Enter your personal SSID setup information. *PRIVATE*
const char* password = "zenakelsocats"; // PRIVATE: Enter your personal PASSWORD setup information. *PRIVATE*
ESP8266WebServer server(80);

// Define and set up some variables
int timezone = 18;  // Chicago = 18
int dst = 0;        // 0 or 1.  1 for DST or "Summer Adjusted Time"
String Callsign = "W1AW";   // ALL CAPS!!!  Used for self test 'beeps' at startup.  Could be anything alphanumic you like
String Time_substring;   // Used for parsing
String Parsedtime; // Feteched internet time boiled down to HHMM in 24 hr format.
String Dit_or_Dah;  // Output a dit or dah to the speaker.  Used when parsing a character

int Pitch = 600;      // Normally around 600Hz.  This this the pitch frequency of the CW tones
float WPM = 12;       // How fast the letters are sent to the speaker using the "Paris Standard" in Words per Minute (8-25; depending on skill level)
//  Paris Standard formula: Tmsec = (1200 / WPM) WPM is the desired speed in words-per-minute. T is the dit time in mSecs
float Tmsec = 1200 / WPM;
float Dit = Tmsec;
float intercharacter_spacing = Dit;
float Dah = Tmsec * 3;
float interword_spacing = Dah;
int Random_CW;  // Random number generated to select a random character
String Random_CW_Character;  // This is the random character sent to speaker/LED

// Load the CW database 'dits' and 'dahs' for each Alphanumeric. Boring....
String A = ".-";;
String B = "-...";
String C = "-.-.";
String D = "-..";
String E = ".";
String F = "..-.";
String G = "--.";
String H = "....";
String I = "..";
String J = ".---";
String K = "-.-";
String L = ".-..";
String M = "--";
String N = "-.";
String O = "---";
String P = ".--.";
String Q = "--.-";
String R = ".-.";
String S = "...";
String T = "-";
String U = "..-";
String V = "...-";
String W = ".--";
String X = "-..-";
String Y = "-.--";
String Z = "--..";
String zero = "-----";
String one = ".----";
String two = "..---";
String three = "...--";
String four = "....-";
String five = ".....";
String six = "-....";
String seven = "--...";
String eight = "---..";
String nine = "----.";

// Output pins
const int led = 2;  // NEGATIVE Logic (LOW is ON) Blue on board LED is on PIN2 for this NoderMCU 1.0 ESP8266.
const int speaker = 14;  // tone outputs to the EXTERNALLY amplified speaker to pin 14
int time_buttonPin = 4;  // When button pressed CW the time to the speaker.  pin 4
boolean buttonState = LOW;  // for the time_buttonPin status

void setup(void){  // This is run once.
  Serial.begin(115200);  // turn on the serial monitor for debug
  //Serial.setDebugOutput(true);  // Turn on the Serial Debugger if desired

  //Define the I/O
  pinMode(led, OUTPUT);  // set up the onboard Blue LED as an output.
  pinMode(time_buttonPin, INPUT); // Input for the button switch.  if HIGH (pressed) play CW the time to the speaker

  // Connect WiFi.  Is the WiFi working?
  WiFi.begin(ssid, password);
  Serial.println("");
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print("Trying to connect to ");
    Serial.print(ssid);
    Serial.print(" on ");
    Serial.println(WiFi.localIP());
    for (int x = 0; x < 20; x++) { //
      digitalWrite(led, !digitalRead(led));  // toggle state of the on board blue LED. Shows program is trying to WiFi connect
      delay(50); 
    } // endfor WiFi blink connect
  }
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.println(WiFi.localIP());

  //Send tones for the variable Callsign to the speaker as a self test.
  Serial.println("Sending " + Callsign + " as Self Test......");
  int L = Callsign.length();
  for (int i=0; i<L; i++) {  // Start Self Test Convert the time numbers to dit and dahs
    Time_substring = Callsign.substring(i, i+1);
    if (Time_substring == "0") { Time_substring = zero; }
    if (Time_substring == "1") { Time_substring = one; }
    if (Time_substring == "2") { Time_substring = two; }
    if (Time_substring == "3") { Time_substring = three; }
    if (Time_substring == "4") { Time_substring = four; }
    if (Time_substring == "5") { Time_substring = five; }
    if (Time_substring == "6") { Time_substring = six; }
    if (Time_substring == "7") { Time_substring = seven; }
    if (Time_substring == "8") { Time_substring = eight; }
    if (Time_substring == "9") { Time_substring = nine; }
    if (Time_substring == "A") { Time_substring = A; }
    if (Time_substring == "B") { Time_substring = B; }
    if (Time_substring == "C") { Time_substring = C; }
    if (Time_substring == "D") { Time_substring = D; }
    if (Time_substring == "E") { Time_substring = E; }
    if (Time_substring == "F") { Time_substring = F; }
    if (Time_substring == "G") { Time_substring = G; }
    if (Time_substring == "H") { Time_substring = H; }
    if (Time_substring == "I") { Time_substring = I; }
    if (Time_substring == "J") { Time_substring = J; }
    if (Time_substring == "K") { Time_substring = K; }
    if (Time_substring == "L") { Time_substring = L; }
    if (Time_substring == "M") { Time_substring = M; }
    if (Time_substring == "N") { Time_substring = N; }
    if (Time_substring == "O") { Time_substring = O; }
    if (Time_substring == "P") { Time_substring = P; }
    if (Time_substring == "Q") { Time_substring = Q; }
    if (Time_substring == "R") { Time_substring = R; }
    if (Time_substring == "S") { Time_substring = S; }
    if (Time_substring == "T") { Time_substring = T; }
    if (Time_substring == "U") { Time_substring = U; }
    if (Time_substring == "V") { Time_substring = V; }
    if (Time_substring == "W") { Time_substring = W; }
    if (Time_substring == "X") { Time_substring = X; }
    if (Time_substring == "Y") { Time_substring = Y; }
    if (Time_substring == "Z") { Time_substring = Z; }  

    // Now send the dit/dahs for the Self Test Callsign Character  
    int CW_length = Time_substring.length();

    for (int j=0; j<CW_length; j++)   {  // Send tones and blink LED in CW
      digitalWrite(led , HIGH); // NEGATIVE LOGIC.  Make sure the onboard LED is off
      Dit_or_Dah = Time_substring.substring(j, j+1);
      //Serial.println(Dit_or_Dah);
    
      if (Dit_or_Dah == ".") {   // process a Dit
        digitalWrite(led , LOW);
        //tone(speaker, Pitch, Dit); // Comment for LED only; no BEEPS on Self Test
        delay(Dit);
        digitalWrite(led , HIGH);
      }  // end if process a Dit

      if (Dit_or_Dah == "-") {   // process a Dah
        digitalWrite(led , LOW);
        // tone(speaker, Pitch, Dah); // Comment for LED only; no BEEPS on Self Test
        delay(Dah);
        digitalWrite(led , HIGH);
      }  // end if process a Dah

      delay(intercharacter_spacing);   //  a delay of "1 Dit" between dit/dahs in a character    
    }  // send for loop dit/dahs for selected time number
    delay (interword_spacing);   // done with sending character, so a longer "3 * Dit" delay"   
  } // end for loop on the Self Test

  // Do we want summertime or standard time?  Default is standard time dst = 0.  1 for DST or "Summer Adjusted Time"
  int buttonState = digitalRead(time_buttonPin);
  if (buttonState == HIGH) {   // if the button is pushed after the self test; then set to Summer Time; dst = 1
    tone(speaker, Pitch, Dit); // Quick 'dit' to say "I'm in Summer Time mode"
    dst = 1;
  }
  
  // Get the current time from "THE INTERNET! and adjust for timezone and dst"
  configTime((timezone + dst) * 3600, 0, "pool.ntp.org", "time.nist.gov");
  Serial.println("\nWaiting for time");
  while (!time(nullptr)) {
    Serial.print(".");
    delay(100);
  }

  Serial.println("Self test completes!!!");
  digitalWrite(led , HIGH);  // turn off the LED
  Serial.println("");

  delay(1000);

} // End void setup loop

void loop(void){  // Loop forever waiting for button push.  If pushed CW the local time as HHMM in CW and go back to waiting for that button
  int buttonState = digitalRead(time_buttonPin);   // check if the pushbutton is pressed. 
    if (buttonState == HIGH) {   // button is pressed, CW time to speaker or random character?
      delay(1000);
      int buttonState = digitalRead(time_buttonPin);   // check if the pushbutton is STILL pressed.
      if (buttonState == HIGH)   { // if the button is still pressed after the delay then rnd character, not time.
        digitalWrite(led , LOW); // NEGATIVE LOGIC.  Blink the LED to show RND char mode entered
        delay(150);
        digitalWrite(led , HIGH); // NEGATIVE LOGIC.  Make sure the  LED is off
        delay(1000);
        do {
          Random_CW = random(48,90); //48 is 0.  57 is 9.  A is 65.  Z is 90        
        } while (Random_CW > 57 and Random_CW < 65);   // if the random# did not map to a letter or number then try again.
        char Random_CW_Character = Random_CW;
        Serial.print("Random CW Character is: ");
        Serial.println(Random_CW_Character);
      
        Time_substring = Random_CW_Character;
        if (Time_substring == "0") { Time_substring = zero; }
        if (Time_substring == "1") { Time_substring = one; }
        if (Time_substring == "2") { Time_substring = two; }
        if (Time_substring == "3") { Time_substring = three; }
        if (Time_substring == "4") { Time_substring = four; }
        if (Time_substring == "5") { Time_substring = five; }
        if (Time_substring == "6") { Time_substring = six; }
        if (Time_substring == "7") { Time_substring = seven; }
        if (Time_substring == "8") { Time_substring = eight; }
        if (Time_substring == "9") { Time_substring = nine; }
        if (Time_substring == "A") { Time_substring = A; }
        if (Time_substring == "B") { Time_substring = B; }
        if (Time_substring == "C") { Time_substring = C; }
        if (Time_substring == "D") { Time_substring = D; }
        if (Time_substring == "E") { Time_substring = E; }
        if (Time_substring == "F") { Time_substring = F; }
        if (Time_substring == "G") { Time_substring = G; }
        if (Time_substring == "H") { Time_substring = H; }
        if (Time_substring == "I") { Time_substring = I; }
        if (Time_substring == "J") { Time_substring = J; }
        if (Time_substring == "K") { Time_substring = K; }
        if (Time_substring == "L") { Time_substring = L; }
        if (Time_substring == "M") { Time_substring = M; }
        if (Time_substring == "N") { Time_substring = N; }
        if (Time_substring == "O") { Time_substring = O; }
        if (Time_substring == "P") { Time_substring = P; }
        if (Time_substring == "Q") { Time_substring = Q; }
        if (Time_substring == "R") { Time_substring = R; }
        if (Time_substring == "S") { Time_substring = S; }
        if (Time_substring == "T") { Time_substring = T; }
        if (Time_substring == "U") { Time_substring = U; }
        if (Time_substring == "V") { Time_substring = V; }
        if (Time_substring == "W") { Time_substring = W; }
        if (Time_substring == "X") { Time_substring = X; }
        if (Time_substring == "Y") { Time_substring = Y; }
        if (Time_substring == "Z") { Time_substring = Z; } 

        int CW_length = Time_substring.length();
      
        for (int j=0; j<CW_length; j++)   {  // Send tones and blink LED in CW
          digitalWrite(led , HIGH); // NEGATIVE LOGIC.  Make sure the onboard LED is off
          Dit_or_Dah = Time_substring.substring(j, j+1);
          //Serial.println(Dit_or_Dah);
        
          if (Dit_or_Dah == ".") {   // process a Dit
            digitalWrite(led , LOW);
            tone(speaker, Pitch, Dit);
            delay(Dit);
            digitalWrite(led , HIGH);
          }  // end if process a Dit
  
          if (Dit_or_Dah == "-") {   // process a Dah
            digitalWrite(led , LOW);
            tone(speaker, Pitch, Dah);
            delay(Dah);
            digitalWrite(led , HIGH);
          }  // end if process a Dah
  
          delay(intercharacter_spacing);   //  a delay of "1 Dit" between dit/dahs in a character    
        }  // send for loop dit/dahs for selected time number
       
      }  // end of if the button is still pressed after the delay then rnd character, not time.
      else { // random character code above; time code below
        time_t now = time(nullptr);
        Serial.println("---------------------------------------");
        Serial.print("Internet time adjusted to tone zone: ");
        Serial.println(ctime(&now));
        Parsedtime = (ctime(&now));
    
        // Build the Parsed time from the internet time. The time is 24 hr format and always 4 characters in HHMM
        Parsedtime = Parsedtime.substring(11,12) + Parsedtime.substring(12,13) + Parsedtime.substring(14,15) + Parsedtime.substring(15,16);
        Serial.print("Parsedtime as HHMM is: ");
        Serial.println(Parsedtime);    
      
        for (int i=0; i<4; i++) {  // Convert the time numbers to dit and dahs. Always 4 characters in HHMM
          Time_substring = Parsedtime.substring(i, i+1);
          Serial.print("Currently sending Parsedtime character: ");
          Serial.println(Time_substring);
        
          if (Time_substring == "0") { Time_substring = zero; }
          if (Time_substring == "1") { Time_substring = one; }
          if (Time_substring == "2") { Time_substring = two; }
          if (Time_substring == "3") { Time_substring = three; }
          if (Time_substring == "4") { Time_substring = four; }
          if (Time_substring == "5") { Time_substring = five; }
          if (Time_substring == "6") { Time_substring = six; }
          if (Time_substring == "7") { Time_substring = seven; }
          if (Time_substring == "8") { Time_substring = eight; }
          if (Time_substring == "9") { Time_substring = nine; }
    
          // Now send the dit/dahs for the time number processed      
          int CW_length = Time_substring.length();
    
          for (int j=0; j<CW_length; j++)   {  // Send tones and blink LED in CW
            digitalWrite(led , HIGH); // NEGATIVE LOGIC.  Make sure the onboard LED is off
            Dit_or_Dah = Time_substring.substring(j, j+1);
                    
            if (Dit_or_Dah == ".") {   // process a Dit
              digitalWrite(led , LOW);
              tone(speaker, Pitch, Dit);
              delay(Dit);
              digitalWrite(led , HIGH);
            }  // end if process a Dit
    
            if (Dit_or_Dah == "-") {   // process a Dah
              digitalWrite(led , LOW);
              tone(speaker, Pitch, Dah);
              delay(Dah);
              digitalWrite(led , HIGH);
            }  // end if process a Dah
    
            delay(intercharacter_spacing);   //          
          }  // send for loop dit/dahs for selected time number  
          delay (interword_spacing);       
        } // end for loop Convert the HHMM time numbers to dit and dahs
    
        Serial.println("Sending time tones completed!!!");
        delay(1000);  // Delay for a sec just beacause...
      }  //  If checking for long press for RND character
    }  // End button press if
}  // End of Loop forever waiting for button push.
________________________________________________________
-----
Thanks for stopping by and 73!