Try   HackMD

Name: Sven Lindstroem
Student credentials: sl224us

Overview

The goal of this project is to develop a remote control system for a Dyson fan, enabling convenient control over both physical and digital interfaces using Grafana. By implementing this remote control, users can easily adjust fan settings, such as speed and on/off state, from a distance. Additionally, the system incorporates an autonomous mode that intelligently adapts the fan's operation based the current temperature. Through predefined thresholds for speed and on/off state, the fan can automatically adjust itself.

All the code is avaliable on GitHub

The time needed: 3 hours

Objective

The objective of this project is to create a remote monitoring and control system for a Dyson fan in a room that experiences high temperatures due to its location under the roof and numerous windows. The project aims to address two primary needs: remote temperature monitoring and autonomous fan control, as well as the convenience of physical control when present in the room.

By remotely monitoring the temperature of the room, the project allows for continuous assessment of the temperature and enables adjustments to the fan's settings.

The autonomous mode ensures that the fan can independently regulate the temperature within predefined thresholds, even when the user is not physically present. This functionality helps to stop the room from becoming uncomfortably hot when unattended.

Recognizing the inconvenience of using a browser-based interface to control the fan when in the room, I added a physical interface. This interface allows for the control of the fan's speed and on/off state, eliminating the need to navigate through menus on a browser.

Collecting data on the fan's behavior and the room's temperature will provide insights into the fan's impact on the temperature and overall climate control within the room. This data can potentially lead to further improvements, such as identifying the optimal fan position in the room for better temperature regulation.

It is important to note that this project has been tested with the "Dyson Purifier Cool Autoreact" model. Different fans may have distinct infrared signal (IR) protocols, such as cycling codes. Therefore, the project's compatibility with other fan models may vary.

Material

Item Specification ID Price (SEK)
Raspberry Pi Pico WH Single-board micro controller equipped with Wi-Fi 41019114 109
Breadboard Connection deck for solder-free connections, so-called breadboard. The holes are interconnected five-by-five, as well as 4 longitudinally. 10160840 69
USB A-male to micro B 5p male Cable for USB 2.0. USB A plug to USB micro B plug. 41003290 39
Lab Cable 20 15cm lab cord connectors with female/male connectors 41012911 29
IR Transmitter Transmitter for IR signals with 38kHz modulation. 41015733 26
IR Receiver Receiver for IR signals with 38kHz modulation. 41015734 32
Push button Module with a momentary push button. 41015723 19
Rotary Encoder Module with a rotary encoder with quadrature output. When the axis is rotated, outputs A and B provide a 2-bit code that can be converted to step and direction. 41015725 29
DHT11 Module with a DHT11 digital temperature and humidity sensor. The sensor outputs a serial data stream that can be read with a digital input on a microcontroller. 41015728 49
RG LED 5mm Module with a red/green LED. The LED is connected with common cathode. As each color can be controlled individually, the diode can be made to glow either green, red or a combination of both. 41015717 19

These are all the materials that are used in this project. As the project involves working with IR signals, there is a possibility that some signals may not be received by the fan, resulting in a desynchronization between the internal state of the program and the physical fan. To address this, I included the RG LED 5mm, which serves as an indicator of the internal on/off state of the fan. This visual indicator allows for quick identification of any desynchronization between the internal and physical states. While the dashboard also provides this information digitally, I find it beneficial to have physical indicators as well.

If you prefer a purely digital interface and are comfortable accessing the dashboard whenever you want to interact with the fan, you can omit the Push button, Rotary Encoder, and RG LED components. The program will continue to function without these components. However, for the core functionality of autonomously controlling the fan, the IR Receiver, IR Transmitter, and DHT11 are necessary.

I bought the starter kit for IoT and the sensor kit which include all the necessary components for a total of 698SEK. You can also buy the individual components for a total of 420SEK from electrokit, all products can be found by entering the product ID into the search bar.

Platform

