Make your own action Climate Clock powered by WisBlock

Be conscious of your time, actions, and the environment

IntermediateFull instructions provided5 hours658
Make your own action Climate Clock powered by WisBlock

Things used in this project

Hardware components

WisBlock Base Board RAK5005-O
RAKwireless WisBlock Base Board RAK5005-O
×1
WisBlock WiFi Module RAK11200
RAKwireless WisBlock WiFi Module RAK11200
×1
WisBlock OLED Display RAK1921
RAKwireless WisBlock OLED Display RAK1921
×1
RAK12002 RV-3028-C7 RTC module
RAKwireless RAK12002 RV-3028-C7 RTC module
×1
Li-Ion/LiPo battery
×1
Jumper wires (female to female)
×4

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
3D printing filament
(red, blue, and green)

Story

Read more

Schematics

Connections diagram for Climate Clock powered by WisBlock

Code

Firmware Climate Clock powered by WisBlock

Arduino
// Install https://github.com/PaulStoffregen/Time
#include <TimeLib.h>

// Include the correct display library
// For a connection via I2C using Wire include
#include <Wire.h>  // Only needed for Arduino 1.6.5 and earlier
#include "SSD1306Wire.h" // legacy include: `#include "SSD1306.h"`

// Include the UI lib
#include "OLEDDisplayUi.h"

// Include custom images
#include "images.h"

// Include the RTC library
#include "Melopero_RV3028.h" //http://librarymanager/All#Melopero_RV3028

// Include the WiFi library
#include "WiFi.h"

#include <ArduinoJson.h>    // library
#include <HTTPClient.h>

// Debug output set to 0 to disable app debug output
#ifndef MY_DEBUG
#define MY_DEBUG 0
#endif

#if MY_DEBUG > 0
#define MYLOG(tag, ...)                   \
    do                                      \
    {                                       \
        if (tag)                              \
            PRINTF("[%s] ", tag);               \
        PRINTF(__VA_ARGS__);                  \
        PRINTF("\n");                         \
    } while (0)
#else
#define MYLOG(...)
#endif

// Initialize the OLED display using Wire library
SSD1306Wire display(0x3c, SDA, SCL);   // ADDRESS, SDA, SCL

OLEDDisplayUi ui ( &display );

// RTC class
Melopero_RV3028 rtc;

struct tm climateClockDeadline;
time_t timeClimateClockDeadline = 1879443963;

int screenW = 128;
int screenH = 64;
int clockCenterX = screenW / 2;
int clockCenterY = (screenH / 2); // top yellow part is 16 px height
int clockRadius = 23;

char wifiSSID[] = "YOUR_WIFI_NAME";
char wifiPassword[] = "YOUR_PASSWORD";

unsigned long previousMillis = 0;       
const long interval = 60000;           // interval at which to update the values (milliseconds)

// utility function for digital clock display: prints leading 0
String twoDigits(int digits) {
    if (digits < 10) {
        String i = '0' + String(digits);
        return i;
    }
    else {
        return String(digits);
    }
}

int LeapYearsBetween(int startYear, int endYear)
{
    int leapYears = 0;
    int i;
    
    for (i= startYear; i<= endYear; i++)   
    {  
         if((i%4==0) && ((i%400==0) || (i%100!=0)))
         {
            leapYears++;
            //Serial.printf("Leap year : %d \n", i);
         }   
    }
    //Serial.printf("Leap years : %d \n", leapYears);
    return leapYears;
}

void clockOverlay(OLEDDisplay *display, OLEDDisplayUiState* state) {

}

void analogClockFrame(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
    //  ui.disableIndicator();

    // Draw the clock face
    //  display->drawCircle(clockCenterX + x, clockCenterY + y, clockRadius);
    display->drawCircle(clockCenterX + x, clockCenterY + y, 2);
    //
    //hour ticks
    for ( int z = 0; z < 360; z = z + 30 ) {
        //Begin at 0° and stop at 360°
        float angle = z ;
        angle = ( angle / 57.29577951 ) ; //Convert degrees to radians
        int x2 = ( clockCenterX + ( sin(angle) * clockRadius ) );
        int y2 = ( clockCenterY - ( cos(angle) * clockRadius ) );
        int x3 = ( clockCenterX + ( sin(angle) * ( clockRadius - ( clockRadius / 8 ) ) ) );
        int y3 = ( clockCenterY - ( cos(angle) * ( clockRadius - ( clockRadius / 8 ) ) ) );
        display->drawLine( x2 + x , y2 + y , x3 + x , y3 + y);
    }

    // display second hand
    float angle = second() * 6 ;
    angle = ( angle / 57.29577951 ) ; //Convert degrees to radians
    int x3 = ( clockCenterX + ( sin(angle) * ( clockRadius - ( clockRadius / 5 ) ) ) );
    int y3 = ( clockCenterY - ( cos(angle) * ( clockRadius - ( clockRadius / 5 ) ) ) );
    display->drawLine( clockCenterX + x , clockCenterY + y , x3 + x , y3 + y);
    //
    // display minute hand
    angle = minute() * 6 ;
    angle = ( angle / 57.29577951 ) ; //Convert degrees to radians
    x3 = ( clockCenterX + ( sin(angle) * ( clockRadius - ( clockRadius / 4 ) ) ) );
    y3 = ( clockCenterY - ( cos(angle) * ( clockRadius - ( clockRadius / 4 ) ) ) );
    display->drawLine( clockCenterX + x , clockCenterY + y , x3 + x , y3 + y);
    //
    // display hour hand
    angle = hour() * 30 + int( ( minute() / 12 ) * 6 )   ;
    angle = ( angle / 57.29577951 ) ; //Convert degrees to radians
    x3 = ( clockCenterX + ( sin(angle) * ( clockRadius - ( clockRadius / 2 ) ) ) );
    y3 = ( clockCenterY - ( cos(angle) * ( clockRadius - ( clockRadius / 2 ) ) ) );
    display->drawLine( clockCenterX + x , clockCenterY + y , x3 + x , y3 + y);
}

void digitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
    display->setFont(ArialMT_Plain_24);
    display->setTextAlignment(TEXT_ALIGN_CENTER);
    char string_to_show[12] = {0};
    sprintf(string_to_show, "%02d:%02d:%02d", hourFormat12(),minute(),second());
    display->drawString(64 + x, 4 + y, string_to_show);

    display->setFont(ArialMT_Plain_16);
    display->setTextAlignment(TEXT_ALIGN_CENTER);
    sprintf(string_to_show, "%02d/%02d/%d", day(),month(),year());
    display->drawString(64 + x, 34 + y, string_to_show);
}

