Add contol node for the LEDs

This commit is contained in:
2026-05-01 11:09:39 +00:00
parent 261e18af83
commit a1f91e834b
7 changed files with 263 additions and 4 deletions
+2
View File
@@ -1,5 +1,6 @@
import { useState } from 'react'
import { CameraControls } from './components/CameraControls.jsx'
import { LedControls } from './components/LedControls.jsx'
import { RobotControls } from './components/RobotControls.jsx'
import { VideoStream } from './components/VideoStream.jsx'
import { useWebSocket } from './hooks/useWebSocket.js'
@@ -34,6 +35,7 @@ export default function App() {
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 16, flex: 1 }}>
<RobotControls send={send} />
<CameraControls send={send} jointStates={jointStates} />
<LedControls send={send} />
</div>
</div>
)
@@ -0,0 +1,93 @@
import { useState } from 'react'
const COLOURS = [
{ label: 'Red', r: 1, g: 0, b: 0 },
{ label: 'Green', r: 0, g: 1, b: 0 },
{ label: 'Blue', r: 0, g: 0, b: 1 },
{ label: 'Yellow', r: 1, g: 1, b: 0 },
{ label: 'Purple', r: 1, g: 0, b: 1 },
{ label: 'Cyan', r: 0, g: 1, b: 1 },
{ label: 'White', r: 1, g: 1, b: 1 },
]
const EFFECTS = ['river', 'breathing', 'gradient', 'random_running', 'starlight']
const swatch = (r, g, b) =>
`rgb(${Math.round(r * 255)},${Math.round(g * 255)},${Math.round(b * 255)})`
export function LedControls({ send }) {
const [active, setActive] = useState(null) // null=off, 'colour:N', 'effect:name'
function sendColour(idx) {
const c = COLOURS[idx]
send({ type: 'led_color', r: c.r, g: c.g, b: c.b, a: 1.0 })
setActive(`colour:${idx}`)
}
function sendEffect(effect) {
send({ type: 'led_effect', effect })
setActive(`effect:${effect}`)
}
function sendOff() {
send({ type: 'led_color', r: 0, g: 0, b: 0, a: 0.0 })
setActive(null)
}
const sectionLabel = {
fontSize: 11, color: '#666', marginBottom: 4, textTransform: 'uppercase', letterSpacing: 1,
}
const btn = (isActive) => ({
background: isActive ? '#4ade80' : '#2a2a2a',
color: isActive ? '#000' : '#aaa',
border: '1px solid ' + (isActive ? '#4ade80' : '#444'),
padding: '3px 7px', borderRadius: 4, cursor: 'pointer', fontSize: 11,
})
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 10, minWidth: 180 }}>
<div style={{ fontSize: 12, color: '#555', borderBottom: '1px solid #2a2a2a', paddingBottom: 4 }}>
LEDs
</div>
{/* Solid colours */}
<div>
<div style={sectionLabel}>Colour</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 5 }}>
{COLOURS.map((c, i) => (
<button
key={c.label}
title={c.label}
onClick={() => sendColour(i)}
style={{
width: 22, height: 22, borderRadius: 4, cursor: 'pointer', border: 'none',
background: swatch(c.r, c.g, c.b),
outline: active === `colour:${i}` ? '2px solid #4ade80' : '2px solid transparent',
outlineOffset: 2,
}}
/>
))}
<button onClick={sendOff} title="Off" style={{
width: 22, height: 22, borderRadius: 4, cursor: 'pointer',
background: '#111', border: '1px solid #444',
outline: active === null ? '2px solid #4ade80' : '2px solid transparent',
outlineOffset: 2, fontSize: 10, color: '#666',
}}></button>
</div>
</div>
{/* Effects */}
<div>
<div style={sectionLabel}>Effect</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
{EFFECTS.map((e) => (
<button key={e} onClick={() => sendEffect(e)} style={btn(active === `effect:${e}`)}>
{e.replace('_', ' ')}
</button>
))}
</div>
</div>
</div>
)
}