# SmartPeg - carefree drying ###### tags: `Quality of life` `IoT` `FiPy` `Sigfox` `MicroPython` `Angular` `Firebase` ### Smart clothes peg - [Web app](https://dry-clothes.web.app/) #### Estimates time needed for clothes to dry using current temperature and relative air humidity ## Intro SmartPeg is a DIY IoT project that uses readily available sensors and a development board to predict when the clothes the peg is attached to will be dry. To be able to submit as the final project for the LNU IoT summer course I had to go for development board and connectivity overkill. But the project can easily be adapted to simple Wifi connectivity using a coin-sized microcontroller like an ESP8622 *Vajean Stan - vs222ue - 11/08/2022* ## Objective The idea of a smart peg emerged after a short brainstorming session looking for a solution that is not just another IoT project but yet not too complex in terms of sensors or logic on the controller. Initially, the idea was to warn the user if it's going to rain in the next xx minutes (xx = the time estimated by the App as to when the clothes will dry) but for now, only the estimation functionality is built. If enough drying cycles data is collected good estimates of times needed to dry clothes in different regions and settings could be produced, especially if a neural network is trained on the data and the estimates versus actual times. Please allocate at least 4 hours to complete the project to accommodate for the odd troubleshooting and Stack Overflow search ## Material | Material Description | Use | Supplier | Price | Image| | -------- | -------- | -------- | --------|--------| | FiPy | Collect sensor data package and transmit to the cloud via Sigfox | Farnelll |76.96 € | ![](https://firebasestorage.googleapis.com/v0/b/dry-clothes.appspot.com/o/Screenshot%20from%202022-08-10%2001-37-13.png?alt=media&token=3721110c-6b66-442c-a73c-d417bca0904c) | | PyScan | Power the development board and upload the program| RS Components | 34.5 € | ![](https://firebasestorage.googleapis.com/v0/b/dry-clothes.appspot.com/o/Screenshot%20from%202022-08-10%2001-38-21.png?alt=media&token=9666468d-fd6f-4f43-b08a-f6628d1e1403) | FiPy Antenna Kit (LoRa Sigfox) | Connect to Sigfox network | RS Components | 10.25 € | ![](https://firebasestorage.googleapis.com/v0/b/dry-clothes.appspot.com/o/Screenshot%20from%202022-08-10%2001-39-27.png?alt=media&token=de7d59fd-b301-4460-b9fa-ed25bea88b52) | Keyestudio DHT 11 | Measure ambient temperature and air relative humidity | Amazon | 1.1 € |![](https://firebasestorage.googleapis.com/v0/b/dry-clothes.appspot.com/o/Screenshot%20from%202022-08-10%2001-40-48.png?alt=media&token=60da62bf-737b-4052-995a-4cba8297fb62) | Keyestudio Soil Humidity Sensor | Measure moist of cloth in contact with sensor| Amazon | 1.1 € |![](https://firebasestorage.googleapis.com/v0/b/dry-clothes.appspot.com/o/Screenshot%20from%202022-08-10%2001-39-51.png?alt=media&token=d83739b6-7c4e-4db9-a122-394872c5d0a6) | Breadboard | Support the prototype circuit | Amazon | 6 € |![](https://firebasestorage.googleapis.com/v0/b/dry-clothes.appspot.com/o/Screenshot%20from%202022-08-10%2001-42-47.png?alt=media&token=288fc3fc-10ee-4b7f-abbb-8d996fff7274) |13 x Jumper Wires|Complete the prototype circuit | Amazon | 1.2 € |![](https://firebasestorage.googleapis.com/v0/b/dry-clothes.appspot.com/o/Screenshot%20from%202022-08-10%2001-48-11.png?alt=media&token=8804a249-11f5-456d-b199-d0f9bc255d02) |2 x Torkis Clothes Peg | Attach the moist sensor to clothes with some pinching force | IKEA | 0.83 €|![](https://firebasestorage.googleapis.com/v0/b/dry-clothes.appspot.com/o/Screenshot%20from%202022-08-10%2001-48-57.png?alt=media&token=cf3bef07-c81b-4a6b-b0f4-4e98ac6763e3) ## Computer setup The instructions of this tutorial assume a Linux setup and using the IDEs detailed below, other operating systems and IDEs will work but you will have to find setup instructions yourself, plenty of resources a search away. >**Best practice**: Update the board firmware to the latest available version using the Pycom firmware tool, download links and instructions can be found [here](https://docs.pycom.io/updatefirmware/device/) - **Atom** as an IDE to program and upload the program to the microcontroller. Installation instructions can be found [here](https://flight-manual.atom.io/getting-started/sections/installing-atom/). Once you have the IDE installed it's time to get the Pycom package to connect to your Pycom board - **Pymakr** atom package that facilitates communication to your Pycom board using the built-in command line REPL. Run a single file to your board, upload your entire project or directly type and execute commands. To install it simply press `CTRL+SHIFT+P` to toggle the command palette search for *Pymakr* and click INSTALL - **Webstorm** is a proprietary IDE developed by JetBrains used for building and deploying both the firebase backend cloud function (callback for the Sigfox handler) and the frontend Dashboard Web App. It can be downloaded [here](https://www.jetbrains.com/webstorm/) - free 30 day trial available. I have chosen this IDE for the ease of pulling GitHub projects and installing dependencies/requirements. #### SOURCE CODE ##### [CONTROLLER PROGRAM](https://github.com/vajean/SmartPeg-program) **!! Connect the Sigfox/LoRa antenna previously to power up the device !!** 1. Clone the repository to your desired location then open the folder using Atom `FILE->OPEN FOLDER` 2. Plug the controller into your USB port and ATOM should identify the connected device. 3. In ATOMs Pymakr window (bottom of the screen) click on `UPLOAD PROJECT TO DEVICE`. 4. The device is now live and transmitting data to the Sigfox network - instructions on how to configure and connect to the Sigfox network in **Connectivity** chapter ##### [CALLBACK CLOUD FUNCTION](https://github.com/vajean/SmartPeg-backend) 1. Clone the repository to your desired location and open the project in Webstorm. Alternatively, create a project from the repository in Webstorm. 2. Please note that [nodejs](https://nodejs.org/en/download/package-manager/), [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) and [Firebase CLI](https://firebase.google.com/docs/cli#install-cli-mac-linux) need to be previously installed, click on the respective links to find instructions how to install them 3. Run `npm install firebase-functions@latest firebase-admin@latest --save` and `npm install -g firebase-tools` 4. Firestore will not work out of the box, you will have to create an account with [Firebase](https://console.firebase.google.com/), create a new project 5. Run `firebase login` to log in via the browser and authenticate the firebase tool. 6. From the project folder run `firebase init firestore`.You can accept the default values when prompted for Firestore rules and index files. You'll also need to select a starting mode and location for Firestore as described in [Get started with Cloud Firestore](https://firebase.google.com/docs/firestore/quickstart#create). 7. Select JavaScript as the language when prompted 8. Run `firebase deploy --only functions` to deploy the function. After you run this command, the Firebase CLI outputs the URL for any HTTP function endpoints. In your terminal. 9. It is now live, you can use [Postman](https://www.postman.com/downloads/) (or Curl if familiar) to test the callback function ##### [FRONTEND DASHBOARD](https://github.com/vajean/PegControl-frontend) 1. Clone the repository to your desired location and open the project in Webstorm. Alternatively, create a project from the repository in Webstorm. 2. Install requirements when WebStorm prompts you. Or by running `nmp install` 3. Please note [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm), [Firebase CLI](https://firebase.google.com/docs/cli#install-cli-mac-linux) and [Angular CLI](https://angular.io/cli) need to be previously installed, click on the respective links to find instructions how to install them (if not already done in the previous chapter) 4. Firestore will not work out of the box, you will have to create an account with [Firebase](https://console.firebase.google.com/), create a new project (if not already done in the previous chapter), add an App, copy the configuration provided to `/environment/environment.ts` as `firestore: {config from Firebase}` 5. Project is ready to run on your local machine, simply run `ng serve` and navigate to `http://localhost:4200/` 6. To deploy to Firebase you will need to `ng build` and `ng deploy` the project. ## Putting everything together The setup I am using is only for development and surely many more calculations, optimization and revisions, are necessary for a commercially ready product For a verbose description, I am pulling the voltage and ground from the PyScan shield's pins `3V3` and `GND` to the breadboard to supply both the DHT11 and the Moisture sensor. The signals from these two sensors are wired to pins `P10` - as a digital input for DHT and `P13` - as analogue input for the Moist sensor. As shown below the moist sensor was broken into three pieces and wires added between them, this is to provide a form factor that can fit in the modified IKEA peg. All the sensors I used come with preinstalled resistors so I did not need to worry about any calculations or voltages. #### Circuit Diagram ![](https://firebasestorage.googleapis.com/v0/b/dry-clothes.appspot.com/o/circuit.png?alt=media&token=0e8db8e5-134e-45fd-93c5-f11cf3b38b91) #### Photos of the prototype ## Platform For a platform I chose to build my own deficient version, more so I can further practice other complementary skills. The platform takes advantage of Sigfox custom callback that sends an HTTP GET with parameters to my Firebase cloud function, this function decodes the data and puts it in a convenient structure on Firestore. Using Firestore and Angular the frontend does not need any other backend, each time data is added this is instantly reflected in the dashboard. The only limitation to having "*nice real-time data*" is using Sigfox (one message every 10 minutes) instead of WiFi or LTE. I used Sigfox to try and use some kind of IoT dedicated technology rather than just WiFi. LTE I could not order a SIM as a private customer and have chosen not to ask anyone with a company to order one for me. ## Explain your code! ##### [CONTROLLER PROGRAM](https://github.com/vajean/SmartPeg-program) The controller program is simple and no calculations are happening here, just sensor data collection, encoding and forwarding via Sigfox. Highlights ```python from dht import DHT ``` The DHT library has to be imported as it is not included in MicroPython. Add this to a lib folder and import it into your `main.py` ```python sigfox = Sigfox(mode=Sigfox.SIGFOX, rcz=Sigfox.RCZ1) s = socket.socket(socket.AF_SIGFOX, socket.SOCK_RAW) s.setsockopt(socket.SOL_SIGFOX, socket.SO_RX, False) s.setblocking(True) ``` Simple code block with default settings opening a socket and setting blocking to true, that is, the program will wait for the socket to complete the transmission before running anything else. ```python adc = ADC() th = DHT(Pin('P10', mode=Pin.OPEN_DRAIN), 0) time.sleep(2) earth = adc.channel(pin='P13', attn=ADC.ATTN_11DB) ``` Initialising the Analog to Digital converter, assigning `PIN13` as ADC channel for the analog moist sensor and using the DHT library for the DHT serial communication on `PIN10` `ATTN_11DB` - because ADC internal reference voltage is around 1.1V voltage attenuation needs to be used to read higher voltages. `OPEN_DRAIN` - Open-drain output works in the following way: if the output value is set to 0 the pin is active at a low level; if the output value is 1 the pin is in a high-impedance state. [[MicroPython documentation 11.08.2022 Online]](https://docs.micropython.org/en/v1.9.3/wipy/library/machine.Pin.html) ```python while True: earth() atm = th.read() payload = pack('>hbb', int(earth.value()), int(atm.temperature), int(atm.humidity)) s.send(payload) print('Data sent:') print(payload) time.sleep(600) ``` Loop forever - read the measurements - pack them as binary data - send it using the socket previously configured - sleep for 10 minutes to respect the limitations imposed on public bands. Sigfox blocks abuse anyway, but good practice regardless of the network used. ##### [SIGFOX CALLBACK HTTP REQUEST](https://github.com/vajean/SmartPeg-program) Full request: `[GET] https://my-censored-function.cloudfunctions.net/addMessage?time={time}&sequenceN={seqNumber}&device={device}&data={data}` As can be seen, the function comes after the first `/` All parameters I chose to forward are those following the `?` sign - Time - the measurement is taken/reached SigFox (not sure) - Sequence Number - Measurement number, this can be reset but otherwise, it just keeps incrementing with each new measurement - Device - Unique device ID Sigfox assigned to my sensor - in the future, this can be used to allow the user to have a couple of different SmartPegs registered to his profile and the measurements won't get mixed. - Data - the data as received from the sensor, no encoding or decoding ##### [CALLBACK CLOUD FUNCTION](https://github.com/vajean/SmartPeg-backend) As soon as a request reaches the cloud function, the query parameters are saved in constants. The Data is decoded using the ParseInt function passing the 16 base parameter: ```javascript const wet = parseInt(payload.substring(0,4), 16) const temp = parseInt(payload.substring(4,6), 16) const hum = parseInt(payload.substring(6,8), 16) ``` Lastly, all the parameters are passed as a dictionary to a Firestore function and are saved in the Firebase Cloudstore. ##### [FRONTEND DASHBOARD](https://github.com/vajean/PegControl-frontend) This would require a tutorial in itself, and I am only going to explain some of the functions and the time left estimation. The rest of the code is Angular and beyond the scope of this tutorial. ```typescript getFrom() { this.lastDrying = this.store.collection('messages', ref => ref.orderBy('time', 'desc').limit(20)).valueChanges() as Observable<Measurement[]>; } getOne() { this.oneMeasurement = this.store.collection('messages', ref => ref.orderBy('time', 'desc').limit( 1)).valueChanges() as Observable<Measurement[]>; } ``` Get the last 20 measurements to display in a table and used to populate the graph as Observable, so as soon as a new measurement is added the 20 items are instantly updated on the App Get the last measurement as an Observable, the dashboard dials are subscribed to this and will update as soon as a new measurement has been saved in the Firestore by the Cloud function. ```typescript stopProgram() { this.stop = Math.floor(Date.now() / 1000) this.status = 'Stopped' this.controlMessage = 'You stopped the program' this.setSettings() } resetProgram() { clearInterval(this.updateID) this.stop = NaN this.start = NaN this.lastMeasurement = NaN this.status = 'Waiting' this.controlMessage = '' this.timeLeft = NaN this.setSettings() } resumeProgram() { this.stop = NaN this.status = 'Drying' this.controlMessage = 'Enjoy Life' this.setSettings() } setSettings() { this.firestore.collection("control").doc('vajean').set({ start: this.start, stop: this.stop, status: this.status, controlMessage: this.controlMessage, fabric: this.fabric, timeLeft: this.timeLeft, lastMeasurement: this.lastMeasurement }) } ``` A series of helper functions that call the `setSettings()` function each time, these are used to save settings to a Firestore collection that saves the parameters of the Drying cycle, this way the browser can be refreshed or a couple of browsers can see the same data at the same Drying cycle. ```typescript if (this.lastMeasurement != this.values[0].time){ this.lastMeasurement = this.values[0].time const current = new Date(); const numberLeft = this.values[0].wet / (Math.min(this.values[0].temp / 7, 3) + Math.min((101 - this.values[0].hum) / 10, 3)) current.setHours(Math.floor(numberLeft / 60)) current.setMinutes(numberLeft % 60) current.setSeconds(0) // @ts-ignore this.timeLeft = current.getTime(); } ``` Condition to only update the time left estimate if a new measurement is registered. Otherwise, a simple interval function decrements the time left each second. The estimate is based on an overly simplified version of the Penman-Monteith equation, ignoring air circulation, sun and many others. In this exercise, the time left is directly proportional to the Moist in the Fabric and inversely proportional to air relative humidity and temperature. Ideally, the program will use a trained neural network and not a formula, be it as complex as it may be. ```typescript countdown() { this.intervalID = setInterval(() => { this.timeLeft = this.timeLeft - 1000 this.setSettings() }, 1000) } updatePrediction() { this.updateID = setInterval(() => { this.calculateTimeLeft() this.setSettings() }, 5000) } ``` Two set intervals, one used to decrease the time left by 1000ms every 1000ms and the other used to recalculate the time left if a new measurement was added. ## Transmitting the data/connectivity The data is transmitted through the LPWAN proprietary protocol of Sigfox S.A. a French company. The PyCom comes with a free 1-year subscription so for this project that is more than enough and has better coverage than TTN in my City :shrug:. I tried countless nights to get a hit on TTN to no success. The 12 bytes 144 messages per day limits Sigfox imposes were above the requirements of this project, you don't need updates more often than once every 10 minutes to know the clothes are dry. Sigfox backend uses a callback to a custom-made webhook (the cloud function mentioned above), each time a new measurement reaches Sigfox it sends a GET HTTP request to the cloud function with the measurements and some other metadata as query parameters. ``` payload = pack('>hbb', int(earth.value()), int(atm.temperature), int(atm.humidity)) ``` The Payload is encoded into a big-endian byte binary data before being transmitted over the network. The analogue moist value is packed into 2 bytes integer, the temperature and RH are both packed into 1-byte integers. ## Presenting the data The dashboard consists of 4 main elements or views. 1. The Gauges, showing "real-time data" - up to ten minutes old. Humidity, Temperature and Fabric moist are shown as gauges with numbers or percentages in the middle ![]() 2. PegControl is where drying cycles can be started paused and stopped. This view will also show an estimated time left and will supposedly send an alert to the user in future updates. A choice between cotton and synthetics is given influencing the time left calculations. ![]() 3. Measurements view shows the last 20 measurements in tabular form ![]() 4. Graph, a graph that should automatically show the evolution of the parameters measured over time, but at the moment two buttons to add and remove data have to manually trigger the graph. ![]() ## Finalizing the design Photos of the final project and some rendering of the product if ever fully developed below. The project took much longer than expected and still is a half-baked product, but some improvements and features can be made to turn it into a great quality of life solution: - Form factor - squeeze everything into a small form factor, battery powered - Connectivity - is the elephant in the room - even though there are some smaller LPWAN, and LORAWAN antennas available, I still think this is the element I could least fit into a production-ready device. Offering a two-tier device could be a solution: one using BLE for small households like apartments, a second more expensive LTE version with a prepaid 10y subscription for big houses or a big yard. - Weather predictions that matter - at the moment the weather forecast is just a placeholder, it should be input into the PegControl core functions and if clothes are hung outdoors it should take more parameters into account for the estimations. - Rain alert - a very important feature that started the project, alert if rain is expected before the clothes are expected to be dry. - Neural network - to replace any kind of formula, with enough data the neural network can become extremely accurate. - User login - not implemented yet, easy fix but comes together with next step - Adding/assigning devices to users - feature to allow the user to add his own device once bought, one or many. - Cheaper everything - especially the controller, small cheap and bulk-built OEM versions of a controller with BLE or LTE and custom sensors is a must. ## Gallery ![](https://firebasestorage.googleapis.com/v0/b/dry-clothes.appspot.com/o/PXL_20220814_094702858.jpg?alt=media&token=c51c0a9e-2f2b-4a2e-842e-46cf70a403e6) ![](https://firebasestorage.googleapis.com/v0/b/dry-clothes.appspot.com/o/PXL_20220814_094726943.jpg?alt=media&token=130133c2-240c-47b7-8d40-1f1131e408ec) ![](https://firebasestorage.googleapis.com/v0/b/dry-clothes.appspot.com/o/PXL_20220814_094145258.jpg?alt=media&token=e5e54878-116c-47f8-9874-a4580dedac34) ![](https://firebasestorage.googleapis.com/v0/b/dry-clothes.appspot.com/o/PXL_20220814_094201966.jpg?alt=media&token=f458d9d3-248a-4a53-a76e-80b5e481d553) ![](https://firebasestorage.googleapis.com/v0/b/dry-clothes.appspot.com/o/PXL_20220814_094239684.jpg?alt=media&token=fd696902-64e4-4ee8-8537-853eef69ccc1) ![](https://firebasestorage.googleapis.com/v0/b/dry-clothes.appspot.com/o/PXL_20220814_094311135.MP.jpg?alt=media&token=663d7167-0248-46f8-a160-786c8e41d0f7) ![](https://firebasestorage.googleapis.com/v0/b/dry-clothes.appspot.com/o/PXL_20220814_094317769.jpg?alt=media&token=d0c9fcc9-09b5-4ff9-97b6-e85911021f76) ![](https://firebasestorage.googleapis.com/v0/b/dry-clothes.appspot.com/o/Screenshot%20from%202022-08-12%2000-23-24.png?alt=media&token=45c10869-7e15-4bd2-91d9-2eeee6d5f806) ![](https://firebasestorage.googleapis.com/v0/b/dry-clothes.appspot.com/o/Screenshot%20from%202022-08-12%2000-22-43.png?alt=media&token=e61e1b82-ec31-46e3-840d-09d8c29a1ab9) ![](https://firebasestorage.googleapis.com/v0/b/dry-clothes.appspot.com/o/Screenshot%20from%202022-08-12%2000-22-51.png?alt=media&token=07250c28-e64a-4465-af97-56fde8dfb7ce) ![](https://firebasestorage.googleapis.com/v0/b/dry-clothes.appspot.com/o/Screenshot%20from%202022-08-12%2000-22-36.png?alt=media&token=141277e2-ee19-4d65-965c-c601b74373f4) ![](https://firebasestorage.googleapis.com/v0/b/dry-clothes.appspot.com/o/Screenshot%20from%202022-08-12%2000-22-28.png?alt=media&token=aedbc220-553d-4bce-91af-201acde84dc2)