IoT (Internet of Things) is a technology that connects various devices through a network, enabling them to communicate and be controlled remotely. This project is a typical IoT application:
Simple analogy: It's like using your phone to remotely control an air conditioner at home — the phone is the remote, the AC is the controlled device, and WiFi is the "phone line" between them.
This project uses a multi-file modular code structure, which is a more professional approach than single-file programming. Each file is responsible for a specific function, making it easier to maintain and extend.
| File Name | Module | Core Functions |
|---|---|---|
Internet of Things WIFI Control System.ino |
Main Program | Initialization, main loop, integrates all modules |
NTPTime.h / NTPTime.cpp |
NTP Time Module | Connects to WiFi, retrieves network time |
DHTSensor.h / DHTSensor.cpp |
Temperature & Humidity Sensor Module | Reads DHT11 sensor data |
OLEDDisplay.h / OLEDDisplay.cpp |
OLED Display Module | Controls OLED screen display |
WebServer.h / WebServer.cpp |
Web Server Module | Creates AP hotspot, handles web requests |
Module Relationship Diagram:
Main Program (.ino)
├── NTPTime Module (get network time)
├── DHTSensor Module (read temperature & humidity)
├── OLEDDisplay Module (display information)
└── WebServer Module (create hotspot and web page)
| Advantage | Description |
|---|---|
| Easy Maintenance | Modify a function by editing only the corresponding file |
| Easy Reuse | Other projects can directly copy a module |
| Clear Code | Each file has a single responsibility, clear logic |
| Team Collaboration | Multiple people can develop different modules simultaneously |
DHT11 is a commonly used digital temperature and humidity sensor with the following features:
Simple analogy: DHT11 is like a "thermometer + hygrometer" that tells you both how hot and how humid the air is.
OLED (Organic Light-Emitting Diode) is a self-emissive display technology:
Simple analogy: OLED is like a mini TV screen that can display time, temperature, humidity, and other information.
Buzzer:
Motor:
NTP (Network Time Protocol) is used to obtain accurate time from the internet:
millis() function for local timekeepingThis project uses multiple libraries that need to be installed manually:
| Library Name | Source | Installation Method |
|---|---|---|
WiFiS3 |
Built into Arduino IDE | Included with board core, no extra installation needed |
WiFiUdp |
Built into Arduino IDE | Included with board core, no extra installation needed |
Wire |
Built into Arduino IDE | I2C communication library, no extra installation needed |
NTPClient |
Arduino Library Manager | Manual installation required |
Adafruit GFX Library |
Arduino Library Manager | Manual installation required |
Adafruit SSD1306 |
Arduino Library Manager | Manual installation required |
dht_nonblocking |
Arduino Library Manager | Manual installation required |
The provided resource package contains the libraries folder. You can load the libraries locally using the method shown in the figure below.
| Function/Variable | Description | Role in This Project |
|---|---|---|
WiFiUDP ntpUDP |
Creates UDP object | NTP communication requires UDP protocol |
NTPClient timeClient(...) |
Creates NTP client | Configures NTP server and timezone |
getNetTime() |
Connects WiFi and gets network time | Syncs time at startup |
timeClient.begin() |
Starts NTP client | Begins NTP communication |
timeClient.update() |
Gets time from NTP server | Syncs network time |
timeClient.getEpochTime() |
Gets Unix timestamp | Gets current seconds count |
timeClient.end() |
Stops NTP client | Releases NTP resources |
| Function | Description | Role in This Project |
|---|---|---|
WiFi.begin(ssid, pass) |
Starts STA mode | Connects to router to get time |
WiFi.status() |
Checks connection status | Confirms successful connection |
WiFi.end() |
Completely turns off WiFi | Prepares for mode switching |
WiFi.beginAP(ssid, pass) |
Starts AP mode | Creates WiFi hotspot |
WiFi.localIP() |
Gets device IP | Views IP in AP mode |
| Function | Description | Role in This Project |
|---|---|---|
DHT_nonblocking dht_sensor(...) |
Creates sensor instance | Configures pin and type |
dht_sensor.measure(&temp, &hum) |
Reads temperature & humidity | Gets sensor data |
measureEnvironment(temp, hum) |
Wrapped measurement function | Measures every 3 seconds |
| Function | Description | Role in This Project |
|---|---|---|
Adafruit_SSD1306 display(...) |
Creates OLED instance | Configures screen parameters |
oledInit() |
Initializes OLED | Sets display parameters |
updateOLED(...) |
Updates display content | Shows time, alarm, temperature & humidity |
| Function | Description | Role in This Project |
|---|---|---|
WiFiServer server(80) |
Creates web server | Listens for HTTP requests |
server.begin() |
Starts the server | Begins accepting client connections |
server.accept() |
Accepts new connection | Gets client object |
client.readStringUntil('\r') |
Reads HTTP request | Parses control commands |
handleWebRequest(...) |
Handles web requests | Parses commands and returns web page |
| Function/Variable | Description | Role in This Project |
|---|---|---|
millis() |
Returns milliseconds since boot | Used to check time intervals |
currentUnixTime++ |
Timestamp +1 | Updates time every second |
alarmHour / alarmMin |
Alarm time | Stores alarm setting |
alarmOn |
Alarm switch | Controls whether alarm is active |
alarmRing |
Alarm ringing flag | Triggers buzzer ringing |
| Request Path | Function | Corresponding Action |
|---|---|---|
GET /H |
Turn on LED | Web page LED ON button |
GET /L |
Turn off LED | Web page LED OFF button |
GET /MOTOR_ON |
Turn on motor | Web page Motor ON button |
GET /MOTOR_OFF |
Turn off motor | Web page Motor OFF button |
GET /ALARM?h=08&m=30 |
Set alarm | Sets alarm at 08:30 |
GET /ALARM_OFF |
Turn off alarm | Stops ringing and resets |
The main program is the entry point of the entire project, responsible for initializing all modules and running the main loop.
#include <WiFiS3.h> // WiFi functionality
#include <Wire.h> // I2C communication (OLED)
#include "OLEDDisplay.h" // OLED display module
#include "WebServer.h" // Web server module
#include "NTPTime.h" // NTP time module
#include "DHTSensor.h" // Temperature & humidity sensor module
// Pin definitions
#define BUZZER_PIN 6 // Buzzer pin
#define MOTOR_PIN 7 // Motor pin
int led = LED_BUILTIN; // Onboard LED
// Global variables
unsigned long currentUnixTime = 0; // Current timestamp
unsigned long lastUpdateTime = 0; // Last update time
// Alarm variables
int alarmHour = 0;
int alarmMin = 0;
bool alarmOn = false;
bool alarmRing = false;
// Temperature & humidity variables
float temperature = 0.0;
float humidity = 0.0;
Code Notes:
setup() Initialization Functionvoid setup() {
// Initialize serial
Serial.begin(115200);
while (!Serial);
delay(1000);
// Initialize pins
pinMode(led, OUTPUT);
pinMode(led, ); // WARNING: This line is missing the second parameter, should be removed
pinMode(BUZZER_PIN, OUTPUT);
pinMode(MOTOR_PIN, OUTPUT);
// Initial state: LOW
digitalWrite(led, LOW);
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(MOTOR_PIN, LOW);
// OLED initialization
oledInit();
// Step 1: Get network time
getNetTime(currentUnixTime);
// Step 2: Switch to AP mode
startAPMode();
// Step 3: Start web server
server.begin();
// Step 4: Record time starting point
lastUpdateTime = millis();
}
Step Breakdown:
oledInit() to initialize the displaygetNetTime() to connect to router via STA modestartAPMode() to create a WiFi hotspotmillis() value as timing starting pointloop() Main Loop Functionvoid loop() {
// ====== Core 1: Automatic Timekeeping ======
if (millis() - lastUpdateTime >= 1000) {
lastUpdateTime = millis();
currentUnixTime++;
// Calculate hours, minutes, seconds
int h = (currentUnixTime % 86400L) / 3600;
int m = (currentUnixTime % 3600) / 60;
int s = currentUnixTime % 60;
// Print time to serial
Serial.print("Time: ");
if(h<10) Serial.print("0"); Serial.print(h);
Serial.print(":");
if(m<10) Serial.print("0"); Serial.print(m);
Serial.print(":");
if(s<10) Serial.print("0"); Serial.println(s);
// Measure temperature & humidity
if(measureEnvironment(temperature, humidity)) {
Serial.print("T = ");
Serial.print(temperature, 1);
Serial.print(" deg. C, H = ");
Serial.print(humidity, 1);
Serial.println("%");
}
// Update OLED display
updateOLED(h, m, s, alarmHour, alarmMin, alarmOn, temperature, humidity);
// Alarm trigger check
if(alarmOn && h == alarmHour && m == alarmMin && s == 0){
alarmRing = true;
}
}
// ====== Core 2: Handle Web Requests ======
handleWebRequest(currentUnixTime, alarmHour, alarmMin, alarmOn, alarmRing, BUZZER_PIN, temperature, humidity, motorState);
// ====== Core 3: Alarm Ringing (Non-blocking) ======
static unsigned long lastBuzzerTime = 0;
if(alarmRing){
if(millis() - lastBuzzerTime >= 40){
lastBuzzerTime = millis();
static bool buzzerState = false;
buzzerState = !buzzerState;
digitalWrite(BUZZER_PIN, buzzerState);
}
} else {
digitalWrite(BUZZER_PIN, LOW);
}
// ====== Core 4: Motor Control ======
static bool lastMotorState = false;
if(motorState != lastMotorState) {
lastMotorState = motorState;
digitalWrite(MOTOR_PIN, motorState ? HIGH : LOW);
Serial.print("Motor state updated: ");
Serial.println(motorState ? "ON" : "OFF");
}
}
Core Function Analysis:
Core 1: Automatic Timekeeping
Uses millis() for non-blocking 1-second timing. Each loop() checks whether 1000ms have elapsed since the last update before executing the update:
if (millis() - lastUpdateTime >= 1000) {
lastUpdateTime = millis();
currentUnixTime++; // Timestamp +1
Then splits the Unix timestamp into hours, minutes, and seconds using modulo operations:
int h = (currentUnixTime % 86400L) / 3600; // 86400 = seconds in a day
int m = (currentUnixTime % 3600) / 60; // 3600 = seconds in an hour
int s = currentUnixTime % 60;
After updating the time, it also measures temperature & humidity, refreshes the OLED, and triggers the alarm at the 0th second of the target minute:
if(alarmOn && h == alarmHour && m == alarmMin && s == 0){
alarmRing = true; // Triggers only once at the 0th second of the target minute
}
Core 2: Web Request Handling
Each loop() calls handleWebRequest() to check for new connections. The function uses server.accept() to get a client, readStringUntil('\r') to read the first line of the HTTP request, then indexOf() to match URL paths and execute corresponding actions:
WiFiClient client = server.accept();
if (client) {
String req = client.readStringUntil('\r');
if (req.indexOf("GET /H") != -1) { digitalWrite(LED_BUILTIN, HIGH); }
if (req.indexOf("GET /MOTOR_ON") != -1) { motorState = true; }
// ... parse other commands
}
After processing the request, the function sends an HTML web page to the client and disconnects.
Core 3: Alarm Ringing
Uses a non-blocking approach to control the buzzer, avoiding delay() which would block the main loop. Uses static variables to remember the last toggle time, flipping the pin level every 40ms to produce a buzzing sound:
static unsigned long lastBuzzerTime = 0;
if(alarmRing){
if(millis() - lastBuzzerTime >= 40){
lastBuzzerTime = millis();
static bool buzzerState = false;
buzzerState = !buzzerState; // Toggle level
digitalWrite(BUZZER_PIN, buzzerState);
}
} else {
digitalWrite(BUZZER_PIN, LOW); // Keep LOW when not ringing
}
The static keyword ensures lastBuzzerTime and buzzerState are not reset between loop() calls.
Core 4: Motor Control
Detects state changes by comparing motorState with lastMotorState, only operating the pin when a change occurs, avoiding redundant writes every loop:
static bool lastMotorState = false;
if(motorState != lastMotorState) {
lastMotorState = motorState;
digitalWrite(MOTOR_PIN, motorState ? HIGH : LOW);
Serial.print("Motor state updated: ");
Serial.println(motorState ? "ON" : "OFF");
}
This "edge detection" approach reduces unnecessary I/O operations and makes it easy to observe state transitions in the serial monitor.
This module is responsible for connecting to WiFi at startup to obtain accurate time, providing the initial time reference for the system.
The NTP (Network Time Protocol) concept and STA connection flow were introduced earlier. Here we only describe the module's role in the system:
getNetTime() is called during setup(), connecting to the home router via STA mode and retrieving the current Unix timestamp from an NTP serverloop() maintains time by incrementing it every second, eliminating the need for constant internet connectivity// NTP server and timezone configuration
const char* ntpServer = "ntp.ntsc.ac.cn"; // China National Time Service Center
const long gmtOffset_sec = 8 * 3600; // UTC+8 (Beijing Time)
Configuration Notes:
ntpServer: Choose a stable and reliable NTP server. Users in China are recommended to use ntp.ntsc.ac.cn (National Time Service Center) or ntp.aliyun.com (Alibaba Cloud)gmtOffset_sec: Timezone offset, UTC+8 is 8 * 3600 seconds. Adjust to the corresponding timezone for use in other countriesThe getNetTime() function internally encapsulates WiFi connection, NTP request, and retry logic (up to 30 retries, 2-second intervals each). Callers only need to pass the currentUnixTime reference to obtain the synced timestamp. If WiFi connection fails, the timestamp remains 0 and the system can still continue running, starting time from 00:00:00.
This module is responsible for reading temperature and humidity data from the DHT11 sensor, providing environmental monitoring capability for the system.
#define DHT_SENSOR_TYPE DHT_TYPE_11 // DHT11 type
#define DHT_SENSOR_PIN 2 // Data pin is D2
Configuration Notes:
DHT_SENSOR_TYPE: Specifies the sensor model. The DHT series also includes DHT22 (higher accuracy), DHT30, etc. Simply change this macro to switchDHT_SENSOR_PIN: The DHT11 data pin is connected to Arduino's D2, which must support bidirectional communication (required by the DHT11 protocol)measureEnvironment() Function Analysisbool measureEnvironment(float &temperature, float &humidity) {
static unsigned long measurement_timestamp = millis();
// Measure once every 3 seconds
if (millis() - measurement_timestamp > 3000ul) {
if (dht_sensor.measure(&temperature, &humidity) == true) {
measurement_timestamp = millis();
return true; // Measurement successful
}
}
return false; // Not yet time to measure or measurement failed
}
Code Logic Step-by-Step Analysis:
static timestamp initialization: measurement_timestamp is initialized to millis() on first call, then retains the time of the last measurementmillis() - measurement_timestamp > 3000ul checks whether 3 seconds have elapsed since the last measurement. The ul suffix on 3000ul ensures unsigned long arithmetic, preventing overflowdht_sensor.measure() to request data from the sensor. This function internally sends a start signal, receives 40 bits of data, and verifies the checksummeasurement_timestamp when measurement succeeds; on failure, the timestamp is not updated and the next loop will retry immediatelytrue indicates a new measurement was completed this round; false indicates no measurement was performed (time not up or measurement failed)Design Features:
DHT_nonblocking library; measure() does not block the main loop. A complete DHT11 communication takes about 20ms, and the non-blocking approach allows other tasks (such as web request handling) to continue while waiting for the sensor responsestatic variable for timestamp: measurement_timestamp is declared as static, preserving its value across multiple function calls without being reset, thereby recording the time of the last measurement. This is the key to implementing "throttling"float & references, allowing the function to write directly to external variables without extra global variables or struct wrappers. This approach is more efficient than returning a struct, especially in embedded systemstrue when a new measurement is completed; returns false when it's not yet time to measure or the measurement hasn't completed, in which case the caller should continue using the last valid datameasurement_timestamp is not updated and the next call will retry immediately without wasting a 3-second waitstatic variable usage pattern is consistent with the "throttling" pattern in multi-threaded environments, facilitating portability to RTOS environmentsThis module is responsible for displaying time, alarm, and temperature & humidity information on the 128×64 OLED screen, serving as the primary human-machine interaction output interface.
// Configuration in OLEDDisplay.h
#define SCREEN_WIDTH 128 // Screen width: 128 pixels
#define SCREEN_HEIGHT 64 // Screen height: 64 pixels
#define OLED_RESET -1 // No external reset pin
Hardware Notes:
OLED_RESET set to -1 means using software reset; some OLED modules have an independent reset pin that can be changed to the corresponding numberoledInit() Initialization Functionvoid oledInit() {
// Initialize OLED
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("OLED initialization failed");
while(1); // Dead loop on failure
}
display.clearDisplay();
display.setTextColor(WHITE);
display.setCursor(0,0);
display.println("OLED OK");
display.display(); // Display initialization message
}
Code Logic Step-by-Step Analysis:
display.begin(): Initializes I2C communication and the OLED chip. The parameter SSD1306_SWITCHCAPVCC enables the OLED's internal charge pump, boosting 3.3V to 7.4V to drive the screen; 0x3C is the I2C slave addressfalse), prints an error message and enters a while(1) dead loop to prevent subsequent code from running without a display. Common failure causes: wiring errors, wrong address, insufficient powerclearDisplay() clears all 1024 bytes of display memorysetTextColor(WHITE) sets pixels to lit state. SSD1306 only supports monochrome; WHITE means pixel lit (white text on black background)setCursor(0,0) moves cursor to top-left corner; println("OLED OK") outputs the initialization success messagedisplay() pushes the buffer content to the OLED hardware — this is the step that actually makes the screen light upInitialization Notes:
0x3C: The most common factory address for SSD1306. Some modules have address 0x3D (determined by the SDO/SA0 pin on the module); if the screen shows nothing, try changing to 0x3DSSD1306_SWITCHCAPVCC vs SSD1306_SETCONTRAST: The former uses an internal charge pump and works with 3.3V; the latter requires external 7-15V power. Most modules use SWITCHCAPVCCclearDisplay, drawPixel, print, etc.) only manipulate the buffer in RAM until display() is called, which then pushes the buffer to the screen via I2C. This mechanism avoids display flickering and tearingwhile(1) dead loop: This is a common "fail-safe" design in embedded systems — when critical hardware initialization fails, the system should not continue running, as this could cause unpredictable behaviorupdateOLED() Display Update Functionvoid updateOLED(int h, int m, int s,
int alarmHour, int alarmMin,
bool alarmOn, float temperature, float humidity) {
display.clearDisplay();
// Display alarm setting (if enabled)
if(alarmOn){
display.setTextSize(1);
display.setCursor(0, 0);
display.print("ALARM ");
display.print(alarmHour);
display.print(":");
display.print(alarmMin);
}
// Display time (large font)
display.setTextSize(2);
display.setCursor(0, 16);
display.print(h);
display.print(":");
display.print(m);
// Display seconds (small font)
display.setTextSize(1);
display.setCursor(65, 20);
display.print(s);
// Display temperature & humidity
display.setCursor(0, 40);
display.print("T:");
display.print(temperature, 1);
display.print("C");
display.setCursor(60, 40);
display.print("H:");
display.print(humidity, 1);
display.print("%");
display.display(); // Update screen display
}
Code Logic Analysis:
clearDisplay() clears display memory before each refresh to avoid ghosting. Unlike traditional LCDs, OLED pixels self-emit, so ghosting is less of an issue but still needs attentionalarmOn is true, displays ALARM HH:MM at row 0. Uses setTextSize(1) small font to save spacesetTextSize(2) makes each character 2×2 pixels, i.e., 16×16 pixels per character, clear and eye-catchingprint(value, 1) to retain 1 decimal placedisplay() once to push everything to the screen in one go, reducing I2C transfersDisplay Layout:
┌──────────────────────────┐
│ ALARM 08:30 │ ← Row 0: Alarm info (shown only when enabled, small font)
│ │
│ 08:30 25 │ ← Row 16: Large font time:minutes + seconds on right
│ │
│ T:25.5C H:60.0% │ ← Row 40: Temperature (left) + Humidity (right)
└──────────────────────────┘
setTextSize(1) is 8×8 pixel characters; setTextSize(2) is 16×16 pixel characters. At 128-pixel width, font size 2 accommodates 8 characters, and font size 1 accommodates 16 charactersif(h<10) Serial.print("0") for zero-padding in the main program, ensuring time always displays as 08:30 instead of 8:30. Hours, minutes, and seconds in the OLED display are all zero-paddedu8g2display() transmits 1024 bytes at once; approximately 80ms in I2C standard mode (100kHz) and 20ms in fast mode (400kHz). UNO R4 defaults to 400kHz, providing sufficient refresh speedDHT11 uses a single-wire protocol, and OLED uses I2C. The two typically do not conflict, but note:
display() operation occupies the I2C bus for about 20msThis module is responsible for creating the AP hotspot, starting the web server, and handling HTTP requests from phone browsers — it is the core of the system's remote control capability.
STA mode configuration (used to connect to a router for NTP time) was introduced in the NTP module. Here, the AP hotspot parameters and web server instance are declared:
// AP mode configuration (create hotspot)
const char* ap_ssid = "R4_AP"; // Hotspot name
const char* ap_password = "12345678"; // Hotspot password (at least 8 characters)
WiFiServer server(80); // Listen for HTTP requests on port 80
Configuration Notes:
ap_ssid: Hotspot name, visible when phones scan for WiFi. Can be changed to a custom name like "MySmartDevice"ap_password: Hotspot password, at least 8 characters (WPA2 encryption requirement). The more complex the password, the harder it is to crackWiFiServer server(80): Creates a TCP server listening on port 80 (the default HTTP port). When a phone browser accesses 192.168.4.1, requests are routed to this serverstartAPMode() Start Hotspotvoid startAPMode() {
Serial.println("\n Starting AP hotspot: R4_AP");
WiFi.beginAP(ap_ssid, ap_password);
delay(3000);
Serial.print("AP IP address: ");
Serial.println(WiFi.localIP()); // Default 192.168.4.1
}
Code Logic Analysis:
WiFi.beginAP(): Switches the UNO R4 WiFi to AP (Access Point) mode. The Arduino itself becomes a WiFi hotspot that nearby phones/computers can search for and connect todelay(3000): Waits for AP startup to complete. The WiFi module takes about 2-3 seconds to start AP, during which it cannot accept connectionsWiFi.localIP() returns the Arduino's IP address in AP mode, defaulting to 192.168.4.1. After connecting to the hotspot, enter this address in the phone browser to access the web pageAP Mode Notes:
192.168.4.x IPs to connected devices. The Arduino itself is fixed at 192.168.4.1handleWebRequest() Handle Web RequestsThis is the most critical function, responsible for parsing client requests, executing control operations, and returning the HTML web page.
void handleWebRequest(int currentUnixTime,
int& alarmHour, int& alarmMin,
bool& alarmOn, bool& alarmRing,
int BUZZER_PIN,
float temperature, float humidity,
bool& motorState) {
WiFiClient client = server.accept();
if (!client) return;
String req = client.readStringUntil('\r');
client.flush();
// Parse control commands
if (req.indexOf("GET /H") != -1) {
digitalWrite(LED_BUILTIN, HIGH); // Turn on LED
}
if (req.indexOf("GET /L") != -1) {
digitalWrite(LED_BUILTIN, LOW); // Turn off LED
}
if (req.indexOf("GET /MOTOR_ON") != -1) {
motorState = true; // Turn on motor
}
if (req.indexOf("GET /MOTOR_OFF") != -1) {
motorState = false; // Turn off motor
}
// Alarm setting
if (req.indexOf("GET /ALARM?") != -1) {
int hIndex = req.indexOf("h=");
int mIndex = req.indexOf("m=");
alarmHour = req.substring(hIndex+2, hIndex+4).toInt();
alarmMin = req.substring(mIndex+2, mIndex+4).toInt();
alarmOn = true;
}
if (req.indexOf("GET /ALARM_OFF") != -1) {
alarmRing = false;
alarmOn = false;
digitalWrite(BUZZER_PIN, LOW);
}
// Return HTML web page
// ... (web page code)
client.stop();
}
Code Logic Analysis:
server.accept() attempts to accept a new TCP connection. If a client connects, returns a WiFiClient object; otherwise returns an empty object, and if (!client) return returns immediately without blocking the main loopreadStringUntil('\r') reads the first line of the HTTP request. HTTP requests end with \r\n, so reading up to \r captures the request line. A typical request is GET /MOTOR_ON HTTP/1.1client.flush() discards the rest of the request header (such as \n and subsequent Host, User-Agent headers), preventing residual data from interfering with the responseindexOf() to search for keywords in the request string, executing the corresponding operation on match. indexOf() is used instead of exact matching because the request line also contains HTTP/1.1 and other extra contentGET /ALARM?h=08&m=30, uses indexOf("h=") to locate the parameter, substring(hIndex+2, hIndex+4) to extract two digits, and toInt() to convert to integerclient.stop() closes the TCP connection after sending is complete, releasing resourcesRequest Handling Notes:
server.accept(): This is the key to the system's non-blocking design. If no client is connected, accept() returns an empty object immediately, the function returns quickly, and the main loop continues with alarm ringing, motor control, and other tasksreadStringUntil('\r') vs readString(): The former returns as soon as the specified character is read, with a shorter timeout; the latter waits until data is available. Using readStringUntil here avoids long blocking| URL Path | Function | Action |
|---|---|---|
GET /H |
Turn on LED | digitalWrite(LED_BUILTIN, HIGH) |
GET /L |
Turn off LED | digitalWrite(LED_BUILTIN, LOW) |
GET /MOTOR_ON |
Turn on motor | motorState = true |
GET /MOTOR_OFF |
Turn off motor | motorState = false |
GET /ALARM?h=XX&m=XX |
Set alarm | Parse parameters and enable alarm |
GET /ALARM_OFF |
Turn off alarm | Stop ringing and reset alarm |
HTTP/1.1 200 OK + Content-Type: text/html + blank line), then outputs HTML line by line. The browser renders the page after receiving the complete responseWeb Interface Functions:
HTML Page Notes:
GET method; setting an alarm causes the browser to navigate to GET /ALARM?h=XX&m=XX<a href> hyperlinks; clicking them causes the browser to automatically request the corresponding URLOpen WebServer.cpp and modify the following parameters:
// Change to your WiFi name and password
const char* sta_ssid = "YourWiFiName";
const char* sta_password = "YourWiFiPassword";
// Custom hotspot name and password
const char* ap_ssid = "R4_AP"; // Can be modified
const char* ap_password = "12345678"; // At least 8 characters
On Successful Startup:
WiFi connected successfully!
NTP sync attempt 1/30: Success!
Current time: Thu Aug 04 15:30:25 2026
Starting AP hotspot: R4_AP
AP IP address: 192.168.4.1
Time: 15:30:26
T = 25.5 deg. C, H = 60.0%
Time: 15:30:27
T = 25.5 deg. C, H = 60.0%
...
On WiFi Connection Failure:
Connecting WiFi: ZNP_2.4G
........................................
WiFi connection failed, unable to sync NTP time
Starting AP hotspot: R4_AP
AP IP address: 192.168.4.1
Time: 00:00:01
...
Q1: Serial monitor shows garbled characters?
A: Please confirm the serial monitor baud rate is set to 115200.
Q2: WiFi connection failed?
A: Please check:
sta_ssid and sta_password correct?Q3: OLED shows nothing?
A: Please check:
Q4: DHT11 read failure, shows 0.0?
A: Please check:
Q5: Phone can't find the "R4_AP" hotspot?
A: Please check:
Q6: Web page opens but buttons don't respond?
A: Please check:
Q7: Alarm doesn't ring?
A: Please check:
Q8: Motor doesn't rotate?
A: Please check:
| Problem | Possible Cause | Solution |
|---|---|---|
| Serial garbled | Wrong baud rate | Change to 115200 |
| WiFi won't connect | Wrong password/5GHz | Check password, use 2.4GHz |
| OLED blank | Wiring error | Check I2C wiring |
| DHT11 no reading | Wiring error | Check D2 pin |
| Can't find hotspot | Code not uploaded | Re-upload |
| Web page won't open | Wrong IP | Use 192.168.4.1 |
| Alarm doesn't ring | Wiring/settings issue | Check buzzer and alarm settings |
| Motor doesn't move | Driver/power issue | Check driver and external power |
Modify in WebServer.cpp:
const char* sta_ssid = "YourWiFi";
const char* sta_password = "YourPassword";
const char* ap_ssid = "CustomHotspotName";
const char* ap_password = "AtLeast8CharPassword";
Modify in NTPTime.cpp:
// Change to another country's time service center
const char* ntpServer = "ntp.jst.mfeed.ad.jp"; // Japan
// Modify timezone
const long gmtOffset_sec = 9 * 3600; // Japan timezone
// Add DS18B20 temperature sensor
// Add BH1750 light sensor
// Add MQ-2 gas sensor
// Alarm array
int alarmHours[3] = {7, 12, 18};
int alarmMins[3] = {0, 0, 30};
bool alarmEnables[3] = {true, true, true};
// Trigger fan when temperature exceeds threshold
if (temperature > 30.0) {
digitalWrite(MOTOR_PIN, HIGH);
}
// Send data to cloud platform
// ThingsBoard, Blynk, Alibaba Cloud IoT, etc.
Location: 3.12 Internet of Things WIFI Control System.ino line 36
Problem Code:
pinMode(led, OUTPUT);
pinMode(led, ); // WARNING: Missing second parameter
Fix:
// Remove or fix the second line
pinMode(led, OUTPUT);
Location: WebServer.h and WebServer.cpp
Problem Description:
// Function declaration
void handleWebRequest(int currentUnixTime, ...);
// Actual variable type
unsigned long currentUnixTime = 0;
Risk: When the timestamp is large, the int type may not store it correctly, causing data truncation.
Fix:
// Modify function declaration and definition
void handleWebRequest(unsigned long currentUnixTime, ...);
Location: motorState variable used in main program
Problem: motorState is declared in the main program but also used in WebServer.cpp, needs to be passed by reference.
Current Implementation: Already passed by reference through function parameters, implemented correctly.
Upload data to cloud platforms for remote monitoring:
Develop a mobile app using MIT App Inventor or Flutter:
Integrate voice assistants:
Set up automation rules:
Expand into a smart home hub:
Display historical data charts on web or app:
Add scheduling functionality: