'use strict';
const { BaseClient, common_header_errors } = require('./common');
const { NoTimeSyncError, _TimeConverter } = require('./robot_command');
const world_object_pb = require('../bosdyn/api/world_object_pb');
const world_object_service = require('../bosdyn/api/world_object_service_grpc_pb');
/**
* 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 The BaseClient's 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.
* @returns {void}
*/
async update_from(other) {
super.update_from(other);
try {
this._timesync_endpoint = (await other.time_sync).endpoint;
} catch (e) {
// Pass
}
}
/**
* 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.
* @returns {Promise<world_object_pb.ListWorldObjectResponse>} 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(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(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.
* @returns {Promise<world_object_pb.MutateWorldObjectResponse>|Promise<void>} 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(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(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.
* @returns {*}
* @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.
* @returns {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.
* @returns {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.
* @returns {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.
* @returns {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,
};
Source