Last active
May 16, 2022 18:34
-
-
Save Conplug/319d91c19615f48821009865cd6a4b39 to your computer and use it in GitHub Desktop.
NTP Clock, Temperature, Humidity and Light Level.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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