Skip to content

Instantly share code, notes, and snippets.

@Conplug
Last active May 16, 2022 18:34
Show Gist options
  • Save Conplug/319d91c19615f48821009865cd6a4b39 to your computer and use it in GitHub Desktop.
Save Conplug/319d91c19615f48821009865cd6a4b39 to your computer and use it in GitHub Desktop.
NTP Clock, Temperature, Humidity and Light Level.
//
// Copyright (c) 2019 Conplug (https://conplug.com.tw)
// Author: Hartman Hsieh
//
// Description :
// - 同步 NTP 時間
// - 顯示溫度值 (C)
// - 顯示溼度值 (%)
// - 顯示光照強度 (LUX)
// - 若尚未設定 WIFI SSID 會直接進入 SmartConfig 模式,可由手機 APP 配置 WIFI SSID 與密碼。
// - 按住按鈕5秒進入 SmartConfig 模式,可由手機 APP 重新配置 WIFI SSID 與密碼。
//
// Building Configurations:
// Board : NodeMCU 1.0(ESP-12E Module)
// Flash Size : 4M(1M SPIFFS)
//
// Connections :
// "DFRobot Gravity I2C LCD1602" => "IIC0" ( "NMNR_EX V1.0" )
// "Light Sensor BH1750" => "IIC1" ( "NMNR_EX V1.0" )
// "DFRobot DHT11 Sensor" => "JD3" ( "NMNR_EX V1.0" )
// "POT_BTN V1.0" => "JA0D4" ( "NMNR_EX V1.0" )
//
// Required Library :
// https://github.com/bblanchon/ArduinoJson
// https://github.com/bearwaterfall/DFRobot_LCD-master
// https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library
// https://github.com/Conplug/Conplug_UnifiedLcd
// https://github.com/beegee-tokyo/DHTesp
// https://github.com/claws/BH1750
// https://github.com/PaulStoffregen/Time
//
/*
* Change Log :
* Revision - 1.6
* 1. 增加功能 - "若尚未設定 WIFI SSID 會直接進入 SmartConfig 模式"
* 2. 變更 NTP 時間同步功能,解決無法同步問題。
*/
#include <Wire.h> // IIC bus
#include <FS.h> // SPIFFS
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecureBearSSL.h> // HTTPS
#include <ArduinoJson.h> // Arduino Json 6 support
#include "Conplug_UnifiedLcd.h" // IIC LCD Module
#include "time.h" // ESP8266 time function support
#include "DHTesp.h" // DHT11 or DHT22
#include <BH1750.h> // Light Sensor (BH1750)
#include <WiFiUdp.h> // For NTP function
#include <TimeLib.h> // Library for Time function
//
// Pin 定義
//
#define PIN_DHT D3 // 溫溼度感測器 (DHT11 or DHT22)
#define PIN_BUTTON D4 // 按鍵
#define PIN_POT A0 // 旋鈕
//
// 韌體版本
//
const float FW_VERSION = 1.6;
//
// NTP 設定
//
//static const char NTP_SERVER[] = "us.pool.ntp.org";
static const char NTP_SERVER[] = "tw.pool.ntp.org";
//static const char NTP_SERVER[] = "pool.ntp.org";
const int TIME_ZONE = 8; // Taipei Time
WiFiUDP Udp;
const unsigned int UDP_LOCAL_PORT = 8888; // local port to listen for UDP packets
const char* WEEK_DAY_NAME[] = {"Sun", "Mon","Tue","Wed","Thu","Fri","Sat"}; // Days Of The Week
//
// 函式定義
//
void ICACHE_RAM_ATTR IsrButton();
time_t getNtpTime();
void sendNtpPacket(IPAddress &address);
//
// IIC LCD Module
//
Conplug_UnifiedLcd* Lcd = 0;
//
// DHT11 or DHT22
//
DHTesp Dht;
//
// Light Sensor (BH1750)
//
BH1750 LightMeter;
//
// 全域變數
//
unsigned long PreviousClickTime = 0;
unsigned long PreviousSensorReadingTime = 0;
unsigned long PreviousDisplayingTime = 0;
unsigned long ButtonDownBeginTime = 0; // For SmartConfig
int PreButtonState = 0; // For SmartConfig
int DisplayIndex = 1; // 顯示模式索引
bool ButtonTriggered = false; // 按鍵被觸發時設成true
//
// 感測器數值
//
float HumidityValue = 0.0;
float TemperatureValue = 0.0;
float LightValue = 0.0;
void setup()
{
//
// 初始化IIC總線
//
Wire.begin(); // Join IIC bus for Light Sensor (BH1750).
pinMode(PIN_BUTTON, INPUT);
pinMode(PIN_POT, INPUT);
Serial.begin(9600);
//
// Initialize LCD module
//
Lcd = new Conplug_UnifiedLcd(16, 2);
Lcd->init();
Lcd->setCursor(0, 0);
Lcd->print("conplug.com.tw ");
Lcd->setCursor(0, 1);
Lcd->print("FW:");
Lcd->print(FW_VERSION, 1);
Lcd->print(" ");
//
// Connect to WIFI AP
//
WifiConn();
//
// 設定 NTP 時間同步
//
Serial.println("Starting UDP");
Udp.begin(UDP_LOCAL_PORT);
Serial.print("Local port: ");
Serial.println(Udp.localPort());
Serial.println("waiting for sync");
setSyncProvider(getNtpTime); // Set the external time provider
setSyncInterval(36000); // Set the number of seconds between re-sync
//
// 初始化 DHT Sensor
//
Dht.setup(PIN_DHT, DHTesp::DHT11);
//
// 初始化 Light Sensor
//
LightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE);
//
// 為按鈕設定中斷處理函式
//
attachInterrupt(digitalPinToInterrupt(PIN_BUTTON), IsrButton, RISING);
}
void loop()
{
int PotValue = analogRead(PIN_POT);
//
// 判斷是否進入 SmartConfig 模式
//
if(digitalRead(PIN_BUTTON) == 1) { // Key down
if(ButtonDownBeginTime != 0) {
Serial.printf("ButtonDownContinueTime=%d\n", millis() - ButtonDownBeginTime);
if((millis() - ButtonDownBeginTime) > 5000) { // 按鈕被按下超過5秒
//
// 進入 Smart Config 模式
//
if(BeginSmartConfig() == true) { // Success
Lcd->setCursor(0, 1);
Lcd->print("Success ");
}
else { // Failure
ButtonDownBeginTime = 0;
PreButtonState = 0;
}
}
}
if(PreButtonState == 0) {
ButtonDownBeginTime = millis(); // 記錄按鈕被按下的開始時間
Serial.printf("ButtonDownBeginTime=%d\n", ButtonDownBeginTime);
}
PreButtonState = 1;
}
else { // Key up
ButtonDownBeginTime = 0;
PreButtonState = 0;
}
//
// Read all sensors every 1.5 seconds
//
if ((millis() - PreviousSensorReadingTime) > 1500) {
//
// Read humidity(Unit:%)
//
HumidityValue = Dht.getHumidity();
Serial.printf("Humidity : %f %%\n", HumidityValue);
//
// Read temperature as Celsius (the default)
//
TemperatureValue = Dht.getTemperature();
Serial.printf("Temperature : %f C\n", TemperatureValue);
//
// Check if any reads failed.
//
if (isnan(HumidityValue) || isnan(TemperatureValue)) {
Serial.println("Failed to read from DHT sensor!");
}
//
// Read Light level (Unit:Lux)
//
LightValue = LightMeter.readLightLevel();
Serial.printf("Light : %f LUX\n", LightValue);
//
// 抓取時間資料
//
Serial.print(year());
Serial.print("-");
Serial.print(month());
Serial.print("-");
Serial.print(day());
Serial.print(" ");
Serial.print(hour());
Serial.print(":");
Serial.print(minute());
Serial.print(":");
Serial.print(second());
Serial.print(" ");
Serial.println(WEEK_DAY_NAME[weekday() - 1]);
PreviousSensorReadingTime = millis();
}
//
// 按鍵被觸發或每0.8秒,刷新畫面。
//
if((ButtonTriggered == true) || ((millis() - PreviousDisplayingTime) > 800)) {
if(DisplayIndex == 1)
DisplayType1();
else if(DisplayIndex == 2)
DisplayType2();
else if(DisplayIndex == 3)
DisplayDht11();
else if(DisplayIndex == 4)
DisplayLight();
else
DisplayType1();
ButtonTriggered = false;
PreviousDisplayingTime = millis();
}
}
void ICACHE_RAM_ATTR IsrButton()
{
//
// 防止按鍵彈跳
//
if ((millis() - PreviousClickTime) > 500) { // Debouncing
Serial.println("Button Rising");
//Serial.printf("PIN_BUTTON=%d\n", digitalRead(PIN_BUTTON));
ButtonTriggered = true;
//
// 移動顯示模式索引,超過最後的值(4)再重設成1。
//
DisplayIndex++;
if(DisplayIndex > 4)
DisplayIndex = 1;
Serial.printf("DisplayIndex=%d\n", DisplayIndex);
PreviousClickTime = millis();
}
}
//
// Display Date, Time, Humidity and Temperature
// 顯示日期,時間,溼度與溫度。
//
void DisplayType1()
{
//
// Print YEAR-MONTH-DAY
//
Lcd->setCursor(0, 0);
Lcd->print(year());
Lcd->print("-");
PrintLcdDigits(month());
Lcd->print("-");
PrintLcdDigits(day());
Lcd->print(" ");
Lcd->setCursor(16 - 5, 0); // Align right
Lcd->printf("%2.1f", TemperatureValue);
Lcd->print("C");
//
// Print HOUR:MIN:SEC WEEK
//
Lcd->setCursor(0, 1);
PrintLcdDigits(hour());
Lcd->print(":");
PrintLcdDigits(minute());
Lcd->print(":");
PrintLcdDigits(second());
Lcd->print(" ");
Lcd->print(WEEK_DAY_NAME[weekday() - 1]);
Lcd->print(" ");
Lcd->setCursor(16 - 3, 1); // Align right
Lcd->printf("%2d", (int)HumidityValue);
Lcd->print("%");
}
//
// Display Date and Time
// 顯示日期與時間。
//
void DisplayType2()
{
//
// Print YEAR-MONTH-DAY
//
Lcd->setCursor(0, 0);
Lcd->print(" ");
Lcd->print(year());
Lcd->print("-");
PrintLcdDigits(month());
Lcd->print("-");
PrintLcdDigits(day());
Lcd->print(" ");
//
// Print HOUR:MIN:SEC WEEK
//
Lcd->setCursor(0, 1);
Lcd->print(" ");
PrintLcdDigits(hour());
Lcd->print(":");
PrintLcdDigits(minute());
Lcd->print(":");
PrintLcdDigits(second());
Lcd->print(" ");
Lcd->print(WEEK_DAY_NAME[weekday() - 1]);
Lcd->print(" ");
}
//
// Display Humidity and Temperature
// 顯示溼度與溫度。
//
void DisplayDht11()
{
Lcd->setCursor(0, 0);
Lcd->print("Temp: ");
Lcd->print(TemperatureValue, 1);
Lcd->print("C");
Lcd->setCursor(0, 1);
Lcd->print("Humidity: ");
Lcd->print(HumidityValue, 0);
Lcd->print("%");
}
//
// Display light level
// 顯示環境亮度。
//
void DisplayLight()
{
//
// Print HOUR:MIN:SEC WEEK
//
Lcd->setCursor(0, 0);
Lcd->print(" ");
PrintLcdDigits(hour());
Lcd->print(":");
PrintLcdDigits(minute());
Lcd->print(":");
PrintLcdDigits(second());
Lcd->print(" ");
Lcd->print(WEEK_DAY_NAME[weekday() - 1]);
Lcd->print(" ");
Lcd->setCursor(0, 1);
Lcd->print("Light:");
Lcd->printf("%7d LX", (int)LightValue); // Align right
}
bool BeginSmartConfig()
{
WiFi.mode(WIFI_STA);
Serial.println("\r\nWaiting for Smartconfig\r\n");
WiFi.beginSmartConfig();
unsigned long time_out = millis();
while(1) {
Serial.print(".");
Lcd->setCursor(0, 1);
Lcd->print(" ");
delay(250);
Lcd->setCursor(0, 1);
Lcd->print(((180 * 1000) - (millis() - time_out)) / 1000); // 倒數計時
Lcd->print(" ");
delay(250);
if(WiFi.smartConfigDone()) {
Serial.println("SmartConfig Success");
Serial.printf("SSID:%s\r\n", WiFi.SSID().c_str());
Serial.printf("PASS:%s\r\n", WiFi.psk().c_str());
return true; // Got SSID and password
}
if((millis() - time_out) > (180 * 1000)) // 180 seconds time out, break the loop
break;
}
return false; // Time out
}
int WifiConn()
{
String ssid = WiFi.SSID();
ssid.trim();
if(ssid.length() <= 0) { // 若沒有設定 WIFI SSID
//
// 進入 Smart Config 模式
//
if(BeginSmartConfig() == true) { // Success
Lcd->setCursor(0, 1);
Lcd->print("Success ");
}
}
Serial.print("Connecting to ");
Serial.print(WiFi.SSID());
Serial.print(",");
Serial.println(WiFi.psk());
/* Explicitly set the ESP8266 to be a WiFi-client, otherwise, it by default,
would try to act as both a client and an access-point and could cause
network-issues with your other WiFi-devices on your WiFi-network. */
WiFi.mode(WIFI_STA);
//WiFi.begin("SSID", "PASSWORD");
WiFi.begin(); // 無參數,使用 Smart Config 功能儲存的 SSID 與 PASSWORD
unsigned long time_out = millis();
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
if((millis() - time_out) > (30 * 1000)) // 30 seconds time out, break the loop
break;
}
if(WiFi.status() == WL_CONNECTED) {
Serial.println("WiFi connected");
Serial.print("IP address : ");
Serial.println(WiFi.localIP());
return 0; // Success
}
else {
Serial.println("WiFi did not connect");
return 1; // Failure
}
}
void PrintLcdDigits(int digits)
{
if (digits < 10)
Lcd->print('0');
Lcd->print(digits);
}
/*-------- NTP code ----------*/
const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets
time_t getNtpTime()
{
IPAddress ntpServerIP; // NTP server's ip address
for (int i = 0; i < 6; i++) { // Retry 6 times if "No NTP Response"
while (Udp.parsePacket() > 0) ; // discard any previously received packets
Serial.println("Transmit NTP Request");
// get a random server from the pool
WiFi.hostByName(NTP_SERVER, ntpServerIP);
Serial.print(NTP_SERVER);
Serial.print(": ");
Serial.println(ntpServerIP);
sendNtpPacket(ntpServerIP);
uint32_t beginWait = millis();
while (millis() - beginWait < 1500) {
int size = Udp.parsePacket();
if (size >= NTP_PACKET_SIZE) {
Serial.println("Receive NTP Response");
Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer
unsigned long secsSince1900;
// convert four bytes starting at location 40 to a long integer
secsSince1900 = (unsigned long)packetBuffer[40] << 24;
secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
secsSince1900 |= (unsigned long)packetBuffer[43];
return secsSince1900 - 2208988800UL + TIME_ZONE * SECS_PER_HOUR;
}
}
Serial.println("No NTP Response :-(");
delay(500);
}
return 0; // return 0 if unable to get the time
}
// send an NTP request to the time server at the given address
void sendNtpPacket(IPAddress &address)
{
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
Udp.beginPacket(address, 123); //NTP requests are to port 123
Udp.write(packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment