193 lines
5.4 KiB
HTML
193 lines
5.4 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>ROS 2 Topic Viewer</title>
|
|
<script src="https://cdn.jsdelivr.net/npm/roslib/build/roslib.min.js"></script>
|
|
<style>
|
|
body {
|
|
font-family: sans-serif;
|
|
padding: 1em;
|
|
background: #f5f5f5;
|
|
}
|
|
|
|
select,
|
|
button,
|
|
input[type=range] {
|
|
margin: 0.5em;
|
|
padding: 0.5em;
|
|
}
|
|
|
|
#output {
|
|
white-space: pre-wrap;
|
|
background: #fff;
|
|
padding: 1em;
|
|
border: 1px solid #ccc;
|
|
}
|
|
|
|
#rateControl {
|
|
margin-top: 2em;
|
|
}
|
|
|
|
#output.flash {
|
|
background: #d0f0c0;
|
|
transition: background 0.5s ease;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<h1>ROS 2 Topic Viewer</h1>
|
|
|
|
<label for="topicSelect">Select a topic:</label>
|
|
<select id="topicSelect"></select>
|
|
<button onclick="subscribe()">Subscribe</button>
|
|
|
|
<div id="output">Waiting for messages...</div>
|
|
|
|
<div id="rateControl">
|
|
<h2>Set Publish Rate (Hz)</h2>
|
|
<input type="range" id="rateSlider" min="0.5" max="60" step="0.5" value="1" onmousedown="isDraggingSlider = true"
|
|
onmouseup="isDraggingSlider = false" oninput="updateRateLabel(this.value)" onchange="setPublishRate(this.value)">
|
|
|
|
<span id="rateValue">1.0</span> Hz
|
|
</div>
|
|
<p>Current publish rate: <span id="currentRate">1.0</span> Hz</p>
|
|
<h2>Live Camera Feed</h2>
|
|
<img id="cameraImage" width="640" height="480" alt="Camera Feed">
|
|
|
|
<script>
|
|
const ros = new ROSLIB.Ros({ url: 'ws://192.168.1.205:9090' });
|
|
|
|
ros.on('connection', () => {
|
|
console.log('Connected to rosbridge');
|
|
ros.getTopics((topics) => {
|
|
const select = document.getElementById('topicSelect');
|
|
topics.topics.forEach((topic) => {
|
|
const option = document.createElement('option');
|
|
option.value = topic;
|
|
option.textContent = topic;
|
|
select.appendChild(option);
|
|
});
|
|
});
|
|
|
|
setInterval(fetchCurrentRate, 1000); // Poll every second
|
|
});
|
|
|
|
ros.on('error', (err) => {
|
|
console.error('Error connecting to rosbridge:', err);
|
|
});
|
|
|
|
let listener = null;
|
|
|
|
function subscribe() {
|
|
const topicName = document.getElementById('topicSelect').value;
|
|
if (listener) listener.unsubscribe();
|
|
|
|
ros.getTopicType(topicName, (type) => {
|
|
listener = new ROSLIB.Topic({
|
|
ros: ros,
|
|
name: topicName,
|
|
messageType: type
|
|
});
|
|
|
|
listener.subscribe((message) => {
|
|
const output = document.getElementById('output');
|
|
output.classList.add('flash');
|
|
setTimeout(() => output.classList.remove('flash'), 300);
|
|
|
|
// Handle image display
|
|
if (type === 'sensor_msgs/msg/CompressedImage') {
|
|
const imgElement = document.getElementById('cameraImage');
|
|
imgElement.src = 'data:image/jpeg;base64,' + message.data;
|
|
|
|
// Show summary instead of full payload
|
|
output.textContent = `Topic: ${topicName}\nType: ${type}\n\n[CompressedImage received: ${message.data.length} bytes]`;
|
|
return;
|
|
}
|
|
|
|
// Default: show full message
|
|
output.textContent =
|
|
`Topic: ${topicName}\nType: ${type}\n\n` + JSON.stringify(message, null, 2);
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
function updateRateLabel(value) {
|
|
document.getElementById('rateValue').textContent = value;
|
|
}
|
|
|
|
let isDraggingSlider = false;
|
|
let rateUpdateTimeout = null;
|
|
|
|
function setPublishRate(value) {
|
|
clearTimeout(rateUpdateTimeout);
|
|
|
|
rateUpdateTimeout = setTimeout(() => {
|
|
const baseValue = parseFloat(value);
|
|
const param = new ROSLIB.Param({
|
|
ros: ros,
|
|
name: 'camera_module:publish_rate'
|
|
});
|
|
|
|
let attempts = 0;
|
|
const maxAttempts = 5;
|
|
const bump = 0.0001;
|
|
|
|
isDraggingSlider = true;
|
|
|
|
function trySetParam() {
|
|
const bumpedValue = baseValue + bump * attempts;
|
|
|
|
param.set(bumpedValue.toFixed(4), () => { // Convert bumpedValue to string
|
|
setTimeout(() => {
|
|
param.get((currentValue) => {
|
|
const numericValue = typeof currentValue === 'number' ? currentValue : NaN;
|
|
|
|
if (!isNaN(numericValue) && Math.abs(numericValue - bumpedValue) < 1e-6) {
|
|
console.log(`✅ Confirmed: publish_rate set to ${bumpedValue.toFixed(4)} Hz`);
|
|
isDraggingSlider = false;
|
|
} else if (++attempts < maxAttempts) {
|
|
console.warn(`🔁 Retry ${attempts}: still ${numericValue}, retrying with ${bumpedValue.toFixed(4)}...`);
|
|
trySetParam();
|
|
} else {
|
|
console.error(`❌ Failed to set publish_rate after ${maxAttempts} attempts`);
|
|
isDraggingSlider = false;
|
|
}
|
|
});
|
|
}, 300);
|
|
});
|
|
}
|
|
|
|
trySetParam();
|
|
}, 300);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function fetchCurrentRate() {
|
|
if (isDraggingSlider) return; // ✅ Skip update if user is dragging
|
|
|
|
const param = new ROSLIB.Param({
|
|
ros: ros,
|
|
name: 'camera_module:publish_rate'
|
|
});
|
|
|
|
param.get((value) => {
|
|
if (value !== null) {
|
|
document.getElementById('currentRate').textContent = value.toFixed(1);
|
|
document.getElementById('rateSlider').value = value;
|
|
document.getElementById('rateValue').textContent = value.toFixed(1);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
</script>
|
|
</body>
|
|
|
|
</html> |