Spaces:
Sleeping
Sleeping
/** | |
* Basic Consumer Example - RobotHub TransportServer | |
* | |
* This example demonstrates: | |
* - Connecting to an existing room as a consumer using workspace_id and room_id | |
* - Receiving joint updates and state sync | |
* - Setting up callbacks with enhanced feedback | |
* - Getting current state and connection info | |
* - Modern error handling and resource management | |
*/ | |
import { RoboticsConsumer } from '../dist/robotics/index.js'; | |
import { createInterface } from 'readline'; | |
async function promptUser(question) { | |
const rl = createInterface({ | |
input: process.stdin, | |
output: process.stdout | |
}); | |
return new Promise((resolve) => { | |
rl.question(question, (answer) => { | |
rl.close(); | |
resolve(answer.trim()); | |
}); | |
}); | |
} | |
async function main() { | |
console.log('π€ RobotHub TransportServer Basic Consumer Example π€'); | |
console.log('π This example will connect to an existing room and listen for updates\n'); | |
// Get connection details from user | |
console.log('π Please provide the connection details:'); | |
const workspaceId = await promptUser('Enter workspace ID: '); | |
if (!workspaceId) { | |
console.error('β Workspace ID is required!'); | |
console.log('π‘ You can get this from the producer example output'); | |
return; | |
} | |
const roomId = await promptUser('Enter room ID: '); | |
if (!roomId) { | |
console.error('β Room ID is required!'); | |
console.log('π‘ You can get this from the producer example output'); | |
return; | |
} | |
console.log('\nβ Connection details received:'); | |
console.log(` Workspace ID: ${workspaceId}`); | |
console.log(` Room ID: ${roomId}`); | |
// Create consumer client | |
const consumer = new RoboticsConsumer('http://localhost:8000'); | |
// Track received data and connection state | |
let updateCount = 0; | |
let stateCount = 0; | |
let isConnected = false; | |
const receivedUpdates = []; | |
const receivedStates = []; | |
// Set up callbacks with enhanced feedback | |
consumer.onJointUpdate((joints) => { | |
updateCount++; | |
console.log(`\nπ₯ [${updateCount}] Joint update received: ${joints.length} joint(s)`); | |
// Show joint details with better formatting | |
joints.forEach(joint => { | |
const speedInfo = joint.speed ? ` (speed: ${joint.speed})` : ''; | |
console.log(` π§ ${joint.name}: ${joint.value}Β°${speedInfo}`); | |
}); | |
receivedUpdates.push({ timestamp: new Date(), joints }); | |
}); | |
consumer.onStateSync((state) => { | |
stateCount++; | |
console.log(`\nπ [${stateCount}] State sync received: ${Object.keys(state).length} joint(s)`); | |
// Show state details with better formatting | |
if (Object.keys(state).length > 0) { | |
Object.entries(state).forEach(([name, value]) => { | |
console.log(` βοΈ ${name}: ${value}Β°`); | |
}); | |
} else { | |
console.log(' (No joint data)'); | |
} | |
receivedStates.push({ timestamp: new Date(), state }); | |
}); | |
consumer.onError((errorMsg) => { | |
console.error('\nβ Consumer error:', errorMsg); | |
}); | |
consumer.onConnected(() => { | |
isConnected = true; | |
console.log('\nβ Consumer connected successfully!'); | |
}); | |
consumer.onDisconnected(() => { | |
isConnected = false; | |
console.log('\nπ Consumer disconnected'); | |
}); | |
try { | |
// Connect to the room with workspace_id and room_id | |
console.log(`\nπ Connecting to room...`); | |
console.log(` Workspace ID: ${workspaceId}`); | |
console.log(` Room ID: ${roomId}`); | |
const success = await consumer.connect(workspaceId, roomId, 'demo-consumer'); | |
if (!success) { | |
console.error('\nβ Failed to connect to room!'); | |
console.log('π‘ Possible reasons:'); | |
console.log(' - Room does not exist'); | |
console.log(' - Server is not running on http://localhost:8000'); | |
console.log(' - Incorrect workspace_id or room_id'); | |
console.log(' - Network connectivity issues'); | |
return; | |
} | |
console.log(`\nβ Successfully connected to room!`); | |
// Show detailed connection info | |
const info = consumer.getConnectionInfo(); | |
console.log('\nπ Connection Details:'); | |
console.log(` Workspace ID: ${info.workspace_id}`); | |
console.log(` Room ID: ${info.room_id}`); | |
console.log(` Role: ${info.role}`); | |
console.log(` Participant ID: ${info.participant_id}`); | |
console.log(` Server URL: ${info.base_url}`); | |
// Get initial state with enhanced feedback | |
console.log('\nπ Retrieving initial robot state...'); | |
try { | |
const initialState = await consumer.getStateSyncAsync(); | |
console.log(`β Initial state retrieved: ${Object.keys(initialState).length} joint(s)`); | |
if (Object.keys(initialState).length > 0) { | |
console.log(' Current joint positions:'); | |
Object.entries(initialState).forEach(([name, value]) => { | |
console.log(` βοΈ ${name}: ${value}Β°`); | |
}); | |
} else { | |
console.log(' π No joints configured yet (empty state)'); | |
console.log(' π‘ Producer can send initial state to populate this'); | |
} | |
} catch (error) { | |
console.log(`β οΈ Could not retrieve initial state: ${error.message}`); | |
} | |
// Listen for updates with enhanced status reporting | |
console.log('\nπ Listening for real-time updates...'); | |
console.log(' Duration: 60 seconds'); | |
console.log(' π‘ Run the producer example to send updates to this consumer'); | |
const startTime = Date.now(); | |
const duration = 60000; // 60 seconds | |
// Show periodic status with detailed information | |
const statusInterval = setInterval(() => { | |
const elapsed = Date.now() - startTime; | |
const remaining = Math.max(0, duration - elapsed); | |
if (remaining > 0) { | |
console.log(`\nπ Status Update (${Math.floor(remaining / 1000)}s remaining):`); | |
console.log(` Connection: ${isConnected ? 'β Connected' : 'β Disconnected'}`); | |
console.log(` Joint updates received: ${updateCount}`); | |
console.log(` State syncs received: ${stateCount}`); | |
console.log(` Total messages: ${updateCount + stateCount}`); | |
if (receivedUpdates.length > 0) { | |
const lastUpdate = receivedUpdates[receivedUpdates.length - 1]; | |
const timeSinceLastUpdate = Date.now() - lastUpdate.timestamp.getTime(); | |
console.log(` Last update: ${Math.floor(timeSinceLastUpdate / 1000)}s ago`); | |
} | |
} | |
}, 15000); // Every 15 seconds | |
// Wait for the duration | |
await new Promise(resolve => setTimeout(resolve, duration)); | |
clearInterval(statusInterval); | |
// Show comprehensive final summary | |
console.log('\nπ Session Summary:'); | |
console.log(` Duration: 60 seconds`); | |
console.log(` Total joint updates: ${updateCount}`); | |
console.log(` Total state syncs: ${stateCount}`); | |
console.log(` Total messages: ${updateCount + stateCount}`); | |
if (updateCount > 0) { | |
console.log(` Average update rate: ${(updateCount / 60).toFixed(2)} updates/second`); | |
} | |
// Get final state for comparison | |
console.log('\nπ Retrieving final robot state...'); | |
try { | |
const finalState = await consumer.getStateSyncAsync(); | |
console.log(`β Final state retrieved: ${Object.keys(finalState).length} joint(s)`); | |
if (Object.keys(finalState).length > 0) { | |
console.log(' Final joint positions:'); | |
Object.entries(finalState).forEach(([name, value]) => { | |
console.log(` βοΈ ${name}: ${value}Β°`); | |
}); | |
} else { | |
console.log(' π Final state is empty'); | |
} | |
} catch (error) { | |
console.log(`β οΈ Could not retrieve final state: ${error.message}`); | |
} | |
// Show data history if available | |
if (receivedUpdates.length > 0) { | |
console.log('\nπ Update History (last 5):'); | |
const recentUpdates = receivedUpdates.slice(-5); | |
recentUpdates.forEach((update, index) => { | |
const timeStr = update.timestamp.toLocaleTimeString(); | |
console.log(` ${index + 1}. [${timeStr}] ${update.joints.length} joint(s) updated`); | |
}); | |
} | |
if (receivedStates.length > 0) { | |
console.log('\nπ State Sync History (last 3):'); | |
const recentStates = receivedStates.slice(-3); | |
recentStates.forEach((stateUpdate, index) => { | |
const timeStr = stateUpdate.timestamp.toLocaleTimeString(); | |
const jointCount = Object.keys(stateUpdate.state).length; | |
console.log(` ${index + 1}. [${timeStr}] ${jointCount} joint(s) synchronized`); | |
}); | |
} | |
console.log('\nπ Consumer example completed successfully!'); | |
} catch (error) { | |
console.error('\nβ Error occurred:', error.message); | |
if (error.stack) { | |
console.error('Stack trace:', error.stack); | |
} | |
} finally { | |
// Always disconnect and cleanup | |
console.log('\nπ§Ή Cleaning up resources...'); | |
if (consumer.isConnected()) { | |
console.log(' Disconnecting from room...'); | |
await consumer.disconnect(); | |
console.log(' β Disconnected successfully'); | |
} | |
console.log('\nπ Consumer example finished. Goodbye!'); | |
} | |
} | |
// Handle Ctrl+C gracefully | |
process.on('SIGINT', async () => { | |
console.log('\n\nπ Received interrupt signal (Ctrl+C)'); | |
console.log('π§Ή Shutting down gracefully...'); | |
process.exit(0); | |
}); | |
// Handle uncaught errors | |
process.on('unhandledRejection', (error) => { | |
console.error('\nβ Unhandled promise rejection:', error); | |
process.exit(1); | |
}); | |
main().catch((error) => { | |
console.error('\nπ₯ Fatal error:', error); | |
process.exit(1); | |
}); |