Author Rahul Likhar ID: rl222xr # Overview The project focuses on the measurement of temperature and humidity, displaying their current values, and monitoring their changes over time. The project could be completed within a few hours to a day (3-4 hours). However, if you're new to any of these components, it might take a bit longer as you learn and troubleshoot along the way. # Objective The purpose of this project is to create a temperature and humidity monitoring system using an affordable and widely available sensor (DHT11) and a microcontroller (Raspberry Pi Pico WH). By connecting the device to WiFi and leveraging MQTT protocol, we can transmit the collected data to a cloud platform like Adafruit IO for storage, visualization, and analysis. By monitoring temperature and humidity, we can gain valuable insights and applications such as: Home automation: Monitor the indoor climate and adjust heating, cooling, or ventilation systems accordingly for comfort and energy efficiency. Plant care: Ensure optimal conditions for plants by monitoring humidity levels and taking appropriate actions like watering or adjusting the environment. Environmental monitoring: Track temperature and humidity in specific areas to identify patterns or anomalies that may impact the environment, such as in greenhouses, storage facilities, or outdoor weather monitoring stations. Health and wellness: Monitor temperature and humidity in spaces where individuals spend significant time, such as offices or bedrooms, to ensure comfortable and healthy conditions. Data analysis: Analyze historical data collected over time to identify trends, correlations, or patterns that can provide insights for decision-making or further optimizations. Overall, this project allows you to collect and analyze temperature and humidity data in real-time, enabling you to make informed decisions, automate processes, and gain insights into environmental conditions in various settings. # Material We will use microcontroller [Raspberry Pi Pico WH](https://datasheets.raspberrypi.com/picow/pico-w-datasheet.pdf). It is is a microcontroller board developed by the Raspberry Pi Foundation. It is a compact and cost-effective board that features the RP2040 microcontroller chip. The Pico W variant specifically adds WiFi connectivity to the Pico, enabling wireless communication capabilities. The [DHT11](https://www.electrokit.com/uploads/productfile/41016/DHT11.pdf) sensor is a popular and low-cost sensor for measuring temperature and humidity. It consists of a capacitive humidity sensor and a thermistor for temperature measurement. The sensor provides digital output and has a limited accuracy range, making it suitable for basic applications. Breadboard: A breadboard is a useful tool for prototyping electronic circuits without the need for soldering. It consists of a grid of holes into which electronic components can be inserted. The holes are connected internally, allowing for easy circuit connections. Breadboards are reusable, providing flexibility for experimentation and modifications. Jumper Cables are used to make connections between various electronic components, such as sensors, microcontrollers, and breadboards. Micro USB: Micro USB is a common type of USB connector used for data transfer and power supply. The all the components were purchased from [Electro-Kit](https://www.electrokit.com/). Below table shows all the components needed for this project with their prices listed. | Image | Material | Price (SEK) | |:---------------------------------------------:|:---------------------:|:-----------:| | ![](https://hackmd.io/_uploads/Hkp6-Fpd2.jpg) | Raspberry Pi Pico WH | 109 | | ![](https://hackmd.io/_uploads/BJNeGKp_3.jpg) | DHT11 | 49 | | ![](https://hackmd.io/_uploads/SJXMfKa_h.jpg) | Breadboard | 69 | | ![](https://hackmd.io/_uploads/rykrfKTd3.jpg) | Jumper cables M -to-M | 29 | | ![](https://hackmd.io/_uploads/B1ZFzKTOh.jpg) | micro USB cable | 19 | # Computer setup I have selected [Visual Studio Code](https://code.visualstudio.com/download/) as the IDE for my project. Additionally, we will utilize the [PyMakr](https://github.com/pycom/pymakr-vsc/blob/HEAD/GET_STARTED.md) extension to transfer code to the Pico WH. To enable the extension, the installation of [NodeJS](https://nodejs.org/en) is also required. # Step-by step 1. Install [Python](https://www.python.org/downloads/). 2. Download and install [NodeJS](https://nodejs.org/en). Ensure to download latest version. 3. Download and install the IDE [Visual Studio Code ](https://code.visualstudio.com/download/)(VSCode). 4. Install the [PyMakr](https://github.com/pycom/pymakr-vsc/blob/HEAD/GET_STARTED.md) extension within Visual Studio Code. 5. Update firmware on the Raspberry Pi Pico: a. Download [MicroPython firmware](https://micropython.org/download/rp2-pico-w/). It is a uf2 file (latest release). Don't download Nightly builds. b. Connect the micro-USB cable to the Raspberry Pi Pico. c. Press and hold the BOOTSEL button on the board, then connect the other end of the micro-USB cable to your computer. Once the cable is plugged in, you can release the BOOTSEL button. d. You will notice a newly appeared drive in your file system called "RPI-RP2," which represents the storage of the Raspberry Pi Pico. Proceed to transfer the previously downloaded UF2 file into this storage location. e. Please wait for the board to undergo an automatic disconnection and reconnection process. # Putting everything together The following diagram depicts the connection setup for integrating the DHT11 sensor with your Raspberry Pi Pico WH using a breadboard. ![](https://hackmd.io/_uploads/B1cUV_pdh.jpg) I have connected the 3.3V (3V3) pin on the Raspberry Pi Pico to the positive (+) rail on the breadboard using a jumper wire and the GND (ground) pin on the Raspberry Pi Pico to the negative (-) rail on the breadboard using another jumper wire. Connect the data pin of the DHT11 sensor to GPIO pin (27) on the Raspberry Pi Pico. # Platform From low code IOT solution, Adafruit is the selected platform. It is known for being user-friendly and free, making it an ideal choice for beginners. Additionally, it provides straightforward visualizations for data presentation. It also supports a wide range of communication protocols and has libraries available for popular hardware platforms. There are many other low code IOT solutions available like Ubidots, Datacake, etc..,. Based on ease of use, data visualization capabilities, data sotrage limit, scalability and available integrations I have selected Adafruit for this project. To begin, we need to create an account on [Adafruit IO](https://io.adafruit.com/). Once registered, we can proceed to establish two feeds: one for temperature and another for humidity. Instructions for setting up the feeds can be found in the provided [link](https://learn.adafruit.com/adafruit-io-basics-feeds). # The Code We adopt a standard code organization structure for our projects, ensuring consistency and clarity. ![](https://hackmd.io/_uploads/BJLIrOT_h.jpg) In the "lib" directory, we store all the necessary libraries for our project. Specifically, for this case, since we are utilizing the MQTT protocol to transmit data to the platform, we require the MQTT library mentioned in the provided image. Additionally, we include a file where we input our wifi credentials ([secrets.py](https://github.com/rahull4/IOT-Project/blob/main/secrets.py)) for establishing a WiFi connection. [mqtt.py](https://github.com/rahull4/IOT-Project/blob/main/mqtt.py) ```python=1 #!/usr/bin/env python # # Copyright (c) 2019, Pycom Limited. # # This software is licensed under the GNU GPL version 3 or any # later version, with permitted additional terms. For more information # see the Pycom Licence v1.0 document supplied with this file, or # available at https://www.pycom.io/opensource/licensing # import usocket as socket import ustruct as struct from ubinascii import hexlify class MQTTException(Exception): pass class MQTTClient: def __init__(self, client_id, server, port=0, user=None, password=None, keepalive=0, ssl=False, ssl_params={}): if port == 0: port = 8883 if ssl else 1883 self.client_id = client_id self.sock = None self.addr = socket.getaddrinfo(server, port)[0][-1] self.ssl = ssl self.ssl_params = ssl_params self.pid = 0 self.cb = None self.user = user self.pswd = password self.keepalive = keepalive self.lw_topic = None self.lw_msg = None self.lw_qos = 0 self.lw_retain = False def _send_str(self, s): self.sock.write(struct.pack("!H", len(s))) self.sock.write(s) def _recv_len(self): n = 0 sh = 0 while 1: b = self.sock.read(1)[0] n |= (b & 0x7f) << sh if not b & 0x80: return n sh += 7 def set_callback(self, f): self.cb = f def set_last_will(self, topic, msg, retain=False, qos=0): assert 0 <= qos <= 2 assert topic self.lw_topic = topic self.lw_msg = msg self.lw_qos = qos self.lw_retain = retain def connect(self, clean_session=True): self.sock = socket.socket() self.sock.connect(self.addr) if self.ssl: import ussl self.sock = ussl.wrap_socket(self.sock, **self.ssl_params) msg = bytearray(b"\x10\0\0\x04MQTT\x04\x02\0\0") msg[1] = 10 + 2 + len(self.client_id) msg[9] = clean_session << 1 if self.user is not None: msg[1] += 2 + len(self.user) + 2 + len(self.pswd) msg[9] |= 0xC0 if self.keepalive: assert self.keepalive < 65536 msg[10] |= self.keepalive >> 8 msg[11] |= self.keepalive & 0x00FF if self.lw_topic: msg[1] += 2 + len(self.lw_topic) + 2 + len(self.lw_msg) msg[9] |= 0x4 | (self.lw_qos & 0x1) << 3 | (self.lw_qos & 0x2) << 3 msg[9] |= self.lw_retain << 5 self.sock.write(msg) #print(hex(len(msg)), hexlify(msg, ":")) self._send_str(self.client_id) if self.lw_topic: self._send_str(self.lw_topic) self._send_str(self.lw_msg) if self.user is not None: self._send_str(self.user) self._send_str(self.pswd) resp = self.sock.read(4) assert resp[0] == 0x20 and resp[1] == 0x02 if resp[3] != 0: raise MQTTException(resp[3]) return resp[2] & 1 def disconnect(self): self.sock.write(b"\xe0\0") self.sock.close() def ping(self): self.sock.write(b"\xc0\0") def publish(self, topic, msg, retain=False, qos=0): pkt = bytearray(b"\x30\0\0\0") pkt[0] |= qos << 1 | retain sz = 2 + len(topic) + len(msg) if qos > 0: sz += 2 assert sz < 2097152 i = 1 while sz > 0x7f: pkt[i] = (sz & 0x7f) | 0x80 sz >>= 7 i += 1 pkt[i] = sz #print(hex(len(pkt)), hexlify(pkt, ":")) self.sock.write(pkt, i + 1) self._send_str(topic) if qos > 0: self.pid += 1 pid = self.pid struct.pack_into("!H", pkt, 0, pid) self.sock.write(pkt, 2) self.sock.write(msg) if qos == 1: while 1: op = self.wait_msg() if op == 0x40: sz = self.sock.read(1) assert sz == b"\x02" rcv_pid = self.sock.read(2) rcv_pid = rcv_pid[0] << 8 | rcv_pid[1] if pid == rcv_pid: return elif qos == 2: assert 0 def subscribe(self, topic, qos=0): assert self.cb is not None, "Subscribe callback is not set" pkt = bytearray(b"\x82\0\0\0") self.pid += 1 struct.pack_into("!BH", pkt, 1, 2 + 2 + len(topic) + 1, self.pid) #print(hex(len(pkt)), hexlify(pkt, ":")) self.sock.write(pkt) self._send_str(topic) self.sock.write(qos.to_bytes(1, 'little')) while 1: op = self.wait_msg() if op == 0x90: resp = self.sock.read(4) #print(resp) assert resp[1] == pkt[2] and resp[2] == pkt[3] if resp[3] == 0x80: raise MQTTException(resp[3]) return # Wait for a single incoming MQTT message and process it. # Subscribed messages are delivered to a callback previously # set by .set_callback() method. Other (internal) MQTT # messages processed internally. def wait_msg(self): res = self.sock.read(1) self.sock.setblocking(True) if res is None: return None if res == b"": raise OSError(-1) if res == b"\xd0": # PINGRESP sz = self.sock.read(1)[0] assert sz == 0 return None op = res[0] if op & 0xf0 != 0x30: return op sz = self._recv_len() topic_len = self.sock.read(2) topic_len = (topic_len[0] << 8) | topic_len[1] topic = self.sock.read(topic_len) sz -= topic_len + 2 if op & 6: pid = self.sock.read(2) pid = pid[0] << 8 | pid[1] sz -= 2 msg = self.sock.read(sz) self.cb(topic, msg) if op & 6 == 2: pkt = bytearray(b"\x40\x02\0\0") struct.pack_into("!H", pkt, 2, pid) self.sock.write(pkt) elif op & 6 == 4: assert 0 # Checks whether a pending message from server is available. # If not, returns immediately with None. Otherwise, does # the same processing as wait_msg. def check_msg(self): self.sock.setblocking(False) return self.wait_msg() ``` The [boot.py](https://github.com/rahull4/IOT-Project/blob/main/Boot.py) file is designated for code that should run during the boot process. In this case, we place the code responsible for establishing an internet connection since it is a prerequisite for executing any other code. [Boot.py](https://github.com/rahull4/IOT-Project/blob/main/Boot.py) ```python=1 def do_connect(): import network from time import sleep from secrets import secrets import machine wlan = network.WLAN(network.STA_IF) # Put modem on Station mode if not wlan.isconnected(): # Check if already connected print('connecting to network...') wlan.active(True) # Activate network interface # set power mode to get WiFi power-saving off (if needed) wlan.config(pm = 0xa11140) wlan.connect(secrets["ssid"], secrets["password"]) # Your WiFi Credential print('Waiting for connection...', end='') # Check if it is connected otherwise wait while not wlan.isconnected() and wlan.status() >= 0: print('.', end='') sleep(1) # Print the IP assigned by router ip = wlan.ifconfig()[0] print('\nConnected on {}'.format(ip)) return ip def http_get(url = 'http://detectportal.firefox.com/'): import socket # Used by HTML get request import time # Used for delay _, _, host, path = url.split('/', 3) # Separate URL request addr = socket.getaddrinfo(host, 80)[0][-1] # Get IP address of host s = socket.socket() # Initialise the socket s.connect(addr) # Try connecting to host address # Send HTTP request to the host with specific path s.send(bytes('GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n' % (path, host), 'utf8')) time.sleep(1) # Sleep for a second rec_bytes = s.recv(10000) # Receve response print(rec_bytes) # Print the response s.close() # Close connection # WiFi Connection try: ip = do_connect() except KeyboardInterrupt: print("Keyboard interrupt") # HTTP request try: http_get() except Exception as err: print("Exception", err) ``` The remaining code resides in the [main.py](https://github.com/rahull4/IOT-Project/blob/main/main.py) file. To begin, we configure our Adafruit IO settings. This involves setting up our Adafruit IO username, key, and the feeds we created earlier. You can obtain the IO key by logging into Adafruit IO and clicking on the key icon. ```python=13 # Adafruit IO (AIO) configuration AIO_SERVER = "io.adafruit.com" AIO_PORT = 1883 AIO_USER = "Your_Adafruit_User_Name" # Your AIO username AIO_KEY = "Your_Adafruit_Application_Key" # Your AIO key AIO_CLIENT_ID = ubinascii.hexlify(machine.unique_id()) # Can be anything AIO_TEMPERATURE_FEED = "Your_temperature_Feed_Address" # username/feeds/feed_name AIO_HUMIDITY_FEED = "Your_humidity_Feed_Address" # username/feeds/feed_name ``` Below functions provide a convenient way to retrieve temperature and humidity readings from a sensor. ```python=31 def get_temperature_reading(): return tempSensor.temperature() def get_humidity_reading(): return tempSensor.humidity() ``` Moving on to our main program, the execution begins with the main() function, which runs after the completion of boot. py and the establishment of a connection. Inside the main() function, a client instance is created to facilitate data retrieval and publication. The program continuously retrieves sensor data, publishes it, and toggles the on-board LED each time data is read. This LED serves as a visual indicator of the device's operational status. ```python=37 def main(): '''Countinuosly retrieves and publishes sensor data''' # Create instance of client and connect client = MQTTClient(AIO_CLIENT_ID, AIO_SERVER, AIO_PORT, AIO_USER, AIO_KEY, keepalive=60) client.connect() while True: global last_publish if (time.time() - last_publish) >= publish_interval: internalLED.toggle() # Get sensor readings tempSensor.measure() temperature = get_temperature_reading() humidity = get_humidity_reading() # Send data to adafruit client.publish(AIO_HUMIDITY_FEED, str(humidity)) client.publish(AIO_TEMPERATURE_FEED, str(temperature)) internalLED.toggle() last_publish = time.time() time.sleep(10) ``` # Transmitting the data / Connectivity To transmit data to Adafruit, we employ the WiFi and MQTT protocols. Data transmission occurs every 30 seconds, allowing us to monitor real-time temperature and humidity while also collecting data for long-term analysis purposes. # Presenting the data Now that we have successfully set up our Raspberry Pi Pico to send weather data to the feeds on Adafruit, we can proceed to visualize this data. By utilizing the two feeds we previously created, we establish a dashboard on Adafruit to display the collected weather data in a visual format. ![](https://hackmd.io/_uploads/HkOrKrxFh.jpg) # Final thoughts and design ![](https://hackmd.io/_uploads/B1EHlsp_h.jpg) This IoT project serves as an excellent entry point for beginners, providing a solid foundation to delve into the world of Internet of Things. Through this endeavor, we have developed a compact yet highly practical device while gaining valuable insights into the intricate mechanics of IoT. It offers an opportunity to embark on an exciting journey of exploration and discovery in this rapidly evolving field. By engaging in this project, we have not only constructed a functional device but also acquired comprehensive knowledge about the underlying principles driving the IoT ecosystem. This hands-on experience equips us with the necessary skills to pursue more advanced IoT projects in the future. Our creation stands as a testament to our dedication and eagerness to comprehend the inner workings of IoT technology. This project acts as a stepping stone, paving the way for further innovation and growth in the vast landscape of IoT.