UNICON- UNIHIKER based portable home automation controller with wireless charging
So, when it comes to controlling home appliances we usually smartphone as the only option because it has Bluetooth and wifi inbuilt, right?
You picked up your smartphone to control an appliance, pin pong! New Instagram notification and you got distracted. So, an appliance control has converted into a regret. Sounds familiar, right?
But not anymore, introducing UNICON, a truly portable and rechargeable home automation controller that allows you to control appliances. But that's not it, it can simultaneously control appliances of multiple rooms.
In this build, we will control 1 appliance of two different rooms, but you can extend it further as per your needs. ENJOY!
We will first begin by prototyping our setup on a breadboard to meet our expectations. For prototyping, I am using two ESP32 boards with one LED and one potentiometer connected to both of them. But why?
I want to keep the prototype as simple as possible and wanted to test out whether I can trigger a digital device connected to one pin that will later act as a relay controller, along with that whether I can receive analog value from the ESP32 board connected to an analog pin on the UNIHIKER which will later act as Temperature sensor(or any other analog sensor of your choice)
Here, I am using one ESP32 WROOM Dev board and one ESP32 C3 Mini Bettle. You can use any of the ESP32 and they should work fine too. Here are the connection diagrams that I am following for this prototype setup.
//This is for ESP32 dev. board
#include <WiFi.h> //Make sure to download this library
#include <WebServer.h> //Make sure to download this library
// WiFi credentials
const char* ssid = "Padmalaya Rawal"; //Change with your WiFi Name
const char* password = "PureCon"; //Change with your WiFi Password
// Static IP configuration
IPAddress local_IP(192,168,1,51); // Set your static IP
IPAddress gateway(192,168,1,1); // Set your router’s gateway IP
IPAddress subnet(255,255,255,0); // Set subnet mask
WebServer server(80); // Create a web server on port 80
const int ledPin = 2; // change to pin on which you are connecting LED
const int sensorPin = 34; // change to pin on which your connecting sensor
// Function to handle turning the LED ON
void handleLedOn() {
digitalWrite(ledPin, HIGH); // Turn the LED ON
Serial.println("LED turned ON");
server.send(200, "text/plain", "LED is ON");
}
// Function to handle turning the LED OFF
void handleLedOff() {
digitalWrite(ledPin, LOW); // Turn the LED OFF
Serial.println("LED turned OFF");
server.send(200, "text/plain", "LED is OFF");
}
// Function to handle invalid URLs
void handleNotFound() {
server.send(404, "text/plain", "Invalid Command");
}
void sensread(){
int sensorValue = analogRead(sensorPin);
server.send(200, "text/plain", String(sensorValue));
}
void setup() {
Serial.begin(115200); // Start Serial communication
pinMode(ledPin, OUTPUT); // Set LED pin as output
pinMode(sensorPin, INPUT);
digitalWrite(ledPin, LOW); // Ensure LED is OFF initially
// Connect to Wi-Fi with static IP
if (!WiFi.config(local_IP, gateway, subnet)) {
Serial.println("Static IP Configuration Failed");
}
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
// Wait until connected to WiFi
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
// Once connected
Serial.println("\nWiFi connected!");
Serial.print("ESP32 IP address: ");
Serial.println(WiFi.localIP()); // Print static IP address
// Define the routes for LED ON and OFF
server.on("/control/led/on", handleLedOn); // Route to turn LED ON
server.on("/control/led/off", handleLedOff); // Route to turn LED OFF
server.on("/read/sensor", sensread);
server.onNotFound(handleNotFound); // Route for invalid commands
// Start the web server
server.begin();
Serial.println("HTTP server started");
}
void loop() {
server.handleClient(); // Handle incoming client requests
}
//This is for DF ROBOT ESP32 Bettle board
#include <WiFi.h> //Make sure to download this library
#include <WebServer.h> //Make sure to download this library
// WiFi credentials
const char* ssid = "Padmalaya Rawal"; //Change with your WiFi Name
const char* password = "PureCon"; //Change with your WiFi Password
// Static IP configuration
IPAddress local_IP(192,168,1,50); // Set your static IP
IPAddress gateway(192,168,1,1); // Set your router’s gateway IP
IPAddress subnet(255,255,255,0); // Set subnet mask
WebServer server(80); // Create a web server on port 80
const int ledPin = 2; // change to pin on which you are connecting LED
const int sensorPin = 1; // change to pin on which your connecting sensor
// Function to handle turning the LED ON
void handleLedOn() {
digitalWrite(ledPin, HIGH); // Turn the LED ON
Serial.println("LED turned ON");
server.send(200, "text/plain", "LED is ON");
}
// Function to handle turning the LED OFF
void handleLedOff() {
digitalWrite(ledPin, LOW); // Turn the LED OFF
Serial.println("LED turned OFF");
server.send(200, "text/plain", "LED is OFF");
}
// Function to handle invalid URLs
void handleNotFound() {
server.send(404, "text/plain", "Invalid Command");
}
void sensread(){
int sensorValue = analogRead(sensorPin);
server.send(200, "text/plain", String(sensorValue));
}
void setup() {
Serial.begin(115200); // Start Serial communication
pinMode(ledPin, OUTPUT); // Set LED pin as output
pinMode(sensorPin, INPUT);
digitalWrite(ledPin, LOW); // Ensure LED is OFF initially
// Connect to Wi-Fi with static IP
if (!WiFi.config(local_IP, gateway, subnet)) {
Serial.println("Static IP Configuration Failed");
}
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
// Wait until connected to WiFi
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
// Once connected
Serial.println("\nWiFi connected!");
Serial.print("ESP32 IP address: ");
Serial.println(WiFi.localIP()); // Print static IP address
// Define the routes for LED ON and OFF
server.on("/control/led/on", handleLedOn); // Route to turn LED ON
server.on("/control/led/off", handleLedOff); // Route to turn LED OFF
server.on("/read/sensor", sensread);
server.onNotFound(handleNotFound); // Route for invalid commands
// Start the web server
server.begin();
Serial.println("HTTP server started");
}
void loop() {
server.handleClient(); // Handle incoming client requests
}
What's the difference between the two codes for ESP32s?
Difference 1:
In Line 11, 1st code has
IPAddress local_IP(192,168,1,51); // Set your static IP
Whereas the 2nd code has
IPAddress local_IP(192,168,1,50); // Set your static IP
Difference 2:
In Line 18, 1st code has
const int sensorPin = 34; // change to pin on which your connecting sensor
whereas the 2nd code has
const int sensorPin = 1; // change to pin on which your connecting sensor
Also, please don't forget to change the Wifi credentials in both the codes
const char* ssid = "Padmalaya Rawal"; //Change with your WiFi Name
const char* password = "PureCon"; //Change with your WiFi Password
Replace the name “Padmalaya Rawal” with the name of your WiFi and “PureCon” with the password. And now it's time for UNIHIKER. Now let's have a look at the UNIHIKER's firmware which is written in Python.
# For UNIHIKER Board
import requests
from unihiker import GUI
import time
from pinpong.board import Board # Import the Board module from the pinpong.board package
from pinpong.extension.unihiker import * # Import all modules from the pinpong.extension.unihiker package
# Instantiate a GUI object.
gui = GUI()
# ESP32's static IP addresses
esp32_ip = "192.168.1.50"
esp32_ip2 = "192.168.1.51"
img_image = gui.draw_image(x=120, y=30, w=400, h=250, image='logo.png', origin='center', onclick=lambda: print("image clicked"))
# URLs to control the LEDs and read the sensor
url_led_on = f"http://{esp32_ip}/control/led/on"
url_led_off = f"http://{esp32_ip}/control/led/off"
url_sensor = f"http://{esp32_ip}/read/sensor" # Fetch sensor data
url_led_on2 = f"http://{esp32_ip2}/control/led/on"
url_led_off2 = f"http://{esp32_ip2}/control/led/off"
url_sensor2 = f"http://{esp32_ip2}/read/sensor" # Fetch sensor data
# Function to turn on the LED on ESP32 No.1
def turn_led_on():
try:
response = requests.get(url_led_on)
print("LED ON Response:", response.text)
except Exception as e:
print("Error turning LED ON:", e)
# Function to turn off the LED on ESP32 No.1
def turn_led_off():
try:
response = requests.get(url_led_off)
print("LED OFF Response:", response.text)
except Exception as e:
print("Error turning LED OFF:", e)
# Function to turn on the LED on ESP32 No.2
def turn_led_on2():
try:
response = requests.get(url_led_on2)
print("LED ON Response:", response.text)
except Exception as e:
print("Error turning LED ON:", e)
# Function to turn off the LED on ESP32 No.2
def turn_led_off2():
try:
response = requests.get(url_led_off2)
print("LED OFF Response:", response.text)
except Exception as e:
print("Error turning LED OFF:", e)
# Function to fetch the analog sensor value from ESP32 No.1
def fetch_sensor_data():
try:
response = requests.get(url_sensor)
sensor_value = response.text
print(f"Sensor Value: {sensor_value}")
value.config(text=sensor_value) # Update the displayed light value
except Exception as e:
print("Error fetching sensor data:", e)
# Function to fetch the analog sensor value from ESP32 No.2
def fetch_sensor_data2():
try:
response = requests.get(url_sensor2)
sensor_value = response.text
print(f"Sensor Value: {sensor_value}")
value2.config(text=sensor_value) # Update the displayed light value
except Exception as e:
print("Error fetching sensor data:", e)
# Add GUI components
gui.draw_text(x=120, y=110, w=200, text='ROOM 1 Control:', origin='bottom')
button_on = gui.add_button(text="ON", x=40, y=120, w=80, h=40, onclick=turn_led_on)
button_off = gui.add_button(text="OFF", x=120, y=120, w=80, h=40, onclick=turn_led_off)
gui.draw_text(x=103, y=180, font_size = 10, text='Temperature:', origin='bottom')
value = gui.draw_text(x=153, y=164, text='0', font_size=10) # Display the initial light value
gui.draw_text(x=120, y=240, w=200, text='ROOM 2 Control:', origin='bottom')
button_on2 = gui.add_button(text="ON", x=40, y=250, w=80, h=40, onclick=turn_led_on2)
button_off2 = gui.add_button(text="OFF", x=120, y=250, w=80, h=40, onclick=turn_led_off2)
gui.draw_text(x=103, y=310, w=200, font_size = 10, text='Temperature:', origin='bottom')
value2 = gui.draw_text(x=154, y=293, text='0', font_size=10) # Display the initial light value
# Main loop to keep the GUI running and fetch sensor data
while True:
fetch_sensor_data()
fetch_sensor_data2()
time.sleep(1) # Delay between fetching sensor data
Note that the IP address in lines 15 and 16 is the same as we added in Code 1 and Code 2. If you wish to change it, make sure to do it all the mentioned lines in all 3 codes to avoid any error.
You can skip this step, if you have already completed the ESP32 boards setup in ARDUINO IDE, but if not, stick with me, this is to make your life easier. Follow along:
1. Download Arduino IDE from https://www.arduino.cc/en/software as per your machine(PC).
2. Simply follow the instructions and install it. Once installed, fire it up
3. ESP32 boards setup, add the below link in preference:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
4. Install ESP32 boards from the boards' manager
6. Select board - DF ROBOT bettle and upload code
7. Select board - ESP32 Dev board and follow the above steps again, press boot button when the code starts uploading (not compiling) or you can add a 10uF cap b/w GND and EN pin.
My recommendation:
If you are buying new parts, I would recommend you to buy two pieces of DF ROBOT bettle boards instead of buying a Generic ESP32 board because code uploads very fast and you don't have to press any button or add any capacitor. Also, you can upload a similar code on both of them by just changing the IP address of one to 50 and 51 as explained above.
Now Let's set up UNIHIKER, there are various methods by which you can program UNIHIKER but the one that I prefer needs the least time to start uploading code. I hope you don't want to skip this step because there is not much detailed tutorial available on UNIHIKER that can guide you through.
1. Connect UNIHIKER board to your PC via TYPE-C data cable included in the package.
2. Once UNIHIKER booted up, visit http://10.1.2.3 in your browser that will open up the following window:
3. Go to Network settings and add your Wi-Fi credentials. This will allow UNIHIKER to connect to the network. Make sure it's the same Wi-Fi as used in ESP32 codes, else it won't work.
4. Now go to toggle service and click “Open Page” under Jupyter section
You will see the below page:
5. Let's make a new python file, rename it and upload the code to UNIHIKER.
Click here to rename the file.
In the highlighted section, we will write our code.
Now copy and paste the code mentioned above and after pasting the code & renaming the file it should look something like this. Notice that, “NOT CONNECTED” should not appear on the top, it indicates that UNIHIKER is not connected with your PC. If it appears, double check that you are using Data cable (preferably the one included in the kit) not the power cable. Once verified, hit “NOT CONNECTED” Label and it will connect successfully which in indicated in next image.
6. Now in a new tab open the below homepage, you can follow the steps listed above and upload DF ROBOT logo named as "logo.png" using the button as shown below. Once uploaded, move back to the previous tab.
Download this image and rename it as “logo” and it should be in .png extension:
7. Hit “RUN” button and boom, you will see UI on UNIHIKER. You will be able to control the LED on both of ESP32s and also you will see values changing if you rotate potentiometer.
DO NOT MISS THIS STEP ELSE IT MIGHT BURN UP YOUR UNIHIKER
If you are using the same Boost convertor Module, make sure to remove resistors A and B to get 5V on output as shown below:
When the receiver coil is energized by the transmitter coil, it begins charging the battery via the TP4056 charging module. When the button is turned on, it turns on the boost converter and powers up UNIHIKER.
Here is the CAD view of the project
Download all the files and 3D print them, make sure to adjust the orientation before printing.
Slicer- Prusa Slicer
3D Printer- Anycubic Kobra Neo
Layer Height- 0.2mm
Nozzle Size - 0.4mm
Infill- 100%
Material- PLA+
Here are all the 3D-printed parts required for this project:
Once all the parts are printed, gather M3*6mm brass threaded inserts and carefully add them to the 3D-printed parts.
Remember to solder the sufficiently large wires so that you can open the LID without breaking connections but not too much because that might reduce the current delivered to UNIHIKER. Here is the assembled view of the UNIHIKER Case. Place the 2 mounts and screw them in place before closing the LID. I have kept the tolerance of the Case LOW so that the LID can stay in place without the use of any screws or glue.
Here is the final look of our project:
Here is a working demo with a prototype setup.
For the final implementation, replace LED with a relay module and potentiometer with LM35 temperature sensor by following the below circuit diagrams. If you want to use any other sensor instead of LM35, make sure to change the code on all three devices by adding the required libraries.
In a world full of distractions, UNICON will help you to stay distraction-free while staying smart. Control and monitor multiple rooms of your home, office, or arsenal altogether just at your fingertips. This is what we call the true smart home automation setup.
The device can be made even more compact by replacing all the modules with a custom-designed PCB. Also, I have planned a lot of features for the next version which are as follow
1. When USB is plugged in, UNIHIKER should not be powered by Boost convertor
2. When USB is not connected, UNIHIKER should be powered by Boost converter
3. When UNIHIKER is on the charging Dock, UNHIKER should not be powered by boost convertor instead it should be powered by Wireless coil.
4. When UNIHIKER is on the charging Dock, and USB is plugged in, it should only be powered by USB cable while the battery keeps on charging via Wireless charging.
5. Addition of more button to control more appliances
6. Addition of more room controls and drop down menu to select the room
7. Sleep mode, shutdown screen to save power when device is inactive for a specific time and wakes up when the device is moved which is detected by the inbuilt IMU.
We have just scratched the surface, possibilities are endless.
Happy Learning!