void climateClockFrame(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {    

    char time_to_show[12] = {0};

    int current_diff, remaining, years, days, hours, minutes, seconds;
        
    current_diff = timeClimateClockDeadline - now();
    
    years = current_diff / 31536000;
    remaining = current_diff % 31536000;

    days = remaining / 86400 - LeapYearsBetween(year(), year(timeClimateClockDeadline));
    remaining = remaining % 86400;

    hours = remaining / 3600;
    remaining = remaining % 3600;

    minutes = remaining / 60;
    seconds = remaining % 60;

    //Serial.printf("Deadline : %d years %d days %d hours %d minutes %d seconds \n", years, days, hours, minutes, seconds);

    
    display->setFont(ArialMT_Plain_10);
    display->setTextAlignment(TEXT_ALIGN_CENTER);
    display->drawString(64 + x, 0 + y, "#ClimateClock");
  
    display->setFont(ArialMT_Plain_16);
    display->setTextAlignment(TEXT_ALIGN_RIGHT);
    sprintf(time_to_show, "%d", years);
    display->drawString(30 + x, 12 + y, time_to_show);
  
    display->setFont(ArialMT_Plain_10);
    display->setTextAlignment(TEXT_ALIGN_LEFT);
    display->drawString(31 + x, 17 + y, "YRS");
  
    display->setFont(ArialMT_Plain_16);
    display->setTextAlignment(TEXT_ALIGN_RIGHT);
    sprintf(time_to_show, "%03d", days);
    display->drawString(84 + x, 12 + y, time_to_show);
  
    display->setFont(ArialMT_Plain_10);
    display->setTextAlignment(TEXT_ALIGN_LEFT);
    display->drawString(85 + x, 17 + y, "DAYS");
  
    display->setFont(ArialMT_Plain_10);
    display->setTextAlignment(TEXT_ALIGN_CENTER);
    display->drawString(64 + x, 50 + y, "#ActInTime");

    display->setFont(ArialMT_Plain_16);
    display->setTextAlignment(TEXT_ALIGN_CENTER);
    //sprintf(rtc_time, "%02d:%02d:%02d", rtc.getHour(),rtc.getMinute(),rtc.getSecond());
    sprintf(time_to_show, "%02d:%02d:%02d", hours, minutes, seconds);
    display->drawString(64 + x, 32 + y, time_to_show);
    
}

// This array keeps function pointers to all frames
// frames are the single views that slide in
FrameCallback frames[] = { analogClockFrame, digitalClockFrame, climateClockFrame };

// how many frames are there?
int frameCount = 3;

// Overlays are statically drawn on top of a frame eg. a clock
OverlayCallback overlays[] = { clockOverlay };
int overlaysCount = 1;

bool init_rtc(void) {

    MYLOG("RTC", "RAK12002 RTC Initialization");

    Wire.begin();
    rtc.initI2C(); // First initialize and create the rtc device

    rtc.writeToRegister(0x35,0x00);

    rtc.writeToRegister(0x37,0xB4); //Direct Switching Mode (DSM): when VDD < VBACKUP, switchover occurs from VDD to VBACKUP

    rtc.set24HourMode();  // Set the device to use the 24hour format (default) instead of the 12 hour format

    return true;
}

bool getClimateClockData(void) {

    Serial.print("Connecting to Wifi");
    //WiFi.forceSleepWake();
    WiFi.begin(wifiSSID, wifiPassword);

    int i = 0;
    int wifi_connection_trials = 10;
    while (WiFi.status() != WL_CONNECTED)
    {
        Serial.print(".");
        i++;
        delay (1250);
        if (i >= wifi_connection_trials) {
            Serial.printf("\nNot connected to WiFi aftter %d connection trials\n", wifi_connection_trials);
            return false;
        }
    }
    
    if ((WiFi.status() == WL_CONNECTED)) {
      
        HTTPClient http;
        
        Serial.println ("\nGet ClimateClock API\n");
        
        /* The #ClimateClock is offered to the worldwide climate movement as 
        an internationally coordinated, participatory project. However, if
        you area private firm, big NGO, city government, or major University,
        please contact them first, to be officially licensed & authorized.
        Get more info at https://climateclock.world/join. */
    
        http.begin ("https://api.climateclock.world/v1/clock");
        int httpCode = http.GET();
    
        if (httpCode > 0) {
    
        }
        Serial.println ("Statuscode: " + String(httpCode));
    
    
        delay (100);
    
        const size_t capacity = 
            3*JSON_ARRAY_SIZE(1) + 2*JSON_ARRAY_SIZE(2) + 7*JSON_ARRAY_SIZE(3) + JSON_ARRAY_SIZE(9) + 
            2*JSON_OBJECT_SIZE(1) + 4*JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(3) + 2*JSON_OBJECT_SIZE(4) + 
            2*JSON_OBJECT_SIZE(5) + 10*JSON_OBJECT_SIZE(6) + JSON_OBJECT_SIZE(7) + 3*JSON_OBJECT_SIZE(12) + 5540;;
            
        DynamicJsonDocument doc(capacity);
        String payload = http.getString();
        const char* json = payload.c_str();
    
        deserializeJson(doc, json);
    
        JsonObject climateClockAPIData = doc["data"];
    
        char climate_clock_deadline[25] = {0};
    
        char climate_clock_time[25] = {0};
    
        strcpy(climate_clock_deadline, climateClockAPIData["modules"]["carbon_deadline_1"]["timestamp"]);
    
        strcpy(climate_clock_time, climateClockAPIData["retrieval_timestamp"]);
        
    
        Serial.println(climate_clock_deadline);
    
        Serial.println(climate_clock_time);
    
        int api_year = atoi(climate_clock_time);
        int api_month = atoi(climate_clock_time + 5);
        int api_day =  atoi(climate_clock_time + 8);
    
        int api_hour =  atoi(climate_clock_time + 11) - 5;
        int api_min =  atoi(climate_clock_time + 14);
        int api_sec =  atoi(climate_clock_time + 17);

        if (api_hour < 0) {
            api_hour = api_hour + 24;
            api_day--; 
        }

        
        Serial.printf("I got from API %d/%d/%d %02d:%02d:%02d\n",api_year,api_month,api_day,api_hour,api_min,api_sec);

        rtc.setTime(api_year, api_month, 6, api_day, api_hour, api_min, api_sec);

        setTime(rtc.getHour(), rtc.getMinute(), rtc.getSecond(), rtc.getDate(), rtc.getMonth(), rtc.getYear());

    
        int api_deadline_year = atoi(climate_clock_deadline);
        int api_deadline_month = atoi(climate_clock_deadline + 5);
        int api_deadline_day =  atoi(climate_clock_deadline + 8);
    
        int api_deadline_hour =  atoi(climate_clock_deadline + 11) - 5;
        int api_deadline_min =  atoi(climate_clock_deadline + 14);
        int api_deadline_sec =  atoi(climate_clock_deadline + 17);

        climateClockDeadline.tm_year = api_deadline_year - 1900;
        climateClockDeadline.tm_mon = api_deadline_month - 1;
        climateClockDeadline.tm_mday = api_deadline_day;
        climateClockDeadline.tm_hour = api_deadline_hour;
        climateClockDeadline.tm_min = api_deadline_min;
        climateClockDeadline.tm_sec = api_deadline_sec;
        
        timeClimateClockDeadline = mktime(&climateClockDeadline);
        Serial.printf("Deadline seconds : %d \n", timeClimateClockDeadline);
        Serial.printf("Now : %d \n", now());

        Serial.printf("Diferencia : %d \n", timeClimateClockDeadline - now());

        Serial.printf("Saltos de año fuera de la función : %d \n", LeapYearsBetween(year(), year(timeClimateClockDeadline)));

        Serial.printf("Desde funcion year : %d, desde climateClockDeadline.tm_year : %d  \n", year(), climateClockDeadline.tm_year + 1900);              
    
        http.end();
    }

    WiFi.disconnect();
    WiFi.mode(WIFI_OFF);
    
    return true;
}

void setup() {
    Serial.begin(115200);
    Serial.println();

    // The ESP is capable of rendering 60fps in 80Mhz mode
    // but that won't give you much time for anything else
    // run it in 160Mhz mode or just set it to 30 fps
    ui.setTargetFPS(60);

    // Customize the active and inactive symbol
    ui.setActiveSymbol(activeSymbol);
    ui.setInactiveSymbol(inactiveSymbol);

    // You can change this to
    // TOP, LEFT, BOTTOM, RIGHT
    ui.setIndicatorPosition(LEFT);

    // Defines where the first frame is located in the bar.
    ui.setIndicatorDirection(LEFT_RIGHT);

    // You can change the transition that is used
    // SLIDE_LEFT, SLIDE_RIGHT, SLIDE_UP, SLIDE_DOWN
    ui.setFrameAnimation(SLIDE_UP);

    // Add frames
    ui.setFrames(frames, frameCount);

    // Add overlays
    ui.setOverlays(overlays, overlaysCount);

    ui.setTimePerFrame(10000);
    ui.setTimePerTransition(500);
    

    // Initialising the UI will init the display too.
    ui.init();

    display.flipScreenVertically();

    init_rtc();

    setTime(rtc.getHour(), rtc.getMinute(), rtc.getSecond(), rtc.getDate(), rtc.getMonth(), rtc.getYear());

    if (getClimateClockData()) {
        Serial.println("Updated corrrectly");
    } else {
        Serial.println("WiFi connection not available");
    }

}


void loop() {
    unsigned long currentMillis = millis();
    
    int remainingTimeBudget = ui.update();

    if (remainingTimeBudget > 0) {
        // You can do some work here
        // Don't do stuff if you are below your
        // time budget.
        delay(remainingTimeBudget);
        
    if (currentMillis - previousMillis >= interval) {

        previousMillis = currentMillis;

        getClimateClockData();
        
        }   
    }

}

Credits

Johan Sebastian Macias

Johan Sebastian Macias

7 projects • 23 followers
Electronics Engineer helping to make IoT easy as a Developer Advocate @ RAKwireless (until January 13th, 2023).
Harold Duarte Rojas

Harold Duarte Rojas

9 projects • 10 followers
Electronics engineer, Content creator at RAKwireless.
RAKwireless

RAKwireless

27 projects • 53 followers
A pioneer in providing innovated and diverse LPWA connectivity solutions for IoT edge devices.

Comments