Alan Wang
Published © CC BY-NC-SA

MetroWatch - Heat & UVI Edition (inspired by Metro Exidus)

My own version of Metro Watch - which detects heat and UVI but not nuclear radiation.

IntermediateShowcase (no instructions)545
MetroWatch - Heat & UVI Edition (inspired by Metro Exidus)

Things used in this project

Hardware components

Wemos D1 Mini
Espressif Wemos D1 Mini
×1
0.96" OLED 64x128 Display Module
ElectroPeak 0.96" OLED 64x128 Display Module
×1
BH1750 Light Intensity Sensor Module
×1
SparkFun Atmospheric Sensor Breakout - BME280
SparkFun Atmospheric Sensor Breakout - BME280
×1
ML8511 UV Detection Module
×1
DS3231 RTC Clock Module
×1
Pushbutton switch 12mm
SparkFun Pushbutton switch 12mm
×1
Buzzer, Piezo
Buzzer, Piezo
×1
WS2812 5050 12 Bits NeoPixel Ring
×1
Breadboard (generic)
Breadboard (generic)
×3
Jumper wires (generic)
Jumper wires (generic)
×20

Story

Read more

Code

MetroWatch

Arduino
/*
 * MetroWatch (Heat & UVI Edition) on ESP8266 by Alan Wang
 * inspired by the game Metro Exodus
 */

#include <math.h>
#include <Wire.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <WiFiUdp.h>
#include <NTPClient.h>         // https://github.com/arduino-libraries/NTPClient
#include <BH1750.h>            // https://github.com/claws/BH1750
#include <Adafruit_NeoPixel.h> // https://github.com/adafruit/Adafruit_NeoPixel
#include <Adafruit_BME280.h>   // https://github.com/adafruit/Adafruit_BME280_Library
#include <TM1637Display.h>     // https://github.com/avishorp/TM1637
#include <U8g2lib.h>           // https://github.com/olikraus/u8g2
#include "EasyBuzzer.h"        // https://github.com/evert-arias/EasyBuzzer
#include "RTClib.h"            // https://github.com/adafruit/RTClib

// pin mappings
#define UV        A0
#define LED       D0
#define NEOPIXEL  D3
#define LED_BOARD D4
#define TM_CLK    D5
#define TM_DIO    D6
#define BTN       D7
#define BUZZER    D8

// user settings
const char *ssid1 = "";
const char *pw1   = "";
const char *ssid2 = "";
const char *pw2   = "";

ESP8266WiFiMulti WiFiMulti;
WiFiUDP ntpUDP;
NTPClient ntpClient(ntpUDP, "pool.ntp.org");
Adafruit_NeoPixel neopixel(12, NEOPIXEL, NEO_GRB + NEO_KHZ800);
Adafruit_BME280 bme;
BH1750 bh;
RTC_DS3231 rtc;
TM1637Display tm(TM_CLK, TM_DIO);
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

const int hour_offset = 8;
const int temp_display_scale[] = {18, 24, 34, 42};
const uint32_t uviColor[] = {
  neopixel.Color(64, 255, 255),
  neopixel.Color(0, 255, 255),
  neopixel.Color(0, 255, 128),
  neopixel.Color(0, 255, 64),
  neopixel.Color(255, 255, 0),
  neopixel.Color(255, 128, 0),
  neopixel.Color(255, 64, 0),
  neopixel.Color(255, 32, 0),
  neopixel.Color(255, 16, 0),
  neopixel.Color(255, 0, 0),
  neopixel.Color(255, 0, 16),
  neopixel.Color(255, 0, 32)
};

int s_prev = 0;
long t;
float lux;
bool colon = true;
bool flashlight = false;
bool uvi_warning = false;
bool tmp_warning = false;

void setup() {

  Wire.begin();
  rtc.begin();

  pinMode(LED, OUTPUT);
  pinMode(LED_BOARD, OUTPUT);
  pinMode(BTN, INPUT_PULLUP);

  digitalWrite(LED_BOARD, LOW);
  delay(2000);

  if (btnPressed()) {

    for (int i = 0; i < 2; i++) {
      digitalWrite(LED_BOARD, HIGH);
      digitalWrite(LED, HIGH);
      delay(50);
      digitalWrite(LED_BOARD, LOW);
      digitalWrite(LED, LOW);
      delay(50);
    }
    
    WiFiMulti.addAP(ssid1, pw1);
    WiFiMulti.addAP(ssid2, pw2);
    while (WiFiMulti.run() != WL_CONNECTED);
    
    ntpClient.begin();
    ntpClient.setTimeOffset(hour_offset * 60 * 60);
    while (!ntpClient.update()) {
      delay(1000);
      
    }

    unsigned long epochTime = ntpClient.getEpochTime();
    struct tm *ptm = gmtime ((time_t *)&epochTime);
    int y = ptm->tm_year + 1900;
    int mo = ptm->tm_mon + 1;
    int d = ptm->tm_mday;
    int h = ntpClient.getHours();
    int mi = ntpClient.getMinutes();
    int s = ntpClient.getSeconds();
    rtc.adjust(DateTime(y, mo, d, h, mi, s));
    
    WiFi.disconnect();

  }

  EasyBuzzer.setPin(BUZZER);
  bme.begin(0x76);
  bh.begin();
  neopixel.begin();
  neopixel.clear();
  tm.clear();
  u8g2.begin();

  digitalWrite(LED_BOARD, HIGH);

}