This project utilizes the TIG (Telegraf, InfluxDB, and Grafana) stack along with Mosquitto, all hosted locally within a Docker container. The decision to opt for a locally hosted solution offers several distinct advantages. Firstly, it grants full control over the data and user experience, allowing for customization and enhanced security measures.

By hosting the TIG stack and Mosquitto locally, we mitigate the limitations of low-code solutions like Adafruit. Low-code solutions may restrict the control we have over our data, posing potential vulnerabilities to data breaches. Although the collected data in our case may not be highly sensitive, it remains a security concern that should be considered. Additionally, Adafruit restrictis the data retention periods, limiting it to only 7 days. This limitation significantly hampers our ability to analyze data over extended periods and impedes the scalability of our project.

In contrast, our locally hosted solution enables us to retain complete control over our data, ensuring its security and allowing for flexible data analysis. Furthermore, with the TIG stack and Mosquitto, we can store and analyze data for as long as necessary, facilitating insights into the fans behavior and room temperature trends.

Computer setup

1. Install Node.js and VS Code

Install Node.js from the official website here, and then install VS Code from here. Please note that you can use a different code editor if preferred, but it will not be covered in this guide.

2. Install Docker

Install Docker by following the instructions provided here.

3. Install PyMakr

After installing VS Code, open the Extensions Manager and search for PyMakr. Install the PyMakr extension to enable communication with the Pico board.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

4. Update Firmware

To update the micro Python firmware on the Pico board:

  • Download the firmware from here.
  • Connect the Pico board to your computer using a micro USB cable.
  • While holding the Bootsel button (white button) on the Pico board, plug the other end of the USB cable into your computer.
  • The Pico board will show up as a new drive. Copy and paste the downloaded firmware file into the Pico drive.
  • Wait until the board automatically disconnects and reconnects.

5. Download the Project Folder

Download the project folder from the GitHub repository here. The downloaded folder should contain the following items:

  • IoT-Project folder
  • config folder
  • Grafana-Dashboard.json
  • README.md

6. Set Up Configurations

In the config folder, open the passwd file and enter the desired MQTT broker password after the colon.

Open telegraf.conf and replace the text "Your broker password" with your MQTT broker password.

Navigate back to the "IoT-Project" folder and open the config.py file using a text editor (e.g., Notepad). Fill in your Wi-Fi credentials at the top by replacing "Your wifi ssid" and "Your wifi password" with your network's SSID and password, respectively.

Towards the bottom of the file, replace "Your broker IP" with the IP address of the device running the MQTT broker and "Your broker password" with your MQTT broker password. Adjust the temperature breakpoint for the autonomous mode if desired.

Save the changes made to the configuration files and close them.

7. Set Up Grafana

Start Docker and navigate to the "config" folder using the command prompt. Run the command "docker-compose up" to start the Docker container.

Open Grafana by accessing "your IP address:3000" in a web browser. Use the default username "king" and password "arthur" to log in. Once logged in, change the password.

In Grafana, hover over the cogwheel on the sidebar and click on "Data Sources." Add Influxdb as a data source with the URL address http://influxdb:8086. Set the database as "iot" and use the credentials "iotlnu" (user) and "micropython" (password). Save the database configuration.

To import the JSON file, hover over the "+" sign on the sidebar and click on "Import." Upload the JSON file downloaded from the GitHub repository.

Open the dashboard and click on the "Speed change" text, then click "Edit." In the "server" tab on the right side, fill in the MQTT broker IP under "Connection." If you want access to the fan from outside your local network, enter the IP address of your router. Make sure to forward ports 3000 and 9001 from your router to the machine running the Docker container. Fill in your MQTT broker password under "Authentication."

Repeat the same steps for the button with the "Power" text and the switch labeled "Automation" on the dashboard.

8. Connect Device and Upload Project

Open the PyMakr extension in VS Code from the sidebar. Create a new empty project and add the Pico board to the project.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Open the file location of the newly created project and copy the contents of the "IoT-Project" folder into it. Overwrite any files that already exist in the folder.

Once the Pico board is connected to the project, power it on and upload the project using the PyMakr extension.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Putting everything together

Wiring

When everything is set up on the computer, disconnect the USB and proceed to connect the components according to the wiring diagram provided. Please note that the diagram represents a proof of concept, and for production, all components would need to be compacted and organized appropriately.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

The project should now be ready to go. Connect the Pico with your computer again. The first time you start the project follow the commands from the VS Code terminal to register the IR signals. After they have been saved you can plug the Pico into power and it will connect to the dashboard. If you have trouble sending commands from the dashboard, try refreshing the page.

Power Consumption

Component Current Consumption
DHT11 1.5 mA
IR Transmitter 30 - 60 mA
IR Receiver 1.5 mA peak
RG LED 20 mA
Pico W 43 mA

By adding up the individual currents, we can estimate the total current draw of the project.
In the worst-case scenario, where all components are drawing their maximum current, the total current draw would be approximately 126 mA.

1.5+60+1.5+20+43=126mA
In the best-case scenario, the total current draw would be around 96 mA.
1.5+30+1.5+20+43=96mA

To evaluate the power requirements, let's consider a hypothetical battery with a capacity of 10,000 mAh, assuming the best-case scenario. In this scenario, the project could be powered for approximately 104 hours or around 4 days. However, if we disconnect the LED and the IR Receiver after saving the IR signals, we can extend the power duration by an additional 1.5 days. Based on this, relying solely on batteries is not a practical power solution for this project. Therefore, I chose to use wall power instead.

10000 96=104h

It's worth noting that the sleep() function from the time library does not significantly decrease power consumption as it simply passes time. Although the deepsleep() function from the machine library can reduce power consumption, it resets the board and restarts the program from the beginning upon waking. Since the sleep cycles in the program are short (3 or 60 seconds) and I intend to use wall power, I decided against implementing the deepsleep() function in this project.

Data Transmission and Wireless Protocols:

Data from the Pico board is transmitted over Wi-Fi using the MQTT (Message Queuing Telemetry Transport) protocol as a JSON object. The transmission occurs under two conditions: firstly, whenever there is a change in the fan's state, the complete fan state, including temperature, is sent. Secondly, if 60 seconds have elapsed since the last data transmission, only the temperature is sent. This data is sent to the MQTT broker, Mosquitto, and subsequently received by Telegraf, which subscribes to the same topic where the Pico publishes the data. The Telegraf agent collects this data every 5 seconds and sends it to InfluxDB, which serves as the database for storage and retrieval. Grafana queries this data from InfluxDB and displays it on the dashboard.

Additionally, the Pico board is subscribed to a command topic, enabling it to receive commands. The MQTT broker, Mosquitto, receives commands from Grafana via web sockets when a user takes action on the dashboard. The Pico board then sends the updated state to Mosquitto, which is received by Telegraf and subsequently sent to InfluxDB. Grafana queries the data from InfluxDB and displays the updated information on the dashboard.

The choice of Wi-Fi as the wireless protocol was based on several factors specific to this project. Despite its higher power consumption and limited range, Wi-Fi is ideal for indoor applications, such as controlling a Dyson fan, as it is expected to provide sufficient coverage within the indoor environment. Power consumption is not a concern since wall power is readily available. While Wi-Fi offers high bandwidth, which is advantageous though not necessary for this project, its use also leverages the existing Wi-Fi infrastructure of the house. In contrast, LoRaWAN would have required the use of existing networks like TTN or Helium, which may have inconsistent coverage and could involve additional charges. Moreover, given my remote location out of reach of any network, setting up a personal gateway for LoRaWAN would have been both expensive and unnecessarily complex.

The MQTT protocol was chosen for its security features and queuing system. Since the project is designed to be accessible from outside the local network, authentication is important to ensure that only authorized users have access. The queuing system allows the dashboard to send multiple commands independently, even when the program is in a different state, such as sleeping, thereby keeping the entire system responsive. MQTT's lightweight nature, with low bandwidth and power consumption, supports scalability without overburdening the network or significantly increasing power consumption.

Choice of Database: InfluxDB

InfluxDB was selected as the database for this project due to its specialization in time series data. As a time series database, InfluxDB associates each data point with a timestamp, making it particularly well-suited for applications that involve collecting data at regular intervals. This design enables efficient retrieval and analysis of data based on temporal criteria. They also offer specialized query languages made to work with time series data, simplifying data analysis.

In this project, as the database is self-hosted, there is the option to preserve the data indefinitely. With over 2 TB of available storage space, immediate concerns regarding space constraints are not a pressing issue. Furthermore, the absence of a retention policy allows for long-term analysis of the collected data.

Visualization Tool: Grafana

For visualizing the data collected by the IoT project, Grafana was chosen as the primary tool. Grafana offers seamless integration with InfluxDB, the selected database, enabling efficient retrieval and visualization of the time series data.

One of the key advantages of Grafana is its extensive customization options for data visualization. It offers various chart types, and flexible data querying. This enables users to tailor the visualization experience to their specific needs and preferences.

In the case of this project, a specific functionality was required that was not available in the official Grafana plugins. Specifically, the need to send data to the MQTT broker Mosquitto directly from a panel. Fortunately, an unofficial plugin developed by geeks-r-ur was available on GitHub that fulfilled this requirement. This plugin utilizes web sockets to enable the transmission of data to Mosquitto.

The combination of Grafana's extensive customization options, integration with InfluxDB, and the added functionality provided by the unofficial plugin by geeks-r-ur enables the creation of a visually appealing and interactive dashboard.

Below you can see what the dashboard looks like after you uploaded the JSON file.

Code

The code is separated into two files.

  • boot - This file runs first and establishes the Wi-Fi connection.
  • main - which runs after boot and is the program that does the sensing, receiving, and transmitting of the data.

In addition, there is a folder called "lib" which contains the libraries used.

  • ir_rx and ir_tx are used for the IR signals written by peterhinch
  • rotary_irq_rp2 and rotary for the rotary encoder written by miketechman
  • mqtt for the MQTT protocol written by Pycom Limited and provided by Lnu

In the boot file first, the WLAN object is created and set to station mode.

# put modem on station mode wlan = network.WLAN(network.STA_IF)

It checks if there is a connection. If there is non the network interface is configured,

# activate network interface wlan.active(True) # Your WiFi credential from the config file wlan.connect(wifi_secrets["ssid"], wifi_secrets["password"])

and a connection is established.

while not wlan.isconnected() and wlan.status() >= 0: print('.',end='') sleep(1)

When boot is finished executing main starts. Since this project contains around 250 lines of code, to keep the tutorial to a reasonable length only the main function of the main.py file will be covered here. If you are interested in a complete overview of the code it is available here.

At the top of the file some important variables are initiated.
Numbers in breakers () refer to the line number in the code black above the section.

state_changed = False # Load the state of the Dyson from the file current_state = { "speed": 1, "is_on": False, "auto_on": False }

state_changed (1) indicates if there has been a change to the Dyson state in the last cycle of the while loop.
dyson_state (3) keeps track of the internal representation of the fan and is initiated with its default values. This is the state the program expects the fan to be in the first time you start the program. The program will then attempt to read the dyson_state.py file.

try: with open('dyson_state.py', 'r') as f: current_state = ujson.load(f) # prints the state for debugging print("Dyson state is: ", current_state) except: print("Failed to load Dyson state file. Using default state.")

In this file, the program keeps the state of the fan, so that if the board is disconnected from power it will remember the state of the fan from that point and will load the file (3) when it is reconnected to power. The first time the program starts the file will not exist but it will be created when a change to the fan occurs.

