spot-sdk-js
Version:
Develop applications and payloads for Spot using the unofficial Boston Dynamics Spot Node.js SDK.
221 lines (203 loc) • 10.2 kB
JavaScript
const {BaseClient, common_header_errors} = require('./common.js');
const {NoTimeSyncError, _TimeConverter} = require('./robot_command.js');
const world_object_pb = require('../bosdyn/api/world_object_pb.js');
const world_object_service_pb = require('../bosdyn/api/world_object_service_pb.js');
const world_object_service = require('../bosdyn/api/world_object_service_grpc_pb.js');
/**
* Client for World Object service.
* @class WorldObjectClient
* @extends BaseClient
*/
class WorldObjectClient extends BaseClient {
static default_service_name = 'world-objects';
static service_type = 'bosdyn.api.WorldObjectService';
/**
* Create an instance of WorldObjectClient's class.
* @param {?string} name
*/
constructor(name=null){
super(world_object_service.WorldObjectServiceClient, name);
this._timesync_endpoint = null;
}
/**
* Update instance from another object.
* @param {object} other The object where to copy from.
* @return {void}
*/
update_from(other){
super.update_from(other);
try {
this._timesync_endpoint = other.time_sync.endpoint;
}catch(e){
}
}
/**
* Accessor for timesync-endpoint that is grabbed via 'update_from()'.
* @type {*}
* @throws {NoTimeSyncError} Could not find the timesync endpoint for the robot.
* @readonly
*/
get timesync_endpoint(){
if(!this._timesync_endpoint){
throw new NoTimeSyncError("[world object service] No timesync endpoint set for the robot");
}
return this._timesync_endpoint;
}
/**
* Get a list of World Objects.
* @param {?Array<world_object_pb.WorldObjectType>} object_type Specific types to include in the response,
* all other types will be filtered out.
* @param {?number} time_start_point A client timestamp to filter objects in the response. All objects
* will have a timestamp after this time.
* @param {object} [args] Extra arguments for controlling RPC details.
* @return {*} The response message, which includes the filtered list of all world objects.
* @throws {RpcError} Problem communicating with the robot.
* @throws {NoTimeSyncError} Couldn't convert the timestamp into robot time.
*/
async list_world_objects(object_type = null, time_start_point = null, args){
if(time_start_point != null){
time_start_point = this._update_time_filter(time_start_point, this.timesync_endpoint);
}
const req = new world_object_pb.ListWorldObjectRequest().setObjectTypeList(object_type).setTimestampFilter(time_start_point);
return await this.call(this._stub.listWorldObjects, req, _get_world_object_value, common_header_errors, args);
}
/**
* Async version of list_world_objects().
* @param {?Array<world_object_pb.WorldObjectType>} object_type Specific types to include in the response,
* all other types will be filtered out.
* @param {?number} time_start_point A client timestamp to filter objects in the response. All objects
* will have a timestamp after this time.
* @param {object} [args] Extra arguments for controlling RPC details.
* @return {*} The response message, which includes the filtered list of all world objects.
* @throws {RpcError} Problem communicating with the robot.
* @throws {NoTimeSyncError} Couldn't convert the timestamp into robot time.
*/
list_world_objects_async(object_type = null, time_start_point = null, args){
if(time_start_point != null){
time_start_point = this._update_time_filter(time_start_point, this.timesync_endpoint);
}
const req = new world_object_pb.ListWorldObjectRequest().setObjectTypeList(object_type).setTimestampFilter(time_start_point);
return this.call_async(this._stub.listWorldObjects, req, _get_world_object_value, common_header_errors, args);
}
/**
* Mutate (add, change, delete) world objects.
* @param {world_object_pb.MutateWorldObjectRequest} mutation_req The request including the object to be mutated and the type of mutation.
* @param {object} [args] Extra arguments for controlling RPC details.
* @return {*} The response message, which includes the filtered list of all world objects.
* @throws {RpcError} Problem communicating with the robot.
* @throws {NoTimeSyncError} Couldn't convert the timestamp into robot time.
*/
async mutate_world_objects(mutation_req, args){
if(mutation_req.getMutation().getObject().hasAcquisitionTime()){
const client_timestamp = mutation_req.getMutation().getObject().getAcquisitionTime();
mutation_req.getMutation().getObject().setAcquisitionTime(this._update_timestamp_filter(client_timestamp, this.timesync_endpoint));
}
return await this.call(this._stub.mutateWorldObjects, mutation_req, _get_status, common_header_errors, args);
}
/**
* Async version of mutate_world_objects().
* @param {world_object_pb.MutateWorldObjectRequest} mutation_req The request including the object to be mutated and the type of mutation.
* @param {object} [args] Extra arguments for controlling RPC details.
* @return {*} The response message, which includes the filtered list of all world objects.
* @throws {RpcError} Problem communicating with the robot.
* @throws {NoTimeSyncError} Couldn't convert the timestamp into robot time.
*/
mutate_world_objects_async(mutation_req, args){
if(mutation_req.getMutation().getObject().hasAcquisitionTime()){
const client_timestamp = mutation_req.getMutation().getObject().getAcquisitionTime();
mutation_req.getMutation().getObject().setAcquisitionTime(this._update_timestamp_filter(client_timestamp, this.timesync_endpoint));
}
return this.call_async(this._stub.mutateWorldObjects, mutation_req, _get_status, common_header_errors, args);
}
/**
* Set or convert fields of the proto that need timestamps in the robot's clock.
* @param {number} timestamp Client time, such as from Date.now().
* @param {TimeSyncEndpoint} timesync_endpoint A timesync endpoint associated with the robot object.
* @return {*}
* @throws {NoTimeSyncError} Couldn't convert the timestamp into robot time.
* @private
*/
_update_time_filter(timestamp, timesync_endpoint){
if(!timesync_endpoint){
throw new NoTimeSyncError("[WORLD OBJECT] No timesync endpoint set for the robot.");
}
const converter = new _TimeConverter(this, timesync_endpoint);
return converter.robot_timestamp_from_local_secs(timestamp);
}
/**
* Set or convert fields of the proto that need timestamps in the robot's clock.
* @param {google.protobuf.Timestamp} timestamp Client time.
* @param {TimeSyncEndpoint} timesync_endpoint A timesync endpoint associated with the robot object.
* @return {google.protobuf.Timestamp}
* @throws {NoTimeSyncError} Couldn't convert the timestamp into robot time.
* @private
*/
_update_timestamp_filter(timestamp, timesync_endpoint){
if(!timesync_endpoint){
throw new NoTimeSyncError("[WORLD OBJECT] No timesync endpoint set for the robot.");
}
const converter = new _TimeConverter(this, timesync_endpoint);
converter.convert_timestamp_from_local_to_robot(timestamp);
return timestamp;
}
}
function _get_world_object_value(response){
return response;
}
function _get_status(response){
if (response.getStatus() != world_object_pb.MutateWorldObjectResponse.Status.STATUS_OK){
if (response.getStatus() == world_object_pb.MutateWorldObjectResponse.Status.STATUS_INVALID_MUTATION_ID){
console.log("[WORLD OBJECT] Object id not found, and could not be mutated.");
}
if (response.getStatus() == world_object_pb.MutateWorldObjectResponse.Status.STATUS_NO_PERMISSION){
console.log("[WORLD OBJECT] Cannot change/delete objects detected by Spot's perception system, only client objects.");
}
}
return response;
}
/**
* Add a world object to the scene.
* @param {world_object_pb.WorldObject} world_obj The world object to be added into the robot's perception scene.
* @return {world_object_pb.MutateWorldObjectRequest} A MutateWorldObjectRequest where the action is to "add" the object to the scene.
*/
function make_add_world_object_req(world_obj){
const add_obj = new world_object_pb.MutateWorldObjectRequest.Mutation()
.setAction(world_object_pb.MutateWorldObjectRequest.Action.ACTION_ADD)
.setObject(world_obj);
const req = new world_object_pb.MutateWorldObjectRequest().setMutation(add_obj);
return req;
}
/**
* Delete a world object from the scene.
* @param {world_object_pb.WorldObject} world_obj The world object to be delete in the robot's perception scene. The
* object must be a client-added object and have the correct world object
* id returned by the service after adding the object.
* @return {world_object_pb.MutateWorldObjectRequest} A MutateWorldObjectRequest where the action is to "delete" the object to the scene.
*/
function make_delete_world_object_req(world_obj){
const del_obj = new world_object_pb.MutateWorldObjectRequest.Mutation()
.setAction(world_object_pb.MutateWorldObjectRequest.Action.ACTION_DELETE)
.setObject(world_obj);
const req = new world_object_pb.MutateWorldObjectRequest().setMutation(del_obj);
return req;
}
/**
* Change/update an existing world object in the scene.
* @param {world_object_pb.WorldObject} world_obj The world object to be changed/updated in the robot's perception scene.
* The object must be a client-added object and have the correct world object
* id returned by the service after adding the object.
* @return {world_object_pb.MutateWorldObjectRequest} A MutateWorldObjectRequest where the action is to "change" the object to the scene.
*/
function make_change_world_object_req(world_obj){
const change_obj = new world_object_pb.MutateWorldObjectRequest.Mutation()
.setAction(world_object_pb.MutateWorldObjectRequest.Action.ACTION_CHANGE)
.setObject(world_obj);
const req = new world_object_pb.MutateWorldObjectRequest().setMutation(change_obj);
return req;
}
module.exports = {
WorldObjectClient,
make_add_world_object_req,
make_delete_world_object_req,
make_change_world_object_req
};