void loop() {

  if (btnPressed()) {
    flashlight = !flashlight;
    flashToggle();
  }

  setLightlevel();
  displayNeoPixels();
  displayTM();
  displayOLED();
  delay(200);

}

float mapfloat(float x, float in_min, float in_max, float out_min, float out_max) {
  float value_scale = (x - in_min) / (in_max - in_min);
  return out_min + (value_scale * (out_max - out_min));
}

bool btnPressed() {
  return !digitalRead(BTN);
}

void playTone(int freq, int duration) {
  EasyBuzzer.beep(freq);
  EasyBuzzer.update();
  delay(duration);
  EasyBuzzer.stopBeep();
}

void setLightlevel() {

  lux = bh.readLightLevel();

  if (!flashlight) {
    digitalWrite(LED, lux >= 50);
    tm.setBrightness(map(lux > 600 ? 600 : lux, 1, 600, 0, 7));
    neopixel.setBrightness(map(lux > 1000 ? 1000 : lux, 1, 1000, 1, 128));
  } else {
    digitalWrite(LED, true);
    tm.setBrightness(7);
  }

}

void flashToggle() {

  neopixel.setBrightness(255);

  if (flashlight) {

    neopixel.fill(neopixel.Color(128, 128, int(128 * 0.5)));
    neopixel.show();
    delay(100);
    neopixel.clear();
    neopixel.show();
    delay(200);
    
    for (int i = 0; i < 256; i += 5) {
      neopixel.fill(neopixel.Color(i, i, int(i * 0.5)));
      neopixel.show();
      delay(10);
    }
    neopixel.fill(neopixel.Color(255, 255, int(255 * 0.5)));
    neopixel.show();

  } else {
    neopixel.clear();
    neopixel.show();
    delay(250);
  }

}

void displayNeoPixels() {

  float uv = float(analogRead(UV)) * 3.0 / 1023;
  float uvi = mapfloat(uv, 0.99, 2.99, 0.0, 60.0);

  if (!flashlight) {
    for (int i = 0; i < 12; i++) {
      if (uvi >= (i + 2)) {
        neopixel.setPixelColor(i, uviColor[i]);
      } else {
        neopixel.setPixelColor(i, neopixel.Color(0, 0, 0));
      }
    }
    neopixel.show();
  }

  if (!uvi_warning) {
    if (uvi >= 8) {
      for (int i = 0; i < 3; i++) {
        playTone(294, 50);
        delay(50);
      }
      uvi_warning = true;
    }
  } else {
    if (uvi <= 7) uvi_warning = false;
  }

}

void displayTM() {

  DateTime currentTime = rtc.now();
  int h = currentTime.hour();
  int m = currentTime.minute();
  int s = currentTime.second();

  if (s != s_prev) colon = !colon;
  tm.showNumberDecEx(h * 100 + m, colon ? 0b01000000 : 0b00000000, true);
  s_prev = s;

}

void displayOLED() {

  float t = bme.readTemperature();
  float h = bme.readHumidity();
  float p = bme.readPressure() / 100.0;
  float e = h / 100 * 6.105 * exp((17.27 * t) / (237.7 + t));
  float ap_tmp = 1.07 * t + 0.2 * e - 2.7;

  float ang = mapfloat(ap_tmp, 10, 50, 0, 180);
  String ang_str = String(int(ang));
  int tri_end_x = round(48 * cos(ang * M_PI / 180));
  int tri_end_y = round(48 * sin(ang * M_PI / 180));
  int tri_side_x = round(4 * cos((ang + 90) * M_PI / 180));
  int tri_side_y = round(4 * sin((ang + 90) * M_PI / 180));

  u8g2.clearBuffer();

  if (lux >= 50 || flashlight) {
    u8g2.drawCircle(64, 64, 60);
    u8g2.drawCircle(64, 64, 40);
  }
  u8g2.drawTriangle(64 - tri_end_x,
                    64 - tri_end_y,
                    64 - tri_side_x,
                    64 - tri_side_y,
                    64 + tri_side_x,
                    64 + tri_side_y);

  u8g2.setFont(u8g2_font_tenfatguys_tn);
  for (int i = 0; i < 4; i++) {
    float ang_font = mapfloat(temp_display_scale[i], 10, 50, 0, 180);
    int pos_len = round(mapfloat(ang_font, 0, 180, 68, 48));
    int font_x = round(pos_len * cos(ang_font * M_PI / 180));
    int font_y = round(pos_len * sin(ang_font * M_PI / 180));
    String value_str = String(temp_display_scale[i]);
    u8g2.drawStr(64 - font_x - 2, 64 - font_y + 5,
                 strcpy(new char[value_str.length() + 1], value_str.c_str()));
  }

  u8g2.setFont(u8g2_font_courB08_tn);
  String tmp_str = String(ap_tmp);
  u8g2.drawStr(8, 63, strcpy(new char[tmp_str.length() + 1], tmp_str.c_str()));
  u8g2.sendBuffer();

  if (!tmp_warning) {
    if (ap_tmp >= 38) {
      for (int i = 0; i < 3; i++) {
        playTone(587, 50);
        delay(50);
      }
      tmp_warning = true;
    }
  } else {
    if (ap_tmp <= 36) tmp_warning = false;
  }

}

Credits

Alan Wang
31 projects • 103 followers
Please do not ask me for free help for school or company projects. My time is not open sourced and you cannot buy it with free compliments.

Comments