DEV Community

Cover image for Finishing What I Started — From a TODO List to a Published PyPI Package
Supun Sriyananda
Supun Sriyananda

Posted on

Finishing What I Started — From a TODO List to a Published PyPI Package

GitHub “Finish-Up-A-Thon” Challenge Submission

This is a submission for the GitHub Finish-Up-A-Thon Challenge

What I Built

I built robmqtt — a resilient MQTT client for edge IoT devices, now published on PyPI.

It came out of a problem that cost me real time. I deploy sensor systems on hardware that lives in the field: Raspberry Pi units on 4G cellular, embedded gateways, battery monitoring systems. On those networks, connectivity is never stable. And the standard MQTT client, paho-mqtt, silently drops messages when the broker is unreachable — no error, no warning, no log entry unless you write one yourself.

I lost data for a long time before I understood why. And finding the cause was brutal. There are no proper logs unless you explicitly add them, so I spent hours — days — chasing a problem that left no trace. I went through forums, blogs, chat AIs, everywhere, looking for how other people had solved it. What I found was the same thing every time: people asking for a production-resilient MQTT client, and no real answer. Just scattered advice and code snippets that handled one piece and ignored the rest.

So I built it myself:

  • Offline queue — when the broker is unreachable, messages are written to SQLite instead of being dropped. They survive process restarts and power cycles.
  • Inflight tracking — messages sent but not yet acknowledged are tracked separately and re-sent on reconnect, closing a gap that even QoS 1 leaves open.
  • Priority eviction — when the queue fills, low-priority telemetry is evicted before critical alerts.
  • Exponential backoff — a fleet of devices reconnecting after an outage won't all hammer the broker at once.
  • TLS support — for brokers that require encrypted connections.

And then everything a library needs to actually be used in production, not just by me:

  • TLS and mutual TLS plus username/password auth, for secured brokers.
  • Bidirectional messagingsubscribe/unsubscribe with full MQTT wildcard support, and subscriptions that survive reconnects.
  • Observability — a built-in HTTP health check endpoint (/health returning healthy / degraded / unhealthy) with Docker HEALTHCHECK and Kubernetes liveness-probe examples.
  • Structured logging throughout — so the next person doesn't lose days to a problem with no trace, the way I did.
  • 73 tests across the storage, tracking, topic-matching, and client layers.
pip install robmqtt
Enter fullscreen mode Exit fullscreen mode

This matters to me because it's not a toy. It's running on real systems I maintain — battery management monitoring and robotics telemetry — where a gap in the data record has actual consequences.

Demo

The most satisfying way to see it work is to watch the offline queue survive a broker outage. The repo includes a simulation script for exactly this:

# Terminal 1 — run a simulated device publishing every 5 seconds
python test_13.py

# Terminal 2 — kill the broker mid-run
sudo systemctl stop mosquitto
Enter fullscreen mode Exit fullscreen mode

The device keeps publishing. Messages start queuing to SQLite instead of erroring. Then:

# Bring the broker back
sudo systemctl start mosquitto
Enter fullscreen mode Exit fullscreen mode

The queue drains automatically, in priority order. Every message that piled up during the outage is delivered. Nothing is lost.

Basic usage looks like this — the application code never has to know whether the broker is reachable:

from robmqtt import ProductionMQTTClient
import json, time

client = ProductionMQTTClient(
    client_id="field_device_001",
    broker_host="mqtt.yourdomain.com",
    broker_port=1883,
    max_queue_size=5000,
    db_path="./device.db",
)
client.connect()
client.start()

while True:
    client.publish(
        topic="sensors/temperature",
        payload=json.dumps(read_sensor()),
        qos=1,
        priority=5,
    )
    time.sleep(30)
Enter fullscreen mode Exit fullscreen mode

robmqtt-architechture

messages publish

sqlite queue drained

The Comeback Story

Here's the honest before-and-after.

Before. Once I'd finally figured out the code that solved my problem, I used it — and it went into my GitHub repo, where it sat. It worked. It solved the thing that had cost me days. But no one knew it existed.

And that was the part that nagged at me. I'd keep seeing people online asking for exactly what I'd built — something resilient enough for production — and getting the same non-answers I'd gotten: random advice, half-solutions, snippets. I had the answer sitting in a corner of my repo, and a thought stuck in the corner of my head: it's right here. I need to show this off.

