NOTE: There are major bugs, inefficiency and security issues. This is not at all the best implementation of the system. I just flew too close to the sun and wanted to see what I am capable of.
This is the repository of my custom built smart home system. This system is a dynamically changeable smart home system built with React TypeScript, firabase and ESP32 devices.
- Feel free to improve, use or fork this repository in your own projects :)
- For any bugs or improvements feel free to make an issue or make a pull request
- Smart home system
- Introduction
- Table of Contents
- Features
- Hardware Requirements
- Software Dependencies
- Installation and usage + Create an IoT device + WebApp + Servers - WebSocket - API - MSSQL
- Configuration
- How it works ?
- Acknowledgements / Source
- Dynamically add custom variables to the system
- Fast and reliable communication
- Many virtual homes within one ESP
- Easy implementation
- Some computer to run the docker containers (preferably raspberry pi or host it on your preferable server hoster)
- Any ESP32
- Sensors or devices of your choise
- Docker (running the webapp and the servers)
- SmartHome library (or the following librarys if you don't want to use the dedicated library)
- On the Webapp side you can find the dependencies in the package.json file
- Clone the repository
- Configure and install the components
- Create a virtual smart home with the SmartHome class with your server urls and device properties:
SmartHome desk("Desk", "1", "1", "http://0.0.0.0:0000");
- Add custom bool or int to your SmartHome:
desk.addVariableBool(13, "deskLamp", deskLamp);
- Validate the home:
- Note: before validation the ESP32 needs to connect to your local network
desk.validateHome();
- Initialize the WebSocket connection:
ws.begin(webSocketServerAddress, webSocketServerPort, "/socket.io/?EIO=4");
ws.onEvent(webSocketEvents);
- Call the WebSocket loop in the loop section:
ws.loop();
- Stringify the home then send it at your neede
String dataDesk = desk.prepareWebSocketData();
ws.sendEVENT(dataDesk);
npm run dev
For running the servers you need to build then execute it on the docker app
docker build . -t smart-home-websocket
docker run -p 5000:5000 smart-home-websocket
docker build . -t smart-home-api
docker run -p 8080:8080 smart-home-api
Pulling the Microsoft SQL database docker images from Microsoft
docker pull mcr.microsoft.com/mssql/server:2022-latest
Creating docker container from the image with cusomt name --name iot-database
and password "MSSQL_SA_PASSWORD=ThisIsThePassword!24"
(A strong system administrator (SA) password: At least 8 characters including uppercase, lowercase letters, base-10 digits and/or non-alphanumeric symbols.)
docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=ThisIsThePassword!24" -p 1433:1433 -d --name iot-database mcr.microsoft.com/mssql/server:2022-latest
Demo data injection
-- Create the devices table
CREATE TABLE devices (
did VARCHAR(255),
dn VARCHAR(255),
dd VARCHAR(MAX),
uid VARCHAR(255)
);
-- Insert data into the devices table
INSERT INTO devices (did, dn, dd, uid)
VALUES
('1001', 'Device1', 'property1-n-0-100-50--property2-b-0-1-0--', '1124'),
('1002', 'Device2', 'setting1-b-0-1-1--setting2-n-0-10-5--', '1124'),
('1003', 'Device3', 'temperature-n-0-50-25--humidity-n-0-100-75--', '1124'),
('1004', 'Device4', 'state-b-0-1-1--brightness-n-0-255-128--', '1124');
In order to use the system you have to configure the webapp and the ESP32 to connect to the servers via IP and their port and also you have to set your wifi ssid
and password
credentials in your ESP32 project
At the top, the necessary modules are imported. Express is a web application framework for Node.js, designed for building web applications and APIs. Socket.IO is a JavaScript library for real-time web applications. It enables real-time, bidirectional, and event-based communication between the browser and the server.
The connectedClients
Map is used to keep track of all connected clients. Each client is identified by a unique clientId
which is a combination of the user's userId
, deviceId
, clientType
, and the socket's id
.
The sendConnectionStatusEvent
function is used to send a connectionStatus
event to a specific socket. It sends an object containing an array of connectedDevices
for a specific userId
.
The io.on("connection", (socket) => {...})
block is where the server handles a new client connection. Inside this block, several event listeners are set up on the socket
object to handle different types of events.
The join
event is used when a client wants to join the server. The client sends its userId
, deviceId
, and clientType
to the server. The server then stores this information in the socket
object and the connectedClients
Map.
If the clientType
is webapp
, the server sets up a webMessage
event listener on the socket
. When a webMessage
event is received, the server tries to find the target device's socket and sends a message
event to it.
If the clientType
is device
, the server sets up a deviceMessage
event listener on the socket
. When a deviceMessage
event is received, the server sends a message event to all webapp sockets
associated with the userId
.
The disconnect
event is used when a client disconnects from the server. The server removes the client's information from the connectedClients
Map.
Finally, the server starts listening on a specific port for incoming connections.
To connect to the WebSocket server, create a new WebSocket instance in your client-side JavaScript:
const socket = io('<http://localhost:5000>');
Replace '<http://localhost:5000>'
with the URL of your WebSocket server.
After connecting, you should emit a "join" event to the server. This event should include your user ID, device ID, and client type:
socket.emit('join', userId, deviceId, clientType);
userId
(string): The ID of the user.deviceId
(string): The ID of the device. This is not required for webapp clients.clientType
(string): The type of the client. This should be either "webapp" or "device".
To send a message, emit a ${clientType}Message
event to the server. This event should include the target device ID and the message:
socket.emit(`${clientType}Message`, targetDeviceId, message);
targetDeviceId
(string): The ID of the target device. This is required for webapp clients.message
(string): The message to send.
To receive messages, listen for the "message" event:
socket.on('message', (message) => {
console.log('Received message:', message);
});
To receive updates about the connection status, listen for the "connectionStatus" event:
socket.on('connectionStatus', (data) => {
console.log('Connected devices:', data.connectedDevices);
});
To disconnect from the server, use the disconnect
method:
socket.disconnect();
Please note that this is a basic API documentation. Depending on your application's requirements, you might need to add more events or data fields.
/**
* @description Get a device from the database by ID or by user ID
* @method GET
* @API /api/devices
* @API /api/devices/device?did={did}
* @API /api/device/device?uid={uid}
* @body -
* @returns {JSON} result
*/
export async function GET(request)
/**
* @description Update device data in the database by ID
* @method PUT
* @API /api/devices/device
* @body {JSON} {did, dd}
* @returns {JSON} result
*/
export async function PUT(request)
/**
* @description Add (insert) a device to the database
* @method POST
* @API /api/devices
* @body {JSON} {did, dn, dd, uid}
* @returns {JSON} result
*/
export async function POST(request)
/**
* @description Delete a device from the database by ID
* @method DELETE
* @API /api/devices/device
* @body {JSON} {did}
* @returns {JSON} result
*/
export async function DELETE(request)
USERS:
/**
* @description Get all user from the database or by ID
* @method GET
* @API /api/users
* @API /api/users/user?uid={uid}
* @body -
* @returns {JSON} result
*/
export async function GET(request)
/**
* @description Update user data in the database by ID
* @method PUT
* @API /api/users/user
* @body {JSON} {uid}
* @returns {JSON} result
*/
export async function PUT(request)
/**
* @description Add (insert) a user to the database
* @method POST
* @API /api/users
* @body {JSON} {uid}
* @returns {JSON} result
*/
export async function POST(request)
/**
* @description Delete a user from the database by ID
* @method DELETE
* @API /api/users/user
* @body {JSON} {uid}
* @returns {JSON} result
*/
export async function DELETE(request)
Example:
https://myserver.com/api/devices/device?did=05b31779-14ee-4233-8c9a-2749e81d3ccb -> GET request
-> response:
{
"DID": "05b31779-14ee-4233-8c9a-2749e81d3ccb",
"DN": "Thermostat",
"DD": "temperature-n-0-100-34--humidity-n-0-100-61--state-b-0-0-0--",
"UID": "80ff2b60-bf4b-42fe-8de4-d21734a393c8"
},
https://myserver.com/api/devices/device -> PUT request
-> request body:
{
"DID": "05b31779-14ee-4233-8c9a-2749e81d3ccb",
"DN": "Thermostat",
"DD": "temperature-n-0-100-34--humidity-n-0-100-61--state-b-0-0-0--",
"UID": "80ff2b60-bf4b-42fe-8de4-d21734a393c8"
},
-> response: "Device updated successfully"
[
{
"did": "1111",
"dn": "Led",
"dd": "ledVariable-b-0-0-true--",
"uid": "1124"
},
{
"did": "123",
"dn": "Desk",
"dd": "deskLamp-b-0-0-true--deskLampBrightness-n-0-255-20--deskMonitor-b-0-0-true--",
"uid": "1124"
},
{
"did": "456",
"dn": "Thermostat",
"dd": "thermostatTemperature-n-0-100-13--thermostatHumidity-n-0-100-31--thermostatPower-b-0-0-true--",
"uid": "1124"
},
{
"did": "789",
"dn": "Bed",
"dd": "bedLamp-b-0-0-false--bedLampBrightness-n-0-255-225--",
"uid": "1124"
}
]
[
{
"UID": "09c007bd-526b-4d8e-a9b9-96daff857759"
},
{
"UID": "d480b324-d6bd-4e05-820f-c807a7a5ed7e"
}
]
export const user = {
uid: "1124",
uName: "Vichnál Martin",
uPhoto: "https://lh3.googleusercontent.com/a/ACg8ocI5cTr4KR7TWUMmnwHdRFaBpEZw6QRUiwtVixPCTQVmuow=s96-c",
uIsAuth: true,
}