UNPKG

robotics

Version:

Robotics.dev P2P ROS2 robot controller CLI with ROS telemetry and video streaming

374 lines (331 loc) 12.3 kB
#!/usr/bin/env node import {createRequire } from "module"; const require = createRequire(import.meta.url); import Configstore from 'configstore'; const config = new Configstore('robotics'); var robotId = config.get('ROBOT_ID'); var apiToken = config.get('API_TOKEN'); const chalk = require('chalk'); const clear = require('clear'); const emoji = require('node-emoji'); const argv = require('minimist')(process.argv.slice(2)); const {exec, spawn} = require('child_process'); // const path = require('path'); import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const getRobotId = async () => { robotId = await config.get('ROBOT_ID'); // console.log(chalk.blue(`ROBOT ID: ${robotId}`)); } const getApiToken = async () => { apiToken = await config.get('API_TOKEN'); // console.log(chalk.blue(`API TOKEN: ${apiToken}`)); } const setRobotId = async (rId) => { const test = await config.set('ROBOT_ID', rId) robotId = rId console.log(chalk.blue(`ROBOT ID: ${robotId}`)); } const setApiToken = async (token) => { const test = await config.set('API_TOKEN', token) console.log(chalk.blue(`API TOKEN: ${token}`)); } const getNewRobotId = async () => { const myHeaders = new Headers(); myHeaders.append("Content-Type", "application/json"); const response = await fetch("https://robotics.dev/new", { method: "POST", headers: myHeaders, }) .then((res) => res.json()) .then((data) => { // console.log("robotId:", data.id); setRobotId(data.id); // config.set('ROBOT_ID', data.id) // robotId = data.id }) } // Fetch robot details var robotCamera; var robotCompute; const fetchRobotDetails = async () => { try { // console.log("RealSense Fetch", robotId, apiToken); if(robotId && apiToken){ const response = await fetch(`https://robotics.dev/robot/${robotId}`, { headers: { "Content-Type": "application/json", "api_token": apiToken } }); const data = await response.json(); // console.log('Robot Details:', data); if(data.camera && data.camera !== ""){ robotCamera = data.camera; } if(data.compute && data.compute !== ""){ robotCompute = data.compute; } } } catch (error) { console.error('Error fetching robot details:', error); } } if (!robotId){ getNewRobotId(); } else { console.log(chalk.blue(`ROBOT ID: ${robotId}`)); } clear(); console.log(emoji.get('robot'),chalk.blue('ROBOTICS.DEV')); // console.log(argv); function killProcessByName(name){ exec(`pkill -f ${name}`, (error, stdout, stderr) => { // console.log(chalk.red('Service stopped.')); if(error){ // console.error(`Error: ${error}`); return; } }); } //HELP (robotics help) if(argv._.includes("help")){ console.log(chalk.blue('Robotics commands: ')); console.log(chalk.blue('robotics help'), chalk.green('returns this list of commands')); console.log(chalk.blue('robotics connect (-s robotics.dev)'), chalk.green('connects robot to cloud/edge via websocket communications')); console.log(chalk.blue('robotics disconnect'), chalk.green('disconnects robot from cloud or edge servers')); console.log(chalk.blue('robotics id'), chalk.green("returns robot's id for connecting with cloud or edge servers")); console.log(chalk.blue('robotics set --token=1234...6789'), chalk.green("sets developer token from robotics.dev API page")); console.log(chalk.blue('robotics status'), chalk.green('returns list of running services')); console.log(chalk.blue('robotics start motors (-b rpi (DEFAULT) or -b firmata (Radxa, LattePanda)) (-p 27,22,17,18 (left to right motor pins)) (-e 13,2,12,11 (left to right motor encoder pins)) (-d /dev/ttyACM0 (overrides default device id for radxa and lattepanda))'), chalk.green('starts ROS2 motors on /cmd_vel (enable rosNamespace in portal for multi-robot fleets)')); console.log(chalk.blue('robotics stop motors'), chalk.green("stops robot's motors")); console.log(chalk.blue('robotics start camera (-d /dev/video0)'), chalk.green("starts robot's 2d camera stream with device override. Note RealSense RGB device is /dev/video4. -r 672x672 -f 15")); console.log(chalk.blue('robotics stop camera'), chalk.green("stops robot's 2d camera stream to cloud or edge servers")); console.log(chalk.blue('robotics start realsense (-p enables pointcloud)'), chalk.green("starts robot's 3d realsense camera")); console.log(chalk.blue('robotics stop realsense'), chalk.green("stops robot's 3d realsense camera")); console.log(chalk.blue('robotics monitor'), chalk.green('starts monitoring service to automatically restart comms if it crashes')); console.log(chalk.blue('robotics stop monitor'), chalk.green('stops the monitoring service')); } // CONNECT (robotics connect -s http://192.168.0.6:3001) if(argv._.includes("connect")){ const filePath = path.join(__dirname, 'comms.js'); var options = [filePath] if(argv.s){ options.push('--server') options.push(argv.s.toLowerCase()) } // Add memory limit and garbage collection flags options.unshift('--max-old-space-size=1024'); // 1GB memory limit options.unshift('--expose-gc'); // Enable garbage collection function startCommsProcess() { var commsProcess = spawn('node', options, { detached: true, stdio: 'ignore' }); commsProcess.on('error', (error) => { console.error(chalk.red(`Comms process error: ${error}`)); setTimeout(startCommsProcess, 5000); // Restart after 5 seconds }); commsProcess.on('exit', (code, signal) => { if (code !== 0) { console.log(chalk.yellow(`Comms process exited with code ${code}, restarting in 5 seconds...`)); setTimeout(startCommsProcess, 5000); // Restart after 5 seconds } }); return commsProcess; } var commsProcess = startCommsProcess(); console.log(chalk.green('Robot connected.')); commsProcess.unref(); if(robotId){ console.log(chalk.blue(`ROBOT ID: ${robotId}`)); // process.exit(0); } } // DISCONNECT (robotics disconnect) if(argv._.includes("disconnect")){ killProcessByName('comms.js'); console.log(chalk.red('Robot disconnected.')); process.exit(0); } // ID if(argv._.includes("id")){ console.log(chalk.blue(`ROBOT ID: ${config.get('ROBOT_ID')}`)); if(robotId){ // console.log(chalk.blue(`ROBOT ID: ${robotId}`)); process.exit(0); } } // SET - robotics set --token=123 || robotics set --id=123 if(argv._.includes("set")){ if(argv.token){ setApiToken(argv.token); } if(argv.id){ setRobotId(argv.id); } process.exit(0); } // STATUS if(argv._.includes("status")){ exec(`ps -aux | grep comms.js`, (err, stdout, stdin) => { if (err) throw err; console.log(stdout); }); exec(`ps -aux | grep motors-rpi5.js`, (err, stdout, stdin) => { if (err) throw err; console.log(stdout); }); exec(`ps -aux | grep motors-firmata.js`, (err, stdout, stdin) => { if (err) throw err; console.log(stdout); }); exec(`ps -aux | grep camera-2d-ros.js`, (err, stdout, stdin) => { if (err) throw err; console.log(stdout); }); // process.exit(0); } // MOTORS START (robotics motors start || robotics start motors -b rpi -p 27,22,17,18) if(argv._.includes("motors") && argv._.includes("start")){ var filePath1 = path.join(__dirname, 'motors-rpi5.js'); // var filePath1 = path.join(__dirname, 'motors-rpi5-odom.js'); var filePath2 = path.join(__dirname, 'motors-firmata.js'); await getRobotId(); await getApiToken(); await fetchRobotDetails(); var options; if(robotCompute === 'Raspberry Pi'){ options = [filePath1]; } else { options = [filePath2]; } if(argv.b && argv.b === 'rpi'){ options = [filePath1]; } else if(argv.b && argv.b === 'firmata'){ options = [filePath2] // options = ['motors-firmata.js']; } if(argv.d){ options.push('--device') options.push(argv.d) } if(argv.p){ options.push('--pins') options.push(argv.p) } if(argv.e){ options.push('--encoderpins') options.push(argv.e) } var commsProcess = spawn('node', options, { detached: true, stdio: 'ignore' }); console.log(chalk.green('Robot motors started.')); speak('Robot motors started.'); commsProcess.unref(); process.exit(0); } // MOTORS STOP (robotics motors stop || robotics stop motors) if(argv._.includes("motors") && argv._.includes("stop")){ killProcessByName('motors-rpi5.js'); killProcessByName('motors-firmata.js'); console.log(chalk.red('Robot motors stopped.')); speak('Robot motors stopped.') process.exit(0); } // CAMERA START (robotics camera start || robotics start camera -s http://192.168.0.6:3001 -d /dev/video0) if(argv._.includes("camera") && argv._.includes("start")){ const filePath = path.join(__dirname, 'camera-2d-ros.js'); var options = [filePath] // if(argv.s){ // options.push('--server') // options.push(argv.s.toLowerCase()) // } if(argv.d){ options.push('--device') options.push(argv.d) } if(argv.r){ options.push('--resolution') options.push(argv.r) } if(argv.f){ options.push('--fps') options.push(argv.f) } var commsProcess = spawn('node', options, { detached: true, stdio: 'ignore' }); console.log(chalk.green('Robot camera started.')); speak('Robot camera started.'); commsProcess.unref(); process.exit(0); } // CAMERA STOP (robotics camera stop || robotics stop camera) if(argv._.includes("camera") && argv._.includes("stop")){ killProcessByName('camera-2d-ros.js'); console.log(chalk.red('Robot camera stopped.')); speak('Robot camera stopped.'); process.exit(0); } // REALSENSE START (robotics realsense start || robotics start realsense -p enables pointcloud) if(argv._.includes("realsense") && argv._.includes("start")){ await getRobotId(); await getApiToken(); await fetchRobotDetails(); // const filePath = path.join(__dirname, 'camera-2d-ros.js'); // var options = [filePath] // var options = ['launch', 'realsense2_camera', 'rs_launch.py']; //ros2 launch realsense2_camera rs_launch.py depth_module.depth_profile:=640x480x6 depth_module.infra_profile:=640x480x6 rgb_camera.color_profile:=640x480x6 enable_rgbd:=true enable_sync:=true align_depth.enable:=true enable_color:=true enable_depth:=true var options; if(robotCamera === 'D421'){ options = ['launch', 'realsense2_camera', 'rs_launch.py', 'depth_module.depth_profile:=424x240x6', 'depth_module.infra_profile:=424x240x6', 'enable_depth:=true']; } else { options = ['launch', 'realsense2_camera', 'rs_launch.py', 'depth_module.depth_profile:=424x240x6', 'depth_module.infra_profile:=424x240x6','rgb_camera.color_profile:=320x180x6', 'enable_rgbd:=true', 'enable_color:=true', 'enable_depth:=true', 'enable_accel:=true', 'enable_gyro:=true', 'unite_imu_method:=2']; } if(argv.p){ options.push('pointcloud.enable:=true') } var commsProcess = spawn('ros2', options, { detached: true, stdio: 'ignore' }); console.log(chalk.green(`Robot realsense started on ${robotCamera} camera`)); speak(`Robot realsense started on ${robotCamera} camera`); commsProcess.unref(); process.exit(0); } // CAMERA STOP (robotics camera stop || robotics stop camera) if(argv._.includes("realsense") && argv._.includes("stop")){ killProcessByName('realsense2_camera_node'); console.log(chalk.red('Robot realsense stopped.')); speak('Robot realsense stopped.'); process.exit(0); } // MONITOR START (robotics monitor) if(argv._.includes("monitor") && !argv._.includes("stop")){ const filePath = path.join(__dirname, 'monitor-comms.js'); var options = [filePath]; var monitorProcess = spawn('node', options, { detached: true, stdio: 'ignore' }); console.log(chalk.green('Monitoring service started.')); monitorProcess.unref(); process.exit(0); } // MONITOR STOP (robotics stop monitor) if(argv._.includes("monitor") && argv._.includes("stop")){ killProcessByName('monitor-comms.js'); console.log(chalk.red('Monitoring service stopped.')); process.exit(0); } //speak("Initializing robot."); function speak(msg){ exec(`espeak "${msg}"`) }