After setting up dozens of Internet of Things (IoT) smart home devices, I started to wonder: how hard could it be to build one from scratch? I needed a project to learn on, so I decided to create something fun: a device that alerts my neighbors when my kids go swimming, extending the invitation for their kids to come swim too. What follows are the lessons I learned from building such an IoT device from scratch. Demo and Code Here's a short video demoing the device and its features: Watch this video on YouTube The instructions and code for building your own Splashflag can be found at the bottom of this post, otherwise keep reading to learn about my journey in building the device. Why SplashFlag? How many times can you play Marco Polo in a pool with an adult and two kids? I know that my kids far prefer the company of their friends who have a lot more energy. Originally our idea was to put up a special "we're swimming" flag outside in our front yard when our kids were in the pool, alerting the neighbors that they are welcome to come over and swim as well. The flag would be an open invitation, without the overhead of planning, group texts, and phone calls. I quickly realized this idea wouldn't work because: 1) The flag wouldn't be easily visible from every neighbor's house 2) By the time people saw the flag and came over, we might already be wrapping up our swimming session What I needed to solve this social problem was technology (or rather, I needed an excuse for a new technology hobby project), which is how the idea for SplashFlag was born. Key Features and Learnings This wasn't my first time building an embedded device, but it was the first time I tried to follow at least some semblance of best practices: main loops less than a thousand lines long, no hardcoded passwords, etc... If this was going to be a true learning project, I wanted to be more organized: use classes, design hardware and software that could handle errors gracefully, and create a way for users to connect the device to their WiFi without me ever needing to know their credentials. While I wouldn't consider this code to be perfect (or even necessarily "good"), it's a huge improvement over hardware projects I've built in the past, so I consider it a success. Below is an overview of the major features I built into the device. Servo Flag This is how the idea started: instead of the physical flag in my front yard, it would be a small plastic flag sitting on the counter in a friend's home. Whenever the device receives a message, the flag goes up until the message expires. Besides being a fun feature, it works well in households where the kids are still too young to read the details of the message - regardless of what the screen says, if the flag is raised, they know the Wagners are swimming and they're welcome to come swim too. Clear/Reset Button This button wasn't originally planned as a feature. The first time I got the servo flag working and realized it might be raised for an hour, I knew that as a parent I'd want a way to clear the notification without my kid seeing it. So the hidden button on the back of the device became a necessary enhancement. Push it, and the message clears while the flag goes down. It also serves double duty for triggering a factory reset. LCD In addition to the servo flag, the LCD displays messages about swimming. The default message indicates how long we plan to swim, but the web app (see below) lets me write any message, so something like "feel free to stay for pizza" is a potential customization. The LCD also displays system messages, like when the device is having trouble connecting to WiFi or when the messaging server is down. The code for the LCD was fun to write: since the screen can only fit two rows of 16 characters, I had to write a function that could split any length message so it fit these constraints and scroll across multiple screens. This is also where I first encountered overflow errors and needed to add max-length validations: The screen works well and serves its purpose, but along with the I2C adapter, it is easily the biggest component in the device. Next time, I plan to look for slimmer options, because the device size (especially the front frame with the LCD) could have been considerably smaller if I had chosen a different board. Captive Portal Before this project, I never knew the magic that allowed login pages to pop up on your device when connecting to a guest wifi network. It turns out, it's DNS! This guest wifi login experience is what I wanted to build into my device. After all, I wanted my neighbors to be able to set this up in their house, all on their own, without me needing to know or hardcode any WiFi passwords. The short explanation of how this works is that when your phone connects to a new WiFi network, the phone will try to visit certain well-known URLs. If you own the WiFi network, you can configure DNS to look for those standard URLs and intercept them, serving your own login page. Fortunately there are some good libraries for setting up a DNS server on ESP32s and intercepting traffic, then serving your own captive portal where people can input their WiFi credentials. Another feature I wanted to include was the ability to update the firmware remotely. Debugging and flashing new firmware while the device was sitting on my desk was easy, but I know my code isn't perfect so I wanted a way to update these devices remotely in the future. Fortunately the ESP32-S3 Nano device I was using allows for OTA firmware updates. I set up the library for this and have it check the GitHub releases page for SplashFlag every day to see if a new version is available. If a new version exists, it will download the update and install it. Here's hoping I don't accidentally brick anyones SplashFlag device. Web App I wanted a simple interface for sending messages to all devices, so I created a single page web application. It defaults to the most common message I would send, with input parameters to adjust the duration of how long the message is displayed on the devices. The website runs on my home server and I expose it through CloudFlare Tunnels so I can easily access it from my phone (or anywhere). I also added HTTP Basic Authentication to the website - not the most robust option, but good enough for this project. In the future, if I upgrade anything, it would be the authentication system. Basic Authentication is secure enough, but it doesn't play nicely with 1Password or Safari on iOS, which causes some minor annoyances. The web app sends a message to the MQTT broker (see below) over WebSockets, which then publishes it to all devices. Because this is just a simple API call, I could easily program a smart button to trigger this in the future, allowing my kids to send the notification themselves when we head out to the pool. MQTT Messaging I didn't want my devices long-polling a web server to check the status of new swim messages. Instead I decided to use an MQTT broker running in my home lab to transmit messages to the SplashFlag devices running the MQTT client code. I am using mosquitto as the MQTT broker. The MQTT broker service runs on my home server and is exposed via WebSockets to the web app. All of the devices subscribe to the broker service, so whenever a message is published, every device receives the message and updates its screen and raises its flag. Debugging Hardware Flag Since I may need to continue development (e.g. fixing bugs) once these devices are in the wild, I wanted a way to send messages to only my debugging device. Fortunately, ESP32-S3 Nanos have a unique mac address identifier, so I wrote code to check whether a device is a development unit under my control. If it is, it subscribes to an additional MQTT topic that only receives debugging messages. Debugging messages are set through the web app via a toggle: The fact that the ESP32-S3 Nanos have their own unique identifier means I don't need different code for production devices versus debugging devices (or at least in that section of the code). While I could have handled this in other ways, I'm satisfied with how this solution works. 3D Printed Case While I learned a ton designing this case, the smartest thing I did was test print each CAD feature as I finished designing it. This meant printing something like the servo holder only took 15 minutes, and then I could dry fit the parts together to ensure they actually fit. This saved a lot of time on iterating fit and printing, as well as minimizing plastic waste. My CAD design experience before SplashFlag was limited to simple enclosures. SplashFlag taught me how to make more complex designs, including screw mounts and snap-fit parts. The case design is simple. The front panel holds the LCD. The remaining components (ESP32-S3 Nano, servo, USB-C power plug, tactile reset button) mount to the back panel. As mentioned earlier, the LCD with I2C breakout took up a lot of room, leading to the wide frame around the LCD. The ESP32-S3 Nano, servo, and USB-C plug attach to the case with small metric screws. The LCD panel bolts onto the front plate with an embedded nut. While I was designing this, it took many iterations to figure out how to get all the parts to fit together while minimizing space. I also had to consider the limitations of 3D printers and assembly so the final case would be possible to print and assemble successfully. One oversight I made was forgetting to leave space for the bolts that would hold the two halves of the case together. Originally I was going to make the case snap-fit together without any hardware, so I didn't include bolts in my design, but I decided to add bolts once I realized how relatively heavy all the components would be and all the wiring that would need to stay confined to the inside of the device. If I had modeled the case properly in Fusion360 with components, I could have easily moved things around after I realized I needed case bolts. But since I didn't realize I could do that until I had spent hours designing the case the wrong way, I decided to be OK settling with the imperfections. My favorite part of the case is the snap-fit housing for the tactile button. It allows for a small breadboard switch to become a neatly integrated button. Definitely a design I will use again in future projects: As a final embellishment, I added the text SplashFlag to the top of the device. I only have a single-color 3D printer, so this meant printing the outline of the letters in blue, then the letters themselves (slightly smaller) in white. Final assembly involved super gluing them together on the top of the case. What Isn't Included As much as I packed this device with features, I didn't include everything. If I wanted to devote more time to this project, I'd probably first enable TLS for all HTTP connections. The idea of writing code to automatically update the CA certs on the device (and maintaining TLS certs on the server app) over time didn't seem worth it for a hobby project. Also, secrets like the WiFi password and MQTT credentials are not encrypted at rest. They are stored in the ESP32-S3 Nano's non-volatile memory, allowing anyone with physical access to the device to potentially retrieve them without too much effort. The ESP32-S3 Nano does have eFuses which can help with storing cryptographic keys (which could then be used to encrypt the credentials), but adding that capability didn't make the cut due to time constraints. Conclusion Who knows if I will use it, but this build taught me I am capable of making steady, regular progress towards long-term projects. I am probably not going to large scale manufacture these for the neighborhood, but I gained confidence in building a complete hardware device on my own that works well. When the next product idea strikes, I'll already have a lot of the knowledge (and code!) required for building it. Is it perfect? No. Is it better than some IoT devices I've purchased, with easier updates and repairs? Definitely. How to Build Your Own SplashFlag Parts List ESP32-S3 Nano 1602 LCD Display with I2C adapter for easier wiring Feetech FS90 9g Servo 6x6x5mm tactile push button switch .1 uF decoupling capacitor 220ohm pull-down resistor USB-C Power Adapter and cable Variety of M1.6-M2.5 screws and bolts to mount all the components Code and Files Complete code for the ESP32-S3 Nano and web app, as well as the 3D printed case are available at the SplashFlag GitHub Repo. The code for the ESP32-S3 Nano is configured with PlatformIO. This configuration handles downloading and installing all C++ dependencies. If you use PlatformIO in Visual Studio Code like I do, you can open the repo's hardware folder, build the code, and upload it to the board. The web app runs in a Docker container. You will need to generate an auth password for the mosquitto service as well as configure a Cloudflare Tunnel if you are going to be self hosting. Details for doing this are in SplashFlag backend README. Case Assembly and Wiring 3D printing the files shouldn't require any supports, with the exception of the cutout for the USB-C cable. I used .10mm layer heights with PLA+ filaments. The snap-fit tactile button is the component with the tightest dimensional tolerance - I recommend slicing up the back panel and printing only this button enclosure at first to ensure your printer is calibrated correctly. If that piece prints correctly, the rest of the case should print without any issues. I don't have a nice wiring schematic, but the photo below with details should help. - The I2C adapter is wired to pins A4 and A5. - The servo is wired to D9 as well as 5V and ground. I put the decoupling capacitor near the servo to ensure smooth power. - The USB-C adapter gets the stable power in, and it is tied to the VIN and ground pins on the ESP32-S3 Nano. - The tactile button is tied to ground with the pull-down resistor and wired to D4 on the ESP32-S3 Nano. After confirming everything worked on a breadboard, I mounted the components to the case. I then soldered the component wires together, tested again, and once I was confident things still worked, encased all connections in hot glue: Note: the red wire hanging off to the left in the above photo was an extra wire I later clipped off - I miscounted while creating my wires. Maybe my next project will involve designing a custom PCB board to make the wiring easier.