# Smart Home Blinds Control and Automation Andreas Westphal, aw224fi # Introduction The goal of this project and minimal viable product is to create a "smart-blind" where I can set the time when the blind should open and close. I have also added two sensors that measure light level and tempature. Using MQTT Broker, Node-Red, InfluxDB 2 and Grafana hosted on a Raspberry Pi 4, which collects these measurements. Node-Red also serves a dashboard where the user can controll the blind. Additionally, there's a Reed-switch "magnet sensor" to set the end-point when the blind is fully closed, a magnet should be fixed furthest down on the blind. To set this up yourself, it would take 1-2days. # Objective The main purpose of creating this project was because I think it's really nice and refreshing to wake up to sunlight. Using all these technologies probably isn't necessary but it was a good way for me learn more about IOT and networks. I read about that you sleep better when the room is cooler, with this smart-blind you will know the temperature of the room, even during the night. And for your curiosity you will be able to see when the sunset/sunrise was. # Material > **Important note: to do this project you will need soldering equipment, wires and access to a 3D printer** I have bought electronics over time from various sources, but most parts are generic and can be switched for others. **Blind unit** | Supplier | Product | Cost | Link | | ---------- | ---------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Electrokit | Raspberry Pi Pico W | 89 SEK | [Link](https://www.electrokit.com/en/raspberry-pi-pico-w) | | Electrokit | Relay Module | 42 SEK | [Link](https://www.electrokit.com/relamodul-5v?gad_source=1&gclid=CjwKCAjwm_SzBhAsEiwAXE2Cv-XJ8eoVWaerVHUKMmZTUPqCfvWUgmoOXiLoD77Ke2RRAKFN-j0fSxoCiB8QAvD_BwE) | | Electrokit | Magnet x 2 | 50 SEK | [Link](https://www.electrokit.com/magnet-neo35-8mm-x-4mm) | | Sizeble | Reed-Switch (magnet sensor) | 19 SEK | [Link](https://sizable.se/P.138CC/Reed-Magnetsensor) | | Sizeble | ds18b20 Probe (tempature sensor) | 39 SEK | [Link](https://sizable.se/P.29Z3D/DS18B20-Temperatursensor-vattentat-med-1-meter-kabel) | | Sizeble | Photoresistor (light sensor) | 17 SEK | [Link](https://sizable.se/P.DNRNG/Ljussensor) | | Kjell | Stepper motor 28BYJ-48 | 159 SEK | [Link](https://www.kjell.com/se/produkter/el-verktyg/elektronik/utvecklingskit/arduino/arduino-tillbehor/luxorparts-stegmotor-med-vaxellada-och-drivare-4-pack-p90775?gad_source=1&gclid=CjwKCAjwm_SzBhAsEiwAXE2Cv8kxnp5MYAvCXi4dm5c-3XLYu2PFMdjGawUbpwfv1oZLPGmeMautZhoCq_0QAvD_BwE) | **Raspberry Pi** | Supplier | Product | Cost | Link | | -------- | ------------------------- | -------- | ---- | | Tradera | Raspberry Pi 4 8GB | ~900 SEK | ❌ | | Unknown | Power adapter | ~100 SEK | ❌ | | Unknown | Eithernet Cable | ~50 SEK | ❌ | | Unknown | Mini HDMI to HDMI adapter | ~50 SEK | ❌ | | Unknown | 32GB Micro-SD Card | ~50SEK | ❌ | For the Raspberry Pi remember you'll need Mini HDMI to HDMI if you can't access the device over SSH. **3D printing** You can find the 3D model [here](https://sketchfab.com/3d-models/box-1-bcf9eda496ca40d09eaca2b6e4a92484), I used my Creaility Ender 3 v2. You will have to create your own adapter for the stepper motor that fits the blind you have. # Computer setup ### Raspberry Pi Pico W I have been programming my Raspberry Pi Pico W on my Windows 11 laptop through the Thonny IDE with Micropython. Thonny is a easy to use IDE, following steps will show you how I have set up the Raspberry Pi Pico W for my project 1. **Download the Micropython firmware [here](https://micropython.org/download/RPI_PICO_W/)** 2. **Download and install Thonny IDE [here](https://thonny.org/)** Hold down the BOOTSEL button on Raspberry Pi Pico W while you plug it into your computer. A folder will be opened or you can find it in your file explorer, drag and drop the firmware into the folder. Your Raspberry Pi Pico will disconnect and connect again, then it's ready to be programmed. Start Thonny IDE and select your device in the bottom right corner, ensure it is selected and it's not saying "Local Python" in the right corner, otherwise, Local Python will be run on your computer and not your device. In Thonny IDE go to Tools -> Manage Packages Search for an install: * **umqtt.simple** * **ds18x20** * **onewire** ### Raspberry Pi 4 > The Raspberry Pi 4 is configured to be used over SSH through the router, as your router/home network likely is not exactly the same as mine, these instructions may need to be adapted to your home network. > > Using SSH is not necessary but convenient as the Raspberry Pi 4 is connected with network cable to the router which may be in another room. <br> <details> <summary><strong>Installation of OS</strong></summary> 1. **Download and Install Raspberry Pi Imager:** - Available [here](https://www.raspberrypi.com/software/). 2. **Prepare Your SD Card:** - Plug your SD card into your computer. 3. **Open Raspberry Pi Imager and Select:** - **Raspberry Pi Device:** Raspberry Pi 4 - **Operating System:** Raspberry Pi OS (64-bit) - **Storage:** Your SD card 4. **Customize OS Settings:** - **Edit settings** when prompted. - In the **General tab**, set your username and password. - In **Services**, enable SSH, then save and close. 5. **Finalize Installation:** - Click **No** on further customization options and install the OS onto your micro SD card. 6. **Setup Raspberry Pi:** - Insert the SD card into your Raspberry Pi 4. - Plug in the power supply, and wait for the raspberry pi to boot up. </details><br> <details> <summary><strong>Connecting Through SSH</strong></summary> 1. **Connect your device** - Plug your eithernet cable to your router and Raspberry Pi 4 2. **Find the IP of your device** - Look in your router settings under devices or alternatively use a tool such as [Angry IP Scanner](https://angryip.org/) to search IP-addresses then sort the device names to find the IP address of your Raspberry Pi 4. 3. **Connect to your device** - In Windows PowerShell write `ssh user_name@ip_address` - You will be prompted to enter your password, after that press enter (When you enter a character it wont be displayed). If done correctly, before the input field it should say `user_name@device_name:~ $` </details><br> <details> <summary><strong>Configuring Static IP</strong></summary> > ⚠️ Caution: > > DO NOT COPY AND PASTE, THE IP-ADDRESSES ARE EXAMPLES. > If you write the wrong IP address, you won't be able to > connect your device over SSH and in the worst case you will have to > reinstall the Raspberry Pi OS 1. **Open and edit network interface in Nano text editor**<br> `sudo nano /etc/network/interfaces.d/eth0` ```= allow-hotplug eth0 iface eth0 inet static address 192.168.99.99 netmask 255.255.255.0 gateway 192.168.99.9 ``` * "address" will be the static IP-address for your Raspberry Pi 4. * "netmask" you need to check against your network, in windows, write ipconfig in terminal and look for Subnet Mask. * "geteway" is the ip-address of your router. 2. **Check DNS setting**<br> `sudo nano /etc/resolv.conf` ```= # Generated by NetworkManager nameserver 192.168.99.9 ``` Ensure the ip-address is your routers IP-address 3. **Reboot your device** - Run `sudo reboot` in your raspberry pi 4 - Connect through SSH again, this time with your new IP address </details> # Putting everything together > ⚠️ Caution: > > When connecting the components, please ensure all wiring is done correctly. Faulty wiring can cause damage to the peripherals or the MCU (Microcontroller Unit). Double-check all connections before powering on your circuit. Pay special attention to: > * Polarity: Ensure that the positive and negative terminals are connected correctly. > * Voltage Levels: Verify that the components are rated for the voltage levels being used. > * Connections: Make sure all wires are securely connected and there are no loose connections. To make everything fit in the 3D model, I needed to desoder screw terminals and input pins on the breakout boards, to be able to solder wires more neatly directly to the boards. There's a small slot for the magnet sensor in the 3D model, so the sensor needs to be desodered, fitted in the hole and wired to its breakout board. I also extended the photoresistor and DS18b20 and wired them through a hole that was drilled on the side of the 3D model. When you look at the schematic, you'll see that the photoresistor is connected at two places. This is a fix as the module I bought only gave me digital value 1 or 0. Here I used a resistor of 1,5k Ohm and connected it to an input pin with ADC capability. **Schematic** ![schematic_breadboard2.0](https://hackmd.io/_uploads/ByldztWUR.jpg) ![download](https://hackmd.io/_uploads/HkU1ftZLR.jpg) **Electrical Calculation of a Pull-Up Resistor** A pull-up resistor is used to ensure a known state for a signal line. Here’s how you can calculate and understand the pull-up resistor. This is used for the DS18b20 tempature sensor. Volt / Ohm = Amps * 5 volts divided by 4600 ohms equals approximately 1.087 milliamps. This current is low enough to not cause damage to the sensor or the microcontroller but sufficient to ensure the line is pulled high when necessary. # Platform **ACCESS POINT** On my Raspberry Pi Pico W I built a so called "captive portal". This is a access point similar to when you share your internet through your phone, the Raspberry Pi Pico W creates a network you can connect to. When you are connected to the network and open the web browser, go to the local IP address, you will be greeted with this interface where you can connect the device to your local network. ![captive-portal-img](https://hackmd.io/_uploads/rJ3RQ0IIC.png) **Raspberry Pi 4 Server** This following platforms are self-hosted on the Raspberry Pi 4 through my local network. * **Mosquitto (MQTT Broker)**: Handles message queuing and ensures reliable communication between devices. * **Node-RED (Server-side logic and bridge)**: Manages server-side logic and acts as a bridge between different components, offering flexibility with its visual programming interface and dashboard. * **InfluxDB 2 (Database)**: Stores time-series data efficiently, making it ideal for IoT applications. * **Grafana (Visualization)**: Provides powerful data visualization and monitoring capabilities, allowing for real-time insights. I choose to make everything self-hosted due to prior experiences with cloud platforms suddenly requiring monthly subscriptions for features I was using. With a self-hosted solution, I retain full control over the firmware and avoid unexpected costs. Self-hosting also helps to prevent unexpected changes or updates from cloud services that could force me to reconfigure the system. For this project, I decided to use free platforms. Initially I considered the TIG-stack (Telegraf, InfluxDB, Grafana), but I decided to replace Telegraf with Node-RED. This change was made because Telegraf doesn't offer the same capabilities with logic, functions and dashboard as Node-RED does. To make all the platforms work together I found this guide using IOTstack that was very helpful [Here](https://learnembeddedsystems.co.uk/easy-raspberry-pi-iot-server#google_vignette) # The Code I the following sections you'll find information on how the different parts work in the Raspberry Pi Pico W. <details> <summary><strong>Captive Portal</strong></summary> The Captive Portal works similar to hosting a webpage on your local network. The main part here is that this is a HTML form being hosted as a webpage. In the backend the webpage is server as a large string, pay attention to the "html_items" variable being passed to the function and used at row 17. ```python= def web_page(html_items): html = """ <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <div class="container"> <h1>Configure Wi-Fi</h1> <form action="/" method="get" style="width: 300px; margin: 0 auto;"> <table style="margin: 0 auto;"> <tr> <td style="text-align: right;"> <label for="ssid">SSID:</label> </td> <td style="text-align: left;"> <select name="ssid" id="ssid">"""+str(html_items)+"""</select> </td> </tr> <tr> <td style="text-align: right;"> <label for="password">Password:</label> </td> <td style="text-align: left;"> <input type="password" id="password" name="password"> </td> </tr> </table> <br> <input type="submit" value="Connect"> </form> </div> </body> </html>""" return html ``` This code searches for nearby networks and generates a long string in the format of a HTML list, later on the variable "list_of_ssid" passed to the web_page function ```python= for net in ap.scan(): ssid = str(net[0]) modified_ssid = ssid[2:-1] print(modified_ssid) list_of_ssid += '<option value="' + modified_ssid + '">'+ modified_ssid + '</option>' ``` In the HTML webpage row 30 `<input type="submit" value="Connect">` is a button, when it's clicked a request is sent and it's decoded then searched through with regular expression ```python= request = conn.recv(1024).decode() # Extract SSID and password from the GET request ssid = None password = None match = ure.search(r'GET /\?ssid=([^&]*)&password=([^ ]*)', request) # Group 1 is the first () in the regular expression ssid = match.group(1) # Group 2 is the second () in the regular expression password = match.group(2) ``` If there is a match to the ssid and password it will be forwarded to Wifi connection </details><br> <details> <summary><strong>Wifi Connection</strong></summary> The start_connection function takes ssid and password as parameters which were gathered throught the captive portal. Then the function repetively try connect to that wifi. ```python= def start_connection(ssid, password): wlan = network.WLAN(network.STA_IF) wlan.active(True) wlan.connect(ssid, password) # Do nothing until the device is connected while not wlan.isconnected(): print('Connecting to WiFi...') time.sleep(1) print('Connected to WiFi') ``` </details><br> <details> <summary><strong>Peripherals</strong></summary> **Photoresistor value** ```python= # set up pin to analog-digital-converter photo_pin = ADC(27) # get function for analog value def get_photo_value(): return photo_pin.read_u16() ``` **Tempature sensor** ```python= # libraries needed for tempature sensor import onewire import ds18x20 # Set up for ds18x20 ds_sensor = ds18x20.DS18X20(onewire.OneWire(ds_pin)) roms = ds_sensor.scan() print('Found DS devices: ', roms) # get function for tempature in celsius def get_temp(): ds_sensor.convert_temp() time.sleep_ms(750) rom = roms[0] tempC = ds_sensor.read_temp(rom) return round(tempC, 2) ``` **Relay** ```python= #set up pin ds_pin = Pin(22) def set_relay(val): relay_pin.value(val) ``` **Reed Switch** ```python= # Define pin in setup reed_switch = Pin(0, Pin.IN, Pin.PULL_UP) # Function that returns 0 or 1 depending if magnet is present def is_open(): return reed_switch.value() ``` * **close_blind**: rolls down the blind 10 full rotations of the stepper motor, the magnet sensor needs to be activated indicating it is rolled up before it can roll down. * **open_blind**: rolls the blind up until it detects the magnet or after 5 minutes elapsed as a safety measure. ```python= def close_blind(): set_relay(1) global step_index if is_open() == False: step_index = 0 step_range(-1, 20480, 0.005) set_relay(0) def open_blind(): set_relay(1) global step_index step_index = 0 start_time = utime.ticks_ms() timeout = 300000 while is_open() == True: current_time = utime.ticks_ms() step_range(1, 1, 0.005) if current_time - start_time >= timeout: break set_relay(0) ``` </details><br> <details> <summary><strong>MQTT</strong></summary> The Raspberry Pi Pico W connects to the MQTT broker, which is represented by the variable "client". It subscribes to two topics and uses the "handle_message" function to process incoming messages. ```python= mqtt_broker = 'your_raspberry_pi_4_static_ip' client = MQTTClient('pico_client', mqtt_broker) client.connect() print('Connected to MQTT broker') client.set_callback(handle_message) client.subscribe(b'home/roll_blind_up') client.subscribe(b'home/roll_blind_down') ``` The handle_message function checks if the blind should open or close and call the corresponding function. ```python= def handle_message(topic, msg): payload = json.loads(msg) command = payload.get("command") if command == "blind_down": peripheral.close_blind() elif command == "blind_up": peripheral.open_blind() else: print("Unknown command:", command) ``` In a repetitive loop we get the values from the peripheral photoresistor and tempature sensor and publishes them to the topic "home/sensors" ```python= while True: light_level = peripheral.get_photo_value() temperature = peripheral.get_temp() print(temperature) sensor_data = { "temperature": temperature, "light_level": light_level } json_data = json.dumps(sensor_data) client.publish('home/sensors', json_data) print('Data published:', json_data) client.check_msg() time.sleep(5) ``` </details> # Transmitting the data / connectivity * The device connects to wifi * using periperal functions to get values * sends data as JSON over mqtt to mosquitto broker * Node-Red gets the MQTT message and trasnfers data to InfluxDB 2 * Grafana is connected to InfluxDB 2 Here's the Node-Red flow for sending the light level and tempature values to influxDB 2 **MQTT message -> Verifying its JSON -> Sending to influxDB** ![node-red-influxdb](https://hackmd.io/_uploads/Skw3Be3IC.png) When the Node-Red dashboard is being used, a MQTT message is being sent to the topics the Raspberry Pi Pico subscribes to. **Dashboard:** The two buttons under Function title will open/close the blind directly and the time fields under Settings title will open/close the blind on the set time every day. ![node-red-dashboard](https://hackmd.io/_uploads/HyPL4gnLC.png) This flow is more complex, The UI node "roll_blind_up" and "roll_blind_down" are both buttons and send a messege directly to the MQTT Broker. UI node "form" is sent to a function Node which parses the message that is submitted from the "Settings" tab in the dasboard. Then it's passed to a change Node which makes it accessible to the whole flow. The "timestamp" node activates the flow every minute, it gets the current time, compares if it matches the time set from the "Settings" tab in the dashboard and activates the correct MQTT Node accordingly. ![node-red-ui-flow](https://hackmd.io/_uploads/HJftvl3I0.png) I noticed MQTT messeges are quite slow even though it's just over my network, it takes a little bit to process. If this device would just controll one blind I think it would be more suitable with just using HTTP requests, or even controlling it fully with hardware such as IR sensor. MQTT is intresting because it's an easy way to controll many blinds. If I create another blind unit which subscribes to the same topics, when click open in the dashboard both blinds would open. This is typically called "one-to-many". # Presenting the data ![grafana_dashboard](https://hackmd.io/_uploads/Skh4ce2UC.png) * **The guage shows the latest tempature value** * **light_level graph gives a high number when it's dark and low when it's bright** * **Tempature graph show the tempature over time** The data is saved about every 5 seconds which is rather excessive. Somtimes there might be a small gap as the microcontroller is busy rolling the blinds up or down, but it's not noticeable in the graph. I used influxDB 2 instead of influxDB, the biggest difference is that it uses a query language called "flux" which was interesting to use. I think influxDB would work as well, both use time-series data which uses timestamps to organize that data, this function is very pratical for IoT. # Finalizing the design In this video I'm demostrating how I connect to the captive portal and connect the device to wifi, then through Node-Red dashboard close the blind, closing the blind is speed up x3 as it takes a while. <iframe width="560" height="315" src="https://www.youtube.com/embed/BPIlsAL8P4E?si=jf0n5sDS004_dQBx" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> This how the final product turned out. ![blind-module-irl](https://hackmd.io/_uploads/SkoemdT80.jpg) There has been many parts to this project, and it has been easy to get carried with wanting to do improvements and add more features. To not get carried away from the minimal viable product I started writing the ideas down. Here are some of them! **Ideas for future improvments** * Save SSID and Password so the blind unit will reconnect to Wifi incase of power-outage. * Add a "factory reset" button incase you want to switch wifi. * Include more configurations in captive portal such as MQTT server address, topics and how many rotations the blind needs to roll down fully. * In case of having more than one unit, improve Node-Red dashboard to be able to control them individually. (Now they all would be controlled as one group). * Thought of controlling the blind automatically with sunrise, sundown and temperature. * Being able to roll down the blind partially. * Schedule, different times to roll the blind up and roll down depending on the weekday. * Running the stepper motor on 12v instead of 5v to give it more power, or replacing it with a stronger stepper motor.