def main(): check_ir_signal_files() set_up_mqtt() indicator_light() global state_changed temp = get_temp() send_data(ujson.dumps({ "temp":temp, "speed":current_state["speed"], "on_off":int(current_state["is_on"]), "auto":int(current_state["auto_on"]) })) max_time_intervall = int(config["maximum time interval"]) last_data_sent = time.ticks_ms() while True: temp = get_temp() mqtt_client.check_msg() if current_state["auto_on"]: autonomous(temp) sleep_time = 60 else: manual() sleep_time = 3 if state_changed: send_data(ujson.dumps({ "temp":temp, "speed":current_state["speed"], "on_off":int(current_state["is_on"]), "auto":int(current_state["auto_on"]) })) last_data_sent = time.ticks_ms() state_changed = False update_state() # checks how long since the last temp has been sent elif (time.ticks_ms() - last_data_sent) > max_time_intervall: send_data(ujson.dumps({"temp":temp})) last_data_sent = time.ticks_ms() time.sleep(sleep_time)

At the top of the main function we first test for the presence of the files that contain the IR signals (2). If they are not present the program will give the user instructions on how to register them in the terminal.
Next, the connection to the MQTT broker is established (3) using the credential from the config file. The callback function is set and a subscription to the "devices/command" topic is established. The callback function will evaluate the commands received from the topic. The indicator light is also started here (4).
Then the current state of the board is sent to the dashboard (7), as a JSON object, to ensure that they are synchronized and the time is recorded of the last time data was sent (14). This is the set-up of the program that will run once when the board is turned on.

Once the setup is done the while loop starts that will run continuously (16). At the top of the loop, a temperature measurement is taken (17) and a check is made if there are any new messages from the MQTT broker (18). The message can be 1 of 3 commands:

  1. auto_power: Inverts the autonomous mode state
  2. power: Inverts the Dyson on/off state
  3. int 1-10: Changes the fan speed

The program will only take action on power or a speed change if the autonomous mode is off. The speed will also only change if the power is on which applies to all sources of speed change not just commands.

Once the messages are evaluated the first of two if-else statements is executed (19). This conditional depends on the state of the autonomous_mode; if it is on, the autonomous() function is executed (20) which checks the temperature against the breakpoints and makes changes accordingly and then sets the sleep time to 60 seconds (21); else, manual() is excecuted (23) that checks the pushbutton and rotary encoder for changes and sets the sleep timer to 3 seconds (24). The sleep timers are different because, if the physical interface is used the program needs to be more responsive than if the decisions are entirely made based on temperature.

The second if-else statement (25) determines what and when data is sent. It first checks if a change has occurred during the last cycle; if it has, the entire state of the fan is sent (26), both the last_data_sent (32) and the state_changed (33) variable are reset, and the updated state is saved to dyson_state.py; else it checks how much time has passed since the last time data was sent (35). If it has been more then max_time_intervall, which is set to 60 seconds by default, the temperature is sent(36), and the time is reset(37).

Lastly, the program sleeps depending on the sleep (38) time set during the first if-else statement.

Finalizing the Design

I am pleased with the overall outcome of the project, particularly considering it was my first venture into IoT development. Successfully building a remote control for the Dyson fan and implementing both physical and digital interfaces, including autonomous temperature control, will hopefully improved the regulation of the room's temperature.

However, there are a few limitations in the current setup that I would address in future iterations. Firstly, analyzing the data collected by the Pico has its limitations since it is confined to this room. For instance, temperature decreases observed after turning on the fan may not conclusively prove that the fan caused the change, as external temperature fluctuations can influence the results. To mitigate this, I would consider adding a second temperature sensor to measure the ambient temperature outside the house. This would provide an additional data point for more accurate analysis and correlation.

Secondly, one of the significant challenges I encountered was the desynchronization between the internal representation of the fan's state and its physical behavior. In some instances, signals were not consistently received by the fan, leading to discrepancies in achieving the desired speed settings. To address this, I recognize the need to implement a mechanism that can sense the fan's actual state, allowing for accurate control. However, at this time I am unsure how I would implment this mechanism.

Despite these limitations, the project has been a valuable learning experience, and I am looking forward to the possibilities for further improvement and refinement in the future.