Sony ICF-205 Radio Clock |
Now having done the ESP8266 NTP WiFi clock using the MAX7129 LED Driver, I figured it would be a doddle to keep the casing and the LEDs and replace the electronics. I would need to do a good bit of wiring, but it would be light work without much programming or mental effort. Boy was I mistaken.
Clock IC SC8560 |
Luckily danman had the perfect solution. You set the time by power-cycling it and then shorting the 'Set Hour' button.
Modifications top left: Live wire cut to attach cable to relay1, extra white & blue AC mains cable to 5V power module. Middle left: button 1 shorted, button 2 attached to cable for relay2. |
Rear view showing 5Vdc Power Supply mounted in 9V battery compartment |
Ready for ESP8266 programming. Click here for youtube video. |
The time is obtained from the WiFi connection via the NTP servers in pool.ntp.gov.
Since I have only 2 relays, I needed to wait till the exact hour, e.g. 01:00 before I can set the clock. There are 3 ways to set the time:
1. on power up, the programs waits for the minute to be 00 then sets the clock to the exact time of day
2. at 01:00 in the morning the program will set the time again
3. using http://12.34.56.78:8000/reset to power-cycle the clock and http://12.34.56.78:8000/hour to advance the hour display
The libraries used are TimeLib.h, ESP8266WiFi.h, WiFiUdp.h and RemoteDebug.h, all available from the Library Manager downloads in the Adruino IDE.
Since the serial debug monitor is difficult to get to, RemoteDebug.h provides a debug link via telnet, e.g. 'telnet 12.34.56.78'.
Click here for the youtube video.
The program is:
#include <TimeLib.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include "RemoteDebug.h" // 2019-03-22
const char ssid[] = "YourAccessPoint"; // your network SSID (name)
const char password[] = "YourPassword"; // your network password
IPAddress staticIP(12,34,56,78);
IPAddress gateway(12,34,56,1);
IPAddress subnet(255,255,255,0);
IPAddress dns(8,8,8,8); // 2019-03-24
WiFiServer server (8000);
// NTP Servers:
static const char ntpServerName[] = "pool.ntp.org"; // 2019-03-24 from us.pool.ntp.org
//static const char ntpServerName[] = "time.nist.gov";
//static const char ntpServerName[] = "time-a.timefreq.bldrdoc.gov";
//static const char ntpServerName[] = "time-b.timefreq.bldrdoc.gov";
//static const char ntpServerName[] = "time-c.timefreq.bldrdoc.gov";
const int timeZone = +8; // Malaysia Time Time
//const int timeZone = 1; // Central European Time
//const int timeZone = -5; // Eastern Standard Time (USA)
//const int timeZone = -4; // Eastern Daylight Time (USA)
//const int timeZone = -8; // Pacific Standard Time (USA)
//const int timeZone = -7; // Pacific Daylight Time (USA)
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);
//Hex command to send to serial for close relay
byte relON[] = {0xA0, 0x01, 0x01, 0xA2};
//Hex command to send to serial for open relay
byte relOFF[] = {0xA0, 0x01, 0x00, 0xA1};
//Hex command to send to serial for close relay
byte rel2ON[] = {0xA0, 0x02, 0x01, 0xA3};
//Hex command to send to serial for open relay
byte rel2OFF[] = {0xA0, 0x02, 0x00, 0xA2};
#define HOSTNAME "NTP_ClockRadio"
RemoteDebug Debug;
int cold_start = 0; // 2019-03-25
void setup()
{
cold_start = 1; // 2019-03-25
Serial.begin(115200);
while (!Serial) ; // Needed for Leonardo only
delay(250);
Serial.println("TimeNTP Example");
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.mode(WIFI_OFF); // 2018-10-09 workaround
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
WiFi.config(staticIP, dns, gateway, subnet); // Static IP. Not required for dhcp
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Debug.begin(HOSTNAME);
Debug.setResetCmdEnabled(true); // Enable the reset command
Debug.showProfiler(true); // Profiler (Good to measure times, to optimize codes)
Debug.showColors(true); // Colors
Serial.println("* Arduino RemoteDebug Library");
Serial.println("*");
Serial.print("* WiFI connected. IP address: ");
Serial.println(WiFi.localIP());
Serial.println("*");
Serial.println("* Please use the telnet client (telnet for Mac/Unix or putty and others for Windows)");
Serial.println("* or the RemoteDebugApp (in browser: http://joaolopesf.net/remotedebugapp)");
Serial.println("*");
Serial.println("* This sample will send messages of debug in all levels.");
Serial.println("*");
Serial.println("* Please try change debug level in client (telnet or web app), to see how it works");
Serial.println("*");
// Start the server
Serial.println("Starting http server ...");
debugI("Starting http server ...");
server.begin();
delay(50);
Serial.println("Starting UDP");
debugI("Starting UDP");
Udp.begin(localPort);
Serial.print("Local port");
debugI("Local port %d", Udp.localPort());
Serial.println(Udp.localPort());
Serial.println("waiting for sync");
debugI("waiting for sync");
Debug.handle();
setSyncProvider(getNtpTime);
setSyncInterval(300);
}
time_t prevDisplay = 0; // when the digital clock was displayed
static int dots = 0;
static int val = 0; // 2018-10-25
static int commas = 0;
static int Minute = 45; // 2019-03-25 Init to nonzero
static int Hour = 0;
static int daily_sync = 0;
#define Minute_SET 0 // should always be zero. Nonzero values for testing only
#define Hour_SET 1 // should always be one. Other values for testing only
void loop()
{
// 2019-02-24 Check for broken wifi links
if (WiFi.status() != WL_CONNECTED) {
delay(10);
// Connect to WiFi network
Serial.println();
delay(10);
debugE("Connection lost. Reconnecting WiFi ...");
Serial.print("Connection lost. Reconnecting WiFi ...");
delay(10);
WiFi.mode(WIFI_OFF); // 2018-10-09 workaround
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
WiFi.config(staticIP, dns, gateway, subnet); // Static IP. Not required for dhcp
while (WiFi.status() != WL_CONNECTED) {
Serial.print("x");
debugW("x");
delay(500);
dots++;
if (dots >= 1000)
{
dots = 0;
Serial.println("Giving up! Waiting for WDT reset ...");
debugE("Giving up! Waiting for WDT reset ...");
delay(20);
while (1)
{
debugE("Death loop waiting for WDT reset ...");
delay(1000);
Debug.handle();
}
}
Debug.handle();
}
Serial.println("");
delay(10);
Serial.println("WiFi reconnected");
debugI("WiFi reconnected");
// Start the server
server.begin();
Serial.println("Server restarted");
debugW("Server restarted");
delay(10);
// Print the IP address
Serial.print("IP address: ");
delay(10);
Serial.println(WiFi.localIP());
delay(10);
}
// 2019-03-24 Run this first
if (timeStatus() != timeNotSet) {
if (now() != prevDisplay) { //update the display only if time has changed
prevDisplay = now();
digitalClockDisplay();
}
// Code to reset the Sony SC8560 clock radio 2019-03-25
if (cold_start) {
switch (cold_start)
{
case 1:
Minute = minute();
if (Minute == Minute_SET) // Wait for the hour
cold_start++;
else {
Serial.print("Cold start reset & sync: waiting till minute is ");
Serial.print(Minute_SET);
Serial.print(" now ");
Serial.println(Minute);
debugV("Cold start reset & sync waiting till minute is %d, now %d", Minute_SET, Minute);
delay(500);
}
break;
case 2:
Serial.println("Cold start reset & sync started");
debugI("Cold start reset & sync started %d:%d", hour(), minute());
// On power-up clock is reset but the minute display will be wrong as the esp8266
// Might need to wait a few minutes for the hour.
Serial.write (relON, sizeof(relON)); // relay is normally closed
delay(2000); // It takes awhile for the clock to reset
Serial.write (relOFF, sizeof(relOFF)); // relay is normally closed
delay(400);
// Now after a reset the clock time is 00:00
Hour = hour();
for (int i = 0; i<Hour;i++) // Set the hour.
{
Serial.write (rel2ON, sizeof(rel2ON)); // relay is normally open
delay(100);
Serial.write (rel2OFF, sizeof(rel2OFF)); // relay is normally open
delay(100);
debugI("Cold start sync-pulse Hour pulse #%d", i);
}
cold_start = 0;
break;
default:
debugE("Error! illegal value cold_start %d", cold_start);
Serial.print("Error! illegal value cold_start ");
Serial.println(cold_start);
delay(1000);
break;
}
}
else {
if (daily_sync == 0)
{
if (minute()==Minute_SET && hour()==Hour_SET)
daily_sync=1;
}
else
{
Serial.println("Daily sync started");
debugI("Daily reset & sync started %d:%d", hour(), minute());
Serial.write (relON, sizeof(relON)); // relay is normally closed
delay(2000);
Serial.write (relOFF, sizeof(relOFF)); // relay is normally closed
delay(500);
// Now after a reset the clock time is 00:00
Serial.write (rel2ON, sizeof(rel2ON)); // relay is normally open
delay(100);
Serial.write (rel2OFF, sizeof(rel2OFF)); // relay is normally open
debugI("Daily sync, 1 Hour pulse");
daily_sync = 0;
}
}
}
else {
debugD("timeStatus() returned timeNotSet!");
delay(1000);
}
Debug.handle();
// Check if a client has connected
WiFiClient client = server.available();
if ( ! client ) {
return;
}
//Wait until the client sends some data
while ( ! client.available () )
{
delay (10); // 2019-02-28 reduced from 100
Serial.print(".");
if (commas++ > 5) {
commas = 0;
Serial.println("Terminating client connecting without reading");
debugW("Terminating client connecting without reading");
delay(20);
client.stop();
return;
}
Debug.handle();
}
client.setTimeout(2000); // 2019-03-24
Serial.println("new client connect, waiting for data ");
debugI("new client connect, waiting for data ");
// Read the first line of the request
String req = client.readStringUntil ('\r');
client.flush ();
// Match the request
String s = "";
// Prepare the response
if (req.indexOf ("/reset") != -1) // Reset power to radio clock
{
Serial.println("Reset & sync command received");
Serial.write (relON, sizeof(relON)); // relay is normally closed
val = 1; // if you want feedback see below
s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\nReset";
s += "</html>\n";
client.print (s);
delay(2000);
Serial.write (relOFF, sizeof(relOFF));
Serial.println("Reset command executed");
debugW("Reset command executed");
}
else if (req.indexOf ("/hour") != -1) {
Serial.println("Set hour command received");
debugI("Set hour command received");
Serial.write (rel2ON, sizeof(rel2ON));
val = 0; // if you want feedback
s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\nIncremented hour";
//s += (val)?"on":"off";
s += "</html>\n";
client.print (s);
delay(100);
Serial.write (rel2OFF, sizeof(rel2OFF));
} else if (req.indexOf("/index.html") != -1) {
s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\nStatus is ";
//s += (val)?"on":"off";
s += val; // 2018-10-25
s += "</html>\n";
client.print (s);
Serial.println("Status command received");
debugI("Status command received");
}
else {
Serial.println("invalid request");
debugW("invalid request");
s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\nInvalid request</html>\n";
client.print (s);
}
delay (10);
client.stop(); // 2019-02-24
Serial.println("Client disonnected");
debugI("Client disonnected");
delay(10);
Debug.handle();
}
void digitalClockDisplay()
{
// digital clock display of the time
Serial.print(hour());
printDigits(minute());
printDigits(second());
Serial.print(" ");
Serial.print(day());
Serial.print(".");
Serial.print(month());
Serial.print(".");
Serial.print(year());
Serial.println();
debugV("%d:%d:%d", hour(), minute(), second());
}
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();
}
[Update 2019-04-25] After a few weeks monitoring it was time for final assembly. Since the wires from the LED radio clock carry 230Vac mains voltages, a little care is in order. I had a long-deceased TP-LINK TD-8610 modem whose plastic case seemed the right size:
TP-LINK 8610 |
If you looked very carefully, you might spot the flash marks from the lightning strike (just to the left of the white cable tie) that killed the original TD-8610. |
Somehow it did not look too out of place |
There you have it, a clock radio that is synchronized to the Internet clock. With a bit of luck that Sony ICF-C205 Radio Clock might last another 20 years. Happy Trails.