Home Electronics Mechanical Random

ESP8266/ESP8285 LED clocks

I built some really simple clocks, just an LED module and an ESP module. This works with the ESP01 or larger ESP16 based modules - the ESP01 supports basic display functions, the larger modules have the option of an alarm sounder and 12/24 hour switch and the capacity to add other devices, as a clock is hardly a taxing job for the ESP82's.
As well as the clocks displaying the time, I can use them to call the children downstairs when it's time to eat, using a button on a display in the kitchen, or display any other 7 segment message.
There is a periodic clock signal broadcast on my home network, which is used by several devices, but these specifically display it.
Here's the video: (sorry for the silly intro)

The Protocol
For these simple devices, I use a very simple UDP based protocol. This can be either direct or broadcast based.

IoT house protocol notes

First 3 bytes in common:
  00	Device Type (Address High)
  01	Device Address (Address Low) or FF for broadcast.
  02	Message Length
  
Device Types
  81	Clock
  
Clocks:
  03	0x Normal
		1x Alarm
		2x Blink
		4x Override Updates For 15 seconds
		8b Brightness (Message length >= 4)
		
		x0 Numeric
		x1 Display Direct 7seg+dot.
		x2 UnixTime
		x4 Seperator : 
		x8 Scroll (not implemented yet)
  04-07 Data
  08-0F	Optional data for larger displays
Example packet:
  81 ff 08 71 71 5c 5c 5e
Display "Food", and beep, ignoring other updates for 30 seconds.

The Code

/* IoT Clock
 * 
 * UDP
 * Address 81 0x
 * ymmv.co.uk
 * 
 */
 
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <TM1637Display.h>
#include <WiFiUdp.h>

#define DEVICE_TYPE 0x81
#define DEVICE_ADDR 0x02

#define CLK 0
#define DIO 2
#define BEEP 4
#define DISP12HR 5 // Pull LOW for 12 hour clock.

ESP8266WiFiMulti wifiMulti;
WiFiUDP UDP;
TM1637Display display(CLK, DIO);

const int PACKET_SIZE = 16;
byte rxBuf[PACKET_SIZE];
uint32_t unixTime = 0;
unsigned long lastMillis = 0;
uint32_t ticks = 0;  // Each tick is half a second.
uint8_t data[] = { 0x40, 0xc0, 0x40, 0x40 };
uint8_t blank[] = { 0x00, 0x00, 0x00, 0x00 };
uint8_t rxMode = 0x00;


  
void setup() {
  pinMode(BEEP,OUTPUT);
  pinMode(DISP12HR,INPUT_PULLUP);
  Serial.begin(115200);
  display.setBrightness(0x0f);
  startWiFi();
  UDP.begin(6502);
  lastMillis=millis();
}

void loop() {
  unsigned long currentMillis = millis();
  bool bHold = ( ((rxMode & 0x40) > 0) && (ticks < 60) );
  uint32_t lastTick = ticks;
  // deal with the clocks!
  if ((currentMillis < lastMillis) ||// rollover
      (currentMillis >= (lastMillis + 500)) ) {
        lastMillis = lastMillis + 500;
        ticks++;
    if ((ticks & 0x01) == 0) unixTime++;  // increment a second
    if ( ((rxMode & 0x10) > 0) && (ticks > 30) ) { // alarm has been beeping for 15 seconds
      rxMode = rxMode & 0xef;
    };
    if ( ((rxMode & 0x01) > 0) && (ticks > 120) ) { // After displaying 7 seg message for 1 minute, revert to clock
      rxMode = 0x06;
    }
  };
  
  

  // do the work.
  if (getPacket()) {
    if ( ((rxBuf[0] == DEVICE_TYPE) && ( (rxBuf[1]==DEVICE_ADDR) || (rxBuf[1]==0xff) )) && (!bHold) ) {
      // our packet
      ticks = 0;
      uint8_t lastrxMode = rxMode;
      rxMode = rxBuf[3];

      if ((rxMode & 0x80) > 0) {  // brightness
        display.setBrightness(rxMode & 0x0f);
      } else {

        if ((rxMode & 0x3) == 0) {  // numbers 0-F
          data[0] = display.encodeDigit(rxBuf[4]);
          data[1] = display.encodeDigit(rxBuf[5]);
          data[2] = display.encodeDigit(rxBuf[6]);
          data[3] = display.encodeDigit(rxBuf[7]);
          // unixTime = (((rxBuf[4] * 10) + rxBuf[5]) * 3600) + (((rxBuf[6] * 10) + rxBuf[7]) * 60); // Aproximate time
        }
  
        if ((rxMode & 0x01) > 0) {  // 7 seg data direct
          data[0] = rxBuf[4];
          data[1] = rxBuf[5];
          data[2] = rxBuf[6];
          data[3] = rxBuf[7];
        }
        
        if ((rxMode & 0x02) > 0) {
          // unixTime - reset with data from the server
          unixTime = (rxBuf[4] << 24) | (rxBuf[5] << 16) | (rxBuf[6] << 8) | rxBuf[7];
          Serial.print("unixTime update: ");
          Serial.println(unixTime);
        };
      };
    }
  }

  //// display and outputs
  if ( lastTick != ticks){
    if ((rxMode & 0x02) > 0) {  // unixtime - update this constantly
      uint8_t hH = getHoursHigh(unixTime);
      if ((digitalRead(DISP12HR) == 0) && (hH == 0))
        data[0] = 0;  // Blank zero.
      else
        data[0] = display.encodeDigit(hH);
        
      
      data[1] = display.encodeDigit(getHoursLow(unixTime));
      data[2] = display.encodeDigit(getMinutesHigh(unixTime));
      data[3] = display.encodeDigit(getMinutesLow(unixTime));
      Serial.print("Time (H H M M S S): ");
      Serial.print(getHoursHigh(unixTime));
      Serial.print(" ");
      Serial.print(getHoursLow(unixTime));
      Serial.print(" ");
      Serial.print(getMinutesHigh(unixTime));
      Serial.print(" ");
      Serial.print(getMinutesLow(unixTime));
      Serial.print(" ");
      Serial.print(getSecondsHigh(unixTime));
      Serial.print(" ");
      Serial.println(getSecondsLow(unixTime));
      
      
    };
    
    // Beep
    if ( ((rxMode & 0x10) > 0) && ((ticks & 0x01) > 0)) {  
      digitalWrite(BEEP, HIGH);
    } else {
      digitalWrite(BEEP, LOW);
    }
  
    // Add colon?
    if ( ( ((rxMode & 0x04) > 0) && ((ticks & 0x01) > 0) ) || (ticks > 28800) ) {
      data[1] = data[1] | 0x80;  // If no update for 4 hours, colon on constant, or flash on command.
    }
  
    // Blank/Display
    if ( ((rxMode & 0x20) > 0) && ((ticks %2) > 0) ) {
      display.setSegments(blank);
    } else {
      display.setSegments(data);
    }
  }
  delay(100);  
}

inline uint8_t getSecondsHigh(uint32_t UNIXTime) {
  int s, t;
  s = UNIXTime % 60;
  t = UNIXTime % 10;
  return (s - t) / 10;
}
inline uint8_t getSecondsLow(uint32_t UNIXTime) {
  return UNIXTime % 10;
}

inline uint8_t getMinutesHigh(uint32_t UNIXTime) {
  int s, t;
  s = UNIXTime / 60 % 60;
  t = UNIXTime / 60 % 10;
  return (s - t) / 10;
}
inline uint8_t getMinutesLow(uint32_t UNIXTime) {
  return UNIXTime / 60 % 10;
}

inline uint8_t getHoursHigh(uint32_t UNIXTime) {
  int s;
  if (digitalRead(DISP12HR) == 1) {
    s = UNIXTime / 3600 % 24;
  } else {
    s = UNIXTime / 3600 % 12;
  }
  return s / 10;
}
inline uint8_t getHoursLow(uint32_t UNIXTime) {
  if (digitalRead(DISP12HR) == 1) {
    return (UNIXTime / 3600 % 24) % 10;
  } else {
    return (UNIXTime / 3600 % 12) % 10;
  }
}

bool getPacket() {
  int packetSize;
  packetSize = UDP.parsePacket();
  if (packetSize == 0) { // If there's no response (yet)
    return false;
  };
  if ( packetSize > PACKET_SIZE ) {
    packetSize = PACKET_SIZE;
  };

  UDP.read(rxBuf, packetSize);
  if (rxBuf[2] == (packetSize & 0x00ff)) {
    Serial.println("Packet Received");
    return true;
  } else {
    Serial.println("Incorrect packet size");
    return false;
  }
}

void startWiFi() { // Try to connect to some given access points. Then wait for a connection
  uint8_t _data[] = { 0x40, 0xc0, 0x40, 0x40 };
  uint8_t _blank[] = { 0x00, 0x80, 0x00, 0x00 };
  bool bOn;
  bOn = true;

  WiFi.mode(WIFI_STA); // Don't broadcast the ESP as an access point.

  wifiMulti.addAP("YourWiFi", "WiFiPassword");
  wifiMulti.addAP("AnotherWiFi", "DifferentPassword");   // add Wi-Fi networks you want to connect to
// wifiMulti.addAP("ssid_from_AP_3", "your_password_for_AP_3");


  Serial.println("Connecting");
  display.setSegments(_data);
  while (wifiMulti.run() != WL_CONNECTED) {  // Wait for the Wi-Fi to connect
    delay(250);
    Serial.print('.');
    bOn = !bOn;
    if (bOn) { 
      display.setSegments(_data);
    } else {
      display.setSegments(_blank);
    };  
  }
  Serial.println("\r\n");
  Serial.print("Connected to ");
  Serial.println(WiFi.SSID());             // Tell us what network we're connected to
  Serial.print("IP address:\t");
  Serial.print(WiFi.localIP());            // Send the IP address of the ESP8266 to the computer
  Serial.println("\r\n");
}



Server
For this video I was using a simple Delphi application on a PC, but the main house server is based on a mixture of C and PHP running on a linux VM.
The code to send the periodic UnixTime messages in delphi:

{ Uses (Network): IdGlobal, IdBaseComponent, IdComponent, IdUDPBase, IdUDPClient
  Uses (Time): DateUtils }

procedure TForm1.Timer1Timer(Sender: TObject);
var
  arData : Array [0..7] of Byte;
  cunixTime : cardinal;
begin
  arData[0] := $81;  // Clock
  arData[1] := $ff;  // Broadcast
  arData[2] := $08;  // 8 byte packet
  arData[3] := $06;  // Mode 02 (UnixTime) + 04 (seperator : on)
  cunixTime := DateTimeToUnix(now());
  arData[4] := byte(cunixTime shr 24);
  arData[5] := byte(cunixTime shr 16);
  arData[6] := byte(cunixTime shr 8);
  arData[7] := byte(cunixTime);
  IdUDPClient1.Host := '10.14.0.255';  // LAN broadcast address
  IdUDPClient1.Port := 6502;
  Label1.Caption := format('%d',[cunixTime]);
  IdUDPClient1.SendBuffer(RawToBytes(arData,8));
end;