Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions README.md
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.
182 changes: 182 additions & 0 deletions index.html
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>
133 changes: 133 additions & 0 deletions script.js
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)}.`);
};
Comment on lines +102 to +117

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle cloud update failures to prevent unhandled rejections and stale local state.

sendDeviceUpdate assumes 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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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)}.`);
};
const sendDeviceUpdate = async (deviceId, payload) => {
const previous = { ...state.devices[deviceId] };
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;
}
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;
}
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@script.js` around lines 102 - 117, sendDeviceUpdate currently performs an
optimistic local update then awaits rainMakerClient.updateDevice without
handling failures; capture the previous device state (const prev =
state.devices[deviceId]) before mutating, wrap the call to
rainMakerClient.updateDevice in a try/catch, and on error revert
state.devices[deviceId] back to prev, call appendLog with the error details and
a clear failure message, and return or propagate a failure indicator (e.g.,
return false or throw) to avoid unhandled rejections; keep the existing
connected-check and success path (use result.deviceId/result.endpoint) but
ensure any thrown error from updateDevice is caught and handled.


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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid sending a cloud write on every slider tick.

Using input for network sync can generate a burst of requests while dragging and overload backend endpoints.

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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fanSpeedInput.addEventListener('input', async (event) => {
const speed = Number(event.target.value);
fanSpeedValue.textContent = String(speed);
await sendDeviceUpdate('fan-main-hall', { speed });
});
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 });
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@script.js` around lines 127 - 131, The handler on fanSpeedInput currently
calls sendDeviceUpdate on every 'input' tick causing many network writes; update
it to only perform the DOM update immediately (fanSpeedValue.textContent) but
debounce or throttle the network call (wrap sendDeviceUpdate('fan-main-hall', {
speed }) with a debounce/throttle helper) or alternatively switch the network
write to the 'change' event so the backend is contacted only once after the user
releases the slider; target the listener attached to fanSpeedInput and the
sendDeviceUpdate invocation to implement this change.


appendLog('Smart Vision IT Solution dashboard is ready.');
Loading