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!