-
Notifications
You must be signed in to change notification settings - Fork 0
Add Smart Vision IT Solution static IoT dashboard with ESP RainMaker-ready controls #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| # Smart Vision IT Solution IoT App | ||
|
|
||
| This repository contains a lightweight web-based IoT dashboard for **Smart Vision IT Solution**. | ||
| The app is branded for the business and prepared to connect with an **ESP RainMaker** backend so users can control: | ||
|
|
||
| - Living room light | ||
| - Bedroom light | ||
| - Main hall fan | ||
|
|
||
| ## Features | ||
|
|
||
| - Smart Vision IT Solution branding and contact details | ||
| - ESP RainMaker connection form for base URL and access token | ||
| - Device cards for light switches and fan power/speed control | ||
| - Activity log showing local updates and backend sync events | ||
| - Responsive glassmorphism UI for desktop and mobile | ||
|
|
||
| ## Contact | ||
|
|
||
| - Phone: [9818429568](tel:9818429568) | ||
| - Email: [svitsolution@outlook.com](mailto:svitsolution@outlook.com) | ||
|
|
||
| ## Run locally | ||
|
|
||
| Because the project is a static web app, you can run it with any static server. For example: | ||
|
|
||
| ```bash | ||
| python3 -m http.server 4173 | ||
| ``` | ||
|
|
||
| Then open `http://localhost:4173` in your browser. | ||
|
|
||
| ## ESP RainMaker integration note | ||
|
|
||
| The `script.js` file includes a `RainMakerClient` abstraction that is ready for wiring into your actual ESP RainMaker API flow. Replace the simulated `connect()` and `updateDevice()` methods with your production API requests and authentication handling when your backend credentials and node schema are available. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,182 @@ | ||
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>Smart Vision IT Solution</title> | ||
| <meta | ||
| name="description" | ||
| content="Smart Vision IT Solution IoT dashboard powered by ESP RainMaker for light and fan control." | ||
| /> | ||
| <link rel="preconnect" href="https://fonts.googleapis.com" /> | ||
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> | ||
| <link | ||
| href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" | ||
| rel="stylesheet" | ||
| /> | ||
| <link rel="stylesheet" href="styles.css" /> | ||
| </head> | ||
| <body> | ||
| <div class="page-shell"> | ||
| <header class="hero card"> | ||
| <div> | ||
| <p class="eyebrow">ESP RainMaker Enabled IoT App</p> | ||
| <h1>Smart Vision IT Solution</h1> | ||
| <p class="hero-copy"> | ||
| Manage connected lights and fan devices from one polished control panel backed by | ||
| an ESP RainMaker-ready API layer. | ||
| </p> | ||
| <div class="hero-actions"> | ||
| <a class="button primary" href="#controls">Open dashboard</a> | ||
| <a class="button secondary" href="#contact">Contact us</a> | ||
| </div> | ||
| </div> | ||
| <div class="status-panel"> | ||
| <span class="status-chip live">RainMaker Cloud Ready</span> | ||
| <h2>Remote automation, scenes, and live device updates.</h2> | ||
| <p> | ||
| Built to connect your switch lights and fan controllers with ESP RainMaker services, | ||
| status sync, and command delivery. | ||
| </p> | ||
| <ul> | ||
| <li>Light switch controls with instant feedback</li> | ||
| <li>Fan on/off and speed regulation</li> | ||
| <li>Prepared for RainMaker authentication token usage</li> | ||
| </ul> | ||
| </div> | ||
| </header> | ||
|
|
||
| <main> | ||
| <section class="connection-grid"> | ||
| <article class="card connection-card"> | ||
| <div class="section-heading"> | ||
| <p class="eyebrow">Backend</p> | ||
| <h2>ESP RainMaker Connection</h2> | ||
| </div> | ||
| <div class="connection-form"> | ||
| <label> | ||
| RainMaker base URL | ||
| <input id="baseUrl" type="url" value="https://api.rainmaker.espressif.com" /> | ||
| </label> | ||
| <label> | ||
| Access token | ||
| <input id="authToken" type="password" placeholder="Paste your ESP RainMaker token" /> | ||
| </label> | ||
| <button id="connectBtn" class="button primary" type="button">Connect backend</button> | ||
| </div> | ||
| <p id="connectionMessage" class="helper-text"> | ||
| Add your ESP RainMaker URL and token to send commands to devices. | ||
| </p> | ||
| </article> | ||
|
|
||
| <article class="card insights-card"> | ||
| <div class="section-heading"> | ||
| <p class="eyebrow">Highlights</p> | ||
| <h2>App Capabilities</h2> | ||
| </div> | ||
| <div class="stats-grid"> | ||
| <div> | ||
| <strong>02</strong> | ||
| <span>Switch light controls</span> | ||
| </div> | ||
| <div> | ||
| <strong>01</strong> | ||
| <span>Fan controller with speed slider</span> | ||
| </div> | ||
| <div> | ||
| <strong>24/7</strong> | ||
| <span>Cloud-ready monitoring experience</span> | ||
| </div> | ||
| <div> | ||
| <strong>100%</strong> | ||
| <span>Branding customized for Smart Vision IT Solution</span> | ||
| </div> | ||
| </div> | ||
| </article> | ||
| </section> | ||
|
|
||
| <section id="controls" class="controls-section"> | ||
| <div class="section-heading"> | ||
| <p class="eyebrow">Controls</p> | ||
| <h2>Rooms & Devices</h2> | ||
| <p>Tap the toggles below to switch lights and control the smart fan.</p> | ||
| </div> | ||
|
|
||
| <div class="device-grid"> | ||
| <article class="card device-card"> | ||
| <div class="device-meta"> | ||
| <span class="device-icon">💡</span> | ||
| <div> | ||
| <h3>Living Room Light</h3> | ||
| <p>Device ID: <code>light-living-room</code></p> | ||
| </div> | ||
| </div> | ||
| <label class="switch-row"> | ||
| <span>Power</span> | ||
| <input class="device-toggle" data-device-id="light-living-room" type="checkbox" /> | ||
| </label> | ||
| </article> | ||
|
|
||
| <article class="card device-card"> | ||
| <div class="device-meta"> | ||
| <span class="device-icon">💡</span> | ||
| <div> | ||
| <h3>Bedroom Light</h3> | ||
| <p>Device ID: <code>light-bedroom</code></p> | ||
| </div> | ||
| </div> | ||
| <label class="switch-row"> | ||
| <span>Power</span> | ||
| <input class="device-toggle" data-device-id="light-bedroom" type="checkbox" /> | ||
| </label> | ||
| </article> | ||
|
|
||
| <article class="card device-card fan-card"> | ||
| <div class="device-meta"> | ||
| <span class="device-icon">🌀</span> | ||
| <div> | ||
| <h3>Main Hall Fan</h3> | ||
| <p>Device ID: <code>fan-main-hall</code></p> | ||
| </div> | ||
| </div> | ||
| <label class="switch-row"> | ||
| <span>Power</span> | ||
| <input class="device-toggle" data-device-id="fan-main-hall" type="checkbox" /> | ||
| </label> | ||
| <label class="range-row" for="fanSpeed"> | ||
| <span>Fan speed</span> | ||
| <div> | ||
| <input id="fanSpeed" type="range" min="1" max="5" value="3" /> | ||
| <strong id="fanSpeedValue">3</strong> | ||
| </div> | ||
| </label> | ||
| </article> | ||
| </div> | ||
| </section> | ||
|
|
||
| <section class="card activity-card"> | ||
| <div class="section-heading"> | ||
| <p class="eyebrow">Activity</p> | ||
| <h2>Recent Commands</h2> | ||
| </div> | ||
| <ul id="activityLog" class="activity-log"> | ||
| <li>Dashboard initialized. Waiting for ESP RainMaker connection.</li> | ||
| </ul> | ||
| </section> | ||
|
|
||
| <section id="contact" class="card contact-card"> | ||
| <div class="section-heading"> | ||
| <p class="eyebrow">Contact</p> | ||
| <h2>Smart Vision IT Solution</h2> | ||
| </div> | ||
| <div class="contact-grid"> | ||
| <a href="tel:9818429568">📞 9818429568</a> | ||
| <a href="mailto:svitsolution@outlook.com">✉️ svitsolution@outlook.com</a> | ||
| </div> | ||
| </section> | ||
| </main> | ||
| </div> | ||
|
|
||
| <script src="script.js"></script> | ||
| </body> | ||
| </html> |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,133 @@ | ||||||||||||||||||||||||||||||
| const state = { | ||||||||||||||||||||||||||||||
| connected: false, | ||||||||||||||||||||||||||||||
| baseUrl: 'https://api.rainmaker.espressif.com', | ||||||||||||||||||||||||||||||
| authToken: '', | ||||||||||||||||||||||||||||||
| devices: { | ||||||||||||||||||||||||||||||
| 'light-living-room': { type: 'light', power: false }, | ||||||||||||||||||||||||||||||
| 'light-bedroom': { type: 'light', power: false }, | ||||||||||||||||||||||||||||||
| 'fan-main-hall': { type: 'fan', power: false, speed: 3 }, | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const activityLog = document.getElementById('activityLog'); | ||||||||||||||||||||||||||||||
| const connectionMessage = document.getElementById('connectionMessage'); | ||||||||||||||||||||||||||||||
| const baseUrlInput = document.getElementById('baseUrl'); | ||||||||||||||||||||||||||||||
| const authTokenInput = document.getElementById('authToken'); | ||||||||||||||||||||||||||||||
| const connectBtn = document.getElementById('connectBtn'); | ||||||||||||||||||||||||||||||
| const fanSpeedInput = document.getElementById('fanSpeed'); | ||||||||||||||||||||||||||||||
| const fanSpeedValue = document.getElementById('fanSpeedValue'); | ||||||||||||||||||||||||||||||
| const toggles = document.querySelectorAll('.device-toggle'); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| class RainMakerClient { | ||||||||||||||||||||||||||||||
| constructor() { | ||||||||||||||||||||||||||||||
| this.baseUrl = state.baseUrl; | ||||||||||||||||||||||||||||||
| this.authToken = state.authToken; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| configure({ baseUrl, authToken }) { | ||||||||||||||||||||||||||||||
| this.baseUrl = baseUrl; | ||||||||||||||||||||||||||||||
| this.authToken = authToken; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| async connect() { | ||||||||||||||||||||||||||||||
| if (!this.authToken.trim()) { | ||||||||||||||||||||||||||||||
| throw new Error('An ESP RainMaker access token is required.'); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| await this.simulateNetwork(); | ||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||
| ok: true, | ||||||||||||||||||||||||||||||
| message: `Connected to ${this.baseUrl}`, | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| async updateDevice(deviceId, payload) { | ||||||||||||||||||||||||||||||
| await this.simulateNetwork(); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||
| ok: true, | ||||||||||||||||||||||||||||||
| endpoint: `${this.baseUrl}/v1/user/nodes/params`, | ||||||||||||||||||||||||||||||
| deviceId, | ||||||||||||||||||||||||||||||
| payload, | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| simulateNetwork() { | ||||||||||||||||||||||||||||||
| return new Promise((resolve) => { | ||||||||||||||||||||||||||||||
| window.setTimeout(resolve, 320); | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const rainMakerClient = new RainMakerClient(); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const appendLog = (message) => { | ||||||||||||||||||||||||||||||
| const entry = document.createElement('li'); | ||||||||||||||||||||||||||||||
| entry.textContent = `${new Date().toLocaleTimeString([], { | ||||||||||||||||||||||||||||||
| hour: '2-digit', | ||||||||||||||||||||||||||||||
| minute: '2-digit', | ||||||||||||||||||||||||||||||
| })} — ${message}`; | ||||||||||||||||||||||||||||||
| activityLog.prepend(entry); | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const syncFormState = () => { | ||||||||||||||||||||||||||||||
| state.baseUrl = baseUrlInput.value.trim() || 'https://api.rainmaker.espressif.com'; | ||||||||||||||||||||||||||||||
| state.authToken = authTokenInput.value.trim(); | ||||||||||||||||||||||||||||||
| rainMakerClient.configure({ | ||||||||||||||||||||||||||||||
| baseUrl: state.baseUrl, | ||||||||||||||||||||||||||||||
| authToken: state.authToken, | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| connectBtn.addEventListener('click', async () => { | ||||||||||||||||||||||||||||||
| syncFormState(); | ||||||||||||||||||||||||||||||
| connectBtn.disabled = true; | ||||||||||||||||||||||||||||||
| connectBtn.textContent = 'Connecting...'; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||
| const result = await rainMakerClient.connect(); | ||||||||||||||||||||||||||||||
| state.connected = true; | ||||||||||||||||||||||||||||||
| connectionMessage.textContent = `${result.message}. Ready to control lights and fan.`; | ||||||||||||||||||||||||||||||
| appendLog('ESP RainMaker backend connected successfully.'); | ||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||
| state.connected = false; | ||||||||||||||||||||||||||||||
| connectionMessage.textContent = error.message; | ||||||||||||||||||||||||||||||
| appendLog(`Connection failed: ${error.message}`); | ||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||
| connectBtn.disabled = false; | ||||||||||||||||||||||||||||||
| connectBtn.textContent = 'Connect backend'; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const sendDeviceUpdate = async (deviceId, payload) => { | ||||||||||||||||||||||||||||||
| state.devices[deviceId] = { | ||||||||||||||||||||||||||||||
| ...state.devices[deviceId], | ||||||||||||||||||||||||||||||
| ...payload, | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (!state.connected) { | ||||||||||||||||||||||||||||||
| appendLog( | ||||||||||||||||||||||||||||||
| `Updated ${deviceId} locally. Connect to ESP RainMaker to send the command to the cloud.`, | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const result = await rainMakerClient.updateDevice(deviceId, payload); | ||||||||||||||||||||||||||||||
| appendLog(`Synced ${result.deviceId} to ${result.endpoint} with ${JSON.stringify(payload)}.`); | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| toggles.forEach((toggle) => { | ||||||||||||||||||||||||||||||
| toggle.addEventListener('change', async (event) => { | ||||||||||||||||||||||||||||||
| const { deviceId } = event.target.dataset; | ||||||||||||||||||||||||||||||
| const power = event.target.checked; | ||||||||||||||||||||||||||||||
| await sendDeviceUpdate(deviceId, { power }); | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| fanSpeedInput.addEventListener('input', async (event) => { | ||||||||||||||||||||||||||||||
| const speed = Number(event.target.value); | ||||||||||||||||||||||||||||||
| fanSpeedValue.textContent = String(speed); | ||||||||||||||||||||||||||||||
| await sendDeviceUpdate('fan-main-hall', { speed }); | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
Comment on lines
+127
to
+131
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid sending a cloud write on every slider tick. Using Suggested traffic-reduction patch-fanSpeedInput.addEventListener('input', async (event) => {
+fanSpeedInput.addEventListener('input', (event) => {
const speed = Number(event.target.value);
fanSpeedValue.textContent = String(speed);
+});
+
+fanSpeedInput.addEventListener('change', async (event) => {
+ const speed = Number(event.target.value);
await sendDeviceUpdate('fan-main-hall', { speed });
});📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| appendLog('Smart Vision IT Solution dashboard is ready.'); | ||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle cloud update failures to prevent unhandled rejections and stale local state.
sendDeviceUpdateassumes success. Once wired to real RainMaker APIs, failures here will surface as unhandled async errors and keep optimistic state without rollback.Suggested reliability patch
const sendDeviceUpdate = async (deviceId, payload) => { + const previous = { ...state.devices[deviceId] }; state.devices[deviceId] = { ...state.devices[deviceId], ...payload, }; @@ - const result = await rainMakerClient.updateDevice(deviceId, payload); - appendLog(`Synced ${result.deviceId} to ${result.endpoint} with ${JSON.stringify(payload)}.`); + try { + const result = await rainMakerClient.updateDevice(deviceId, payload); + appendLog(`Synced ${result.deviceId} to ${result.endpoint} with ${JSON.stringify(payload)}.`); + return true; + } catch (error) { + state.devices[deviceId] = previous; + appendLog(`Sync failed for ${deviceId}: ${error?.message ?? 'Unknown error'}`); + return false; + } };📝 Committable suggestion
🤖 Prompt for AI Agents