React + Vite + TailwindCSS dashboard served on port 8080. Connects to ROS2 via rosbridge_server WebSocket (default ws://localhost:9090). Panels: - StatusPanel: pipeline state (idle/listening/thinking/speaking/throttled) with animated pulse indicator, GPU memory bar, per-stage latency stats - FaceGallery: enrolled persons grid with enroll/delete via /social/enrollment/* services; live detection indicator - ConversationLog: real-time transcript with human/bot bubbles, streaming partial support, auto-scroll - PersonalityTuner: sass/humor/verbosity sliders (0–10) writing to personality_node via rcl_interfaces/srv/SetParameters; live PersonalityState display - NavModeSelector: shadow/lead/side/orbit/loose/tight mode buttons publishing to /social/nav/mode; voice command reference table Usage: cd ui/social-bot && npm install && npm run dev # dev server port 8080 npm run build && npm run preview # production preview Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
117 lines
3.3 KiB
JavaScript
117 lines
3.3 KiB
JavaScript
/**
|
|
* useRosbridge.js — React hook for ROS2 rosbridge_server WebSocket connection.
|
|
*
|
|
* rosbridge_server default: ws://<robot-ip>:9090
|
|
* Provides subscribe/publish/callService helpers bound to the active connection.
|
|
*/
|
|
|
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
|
import ROSLIB from 'roslib';
|
|
|
|
export function useRosbridge(url) {
|
|
const [connected, setConnected] = useState(false);
|
|
const [error, setError] = useState(null);
|
|
const rosRef = useRef(null);
|
|
const subscribersRef = useRef(new Map()); // topic -> ROSLIB.Topic
|
|
|
|
useEffect(() => {
|
|
if (!url) return;
|
|
|
|
const ros = new ROSLIB.Ros({ url });
|
|
rosRef.current = ros;
|
|
|
|
ros.on('connection', () => {
|
|
setConnected(true);
|
|
setError(null);
|
|
});
|
|
|
|
ros.on('error', (err) => {
|
|
setError(err?.toString() || 'Connection error');
|
|
});
|
|
|
|
ros.on('close', () => {
|
|
setConnected(false);
|
|
});
|
|
|
|
return () => {
|
|
subscribersRef.current.forEach((topic) => topic.unsubscribe());
|
|
subscribersRef.current.clear();
|
|
ros.close();
|
|
rosRef.current = null;
|
|
};
|
|
}, [url]);
|
|
|
|
/** Subscribe to a ROS2 topic. Returns an unsubscribe function. */
|
|
const subscribe = useCallback((name, messageType, callback) => {
|
|
if (!rosRef.current) return () => {};
|
|
|
|
const key = `${name}::${messageType}`;
|
|
if (subscribersRef.current.has(key)) {
|
|
subscribersRef.current.get(key).unsubscribe();
|
|
}
|
|
|
|
const topic = new ROSLIB.Topic({
|
|
ros: rosRef.current,
|
|
name,
|
|
messageType,
|
|
});
|
|
topic.subscribe(callback);
|
|
subscribersRef.current.set(key, topic);
|
|
|
|
return () => {
|
|
topic.unsubscribe();
|
|
subscribersRef.current.delete(key);
|
|
};
|
|
}, []);
|
|
|
|
/** Publish a single message to a ROS2 topic. */
|
|
const publish = useCallback((name, messageType, data) => {
|
|
if (!rosRef.current) return;
|
|
const topic = new ROSLIB.Topic({
|
|
ros: rosRef.current,
|
|
name,
|
|
messageType,
|
|
});
|
|
topic.publish(new ROSLIB.Message(data));
|
|
}, []);
|
|
|
|
/** Call a ROS2 service. Returns a Promise resolving to the response. */
|
|
const callService = useCallback((name, serviceType, request = {}) => {
|
|
return new Promise((resolve, reject) => {
|
|
if (!rosRef.current) {
|
|
reject(new Error('Not connected'));
|
|
return;
|
|
}
|
|
const svc = new ROSLIB.Service({
|
|
ros: rosRef.current,
|
|
name,
|
|
serviceType,
|
|
});
|
|
svc.callService(new ROSLIB.ServiceRequest(request), resolve, reject);
|
|
});
|
|
}, []);
|
|
|
|
/** Set ROS2 node parameters via rcl_interfaces/srv/SetParameters. */
|
|
const setParam = useCallback((nodeName, params) => {
|
|
// params: { name: string, type: 'bool'|'int'|'double'|'string', value: any }[]
|
|
const TYPE_MAP = { bool: 1, integer: 2, double: 3, string: 4, int: 2 };
|
|
const parameters = params.map(({ name, type, value }) => {
|
|
const typeInt = TYPE_MAP[type] ?? 4;
|
|
const valueKey = {
|
|
1: 'bool_value',
|
|
2: 'integer_value',
|
|
3: 'double_value',
|
|
4: 'string_value',
|
|
}[typeInt];
|
|
return { name, value: { type: typeInt, [valueKey]: value } };
|
|
});
|
|
return callService(
|
|
`/${nodeName}/set_parameters`,
|
|
'rcl_interfaces/srv/SetParameters',
|
|
{ parameters }
|
|
);
|
|
}, [callService]);
|
|
|
|
return { connected, error, subscribe, publish, callService, setParam };
|
|
}
|