A simulated smart lock built for a Computer Networks and Distributed Processing course. The lock runs as an MQTT client — subscribing to command topics, processing lock and unlock requests, and publishing responses back through the broker. Built in C# using MQTTnet with a Mosquitto broker.
MQTT is a lightweight publish/subscribe messaging protocol designed for constrained devices and low-bandwidth networks. Rather than direct communication between clients, all messages flow through a central broker. Publishers send messages to named topics, and any client subscribed to that topic receives the message — the publisher never needs to know who is listening.
In this project the Mosquitto broker sits at the center. The smart lock client subscribes to command
topics and waits for messages. A separate publisher client sends commands to those topics, and a
subscriber client listens on smartlock/response to see the lock's replies —
simulating a real IoT deployment where the lock, the controller, and the monitor are all independent
nodes.
smartlock/temporary/+ with a single-level wildcard,
catching both activate and deactivate in one
subscription. The switch statement then handles each sub-topic individually.
Event-driven message handling — the core of the lock is an async event handler
registered on ApplicationMessageReceivedAsync. Every time a message arrives
on a subscribed topic, the handler reads the current lock state from a file, inspects the topic and
payload, and builds a response. Lock state is persisted to disk so it survives application restarts —
1 for locked, 0 for unlocked.
Temporary password — a second password can be granted to a guest and activated/deactivated by the owner using the permanent password. Once used to lock or unlock, the temporary password automatically deactivates — a single-use access grant. This is enforced in the event handler by toggling a boolean and only resetting it after a successful operation.
case "smartlock/lock": if (locked) { lock_message = "Lock is already locked"; } else { if (client_message == main_password) { // Write locked state to file File.WriteAllText(lock_file, "1"); lock_message = "Lock has been locked"; } else if (client_message == temp_password) { if (tempActivated) { File.WriteAllText(lock_file, "1"); lock_message = "Locked with temp password"; // Single-use — deactivate after use tempActivated = false; } else { lock_message = "Temp password is deactivated"; } } else { lock_message = "Password does not match"; } } break;
smartlock/response with the payload "The lock was broken or died", so any subscriber monitoring the lock gets
notified immediately if the lock goes offline unexpectedly rather than silently.
// Last Will Testament registered at connect time var options = new MqttClientOptionsBuilder() .WithTcpServer("127.0.0.1") .WithWillTopic("smartlock/response") .WithWillPayload("The lock was broken or died") .Build(); // Subscribe with wildcard for temp topics .WithTopicFilter(f => f.WithTopic("smartlock/temporary/+")) // Publish response after every handled message var reply = new MqttApplicationMessageBuilder() .WithTopic("smartlock/response") .WithPayload(lock_message) .Build(); await mqttClient.PublishAsync(reply);
Clean disconnect — when the lock exits normally it calls DisconnectAsync with explicit disconnect options, which sends a proper
DISCONNECT packet to the broker. This tells the broker the client left intentionally, so the Last Will
Testament is not triggered — the will only fires on ungraceful disconnects.
The demo runs four terminals side by side — the Mosquitto broker (top left), the smart lock client (top
right), a subscriber listening on smartlock/response (bottom left), and a
publisher sending commands (bottom right). Every major code path is exercised: permanent password, wrong
password, and the full temporary password lifecycle — activate, use, and deactivate. Last will testament is
also showcased.