spot-sdk-js
Version:
Develop applications and payloads for Spot using the unofficial Boston Dynamics Spot Node.js SDK.
167 lines (134 loc) • 5.67 kB
JavaScript
const {
TimeSyncEndpoint,
TimeSyncClient,
NotEstablishedError,
robot_time_range_from_nanoseconds,
timespec_to_robot_timespan
} = require('./time_sync');
const {TIME_FORMAT_DESC} = require('../bosdyn-core/util');
const fetch = require('node-fetch');
const {writeFileSync} = require('fs');
const REQUEST_CHUNK_SIZE = 10 * (1024**2);
const DEFAULT_OUTPUT = "./download.bddf";
function _print_help_timespan(){
console.log(`
A timespan is {{timeval}} or {{timeval}}-{{timeval}}.
${TIME_FORMAT_DESC}
For example:
'5m' From 5 minutes ago until now.
'20201107-20201108' All of 2020/11/07.
`);
}
function _bddf_url(hostname, data = null){
var str = '';
if(data){
for (const [key, value] of Object.entries(data)) {
str += `${key}=${value}`;
}
}
return `https://${hostname}/v1/data-buffer/bddf/${str == '' ? '' : `?${str}`}`;
}
function _http_headers(robot){
return {"Authorization": `Bearer ${robot.user_token}`};
}
function _request_timespan_from_time_range(time_range){
var ret = {};
if('start' in time_range) ret['from_sec'] = `${time_range.start.seconds}`;
if('end' in time_range) ret['to_sec'] = `${time_range.end.seconds}`;
return ret;
}
function _request_timespan_from_spec(timespec, time_sync_endpoint){
return _request_timespan_from_time_range(timespec_to_robot_timespan(timespec, time_sync_endpoint));
}
function _request_timespan_from_nanoseconds(start_nsec, end_nsec, time_sync_endpoint){
return _request_timespan_from_time_range(robot_time_range_from_nanoseconds(start_nsec, end_nsec, time_sync_endpoint));
}
async function download_data(robot, hostname, start_nsec = null, end_nsec = null, timespan_spec = null, output_filename = null,
robot_time = false, channel = null, message_type = null, grpc_service = null, show_progress = false){
var time_sync_endpoint = null;
if(!robot_time){
var time_sync_client = robot.ensure_client(TimeSyncClient.default_service_name);
time_sync_endpoint = new TimeSyncEndpoint(time_sync_client);
if(!time_sync_endpoint.establish_timesync()) throw new NotEstablishedError("time sync not established");
}
if(start_nsec || end_nsec){
var get_params = _request_timespan_from_nanoseconds(start_nsec, end_nsec, time_sync_endpoint);
}else{
var get_params = _request_timespan_from_spec(timespan_spec, time_sync_endpoint);
}
if(channel) get_params['channel'] = channel;
if(message_type) get_params['type'] = message_type;
if(grpc_service) get_params['grpc_service'] = grpc_service;
let url = _bddf_url(hostname, get_params);
const options = {
method: 'GET',
headers: _http_headers(robot)
}
let outfile = '';
let data = Buffer.allocUnsafe(REQUEST_CHUNK_SIZE);
let res = await fetch(url, options)
.then(res => res.buffer())
.then(res => {
if(!res.ok){
console.error(`${url} response: ${res.status}`);
return null;
}
outfile = output_filename ? output_filename : _output_filename(res);
writeFileSync(outfile, res);
}).catch(err => console.error(err));
return outfile;
}
function _output_filename(response){
var content = response.headers.get('Content-Disposition');
if(content.length < 2){
console.debug("Content-Disposition not set correctly.");
return DEFAULT_OUTPUT;
}
var match = content.search('filename=\"?([^\"]+)');
if(match == -1) return DEFAULT_OUTPUT;
return content[match];
}
function main(){
const argparse = require('argparse');
const {create_standard_sdk} = require('./sdk');
const {InvalidLoginError} = require('./auth');
const {add_common_arguments} = require('./util');
var parser = argparse.ArgumentParser();
parser.add_argument('-T', '--timespan', {default: '5m', help: 'Time span (default last 5 minutes)'});
parser.add_argument('--help-timespan', {action: 'store_true', help: 'Print time span formatting options'});
parser.add_argument('-c', '--channel', {help: 'Specify channel for data (default=all)'});
parser.add_argument('-t', '--type', {help: 'Specify message type (default=all)'});
parser.add_argument('-s', '--service', {help: 'Specify service name (default=all)'});
parser.add_argument('-o', '--output', {help: 'Output file name (default is "download.bddf"'});
parser.add_argument('-R', '--robot-time', {action: 'store_true', help: 'Specified timespan is in robot time'});
add_common_arguments(parser);
var options = parser.parse_args();
if(options.help_timespan){
_print_help_timespan();
return false;
}
var sdk = create_standard_sdk('bddf');
var robot = sdk.create_robot(options.hostname);
try{
robot.authenticate(options.username, options.password)
}catch(e){
if(e instanceof InvalidLoginError){
console.error(`Cannot authenticate to robot to obtain token: ${e}`);
return true;
}
console.error(e);
}
var output_filename = download_data(robot, options.hostname, options.timespan, undefined, undefined, undefined, options.robot_time, options.channel,
options.type, options.service, true);
if(!output_filename) return true;
console.info(`Wrote '${output_filename}'.`);
return false;
}
if (require.main === module) {
main();
process.exit(0);
}else{
module.exports = {
main
};
}