Code Lock with ESP8266
Hardware needed:
Wemos D1 Mini (ESP8266)
Some kind of electric lock.
A connected matrix keypad with digits 0 through 9, size 3x4.
The matrix is connected to pins D1 through D7:
D1 to D4 are connected to the rows.
D5 to D7 are connected to the columns.
A relay board to control the lock connected to D8.
Optional reed switch to detect unlocked/locked/door opened/closed connected to D0.
Additionally, a 5V power supply is required, such as from a USB charger.
Features:
WiFi connectivity.
Admin web page.
MQTT connectivity for automations with Home Assistant or similar.
Supports 8 multiple individual codes, consisting of the digits 0 through 9, with a maximum length of 8 digits.
Different codes can be valid at specific hours of the day.
Unlock the lock using the keypad by entering a valid code, or
unlock from admin page, or unlock from MQTT.
If "AlwaysOpen" is set, the lock will be unlocked all the time.
Automatically connects to an MQTT broker if one is configured.
Unlock and set AlwaysOpen from MQTT.
The program updates an MQTT server/topic with activity, including timestamps, who unlocked the door, commands published from MQTT and admin page, and input from the keypad.
Optional detection when the lock is in the unlocked state using a reed switch connected to D8. (Switch is assumed closed when the door is locked.)
You can detect if the door is opened/closed instead, if there is trouble detecting the locked/unlocked state of your lock.
Serial interface enabled in USB, 9600 baud.
Wi-Fi Connectivity:
Configure initial Wi-Fi settings via a simple Access Point (AP) and a web page.
WiFi Settings are stored in non volatile memory.
Password-Protected Admin Web Page:
The admin page features:
A button to manually unlock the door.
A button to permanently put the lock in always-unlocked mode".
Managing codes, their validity periods, and comments. There is a button to delete configuration file if needed which at reboot will create the default config file.
Configuring the MQTT broker (address, username, password, topic).
If you have multiple doors, door name and MQTT topic can be set for ease of identification.
Changing the admin password for the admin page.
Configuration Storage:
Configuration is saved in a JSON file on a non-volatile partition, ensuring it persists across reboots or firmware uploads.
A default configuration is created if no valid configuration file exists or if it becomes corrupted.
Default Settings:
Default code #1: 12345678
Default admin web page password: adminpass
Default door/lock name: Door
Known issues:
Some unidentified bugs in admin page when adding or changing codes or settings;
Always refresh and only edit and save one code/section at a time, and if it doesn't work to save the first time, retry.)
No https encryption on web page.
No TLS encryption for MQTT connection.
TBD: Setting correct time zone from admin page.
#include <Arduino.h>
#include <LittleFS.h>
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <PubSubClient.h>
#include <WiFiManager.h>
#include <NTPClient.h>
#include <Keypad.h>
#define JSON_FILENAME "/config.json"
/*
[env:esp12e]
platform = espressif8266
board = esp12e
framework = arduino
lib_deps =
bbx10/DNSServer
bblanchon/ArduinoJson
tzapu/WiFiManager
arduino-libraries/NTPClient
knolleary/PubSubClient
chris--a/Keypad
This is working code for CodeLock 0.97 /hh with:
WiFi connectivity.
Admin web page.
MQTT connectivity for automations with Home Assistant or similar.
Supports 8 multiple individual codes, consisting of the digits 0 through 9, with a maximum length of 8 digits.
Different codes can be valid at specific hours of the day.
Unlock the lock using the keypad by entering a valid code, or
unlock from admin page, or unlock from MQTT.
If "AlwaysOpen" is set, the lock will be unlocked all the time.
Automatically connects to an MQTT broker if one is configured.
Unlock and set AlwaysOpen from MQTT.
The program updates an MQTT server/topic with activity, including timestamps, who unlocked the door, commands published from MQTT and admin page, and input from the keypad.
Optional detection when the lock is in the unlocked state using a reed switch connected to D8. (Switch is assumed closed when the door is locked.)
You can detect if the door is opened/closed instead, if there is trouble detecting the locked/unlocked state of your lock.
Serial interface enabled in USB, 9600 baud.
*/
ESP8266WebServer server(80);
// Admin credentials
String adminUser = "admin";
struct Code {
String code;
int validFrom;
int validTo;
String remark;
int counter = 0; // Counter keeping track of correctly entered keypad digits
};
// Default values
const char* defaultDoorName = "Door";
const char* defaultAdminPassword = "adminpass";
const char* defaultCode = "12345678";
const int defaultValidFrom = 0;
const int defaultValidTo = 23;
const char* defaultRemark = "Default";
const int relayPin = D8; // GPIO15 (D8) // Using this pin does not activate relay during boot
const int reedSwitchPin = D0; // GPIO15 (D8) Detects if handle is engaged
char topic[300]; // Ensure this buffer is large enough for your topic string
bool alwaysOpen = false; // State variable
bool unLocked = true; // State variable, always unlocked after a power on
bool lastState = HIGH; // Track reed switch state, HIGH means unlocked
// bool currentState;
WiFiClient espClient;
PubSubClient client(espClient);
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 3600, 60000); // Timezone adjusted (+1h)
Code accessCodes[8];
String mqttServer = "";
String mqttUser = "";
String mqttPassword = "";
String mqttTopic = "";
// String topic = "CodeLock";
String adminPassword = defaultAdminPassword;
String doorName = defaultDoorName;
String message = "";
// Matris-keypad configuration
const byte ROWS = 4;
const byte COLS = 3;
char keys[ROWS][COLS] = {
{'1', '2', '3'},
{'4', '5', '6'},
{'7', '8', '9'},
{'*', '0', '#'}
};
byte rowPins[ROWS] = {D1, D2, D3, D4}; // The ESP8266 pins connect to the row pins
byte colPins[COLS] = {D5, D6, D7}; // The ESP8266 pins connect to the column pins
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// Function to initialize the JSON file
void initializeJson() {
// Serial.println("Initializing JSON file...");
message = "Initializing JSON file...";
Serial.println(message.c_str());
snprintf(topic, sizeof(topic), "%s/CodeLock/activity", mqttTopic.c_str());
client.publish(topic, message.c_str());
StaticJsonDocument<2048> doc;
// Populate default values for codes
JsonArray codesArray = doc.createNestedArray("codes");
for (int i = 0; i < 8; i++) {
JsonObject codeObj = codesArray.createNestedObject();
if (i == 0) { // Default first code
codeObj["code"] = defaultCode;
codeObj["validFrom"] = defaultValidFrom;
codeObj["validTo"] = defaultValidTo;
codeObj["remark"] = defaultRemark;
} else {
codeObj["code"] = "";
codeObj["validFrom"] = defaultValidFrom;
codeObj["validTo"] = defaultValidTo;
codeObj["remark"] = "";
}
}
// Add MQTT and admin settings
doc["mqttServer"] = "";
doc["mqttUser"] = "";
doc["mqttPassword"] = "";
doc["mqttTopic"] = "";
doc["adminPassword"] = defaultAdminPassword;
doc["doorName"] = defaultDoorName;
// Save to LittleFS
File file = LittleFS.open(JSON_FILENAME, "w");
if (!file) {
Serial.println("Failed to create JSON file.");
return;
}
serializeJson(doc, file);
file.close();
// Serial.println("JSON file initialized.");
message = "JSON file initialized.";
Serial.println(message.c_str());
snprintf(topic, sizeof(topic), "%s/CodeLock/activity", mqttTopic.c_str());
client.publish(topic, message.c_str());
}
// Function to load JSON configuration
void loadJson() {
File file = LittleFS.open(JSON_FILENAME, "r");
if (!file) {
Serial.println("JSON file not found. Initializing...");
initializeJson();
file = LittleFS.open(JSON_FILENAME, "r");
if (!file) {
// Serial.println("Failed to read JSON file after initialization.");
message = "Failed to read JSON file after initialization.";
Serial.println(message.c_str());
snprintf(topic, sizeof(topic), "%s/CodeLock/activity", mqttTopic.c_str());
client.publish(topic, message.c_str());
return;
}
}
StaticJsonDocument<2048> doc;
DeserializationError error = deserializeJson(doc, file);
if (error) {
message = "Failed to parse JSON. Reinitializing...";
Serial.println(message.c_str());
snprintf(topic, sizeof(topic), "%s/CodeLock/activity", mqttTopic.c_str());
client.publish(topic, message.c_str());
initializeJson();
return;
}
// Load codes
JsonArray codesArray = doc["codes"];
for (size_t i = 0; i < codesArray.size(); i++) {
JsonObject codeObj = codesArray[i];
accessCodes[i].code = codeObj["code"].as<String>();
accessCodes[i].validFrom = codeObj["validFrom"];
accessCodes[i].validTo = codeObj["validTo"];
accessCodes[i].remark = codeObj["remark"].as<String>();
}
// Load MQTT settings
mqttServer = doc["mqttServer"].as<String>();
mqttUser = doc["mqttUser"].as<String>();
mqttPassword = doc["mqttPassword"].as<String>();
mqttTopic = doc["mqttTopic"].as<String>();
// Load admin password
adminPassword = doc["adminPassword"].as<String>();
// Load door name
doorName = doc["doorName"].as<String>();
file.close();
}
// Function to handle the /opendoor request
void handleOpenDoor() {
unlockDoor(); // Call the unlock routine
server.send(200, "text/plain", "Door unlocked successfully"); // Send response
String timestamp = getFormattedTime();
message = timestamp + " Door unlocked by admin page.";
Serial.println(message.c_str());
snprintf(topic, sizeof(topic), "%s/CodeLock/activity", mqttTopic.c_str());
client.publish(topic, message.c_str());
}
// Function to handle the /deleteConfig request
void handleDeleteConfig() {
deleteConfig(); // Call the delete routine
server.send(200, "text/plain", "JSON config file deleted successfully"); // Send response
message = "JSON config file deleted from admin page.";
Serial.println(message.c_str());
snprintf(topic, sizeof(topic), "%s/CodeLock/activity", mqttTopic.c_str());
// client.publish(topic, message.c_str());
}
// Function to handle the /reboot request
void handleReboot() {
server.send(200, "text/plain", "Rebooting...."); // Send response
message = "Rebooting....";
Serial.println(message.c_str());
snprintf(topic, sizeof(topic), "%s/CodeLock/activity", mqttTopic.c_str());
client.publish(topic, message.c_str());
delay(500);
reBoot(); // Call the Reboot routine
}
// Function to handle the /alwaysOpen request
void handleAlwaysOpen() {
alwaysOpen = !alwaysOpen; // Toggle state
String timestamp = getFormattedTime();
if (alwaysOpen == true) {
message = timestamp + " AlwaysOpen is now on.";
} else {
message = timestamp + " AlwaysOpen is now off.";
}
Serial.println(message.c_str());
snprintf(topic, sizeof(topic), "%s/CodeLock/activity", mqttTopic.c_str());
client.publish(topic, message.c_str());
server.send(200, "text/plain", alwaysOpen ? "Always Open Enabled" : "Always Open Disabled");
//server.sendContent(""); // Sends no content, but toggles as long button is pressed.
}
// Function to handle configuration page
void handleConfigPage() {
// Basic authentication handler
if (!server.authenticate(adminUser.c_str(), adminPassword.c_str())) {
return server.requestAuthentication();
}
String html = "<html><body>";
html += "<h1>" + doorName + " CodeLock Configuration</h1>";
// Existing form fields for codes, MQTT settings, etc.
html += "<form action=\"/save\" method=\"post\">"; // Correct form start
// New buttons with properly escaped attributes
// html += "<button type=\"button\" onclick=\"saveConfig()\">Save</button>";
html += "<button type=\"button\" id=\"toggleLockBtn\" onclick=\"openDoor()\">Unlock</button>";
// Button with dynamic text based on `alwaysOpen` state
html += "<button type=\"button\" id=\"alwaysOpenBtn\" onclick=\"toggleAlwaysOpen()\">"
+ String(alwaysOpen ? "Disable Always Unlocked" : "Enable Always Unlocked") + "</button>";
html += "<button type=\"button\" onclick=\"deleteConfig()\">Delete Config File</button>";
html += "<button type=\"button\" onclick=\"reBoot()\">Reboot</button>";
// JavaScript functions for buttons
html += "<script>";
// JavaScript functions for Open Door button
html += "function openDoor() {";
html += " fetch('/opendoor')";
html += " .then(response => response.text())";
html += " .then(text => {";
html += " updateToggleLockBtn(text);"; // Update button text and color
html += " });";
html += "}";
html += "function updateToggleLockBtn(state) {";
html += " let btn = document.getElementById('updateToggleLockBtn');";
html += " if (state.includes('Enabled')) {";
html += " btn.innerText = 'Disable Always Unlocked';";
html += " btn.style.backgroundColor = '#77dd77';"; // Red when enabled
html += " } else {";
html += " btn.innerText = 'Enable Always Unlocked';";
html += " btn.style.backgroundColor = '#ff6961';"; // Green when disabled
html += " }";
html += "}";
// JavaScript to toggle the Always Open button
html += "function toggleAlwaysOpen() {";
html += " fetch('/alwaysopen')"; // Send request to toggle state
html += " .then(response => response.text())";
html += " .then(text => {";
html += " alert(text);"; // Display the status message
html += " updateButton(text);"; // Update button text and color
html += " });";
html += "}";
// Function to update the AlwaysOpen button based on response
html += "function updateButton(state) {";
html += " let btn = document.getElementById('alwaysOpenBtn');";
html += " if (state.includes('Enabled')) {";
html += " btn.innerText = 'Disable Always Unlocked';";
html += " btn.style.backgroundColor = '#77dd77';"; // Red when enabled
html += " } else {";
html += " btn.innerText = 'Enable Always Unlocked';";
html += " btn.style.backgroundColor = '#ff6961';"; // Green when disabled
html += " }";
html += "}";
// JavaScript functions for Delete Config button
html += "function deleteConfig() {";
html += " fetch('/deleteconfig')";
html += " .then(response => response.text())";
html += " .then(alert);";
html += "}";
// JavaScript functions for Reboot button
html += "function reBoot() {";
html += " fetch('/reboot')";
html += " .then(response => response.text())";
html += " .then(alert);";
html += "}";
html += "window.onload = function() {";
html += " updateButton('" + String(alwaysOpen ? "Enabled" : "Disabled") + "');";
html += "}";
html += "</script>";
// Form fields for codes
for (int i = 0; i < 8; i++) {
html += "<h3>Code " + String(i + 1) + "</h3>";
html += "Code: <input type=\"text\" name=\"code" + String(i) + "\" value=\"" + accessCodes[i].code + "\" maxlength=\"8\"><br>";
html += "Valid From (0-23): <input type=\"number\" name=\"validFrom" + String(i) + "\" value=\"" + String(accessCodes[i].validFrom) + "\" min=\"0\" max=\"23\"><br>";
html += "Valid To (0-23): <input type=\"number\" name=\"validTo" + String(i) + "\" value=\"" + String(accessCodes[i].validTo) + "\" min=\"0\" max=\"23\"><br>";
html += "Remark: <input type=\"text\" name=\"remark" + String(i) + "\" value=\"" + accessCodes[i].remark + "\" maxlength=\"10\"><br>";
}
// MQTT settings fields
html += "<h3>MQTT Settings</h3>";
html += "Server: <input type=\"text\" name=\"mqttServer\" value=\"" + mqttServer + "\"><br>";
html += "User: <input type=\"text\" name=\"mqttUser\" value=\"" + mqttUser + "\"><br>";
html += "Password: <input type=\"text\" name=\"mqttPassword\" value=\"" + mqttPassword + "\"><br>";
html += "Topic: <input type=\"text\" name=\"mqttTopic\" value=\"" + mqttTopic + "\"><br>";
// Admin password field
html += "<h3>Admin Password</h3>";
html += "Password: <input type=\"text\" name=\"adminPassword\" value=\"" + adminPassword + "\"><br>";
// Door name field
html += "<h3>Door Name</h3>";
html += "Door Name: <input type=\"text\" name=\"doorName\" value=\"" + doorName + "\"><br>";
// Submit button
html += "<br><input type=\"submit\" value=\"Save\"></form>";
html += "</body></html>";
server.send(200, "text/html", html);
}
// Function to save configuration
void handleSave() {
StaticJsonDocument<2048> doc;
Serial.println("Saving JSON file.");
JsonArray codesArray = doc.createNestedArray("codes");
for (int i = 0; i < 8; i++) {
JsonObject codeObj = codesArray.createNestedObject();
codeObj["code"] = server.arg("code" + String(i));
codeObj["validFrom"] = server.arg("validFrom" + String(i)).toInt();
codeObj["validTo"] = server.arg("validTo" + String(i)).toInt();
codeObj["remark"] = server.arg("remark" + String(i));
}
doc["mqttServer"] = server.arg("mqttServer");
doc["mqttUser"] = server.arg("mqttUser");
doc["mqttPassword"] = server.arg("mqttPassword");
doc["mqttTopic"] = server.arg("mqttTopic");
doc["adminPassword"] = server.arg("adminPassword");
doc["doorName"] = server.arg("doorName");
File file = LittleFS.open(JSON_FILENAME, "w");
if (!file) {
server.send(500, "text/plain", "Failed to save configuration.");
return;
}
serializeJson(doc, file);
file.close();
loadJson(); // Reload saved configuration
server.sendHeader("Location", "/");
server.send(303);
}
void unlockDoor() {
digitalWrite(relayPin, HIGH); // Pull relay
delay(500); // for 0.5 s.
digitalWrite(relayPin, LOW); // Release relay
unLocked = true; // True until reedSwitch indicates door handle has been pressed, or door has been opened.
}
void deleteConfig() {
if (!LittleFS.begin()) {
Serial.println("Failed to mount LittleFS.");
return;
}
// Check if the file exists
if (LittleFS.exists(JSON_FILENAME)) {
// Delete the file
if (LittleFS.remove(JSON_FILENAME)) {
Serial.println("JSON file deleted successfully.");
} else {
Serial.println("Failed to delete JSON file.");
}
} else {
Serial.println("JSON file does not exist.");
}
loadJson();
}
void reBoot() {
Serial.println("Rebooting...");
delay(100); // Short delay to allow the message to be printed
ESP.restart(); // Reboot the ESP8266
}
// MQTT callback for commands
void callback(char* topic, byte* payload, unsigned int length) {
String command = "";
for (int i = 0; i < length; i++) {
command += (char)payload[i];
}
if (command == "unlock") {
unlockDoor();
String timestamp = getFormattedTime();
message = timestamp + " Door unlocked via MQTT";
Serial.println(message.c_str());
// Use a different topic buffer here
char pub_topic[200]; // Publish topic buffer
snprintf(pub_topic, sizeof(pub_topic), "%s/CodeLock/activity", mqttTopic.c_str());
client.publish(pub_topic, message.c_str());
}
if (command == "AlwaysOpenOn") {
alwaysOpen = true;
String timestamp = getFormattedTime();
message = timestamp + " AlwaysOpenOn set via MQTT";
Serial.println(message.c_str());
// Use a different topic buffer here
char pub_topic[200]; // Publish topic buffer
snprintf(pub_topic, sizeof(pub_topic), "%s/CodeLock/activity", mqttTopic.c_str());
client.publish(pub_topic, message.c_str());
}
if (command == "AlwaysOpenOff") {
alwaysOpen = false;
String timestamp = getFormattedTime();
message = timestamp + " AlwaysOpenOff set via MQTT";
Serial.println(message.c_str());
// Use a different topic buffer here
char pub_topic[200]; // Publish topic buffer
snprintf(pub_topic, sizeof(pub_topic), "%s/CodeLock/activity", mqttTopic.c_str());
client.publish(pub_topic, message.c_str());
}
}
// MQTT reconnect
void reconnect() {
if (client.connect("CodeLock", mqttUser.c_str(), mqttPassword.c_str())) {
char sub_topic[300]; // Subscription topic buffer
snprintf(sub_topic, sizeof(sub_topic), "%s/CodeLock/command", mqttTopic.c_str());
client.subscribe(sub_topic); // Subscribe using sub_topic
snprintf(topic, sizeof(topic), "%s/CodeLock/activity", mqttTopic.c_str());
client.publish(topic, "Connected to MQTT");
}
}
// Kontrollera giltighetstid
bool isValidTime(int startHour, int endHour) {
int currentHour = timeClient.getHours(); // Använder NTP-tid
return currentHour >= startHour && currentHour <= endHour;
}
void handleKeyInput(char key) {
bool codeMatched = false;
Serial.print("Key pressed: ");
Serial.println(key);
// Make single keypress to string and publish
char keyStr[2]; // Buffer to hold the character and a null terminator
keyStr[0] = key; // Store the character
keyStr[1] = '\0'; // Null terminator to make it a valid C-string
// topic = String(mqttTopic.c_str()) + "/CodeLock/keypressed";
snprintf(topic, sizeof(topic), "%s/CodeLock/keypressed", mqttTopic.c_str());
client.publish(topic, keyStr);
for (int i = 0; i < sizeof(accessCodes) / sizeof(accessCodes[0]); i++) {
// Check if the key matches the expected character in the code sequence
if (key == accessCodes[i].code[accessCodes[i].counter]) {
accessCodes[i].counter++; // Move to the next character
Serial.print("Correct input for code ");
Serial.print(i);
Serial.print(". Counter: ");
Serial.println(accessCodes[i].counter);
// Check if the entire code has been entered correctly
if (accessCodes[i].counter == accessCodes[i].code.length()) {
if (isValidTime(accessCodes[i].validFrom, accessCodes[i].validTo)) {
unlockDoor(); // Unlock if time is valid
String timestamp = getFormattedTime();
message = timestamp + " Door unlocked by " + accessCodes[i].remark;
Serial.println(message.c_str());
snprintf(topic, sizeof(topic), "%s/CodeLock/activity", mqttTopic.c_str());
client.publish(topic, message.c_str());
accessCodes[i].counter = 0; // Reset counter after successful entry
codeMatched = true;
} else {
String timestamp = getFormattedTime();
message = timestamp + " Code valid for " + accessCodes[i].remark + ", but not within allowed time.";
Serial.println(message.c_str());
snprintf(topic, sizeof(topic), "%s/CodeLock/activity", mqttTopic.c_str());
client.publish(topic, message.c_str());
}
}
} else {
// Incorrect input, reset counter for this code
if (accessCodes[i].counter > 0) { // Only print if counter was non-zero
Serial.print("Incorrect input. Resetting counter for code ");
Serial.println(i);
}
accessCodes[i].counter = 0;
}
}
if (!codeMatched) {
Serial.println("No code matched. Waiting for next input.");
}
}
void setup() {
Serial.begin(9600);
unsigned long startTime = millis();
while (!Serial && (millis() - startTime) < 3000) { // Wait for 3 seconds
// Optional: Blink LED to indicate waiting
}
if (Serial) {
Serial.println("Serial monitor detected.");
} else {
// Proceed without serial connection
delay(500);
}
message.reserve(300); // Reserve space for up to 300 characters
pinMode(relayPin, OUTPUT);
digitalWrite(relayPin, LOW);
pinMode(reedSwitchPin, INPUT_PULLUP);
if (!LittleFS.begin()) {
if (Serial) { Serial.println("Failed to mount LittleFS."); }
return;
}
loadJson();
// Wi-Fi & AP-konfiguration
WiFiManager wifiManager;
wifiManager.autoConnect("CodeLock-AP"); // Startar AP om nätverk saknas
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
if (Serial) {
Serial.println("\nWiFi connected!");
}
Serial.println(adminPassword.c_str());
Serial.println(mqttServer.c_str());
Serial.println(mqttUser.c_str());
Serial.println(mqttPassword.c_str());
Serial.println(mqttTopic.c_str());
// Connect to MQTT
client.setServer(mqttServer.c_str(), 1883);
client.setCallback(callback);
String topic = "CodeLock";
// Starta NTP-tjänsten
timeClient.begin();
server.on("/", handleConfigPage);
server.on("/save", HTTP_POST, handleSave);
// Define the routine for unlocking the door
server.on("/opendoor", HTTP_GET, handleOpenDoor);
// Define the routine for deleting the JSON config file
server.on("/deleteconfig", HTTP_GET, handleDeleteConfig);
// Define the routine for reboot
server.on("/reboot", HTTP_GET, handleReboot);
// Define the routine for the alwaysOpen state
server.on("/alwaysopen", HTTP_GET, handleAlwaysOpen);
// Start the server
server.begin();
String timestamp = getFormattedTime();
message = timestamp + " HTTP server started:80";
Serial.println(message.c_str());
}
String getFormattedTime() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
return "TimeUnavailable"; // Handle case if time retrieval fails
}
char buffer[20];
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &timeinfo);
return String(buffer);
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
server.handleClient(); // Handle webrequests
timeClient.update(); // Update time from NTP
lastState = unLocked; // Save previous state to see if something changes just now.
unLocked = digitalRead(reedSwitchPin); //Check if lock is unLocked
if (unLocked == false && lastState == true) {
// ReedSwitch changed state.
// ReedSwitch indicates door handle has been pressed,
// when released, lock will be locked.
String timestamp = getFormattedTime();
message = timestamp + " Door handle pressed.";
Serial.println(message.c_str());
snprintf(topic, sizeof(topic), "%s/CodeLock/activity", mqttTopic.c_str());
client.publish(topic, message.c_str());
delay(1000);
timestamp = getFormattedTime();
message = "If closed, door will be locked";
Serial.println(message.c_str());
snprintf(topic, sizeof(topic), "%s/CodeLock/activity", mqttTopic.c_str());
client.publish(topic, message.c_str());
}
if (unLocked == true && lastState == false) {
//ReedSwitch changed state, Door is now Unlocked
// reedSwitch indicates door handle is engaged, but not yet pressed
String timestamp = getFormattedTime();
message = timestamp + " Door is now unlocked ";
Serial.println(message.c_str());
snprintf(topic, sizeof(topic), "%s/CodeLock/activity", mqttTopic.c_str());
client.publish(topic, message.c_str());
}
if (alwaysOpen == true && unLocked == false) { // Is this right?
// alwaysOpen is set but door is locked, so unlock door again
String timestamp = getFormattedTime();
message = timestamp + " Door unlocked because AlwaysOpen is enabled. ";
Serial.println(message.c_str());
snprintf(topic, sizeof(topic), "%s/CodeLock/activity", mqttTopic.c_str());
client.publish(topic, message.c_str());
delay(500);
unlockDoor(); // Call existing unlock function
}
lastState = unLocked;
char key = keypad.getKey();
if (key) {
handleKeyInput(key);
}
}