Skip to content

Instantly share code, notes, and snippets.

@wyojustin
Last active October 28, 2018 12:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wyojustin/769e44585f0dbdcb6c3afe78cc8b8849 to your computer and use it in GitHub Desktop.
Save wyojustin/769e44585f0dbdcb6c3afe78cc8b8849 to your computer and use it in GitHub Desktop.
ClockIOT for esp8266
#include <Time.h>
#include <Wire.h>
#include <WiFiManager.h>
#include <FastLED.h>
#include <PubSubClient.h>
#include <EEPROM.h>
#include <EEPROMAnything.h>
#include <NTPClient.h>
#include <WebSocketsServer.h>
#define ULTIM8x16 // DullesKlok
//#define CLOCKIOT
#include <MatrixMaps.h>
#include <HTTPClient.h>
#include "klok.h"
#include "textures.h"
#include "logic.h"
#include "Faceplate.h"
#include "get_time.h"
#include "dutch_v1.h"
#include "french_v1.h"
#include "german_v3.h"
#include "hebrew_v1.h"
#include "hungarian_v2.h"
#include "irish_v1.h"
#include "italian_v1.h"
#include "english_v3.h"
#include "spanish_v1.h"
#include "config.h"
struct config_t{
int timezone;
uint8_t brightness;
uint8_t display_idx;
bool factory_reset;
bool use_wifi;
bool use_ip_timezone;
byte mqtt_ip[4];
bool flip_display;
uint32_t last_tz_lookup; // look up tz info every Sunday at 3:00 AM
uint8_t solid_color_rgb[3];
} config;
//const bool ON = true;
//const bool OFF = !ON;
// How many leds are in the strip?
const uint8_t N_BOARD = 2;
const uint8_t NUM_LEDS = 64 * N_BOARD;
bool mask[NUM_LEDS];
bool wipe[NUM_LEDS];
CRGB leds[NUM_LEDS];
WiFiClient espClient;
PubSubClient mqtt_client(espClient);
#ifdef CLOCKIOT
#define DATA_PIN 4
#define CLK_PIN 16
#else
#define DATA_PIN MOSI
#define CLK_PIN SCK
#endif
#define COLOR_ORDER BGR
#define LED_TYPE APA102
#define MILLI_AMPS 500 // IMPORTANT: set the max milli-Amps of your power supply (4A = 4000mA)
uint32_t last_time;
//********************************************************************************
// Displays
typedef void (*Init)();
typedef void (*DisplayTime)(uint32_t last_tm, uint32_t tm);
NTPClock ntp_clock;
DS3231Clock ds3231_clock;
DoomsdayClock doomsday_clock;
WiFiManager wifiManager;
WiFiUDP ntpUDP;
Faceplate faceplates[] = {
english_v3,
spanish_v1,
//hungarian_v2,
};
uint8_t num_faceplates = 2;
uint8_t faceplate_idx = 0;
NTPClient timeClient(ntpUDP, "us.pool.ntp.org", 0, 60000);
Klok klok(faceplates[0], timeClient);
String jsonLookup(String s, String name){
int start = s.indexOf(name) + name.length() + 3;
int stop = s.indexOf('"', start);
Serial.println(s.substring(start, stop));
return s.substring(start, stop);
}
void set_timezone_from_ip(){
HTTPClient http;
Serial.print("[HTTP] begin...\n");
// configure traged server and url
//http.begin("https://www.howsmyssl.com/a/check", ca); //HTTPS
// http.begin("http://example.com/index.html"); //HTTP
//http.begin("https://timezoneapi.io/api/ip");// no longer works!
//http.begin("https://ipapi.co/json");
String url = String("https://www.wyolum.com/utc_offset/utc_offset.py") +
String("?refresh=") + String(millis()) +
String("&localip=") +
String(WiFi.localIP()[0]) + String('.') +
String(WiFi.localIP()[1]) + String('.') +
String(WiFi.localIP()[2]) + String('.') +
String(WiFi.localIP()[3]) + String('&') +
String("macaddress=") + WiFi.macAddress() + String('&') +
String("dev_type=ClockIOT");
Serial.println(url);
http.begin(url);
Serial.print("[HTTP] GET...\n");
// start connection and send HTTP header
int httpCode = http.GET();
// httpCode will be negative on error
if(httpCode > 0) {
// HTTP header has been send and Server response header has been handled
Serial.printf("[HTTP] GET... code: %d\n", httpCode);
// file found at server
//String findme = String("offset_seconds");
if(httpCode == HTTP_CODE_OK) {
String payload = http.getString();
Serial.print("payload:");
Serial.println(payload);
payload.replace(" ", "");
String offset_str = jsonLookup(payload, String("utc_offset"));
int hours = offset_str.substring(0, 3).toInt();
int minutes = offset_str.substring(3, 5).toInt();
if(hours < 0){
minutes *= -1;
}
int offset = hours * 3600 + minutes * 60;
Serial.print("timezone_offset String:");
Serial.println(offset_str);
Serial.print("timezone_offset:");
Serial.println(offset);
set_timezone_offset(offset);
config.last_tz_lookup = Now();
saveSettings();
}
else{
Serial.println("No timezone found");
}
}
}
void setPixel(byte row, byte col, const struct CRGB & color){
int pos = XY(col, row);
leds[pos] = color;
}
void setPixelMask(bool* mask, uint8_t row, uint8_t col, bool b){
if(row >= MatrixHeight){
}
else if(col >= MatrixWidth){
}
else{
uint16_t pos = XY(col, row);
if(pos < NUM_LEDS){
mask[pos] = b;
}
}
}
void wipe_around(bool val){
float dtheta = 31.4 / 180;
float theta = -3.14 - dtheta;
int row, col;
bool tmp[NUM_LEDS];
int cx = random(0, MatrixWidth-1);
int cy = random(0, MatrixHeight-1);
cx = 8;
cy = 4;
fillMask(wipe, !val);
while (theta < 3.14 + dtheta){
for(row=0; row < MatrixHeight; row++){
for(col=0; col < MatrixWidth; col++){
if(atan2(row - cy, col - cx) < theta){
setPixelMask(wipe, row, col, val);
}
}
}
logical_or(NUM_LEDS, wipe, mask, tmp);
//rainbow_slow();
apply_mask(tmp);
FastLED.show();
theta += dtheta;
delay(10);
}
}
typedef struct{
Init init; // called when display changes
DisplayTime display_time; // called in main loop to update time display (if needed)
String name;
int id;
} Display;
void Plain_init(){
uint32_t current_time = Now();
last_time = current_time;
blend_to_rainbow();
}
void Plain_display_time(uint32_t last_tm, uint32_t tm){
fillMask(mask, OFF);
faceplates[faceplate_idx].maskTime(tm, mask);
rainbow_fast();
apply_mask(mask);
}
Display *CurrentDisplay_p;
Display PlainDisplay = {Plain_init, Plain_display_time, String("Plain"), 0};
Display WordDropDisplay = {WordDrop_init, WordDrop_display_time, String("Word Drop"), 2};
Display TheMatrixDisplay = {TheMatrix_init, TheMatrix_display_time, String("The Matrix"), 1};
Display SolidColorDisplay = {SolidColor_init, SolidColor_display_time, String("Solid Color"), 1};
const uint8_t N_DISPLAY = 4;
Display Displays[N_DISPLAY] = {PlainDisplay, TheMatrixDisplay, WordDropDisplay, SolidColorDisplay};
/*
Display WipeAroundDisplay = {blend_to_rainbow, rainbow, wipe_around_transition, String("Wipe Around"), 3};
*/
//--------------------------------------------------------------------------------
//uint32_t current_time;
void blend_to_rainbow(){
int i;
CHSV newcolor;
uint32_t current_time = Now();
//current_time = Now();
int count = ((current_time % 300) * 255) / 300;
newcolor.val = 255;
newcolor.sat = 255;
for(int ii=0; ii<NUM_LEDS; ii++){
for( int row = 0; row < MatrixHeight; row++) {
for( int col = 0; col < MatrixWidth; col++) {
i = XY(col, row);
if(mask[i]){
newcolor.hue = (count + (MatrixWidth * row + col) * 2) % 256;
nblend(leds[XY(col, row)], newcolor, 1);
}
}
}
FastLED.show();
delay(1);
}
}
void blend_to_color(CRGB color){
for(int kk=0; kk<128; kk++){
for(int ii=0; ii<NUM_LEDS; ii++){
if(mask[ii]){
nblend(leds[ii], color, 1);
}
}
FastLED.show();
delay(1);
}
}
void blend_to_red(){
blend_to_color(CRGB::Red);
}
void blend_to_green(){
blend_to_color(CRGB::Green);
}
void blend_to_blue(){
blend_to_color(CRGB::Blue);
}
void fill_red(){
fill_solid(leds, NUM_LEDS, CRGB::Red);
}
void fill_green(){
fill_solid(leds, NUM_LEDS, CRGB::Green);
}
void fill_blue(){
fill_solid(leds, NUM_LEDS, CRGB::Blue);
}
void fill_black(){
fill_solid(leds, NUM_LEDS, CRGB::Black);
}
void noop(){
}
void setWordMask(bool *mask, uint8_t* word, bool b){
// word = [row, col, len]
for(int i=0; i < word[2]; i++){
setPixelMask(mask, word[1], word[0] + i, b);
}
}
bool last_orientation;
void WordDrop_init(){
uint32_t current_time = Now();
last_time = current_time;
fillMask(mask, OFF);
faceplates[faceplate_idx].maskTime(current_time, mask);
blend_to_rainbow();
last_orientation = config.flip_display;
}
void word_drop_in(uint16_t time_inc){
uint8_t bits; // holds the on off state for 8 words at a time
uint8_t word[3]; // start columm, start row, length of the current word
bool tmp_mask[NUM_LEDS];
uint8_t tmp_word[3];
uint8_t n_byte_per_display = faceplates[faceplate_idx].displays[0];
fillMask(mask, false);
fillMask(wipe, false);
fillMask(tmp_mask, false);
for(uint8_t j = 0; j < n_byte_per_display; j++){ // j is a byte index
// read the state for the next set of 8 words
bits = pgm_read_byte(faceplates[faceplate_idx].displays + 1 + (time_inc * n_byte_per_display) + j);
for(uint8_t k = 0; k < 8; k++){ // k is a bit index
if((bits >> k) & 1){ // check to see if word is on or off
faceplates[faceplate_idx].getword(j * 8 + k, word); // if on, read location and length
tmp_word[0] = word[0];
tmp_word[1] = word[1];
tmp_word[2] = word[2];
for(int rr = 0; rr <= word[1]; rr++){
tmp_word[1] = rr;
setWordMask(wipe, tmp_word, true);
logical_or(NUM_LEDS, mask, wipe, tmp_mask);
rainbow_slow();
apply_mask(tmp_mask);
FastLED.show();
delay(25);
}
setWordMask(mask, word, true);
for(int rr = 0; rr < word[1]; rr++){
tmp_word[1] = rr;
setWordMask(wipe, tmp_word, false);
logical_or(NUM_LEDS, mask, wipe, tmp_mask);
rainbow_slow();
apply_mask(tmp_mask);
FastLED.show();
delay(25);
}
}
}
}
}
void word_drop_out(uint16_t time_inc){
uint8_t bits; // holds the on off state for 8 words at a time
uint8_t word[3]; // start columm, start row, length of the current word
bool tmp_mask[NUM_LEDS];
uint8_t tmp_word[3];
uint8_t n_byte_per_display = faceplates[faceplate_idx].displays[0];
//fillMask(mask, false);
//fillMask(wipe, false);
//fillMask(tmp_mask, false);
logical_copy(NUM_LEDS, mask, wipe);
logical_copy(NUM_LEDS, mask, tmp_mask);
for(uint8_t j = 0; j < n_byte_per_display; j++){ // j is a byte index
// read the state for the next set of 8 words
bits = pgm_read_byte(faceplates[faceplate_idx].displays + 1 + (time_inc * n_byte_per_display) + j);
for(uint8_t k = 0; k < 8; k++){ // k is a bit index
if((bits >> k) & 1){ // check to see if word is on or off
faceplates[faceplate_idx].getword(j * 8 + k, word); // if on, read location and length
tmp_word[0] = word[0];
tmp_word[1] = word[1];
tmp_word[2] = word[2];
for(int rr = word[1]; rr <= 8; rr++){
tmp_word[1] = rr;
setWordMask(wipe, tmp_word, true);
logical_or(NUM_LEDS, mask, wipe, tmp_mask);
rainbow_slow();
apply_mask(tmp_mask);
FastLED.show();
delay(25);
}
setWordMask(mask, word, false);
for(int rr = word[1]; rr <= 8; rr++){
tmp_word[1] = rr;
setWordMask(wipe, tmp_word, false);
logical_or(NUM_LEDS, mask, wipe, tmp_mask);
rainbow_slow();
apply_mask(tmp_mask);
FastLED.show();
delay(25);
}
}
}
}
}
void word_drop(uint16_t last_time_inc, uint16_t time_inc){
bool tmp_d[NUM_LEDS];
rainbow_slow();
// swipe rainbow from the left
//wipe_around(ON);
//delay(1000);
if(last_time_inc != 289){
word_drop_out(last_time_inc);
}
// clear the new display
fillMask(tmp_d, false);
// read display for next time incement
faceplates[faceplate_idx].maskTime(time_inc * 300, mask);
// clear rainbow to reveal the time
//wipe_off_left();
//wipe_around(OFF);
word_drop_in(time_inc);
}
void WordDrop_display_time(uint32_t last_tm, uint32_t next_tm){
int last_tm_inc = (last_tm / 300) % 288;
int tm_inc = (next_tm / 300) % 288;
if(last_tm_inc == tm_inc - 1 || (last_tm_inc == 287 && tm_inc == 0)){
word_drop(last_tm_inc, tm_inc);
}
rainbow_slow();
fillMask(mask, false);
faceplates[faceplate_idx].maskTime(next_tm, mask);
apply_mask(mask);
}
void TheMatrix_drop(uint32_t last_tm_inc, uint32_t current_tm_inc){
int n_drop = 0;
int n_need = 8;
const struct CRGB color = CRGB::Green;
uint8_t cols[NUM_LEDS];
uint8_t rows[NUM_LEDS];
uint8_t pause[NUM_LEDS];
bool have[NUM_LEDS];
int col;
int i, j;
Serial.print("TheMatrix: Change Time\n");
// clear all masks
fillMask(mask, false);
fillMask(wipe, false);
fillMask(have, false);
// set masks to appropriate times
faceplates[faceplate_idx].maskTime(last_tm_inc * 300, mask);
faceplates[faceplate_idx].maskTime(current_tm_inc * 300, wipe);
fill_green();
apply_mask(mask);
FastLED.show();
for(i=0; i < MatrixWidth; i++){
for(j=0; j < MatrixHeight; j++){
interact_loop();
if(leds[XY(i, j)].red > 0 ||
leds[XY(i, j)].green > 0 ||
leds[XY(i, j)].blue > 0){
rows[n_drop] = j;
cols[n_drop] = i;
n_drop++;
}
if(wipe[XY(i, j)]){
n_need++;
}
}
}
delay(10);
for(j = 0; j < 255 * 3; j++){
for(i=0; i < NUM_LEDS; i++){
interact_loop();
leds[i].red = blend8(leds[i].red, 0, 1);
leds[i].green = blend8(leds[i].green, 255, 1);
leds[i].blue = blend8(leds[i].blue, 0, 1);
}
apply_mask(mask);
FastLED.show();
delay(5);
}
for(i = n_drop; i < n_need; i++){/// add enough drops to complete
interact_loop();
cols[i] = random(0, MatrixWidth);
rows[i] = -random(0, MatrixHeight);
n_drop++;
}
int end = millis() + 5000; // go for 5 seconds
// while new display is not filled out
while(!logical_equal(NUM_LEDS, wipe, have)){
// while(millis() < end){
fadeToBlackBy(leds, NUM_LEDS, 75);
for(i = 0; i < n_drop; i++){
interact_loop();
if(millis() > end && wipe[XY(cols[i], rows[i])]){
if(random(0, 3) == 0){
have[XY(cols[i], rows[i])] = true;
}
}
if(random(0, 16) == 0){ // pause at random times
pause[i] = random(6, 9); // for random duration
}
if(pause[i] == 0){
rows[i]++;
}
else{
pause[i]--;
}
if(rows[i] > MatrixHeight - 1){
if(n_drop > n_need){
for(j = i; j < n_drop; j++){ // slide drops down by one
rows[j] = rows[j + 1];
cols[j] = cols[j + 1];
}
n_drop--;
Serial.print("n_drop:");
Serial.println(n_drop);
}
else{
rows[i] = -random(0, MatrixHeight);
cols[i] = random(0, MatrixWidth);
}
}
if(0 <= rows[i] && rows[i] < MatrixHeight){
leds[XY(cols[i], rows[i])] = color;
}
}
for(int ii = 0; ii < NUM_LEDS; ii++){
if(have[ii]){
//leds[ii] = CRGB::Blue;
leds[ii] = CRGB(config.solid_color_rgb[0],
config.solid_color_rgb[1],
config.solid_color_rgb[2]);
}
}
FastLED.show();
delay(75);
}
for(int ii=0; ii< MatrixHeight * 10; ii++){
// while(millis() < end){
fadeToBlackBy(leds, NUM_LEDS, 75);
for(i = 0; i < n_drop; i++){
interact_loop();
rows[i]++;
if(0 <= rows[i] && rows[i] < MatrixHeight){
leds[XY(cols[i], rows[i])] = color;
}
}
for(int ii = 0; ii < NUM_LEDS; ii++){
if(have[ii]){
//leds[ii] = CRGB::Blue;
leds[ii] = CRGB(config.solid_color_rgb[0],
config.solid_color_rgb[1],
config.solid_color_rgb[2]);
}
}
FastLED.show();
delay(75);
}
}
void TheMatrix_init(){
uint32_t current_time = Now();
last_time = current_time;
blend_to_blue();
fill_blue();
fillMask(mask, false);
faceplates[faceplate_idx].maskTime(current_time, mask);
apply_mask(mask);
last_orientation = config.flip_display;
}
void TheMatrix_display_time(uint32_t last_tm, uint32_t tm){
int last_tm_inc = (last_tm / 300) % 288;
int tm_inc = ( tm / 300) % 288;
if(last_tm_inc == tm_inc - 1 || (last_tm_inc == 287 && tm_inc == 0)){
TheMatrix_drop(last_tm_inc, tm_inc);
}
else if((last_tm_inc != tm_inc) || (last_orientation != config.flip_display)){
//fill_blue();
//fill_solid(leds, NUM_LEDS, CRGB(config.solid_color_rgb[0],
//config.solid_color_rgb[1],
//config.solid_color_rgb[2]));
//fillMask(mask, false);
//faceplates[faceplate_idx].maskTime(tm, mask);
//apply_mask(mask);
//last_orientation = config.flip_display;
}
fill_solid(leds, NUM_LEDS, CRGB(config.solid_color_rgb[0],
config.solid_color_rgb[1],
config.solid_color_rgb[2]));
fillMask(mask, false);
faceplates[faceplate_idx].maskTime(tm, mask);
apply_mask(mask);
}
void rainbow_cycle(int count){
int i, dx, dy;
CHSV hsv;
float dist;
hsv.hue = 0;
hsv.val = 255;
hsv.sat = 240;
for( int row = 0; row < MatrixHeight; row++) {
for( int col = 0; col < MatrixWidth; col++) {
// dx, dy, dist used for radial pattern, not used here
dy = (row - 4) * 2;
dx = col - 8;
dist = sqrt(dx * dx + dy * dy);
i = XY(col, row);
//hsv.hue = ((int)(dist * 16) - count) % 256;
hsv.hue = (count + (MatrixWidth * row + col) * 2) % 256;
leds[i] = hsv;
}
}
}
void rainbow_fast() {
uint32_t current_time = Now();
int count = millis() / 100;
rainbow_cycle(millis()/25);
// Show the leds (only one of which is set to white, from above)
//delay(100);
}
void rainbow_slow() {
uint32_t current_time = Now();
int count = ((current_time % 300) * 255) / 300;
rainbow_cycle(count);
}
void SolidColor_init(){
}
void SolidColor_display_time(uint32_t last_tm, uint32_t tm){
if(last_tm != tm){
wipe_around(false);
apply_mask(mask);
fill_solid(leds, NUM_LEDS, CRGB(config.solid_color_rgb[0],
config.solid_color_rgb[1],
config.solid_color_rgb[2]));
wipe_around(true);
faceplates[faceplate_idx].maskTime(last_tm, mask);
}
fill_solid(leds, NUM_LEDS, CRGB(config.solid_color_rgb[0],
config.solid_color_rgb[1],
config.solid_color_rgb[2]));
fillMask(mask, false);
faceplates[faceplate_idx].maskTime(last_tm, mask);
apply_mask(mask);
}
// end Displays
//********************************************************************************
uint8_t logo_rgb[] = {
0x11,0x00,0x29,0x00,0x25,0x00,0x23,0x00,0x25,0x00,0x29,0x00,0x31,0x00,0xe0,0x01,
0x00,0x03,0x80,0x04,0x80,0x04,0x80,0x04,0x80,0x04,0x00,0x03,0x00,0x00,0x00,0x00,
0x11,0x00,0x09,0x88,0x05,0x48,0x03,0x28,0x05,0x18,0x09,0x28,0x11,0x48,0x00,0x88
};
void ChangeDisplay(Display* display_p);
void ChangeDisplay(Display* display_p){
CurrentDisplay_p = display_p;
CurrentDisplay_p->init();
}
// Common Interface for buttons and MQTT
void set_brightness(uint8_t brightness){
if(brightness < 256){
config.brightness = brightness;
FastLED.setBrightness(config.brightness);
Serial.print("Adjust brightness to ");
Serial.println(config.brightness);
saveSettings();
}
}
void adjust_brightness(int delta){
int new_val = delta + config.brightness;
if(delta != 0){
if(0 < new_val && new_val < 256){
set_brightness(new_val);
}
}
}
void dimmer(){
byte b;
b = config.brightness;
if(b == 255){
b = 128;
}
else if(b > 3){
b /= 2;
}
else if (b == 3){
b = 2;
}
else{
b = 2;
}
set_brightness(b);
}
void brighter(){
byte b;
b = config.brightness;
if(b >= 128){
b = 255;
}
else if(b < 128){
b *= 2;
}
else{
b = 128;
}
set_brightness(b);
}
void set_display(uint8_t display_idx){
config.display_idx = display_idx % N_DISPLAY;
ChangeDisplay(&Displays[display_idx % N_DISPLAY]);
saveSettings();
}
void next_display(){
config.display_idx = (config.display_idx + 1) % N_DISPLAY;
ChangeDisplay(&Displays[config.display_idx]);
saveSettings();
}
void add_to_timezone(int32_t offset){
config.timezone += offset;
config.use_ip_timezone = false; // time zone manually changed... ignore internate timezone
saveSettings();
if(config.use_wifi){
ntp_clock.setOffset(config.timezone);
}
}
void set_timezone_offset(int32_t offset){
config.timezone = offset % 86400;
saveSettings();
if(config.use_wifi){
ntp_clock.setOffset(config.timezone);
}
}
void display_bitmap_rgb(uint8_t* bitmap){
uint8_t n = 16;
uint8_t h = 8;
uint8_t w = 16;
uint8_t xy[2];
uint8_t r, g, b;
int i, j;
int led_idx, byte_idx;
//struct CRGB color;
int x, y;
for(i=0; i<n; i++){
r = bitmap[i + 0 * w];
g = bitmap[i + 1 * w];
b = bitmap[i + 2 * w];
// 012345678pabcdef
// 0 0000000011111111
// 1 2222222233333333
// 2 4444444455555555
// 3 6666666677777777
// 4 8888888899999999
// 5 aaaaaaaabbbbbbbb
// 6 ccccccccdddddddd
// 7 eeeeeeeeffffffff
for(j=0; j<8; j++){
x = (i * 8 + j) % 16;
y = i / 2;
led_idx = XY(x, y);
leds[led_idx].red = 255 * ((r >> j) & 1);
leds[led_idx].green = 255 * ((g >> j) & 1);
leds[led_idx].blue = 255 * ((b >> j) & 1);
if(leds[led_idx].red || leds[led_idx].green || leds[led_idx].blue){
mask[led_idx] = true;
}
}
}
}
void apply_mask(bool* mask){
uint16_t b, k;
for(uint16_t i=0; i < NUM_LEDS; i++){
if(!mask[i]){
leds[i] = CRGB::Black;
}
}
}
void fillMask(bool* mask, bool b){
fillMask(mask, b, 0, NUM_LEDS);
}
void fillMask(bool* mask, bool b, int start, int stop){
for(int i = start; i < stop && i < NUM_LEDS; i++){
mask[i] = b;
}
}
void loadSettings(){
EEPROM_readAnything(0, config);
}
void saveSettings(){
EEPROM_writeAnything(0, config);
EEPROM.commit();
}
uint16_t XY( uint8_t x, uint8_t y){
uint16_t out = 0;
if(config.flip_display){
x = MatrixWidth - x - 1;
y = MatrixHeight - y - 1;
}
if(x < MatrixWidth && y < MatrixHeight){
out = MatrixMap[y][x];
}
return out;
}
uint8_t hex2dig(char h){
uint8_t d = 0;
if('0' <= h && h <= '9'){
d = (uint8_t)(h - '0');
}
else if('a' <= h && h <= 'f'){
d = (uint8_t)(10 + h - 'a');
}
else if('A' <= h && h <= 'F'){
d = (uint8_t)(10 + h - 'A');
}
return d;
}
uint8_t hh2dd(char *hh){
return hex2dig(hh[0]) * 16 + hex2dig(hh[1]);
}
void handle_msg(char* topic, byte* payload, unsigned int length) {
bool handled = false;
char str_payload[length + 1];
char *subtopic = topic + 9;
// copy bytes to normal char array
for(int i = 0; i < length; i++){
str_payload[i] = payload[i];
}
str_payload[length] = 0;
Serial.print("msg\n subtopic:");
Serial.println(subtopic);
Serial.print(" payload:");
Serial.println(str_payload);
if(strcmp(subtopic, "timezone_offset") == 0){
Serial.println("Change timezone!!");
set_timezone_offset(String(str_payload).toInt());
}
else if(strcmp(subtopic, "add_to_timezone") == 0){
Serial.println("Add to timezone!");
add_to_timezone(String(str_payload).toInt());
}
else if(strcmp(subtopic, "display_idx") == 0){
Serial.println("Change display_idx!!");
set_display(String(str_payload).toInt());
}
else if(strcmp(subtopic, "next_display") == 0){
Serial.println("Increment display!!");
next_display();
}
else if(strcmp(subtopic, "brighter") == 0){
Serial.println("Increment brigtness!!");
brighter();
}
else if(strcmp(subtopic, "dimmer") == 0){
Serial.println("Decrement brigtness!!");
dimmer();
}
else if(strcmp(subtopic, "flip_display") == 0){
if(config.flip_display){
config.flip_display = false;
}
else{
config.flip_display = true;
}
Serial.print("Flip Display:");
Serial.println(config.flip_display);
saveSettings();
}
else if(strcmp(subtopic, "mqtt_ip") == 0){
Serial.println("Update mqtt_ip address!!");
byte tmp_ip[4];
if(ip_from_str(str_payload, tmp_ip)){
for(int i=0; i<4; i++){
config.mqtt_ip[i] = tmp_ip[i];
}
saveSettings();
mqtt_setup();
}
}
else if(strcmp(subtopic, "set_rgb") == 0 && length == 6){
// payload: rrggbb lowercase html color code example "ff0000" is RED
config.solid_color_rgb[0] = hh2dd((char*)payload);
config.solid_color_rgb[1] = hh2dd((char*)payload + 2);
config.solid_color_rgb[2] = hh2dd((char*)payload + 4);
saveSettings();
}
else if(strcmp(subtopic, "set_time") == 0){
// payload: ascii unix time
}
else if(strcmp(subtopic, "notify") == 0){
// payload: ascii notification
}
}
void handle_mqtt_msg(char* topic, byte* payload, unsigned int length){
handle_msg(topic, payload, length);
}
bool ip_from_str(char* str, byte* ip){
byte my_ip[4];
int end_poss[4];
int i = 0, j = 0;
int dots_found = 0;
bool out = false;
byte num;
String strstr = String(str);
Serial.println("ip_from_str");
Serial.println(str);
while(i < strlen(str) && dots_found < 3){
if(str[i] == '.'){
end_poss[dots_found] = i;
dots_found++;
}
i++;
}
if(dots_found == 3){
out = true;
end_poss[3] = strlen(str);
for(i = 0; i < 4; i++){
num = String(strstr.substring(j, end_poss[i])).toInt();
if(num == 0){
out = false;
}
ip[i] = num;
j = end_poss[i] + 1;
}
}
if(out){
Serial.print("IP: ");
for(i=0; i<4; i++){
Serial.print(ip[i]);
if(i < 3){
Serial.print(".");
}
}
Serial.println();
}
return out;
}
void mqtt_subscribe(){
mqtt_client.subscribe("clockiot/#");
}
uint32_t next_mqtt_attempt = 0;
bool mqtt_connect(){
String str;
String unique_id = String("ClockIOT") + String(WiFi.macAddress());
if(!mqtt_client.connected() && next_mqtt_attempt < millis()){
if(mqtt_client.connect(unique_id.c_str())){
Serial.println("mqtt connected");
Serial.println(unique_id);
// Once connected, publish an announcement...
// ... and resubscribe
mqtt_subscribe();
}
}
uint32_t n = millis();
next_mqtt_attempt = n + 5000;
return mqtt_client.connected();
}
void mqtt_setup(){
//uint8_t server[4] = {192, 168, 1, 159};
//uint8_t server[4] = {10, 10, 10, 2};
mqtt_client.setServer(config.mqtt_ip, 1883);
mqtt_client.setCallback(handle_mqtt_msg);
mqtt_connect();
Serial.println("USE MQTT!!");
}
void led_setup(){
FastLED.addLeds<LED_TYPE, DATA_PIN, CLK_PIN, COLOR_ORDER>(leds, NUM_LEDS);
FastLED.setDither(true);
FastLED.setCorrection(TypicalLEDStrip);
FastLED.setMaxPowerInVoltsAndMilliamps(5, MILLI_AMPS);
fill_solid(leds, NUM_LEDS, CRGB::Black);
FastLED.show();
}
void wifi_setup(){
if(config.factory_reset){
config.factory_reset = false;
saveSettings();
wifiManager.startConfigPortal("KLOK");
}
else{
wifiManager.autoConnect("KLOK");
}
Serial.println("Yay connected!");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
/*********************************************************************************/
// Web Socket Server stuff
WebSocketsServer webSocket = WebSocketsServer(81);
void hexdump(const void *mem, uint32_t len, uint8_t cols) {
const uint8_t* src = (const uint8_t*) mem;
Serial.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len);
for(uint32_t i = 0; i < len; i++) {
if(i % cols == 0) {
Serial.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i);
}
Serial.printf("%02X ", *src);
src++;
}
Serial.printf("\n");
}
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * ws_payload, size_t length) {
char topic_payload[length + 1];
String str_topic_payload;
int i;
int start, stop;
switch(type) {
case WStype_DISCONNECTED:
Serial.printf("[%u] Disconnected!\n", num);
break;
case WStype_CONNECTED:
{
IPAddress ip = webSocket.remoteIP(num);
Serial.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], ws_payload);
// send message to client
webSocket.sendTXT(num, "Connected");
}
break;
case WStype_TEXT:
Serial.printf("[%u] get Text: %s\n", num, ws_payload);
for(i=0; i < length; i++){
topic_payload[i] = (char)ws_payload[i];
}
topic_payload[length] = 0;
str_topic_payload = String(topic_payload);
start = str_topic_payload.indexOf("//");
stop = start + 2;
if(start < 0){
start = length;
stop = length;
}
char topic[100];
byte payload[100];
for(i = 0; i < start; i++){
topic[i] = topic_payload[i];
}
topic[start] = 0;
for(i = 0; i < length - stop; i++){
payload[i] = (byte)topic_payload[stop + i];
}
payload[length - stop] = 0;
handle_msg(topic, payload, length - stop);
// send message to client
//webSocket.sendTXT(num, "message here");
// send data to all connected clients
// webSocket.broadcastTXT("message here");
break;
case WStype_BIN:
Serial.printf("[%u] get binary length: %u\n", num, length);
hexdump(ws_payload, length, 16);
// send message to client
// webSocket.sendBIN(num, ws_payload, length);
break;
case WStype_ERROR:
case WStype_FRAGMENT_TEXT_START:
case WStype_FRAGMENT_BIN_START:
case WStype_FRAGMENT:
case WStype_FRAGMENT_FIN:
break;
}
}
void websocket_setup(){
webSocket.begin();
webSocket.onEvent(webSocketEvent);
}
// Web Socket Server stuff
/*********************************************************************************/
/*********************************************************************************/
// Button stuff
byte read_buttons(bool *enter_p, bool *inc_p, bool *decr_p, bool *mode_p){
byte state;
*enter_p = digitalRead(ENTER);
*inc_p = digitalRead(INC);
*decr_p = digitalRead(DECR);
*mode_p = digitalRead(MODE);
state |= (*enter_p) << 3;
state |= (*inc_p) << 2;
state |= (*decr_p) << 1;
state |= (*mode_p) << 0;
return state;
}
void test_leds(){
int i;
for(i=0; i < NUM_LEDS; i++){
leds[i] = CRGB::Red;
FastLED.show();
delay(10);
}
delay(100);
for(i=0; i < NUM_LEDS; i++){
leds[i] = CRGB::Green;
FastLED.show();
delay(10);
}
delay(100);
for(i=0; i < NUM_LEDS; i++){
leds[i] = CRGB::Blue;
FastLED.show();
delay(10);
}
for(i=0; i < NUM_LEDS; i++){
leds[i] = CRGB::White;
FastLED.show();
delay(10);
}
delay(1000);
fill_black();
FastLED.show();
}
void bigX(){
for(int i=0;i < 8; i++){
setPixel(i, 2 * i - 1, CRGB::Red);
setPixel(i, 2 * i, CRGB::Red);
setPixel(i, 2 * i + 1, CRGB::Red);
setPixel(7 - i, 2 * i, CRGB::Red);
}
FastLED.show();
Serial.println("Big X");
while(1) delay(100);
}
void test_ds3231(){
int first = ds3231_clock.now();
delay(1500);
int second = ds3231_clock.now();
if(second <= first){
Serial.println("RCT Failed");
bigX();
}
else{
Serial.println("RCT Passed");
}
}
void factory_reset(){
Serial.println("Factory RESET!!");
config.timezone = 255; //?
config.brightness = 8;
config.display_idx = 255;
config.factory_reset = 255;
config.use_wifi = 255;
config.use_ip_timezone = 255;
config.mqtt_ip[0] = 255;
config.mqtt_ip[1] = 255;
config.mqtt_ip[2] = 255;
config.mqtt_ip[3] = 255;
config.flip_display = 255;
config.last_tz_lookup = 0;
saveSettings();
Serial.println("Hit reset again to complete");
test_leds();
test_ds3231();
delay(1000);
while(1) delay(100);
}
void button_set_time(){
Serial.println("Set Time w/ INC/DEC buttons");
uint32_t current_time = ds3231_clock.now();
current_time -= current_time % 60;
bool enter, inc, decr, mode;
byte button_state;
byte last_button_state = 0b000;
while(1){
button_state = read_buttons(&enter, &inc, &decr, &mode);
if(button_state != last_button_state){
if(button_state == 0b0100){
Serial.println("inc");
current_time += 60;
}
else if(button_state == 0b0010){
Serial.println("decr");
current_time -= 60;
}
else if(button_state == 0b1000){
Serial.println("enter");
current_time += 60 * 60;
}
else if(button_state == 0b0001){
Serial.println("mode");
current_time -= 60 * 60;
}
if(button_state){
ds3231_clock.set(current_time);
}
}
last_button_state = button_state;
if((millis() % 1000) < 900){
fill_blue();
}
else{
fill_green();
}
fillMask(mask, OFF);
faceplates[faceplate_idx].maskTime(current_time, mask);
apply_mask(mask);
for(int min = 0; min < (current_time % 300)/60; min++){
setPixel(min, 15, CRGB::Green);
}
FastLED.show();
}
}
void button_setup(){
bool enter, inc, decr, mode;
pinMode(ENTER, INPUT);
pinMode(INC, INPUT);
pinMode(DECR, INPUT);
pinMode(MODE, INPUT);
byte button_state = read_buttons(&enter, &inc, &decr, &mode);
Serial.print("Button state: ");
Serial.println(button_state);
if(button_state == (1 << 3 | 1)){ // MODE + ENTER does a factory reset
factory_reset();
}
else if(mode){ // mode configures stand alone
if(config.use_wifi){ // toggle use_wifi
config.use_wifi = false;
button_set_time(); // if(mode) body
saveSettings();
}
else{
config.use_wifi = true;
saveSettings();
}
}
}
uint32_t last_pressed = 0;
byte last_button_state = 0b0000;
void button_loop(){
bool enter, inc, decr, mode;
byte button_state = read_buttons(&enter, &inc, &decr, &mode);
if(button_state != last_button_state){
if(mode){
next_display();
}
if(inc){
brighter();
}
if(decr){
dimmer();
}
}
last_button_state = button_state;
}
// Button stuff
/*********************************************************************************/
bool use_mqtt(){
bool out = false;
for(int i=0; i<4; i++){
if(config.mqtt_ip[i] != 255){
out = true;
break;
}
}
return out;
}
void setup(){
last_time = 0;
CurrentDisplay_p = &PlainDisplay;
//CurrentDisplay_p = &TheMatrixDisplay;
//CurrentDisplay_p = &WordDropDisplay;
Wire.begin();
Serial.begin(115200);
delay(200);
Serial.println("setup() starting");
EEPROM.begin(1024);
loadSettings();
Serial.println("Settings");
Serial.print("timezone:");Serial.println(config.timezone);
Serial.print("use IP timezone:");Serial.println(config.use_ip_timezone);
Serial.print("brightness:");Serial.println(config.brightness);
Serial.print("display_idx:");Serial.println(config.display_idx);
Serial.print("factory_reset:");Serial.println(config.factory_reset);
Serial.print("use_wifi:");Serial.println(config.use_wifi);
Serial.print("mqtt_ip:");
for(int i = 0; i< 4; i++){
Serial.print(config.mqtt_ip[i]);
Serial.print(", ");
}
Serial.println();
led_setup(); // set up leds first so buttons can affect display if needed
CurrentDisplay_p = &Displays[config.display_idx % N_DISPLAY];
for(int ii = 0; ii < num_faceplates; ii++){
faceplates[ii].setup(MatrixWidth, MatrixHeight, XY);
}
// logo
if( config.brightness == 0 || config.brightness == 255){
config.brightness == 8;
}
FastLED.setBrightness(config.brightness);
ds3231_clock.setup();
button_setup();
wipe_around(ON);
display_bitmap_rgb(logo_rgb);
FastLED.show();
wipe_around(OFF);
display_bitmap_rgb(logo_rgb);
FastLED.show();
if(config.use_wifi){
wifi_setup();
}
if(use_mqtt()){
mqtt_setup();
}
wipe_around(ON);
fillMask(mask, false);
wipe_around(OFF);
//CurrentDisplay_p->init();
//while(1)delay(100);
if(config.use_wifi){
ntp_clock.setup(&timeClient);
ntp_clock.setOffset(config.timezone);
ds3231_clock.set(ntp_clock.now());
doomsday_clock.setup(&ntp_clock, &ds3231_clock);
if(config.use_ip_timezone){
set_timezone_from_ip();
}
websocket_setup();
}
Serial.print("config.timezone: ");
Serial.println(config.timezone);
Serial.print("config.use_ip_timezone: ");
Serial.println((bool)config.use_ip_timezone);
Serial.println("setup() complete");
}
uint32_t Now(){
uint32_t out;
#ifdef CLOCKIOT
if(config.use_wifi){
out = doomsday_clock.now();
if(weekday(out) == 0){ // refresh utc offset sunday between 3 and 4 AM
if(hour(out) == 3){
if(out - config.last_tz_lookup > 3601){
set_timezone_from_ip();
}
}
}
}
else{
out = ds3231_clock.now();
}
#else
out = ntp_clock.now();
#endif
return out;
}
void tobytes(const char *frm, byte *_to, int len){
for(int ii = 0; ii < len; ii++){
_to[ii] = frm[ii];
}
}
void tochars(const char *frm, char *_to, int len){
for(int ii = 0; ii < len; ii++){
_to[ii] = frm[ii];
}
_to[len] = 0;
}
#define SERMAXLEN 100
char ser_msg[SERMAXLEN + 1];
uint8_t ser_msg_len = 0;
void serial_loop(){/// allow same msgs as mqtt
String ser_str, topic, payload;
int start, stop;
char topic_c_str[101];
byte payload_bytes[100];
// msg format: topic//payload. Example: "clockiot/timezone_offset//-14400"
while(Serial.available() && ser_msg_len < SERMAXLEN){
ser_msg[ser_msg_len++] = Serial.read();
}
if(ser_msg_len > 0){
ser_str = String(ser_msg);
start = ser_str.indexOf("clockiot");
if(start >= 0){
stop = ser_str.indexOf("//", start);
if(stop < 0){
stop = ser_str.length();
}
else{
}
topic = ser_str.substring(start, stop);
if(stop == ser_str.length()){
}
else{
stop += 2; // skip slashes "//"
}
payload = ser_str.substring(stop, ser_str.length());
tochars(topic.c_str(), topic_c_str, topic.length());
tobytes(payload.c_str(), payload_bytes, payload.length());
handle_msg(topic_c_str, payload_bytes, payload.length());
}
}
// clear msg
for(int ii=0; ii < ser_msg_len + 1; ii++){
ser_msg[ii] = 0;
}
ser_msg_len = 0;
}
void interact_loop(){
if (use_mqtt()){
mqtt_client.loop();
}
if(config.use_wifi){
webSocket.loop();
}
button_loop();
serial_loop();
}
void loop(){
uint8_t word[3];
uint32_t current_time = Now();
interact_loop();
CurrentDisplay_p->display_time(last_time, current_time);
FastLED.show();
/*
Serial.print("NTP Time:");
Serial.print(timeClient.getHours());
Serial.print(":");
Serial.print(timeClient.getMinutes());
Serial.print(":");
Serial.print(timeClient.getSeconds());
Serial.println("");
Serial.print("ds3231_clock.Time:");
Serial.print(ds3231_clock.year());
Serial.print("/");
Serial.print(ds3231_clock.month());
Serial.print("/");
Serial.print(ds3231_clock.day());
Serial.print(" ");
Serial.print(ds3231_clock.hours());
Serial.print(":");
Serial.print(ds3231_clock.minutes());
Serial.print(":");
Serial.println(ds3231_clock.seconds());
Serial.println(ds3231_clock.now());
Serial.println(ds3231_clock.rtc.now().unixtime());
Serial.println();
delay(1000);
*/
if(config.use_wifi){
if(doomsday_clock.seconds() == 0 and millis() < 1){
Serial.print("Doomsday Time:");
Serial.print(doomsday_clock.year());
Serial.print("/");
Serial.print(doomsday_clock.month());
Serial.print("/");
Serial.print(doomsday_clock.day());
Serial.print(" ");
if(doomsday_clock.hours() < 10)Serial.print('0');
Serial.print(doomsday_clock.hours());
Serial.print(":");
if(doomsday_clock.minutes() < 10)Serial.print('0');
Serial.print(doomsday_clock.minutes());
Serial.print(":");
if(doomsday_clock.seconds() < 10)Serial.print('0');
Serial.println(doomsday_clock.seconds());
}
}
last_time = current_time;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment