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)538
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.
Contact

Comments

Please log in or sign up to comment.