update todo
This commit is contained in:
@@ -1 +1 @@
|
|||||||
Implement mechanical stall homing
|
I want you to implement proper motor homing. I think the original used stall homing, please look it up and reimplement it properly. Make sure map on the website starts in proper place. Please update both web (kinematics.js and stepper-test.js) and mcu (main.cpp) code and make sure we are updated on this topic in gemini.md. I have had some desync issues with agent before so there may be some remnants.
|
||||||
+163
-168
@@ -1,10 +1,12 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>WijiBoard – Control Dashboard</title>
|
<title>WijiBoard – Control Dashboard</title>
|
||||||
<meta name="description" content="WijiBoard SCARA arm control dashboard. Web Bluetooth interface for the ESP32-S3 stepper controller." />
|
<meta name="description"
|
||||||
|
content="WijiBoard SCARA arm control dashboard. Web Bluetooth interface for the ESP32-S3 stepper controller." />
|
||||||
|
|
||||||
<!-- Shared styles -->
|
<!-- Shared styles -->
|
||||||
<link rel="stylesheet" href="web/styles/base.css" />
|
<link rel="stylesheet" href="web/styles/base.css" />
|
||||||
@@ -12,193 +14,186 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="app-shell">
|
<div class="app-shell">
|
||||||
|
|
||||||
<!-- ── Sidebar overlay (click to close) ──────────────────────── -->
|
<!-- ── Sidebar overlay (click to close) ──────────────────────── -->
|
||||||
<div class="sidebar-overlay" id="sidebar-overlay"></div>
|
<div class="sidebar-overlay" id="sidebar-overlay"></div>
|
||||||
|
|
||||||
<!-- ── Sidebar ───────────────────────────────────────────────── -->
|
<!-- ── Sidebar ───────────────────────────────────────────────── -->
|
||||||
<nav class="sidebar" id="sidebar" aria-label="Main navigation">
|
<nav class="sidebar" id="sidebar" aria-label="Main navigation">
|
||||||
|
|
||||||
<span class="sidebar-section-label">Navigation</span>
|
<span class="sidebar-section-label">Navigation</span>
|
||||||
|
|
||||||
<a class="sidebar-nav-item active" id="nav-home"
|
<a class="sidebar-nav-item active" id="nav-home" href="#home" data-route="home" role="button"
|
||||||
href="#home" data-route="home"
|
aria-label="Go to Home">
|
||||||
role="button" aria-label="Go to Home">
|
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none"
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
stroke="currentColor" stroke-width="2"
|
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
|
||||||
stroke-linecap="round" stroke-linejoin="round">
|
<polyline points="9 22 9 12 15 12 15 22" />
|
||||||
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
</svg>
|
||||||
<polyline points="9 22 9 12 15 12 15 22"/>
|
Home
|
||||||
</svg>
|
</a>
|
||||||
Home
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a class="sidebar-nav-item" id="nav-stepper-test"
|
<a class="sidebar-nav-item" id="nav-stepper-test" href="#stepper-test" data-route="stepper-test" role="button"
|
||||||
href="#stepper-test" data-route="stepper-test"
|
aria-label="Go to Stepper Test">
|
||||||
role="button" aria-label="Go to Stepper Test">
|
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none"
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
stroke="currentColor" stroke-width="2"
|
<circle cx="12" cy="12" r="3" />
|
||||||
stroke-linecap="round" stroke-linejoin="round">
|
<path
|
||||||
<circle cx="12" cy="12" r="3"/>
|
d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83" />
|
||||||
<path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83"/>
|
</svg>
|
||||||
</svg>
|
Stepper Test
|
||||||
Stepper Test
|
</a>
|
||||||
</a>
|
|
||||||
|
|
||||||
<!-- Spacer + coming soon -->
|
<!-- Spacer + coming soon -->
|
||||||
<div style="flex:1"></div>
|
<div style="flex:1"></div>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<span class="sidebar-section-label">Coming Soon</span>
|
<span class="sidebar-section-label">Coming Soon</span>
|
||||||
|
|
||||||
<div class="sidebar-nav-item" style="opacity:0.35;cursor:default" aria-disabled="true">
|
<div class="sidebar-nav-item" style="opacity:0.35;cursor:default" aria-disabled="true">
|
||||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none"
|
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||||
stroke="currentColor" stroke-width="2"
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
stroke-linecap="round" stroke-linejoin="round">
|
<path d="M12 20h9M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" />
|
||||||
<path d="M12 20h9M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>
|
</svg>
|
||||||
</svg>
|
Board Control
|
||||||
Board Control
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="sidebar-nav-item" style="opacity:0.35;cursor:default" aria-disabled="true">
|
|
||||||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none"
|
|
||||||
stroke="currentColor" stroke-width="2"
|
|
||||||
stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M3 3h7v7H3zM14 3h7v7h-7zM14 14h7v7h-7zM3 14h7v7H3z"/>
|
|
||||||
</svg>
|
|
||||||
Sequence Editor
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- ── Main area ──────────────────────────────────────────────── -->
|
|
||||||
<div style="display:flex;flex-direction:column;flex:1;overflow:hidden">
|
|
||||||
|
|
||||||
<!-- Top bar -->
|
|
||||||
<header class="topbar" role="banner">
|
|
||||||
<!-- Hamburger -->
|
|
||||||
<button class="hamburger" id="hamburger-btn"
|
|
||||||
aria-label="Toggle sidebar" aria-expanded="true" aria-controls="sidebar">
|
|
||||||
<div class="hamburger-bar"></div>
|
|
||||||
<div class="hamburger-bar"></div>
|
|
||||||
<div class="hamburger-bar"></div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Brand -->
|
|
||||||
<div class="topbar-brand">
|
|
||||||
<div class="brand-icon" aria-hidden="true">
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
|
||||||
<path d="M12 2L3 7v10l9 5 9-5V7L12 2Z"
|
|
||||||
stroke="#448aff" stroke-width="1.5" stroke-linejoin="round"/>
|
|
||||||
<circle cx="12" cy="12" r="2.5" fill="#448aff" opacity="0.7"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
WijiBoard
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- BLE actions -->
|
<div class="sidebar-nav-item" style="opacity:0.35;cursor:default" aria-disabled="true">
|
||||||
<div class="topbar-actions">
|
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||||
<!-- BLE status pill -->
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
<div class="ble-status" id="ble-status" aria-live="polite">
|
<path d="M3 3h7v7H3zM14 3h7v7h-7zM14 14h7v7h-7zM3 14h7v7H3z" />
|
||||||
<div class="ble-dot" id="ble-dot"></div>
|
</svg>
|
||||||
<span class="ble-label" id="ble-label">Disconnected</span>
|
Sequence Editor
|
||||||
</div>
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<!-- Connect / Disconnect button -->
|
<!-- ── Main area ──────────────────────────────────────────────── -->
|
||||||
<button class="btn btn-sm btn-primary" id="btn-ble-connect"
|
<div style="display:flex;flex-direction:column;flex:1;overflow:hidden">
|
||||||
aria-label="Connect to WijiBoard via Bluetooth">
|
|
||||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none"
|
<!-- Top bar -->
|
||||||
stroke="currentColor" stroke-width="2.5"
|
<header class="topbar" role="banner">
|
||||||
stroke-linecap="round" stroke-linejoin="round">
|
<!-- Hamburger -->
|
||||||
<path d="M6.5 6.5l11 11M17.5 6.5l-5 5 5 5"/>
|
<button class="hamburger" id="hamburger-btn" aria-label="Toggle sidebar" aria-expanded="true"
|
||||||
<path d="M6 17.5l5-5-5-5"/>
|
aria-controls="sidebar">
|
||||||
</svg>
|
<div class="hamburger-bar"></div>
|
||||||
<span class="btn-text">Connect</span>
|
<div class="hamburger-bar"></div>
|
||||||
|
<div class="hamburger-bar"></div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<!-- Router outlet -->
|
<!-- Brand -->
|
||||||
<main class="main-content" id="view" role="main" aria-label="Page content">
|
<div class="topbar-brand">
|
||||||
<!-- Sections are injected here by router.js -->
|
<div class="brand-icon" aria-hidden="true">
|
||||||
</main>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path d="M12 2L3 7v10l9 5 9-5V7L12 2Z" stroke="#448aff" stroke-width="1.5" stroke-linejoin="round" />
|
||||||
|
<circle cx="12" cy="12" r="2.5" fill="#448aff" opacity="0.7" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
WijiBoard
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- BLE actions -->
|
||||||
|
<div class="topbar-actions">
|
||||||
|
<!-- BLE status pill -->
|
||||||
|
<div class="ble-status" id="ble-status" aria-live="polite">
|
||||||
|
<div class="ble-dot" id="ble-dot"></div>
|
||||||
|
<span class="ble-label" id="ble-label">Disconnected</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Connect / Disconnect button -->
|
||||||
|
<button class="btn btn-sm btn-primary" id="btn-ble-connect" aria-label="Connect to WijiBoard via Bluetooth">
|
||||||
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M6.5 6.5l11 11M17.5 6.5l-5 5 5 5" />
|
||||||
|
<path d="M6 17.5l5-5-5-5" />
|
||||||
|
</svg>
|
||||||
|
<span class="btn-text">Connect</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Router outlet -->
|
||||||
|
<main class="main-content" id="view" role="main" aria-label="Page content">
|
||||||
|
<!-- Sections are injected here by router.js -->
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
<!-- ── App bootstrap (ES module entry point) ─────────────────── -->
|
||||||
|
<script type="module">
|
||||||
|
const V = Date.now(); // cache-buster – ensures fresh module load on every page refresh
|
||||||
|
const { default: Router } = await import(`./web/js/router.js?v=${V}`);
|
||||||
|
const { default: BLE } = await import(`./web/js/ble.js?v=${V}`);
|
||||||
|
const { default: UI } = await import(`./web/js/ui.js?v=${V}`);
|
||||||
|
const { default: HomeSection } = await import(`./web/js/sections/home.js?v=${V}`);
|
||||||
|
const { default: StepperSection } = await import(`./web/js/sections/stepper-test.js?v=${V}`);
|
||||||
|
|
||||||
<!-- ── App bootstrap (ES module entry point) ─────────────────── -->
|
// ── Register routes ────────────────────────────────────────────
|
||||||
<script type="module">
|
Router.register('home', HomeSection);
|
||||||
const V = Date.now(); // cache-buster – ensures fresh module load on every page refresh
|
Router.register('stepper-test', StepperSection);
|
||||||
const { default: Router } = await import(`./web/js/router.js?v=${V}`);
|
|
||||||
const { default: BLE } = await import(`./web/js/ble.js?v=${V}`);
|
|
||||||
const { default: UI } = await import(`./web/js/ui.js?v=${V}`);
|
|
||||||
const { default: HomeSection } = await import(`./web/js/sections/home.js?v=${V}`);
|
|
||||||
const { default: StepperSection } = await import(`./web/js/sections/stepper-test.js?v=${V}`);
|
|
||||||
|
|
||||||
// ── Register routes ────────────────────────────────────────────
|
// ── Sidebar toggle logic ───────────────────────────────────────
|
||||||
Router.register('home', HomeSection);
|
const sidebar = document.getElementById('sidebar');
|
||||||
Router.register('stepper-test', StepperSection);
|
const overlay = document.getElementById('sidebar-overlay');
|
||||||
|
const hamburger = document.getElementById('hamburger-btn');
|
||||||
|
const mainContent = document.getElementById('view');
|
||||||
|
|
||||||
// ── Sidebar toggle logic ───────────────────────────────────────
|
let sidebarOpen = true;
|
||||||
const sidebar = document.getElementById('sidebar');
|
|
||||||
const overlay = document.getElementById('sidebar-overlay');
|
|
||||||
const hamburger = document.getElementById('hamburger-btn');
|
|
||||||
const mainContent = document.getElementById('view');
|
|
||||||
|
|
||||||
let sidebarOpen = true;
|
function setSidebarOpen(open) {
|
||||||
|
sidebarOpen = open;
|
||||||
|
sidebar.classList.toggle('collapsed', !open);
|
||||||
|
overlay.classList.toggle('visible', open && window.innerWidth < 900);
|
||||||
|
hamburger.classList.toggle('open', open);
|
||||||
|
hamburger.setAttribute('aria-expanded', open);
|
||||||
|
|
||||||
function setSidebarOpen(open) {
|
// Shift main content on desktop
|
||||||
sidebarOpen = open;
|
const mc = hamburger.closest('.app-shell')?.querySelector('.main-content');
|
||||||
sidebar.classList.toggle('collapsed', !open);
|
if (mc) mc.classList.toggle('sidebar-collapsed', !open);
|
||||||
overlay.classList.toggle('visible', open && window.innerWidth < 900);
|
|
||||||
hamburger.classList.toggle('open', open);
|
|
||||||
hamburger.setAttribute('aria-expanded', open);
|
|
||||||
|
|
||||||
// Shift main content on desktop
|
|
||||||
const mc = hamburger.closest('.app-shell')?.querySelector('.main-content');
|
|
||||||
if (mc) mc.classList.toggle('sidebar-collapsed', !open);
|
|
||||||
}
|
|
||||||
|
|
||||||
hamburger.addEventListener('click', () => setSidebarOpen(!sidebarOpen));
|
|
||||||
overlay.addEventListener('click', () => setSidebarOpen(false));
|
|
||||||
|
|
||||||
// Close sidebar on small screens when nav link clicked
|
|
||||||
document.querySelectorAll('.sidebar-nav-item[data-route]').forEach(link => {
|
|
||||||
link.addEventListener('click', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const route = link.dataset.route;
|
|
||||||
Router.navigate('#' + route);
|
|
||||||
if (window.innerWidth < 900) setSidebarOpen(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Collapse sidebar by default on small screens
|
|
||||||
if (window.innerWidth < 900) setSidebarOpen(false);
|
|
||||||
|
|
||||||
// ── Wire BLE events to shared UI ──────────────────────────────
|
|
||||||
UI.initBLEListeners();
|
|
||||||
|
|
||||||
// Connect button handler (initial state)
|
|
||||||
document.getElementById('btn-ble-connect').addEventListener('click', () => {
|
|
||||||
if (BLE.isConnected()) {
|
|
||||||
BLE.disconnect();
|
|
||||||
} else {
|
|
||||||
BLE.connect().catch(e => {
|
|
||||||
if (!e.message?.includes('cancelled') && !e.name?.includes('NotFound')) {
|
|
||||||
UI.log(`Connect error: ${e.message}`, 'error');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// ── Init router (last step) ────────────────────────────────────
|
hamburger.addEventListener('click', () => setSidebarOpen(!sidebarOpen));
|
||||||
Router.init('#view', '#home');
|
overlay.addEventListener('click', () => setSidebarOpen(false));
|
||||||
|
|
||||||
// ── Check Web Bluetooth availability ──────────────────────────
|
// Close sidebar on small screens when nav link clicked
|
||||||
if (!navigator.bluetooth) {
|
document.querySelectorAll('.sidebar-nav-item[data-route]').forEach(link => {
|
||||||
setTimeout(() => {
|
link.addEventListener('click', (e) => {
|
||||||
UI.log('⚠ Web Bluetooth not available. Use Chrome or Edge on localhost/HTTPS.', 'error');
|
e.preventDefault();
|
||||||
}, 600);
|
const route = link.dataset.route;
|
||||||
}
|
Router.navigate('#' + route);
|
||||||
</script>
|
if (window.innerWidth < 900) setSidebarOpen(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Collapse sidebar by default on small screens
|
||||||
|
if (window.innerWidth < 900) setSidebarOpen(false);
|
||||||
|
|
||||||
|
// ── Wire BLE events to shared UI ──────────────────────────────
|
||||||
|
UI.initBLEListeners();
|
||||||
|
|
||||||
|
// Connect button handler (initial state)
|
||||||
|
document.getElementById('btn-ble-connect').addEventListener('click', () => {
|
||||||
|
if (BLE.isConnected()) {
|
||||||
|
BLE.disconnect();
|
||||||
|
} else {
|
||||||
|
BLE.connect().catch(e => {
|
||||||
|
if (!e.message?.includes('cancelled') && !e.name?.includes('NotFound')) {
|
||||||
|
UI.log(`Connect error: ${e.message}`, 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Init router (last step) ────────────────────────────────────
|
||||||
|
Router.init('#view', '#home');
|
||||||
|
|
||||||
|
// ── Check Web Bluetooth availability ──────────────────────────
|
||||||
|
if (!navigator.bluetooth) {
|
||||||
|
setTimeout(() => {
|
||||||
|
UI.log('⚠ Web Bluetooth not available. Use Chrome or Edge on localhost/HTTPS.', 'error');
|
||||||
|
}, 600);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
|
||||||
|
</html>
|
||||||
+8
-8
@@ -37,15 +37,15 @@ const UI = (() => {
|
|||||||
// ── BLE status indicator (top bar) ──────────────────────────
|
// ── BLE status indicator (top bar) ──────────────────────────
|
||||||
function setConnectionStatus(connected, deviceName = null) {
|
function setConnectionStatus(connected, deviceName = null) {
|
||||||
const statusEl = document.getElementById('ble-status');
|
const statusEl = document.getElementById('ble-status');
|
||||||
const labelEl = document.getElementById('ble-label');
|
const labelEl = document.getElementById('ble-label');
|
||||||
const btn = document.getElementById('btn-ble-connect');
|
const btn = document.getElementById('btn-ble-connect');
|
||||||
|
|
||||||
if (statusEl) statusEl.classList.toggle('connected', connected);
|
if (statusEl) statusEl.classList.toggle('connected', connected);
|
||||||
if (labelEl) labelEl.textContent = connected
|
if (labelEl) labelEl.textContent = connected
|
||||||
? (deviceName ?? 'Connected')
|
? (deviceName ?? 'Connected')
|
||||||
: 'Disconnected';
|
: 'Disconnected';
|
||||||
if (btn) {
|
if (btn) {
|
||||||
btn.disabled = connected;
|
btn.disabled = connected;
|
||||||
btn.innerHTML = connected
|
btn.innerHTML = connected
|
||||||
? '<span class="btn-text">Disconnect</span>'
|
? '<span class="btn-text">Disconnect</span>'
|
||||||
: '<span class="btn-text">Connect</span>';
|
: '<span class="btn-text">Connect</span>';
|
||||||
@@ -57,10 +57,10 @@ const UI = (() => {
|
|||||||
btn.onclick = connected
|
btn.onclick = connected
|
||||||
? () => BLE.disconnect()
|
? () => BLE.disconnect()
|
||||||
: () => BLE.connect().catch(e => {
|
: () => BLE.connect().catch(e => {
|
||||||
if (!e.message?.includes('cancelled')) {
|
if (!e.message?.includes('cancelled')) {
|
||||||
log(`Connect failed: ${e.message}`, 'error');
|
log(`Connect failed: ${e.message}`, 'error');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user