The things I knew I should do but hadn't:

  • Package it for PyPI so people could pip install it instead of cloning the repo
  • Add TLS/mTLS support for secure broker connections
  • Expose a metrics endpoint for observability

It was the classic unfinished side project. Functional, but not shipped. Not something anyone other than me could actually use. The plan was always "publish it once it's polished" — and polishing it was the part that kept not happening.

The hard part. I have a full-time job. Finishing this wasn't something I could do in work hours. So it happened on weekends, late at night, and in the gaps — I drew flowcharts and planned the package structure on my commute, in my head, on paper. Progress came in small pieces, squeezed around everything else. There were plenty of stretches where it would have been easier to just leave it in the repo and move on.

After. I pushed it over the line:

  • Published to PyPI as robmqtt v1.0.0. This was more work than I expected — restructuring the code into a proper installable package, writing the pyproject.toml, sorting out the module layout, testing the install in a clean environment.
  • Added TLS support. The client now takes use_tls, ca_certs, certfile, keyfile, and broker auth parameters, so it works with secured brokers, not just local plaintext ones.

  • Observability Built an HTTP health check endpoint with Docker and Kubernetes probe examples — so the library reports its own health instead of failing silently.

  • Structured logging so the next person doesn't suffer days to a problem with no trace, the way I did!

  • Data Integrity Fixed three bugs I'm glad I caught before anyone relied on it: binary payloads being corrupted by string conversion, a resend-tracking gap that could silently lose messages on a second disconnect, and a race condition in publish() that could drop a message between the connection check and the send.

  • Started building a real project on top of it. To put the library through its paces in a full system, I'm building an IoT fleet analytics platform around it — simulated devices publishing through robmqtt, an MQTT-to-InfluxDB bridge, Grafana dashboards, and statistical anomaly detection. It's still in progress, but it's already doing what mattered most: proving the library holds up as the foundation of a real pipeline, not just in isolated tests.

When it was finally published and I ran the test scripts and watched the offline queue fill and drain exactly the way it was supposed to — messages held through an outage, then delivered, nothing lost — there was nothing more rewarding. The thing that had cost me days of frustration was now one pip install away for anyone who hits the same wall I did.

My Experience with GitHub Copilot

I'll be honest about my workflow, because the reason I relied on Copilot is specific.

I use a few AI tools when I build things. The general-purpose chat assistants are useful for sketching out an approach or talking through a design — but when it came to actual code, they kept giving me snippets that didn't quite work. That's not surprising: they can't see my project. They don't know my file structure, my variable names, the exact version of a library I'm using, or how the piece they're suggesting fits the rest of the code. So I'd paste in a snippet, hit an error, paste the error back, get a revised snippet, hit another error. Going back and forth with a chat window that can't see my codebase got slow and frustrating.

Copilot was different because it lives in my editor and can see everything — all my open files, the actual code around the cursor, the real context. That's why it became the tool I leaned on.

Two ways I used it:

Inline completion, always on. As I typed, Copilot autocompleted the repetitive, structural parts — the device profile dictionaries, the JSON payload construction, the boilerplate around publish() calls with their QoS and priority arguments. Because it could see the patterns already in my file, its suggestions actually fit, instead of being generically plausible code I'd have to rewrite.

Copilot Chat for debugging in context. This is where it earned its place. When something broke, I'd ask it directly — "Why am I getting this error, and how can I fix it?" or "Can you check this code snippet?" — and because it could read my actual files, the answers were grounded in my real code, not a guess about what my code might look like. That's the difference that made me stop pasting snippets into external chat windows. The tool that can see your codebase gives you answers that apply to your codebase.

That freed me to spend my real thinking time on the parts that mattered: the sine-wave drift model for realistic sensor data, the priority eviction logic, and the inflight-tracking design that closes the QoS 1 gap. The hard design decisions were mine. Copilot removed the friction around them — the typing, the boilerplate, and the slow debugging loop — so I could stay focused on the actual engineering.


Built by Supun Sriyananda — R&D Engineer working on embedded and IoT systems. robmqtt is open source on GitHub and PyPI.

Top comments (0)