Source

bosdyn-client/data_acquisition.js

'use strict';

const { common_header_errors, error_factory, error_pair, BaseClient } = require('./common');
const { ResponseError } = require('./exceptions');
const { now_timestamp } = require('../bosdyn-core/util');
const data_acquisition = require('../bosdyn/api/data_acquisition_pb');
const data_acquisition_service = require('../bosdyn/api/data_acquisition_service_grpc_pb');

class DataAcquisitionResponseError extends ResponseError {
  constructor(msg) {
    super(msg);
    this.name = 'DataAcquisitionResponseError';
  }
}

class RequestIdDoesNotExistError extends DataAcquisitionResponseError {
  constructor(msg) {
    super(msg);
    this.name = 'RequestIdDoesNotExistError';
  }
}

class UnknownCaptureTypeError extends DataAcquisitionResponseError {
  constructor(msg) {
    super(msg);
    this.name = 'UnknownCaptureTypeError';
  }
}

class CancellationFailedError extends DataAcquisitionResponseError {
  constructor(msg) {
    super(msg);
    this.name = 'CancellationFailedError';
  }
}

/**
 * A client for triggering data acquisition and logging.
 * @class DataAcquisitionClient
 * @extends BaseClient
 */
class DataAcquisitionClient extends BaseClient {
  static default_service_name = 'data-acquisition';
  static service_type = 'bosdyn.api.DataAcquisitionService';

  /**
   * Create an instance of AuthClient's class.
   * @param {?string} name BaseClient name.
   */
  constructor(name = null) {
    super(data_acquisition_service.DataAcquisitionServiceClient, name);
    this._timesync_endpoint = null;
  }

  /**
   * Update instance from another object.
   * @param {Object} other The object where to copy from.
   * @returns {Promise<void>}
   */
  async update_from(other) {
    super.update_from(other);
    try {
      this._timesync_endpoint = (await other.time_sync).endpoint;
    } catch (e) {
      // Continue regardless of error
    }
  }

  /**
   * Trigger a data acquisition to save data and metadata to the data buffer.
   * @param {Object} acquisition_requests The different image sources and
   * data sources to capture from and save to the data buffer with the same timestamp.
   * @param {string} action_name The unique action name that all data will be saved with.
   * @param {string} group_name The unique group name that all data will be saved with.
   * @param {?google.protobuf.Timestamp} data_timestamp The unique timestamp that all data will be
   * saved with.
   * @param {?(Metadata|object)} metadata The JSON structured metadata to be associated with
   * the data returned by the DataAcquisitionService when logged in the data buffer
   * service.
   * @param {Object} [args] Extra arguments for controlling RPC details.
   * @returns {Promise<data_acquisition.AcquireDataResponse.RequestId>} If the RPC is successful, then it will return
   * the acquire data request id, which can be used to check the status of the acquisition and get feedback.
   * @throws {RpcError} Problem communicating with the robot.
   */
  acquire_data(acquisition_requests, action_name, group_name, data_timestamp = null, metadata = null, args) {
    if (data_timestamp === null) {
      if (!this._timesync_endpoint) {
        data_timestamp = now_timestamp();
      } else {
        data_timestamp = this._timesync_endpoint.robot_timestamp_from_local_secs(Date.now());
      }
    }
    const action_id = new data_acquisition.CaptureActionId()
      .setActionName(action_name)
      .setGroupName(group_name)
      .setTimestamp(data_timestamp);

    const metadata_proto = metadata_to_proto(metadata);

    const request = new data_acquisition.AcquireDataRequest()
      .setActionId(action_id)
      .setMetadata(metadata_proto)
      .setAcquisitionRequests(acquisition_requests);

    return this.call(this._stub.acquireData, request, get_request_id, acquire_data_error, args);
  }

  /**
   * Check the status of a data acquisition based on the request id.
   * @param {number} request_id The request id associated with an AcquireData request.
   * @param {Object} [args] Extra arguments for controlling RPC details.
   * @returns {Promise<data_acquisition.GetStatusResponse>} If the RPC is successful, then it will return
   * the full status response, which includes the status as well as other information about any possible errors.
   * @throws {RpcError} Problem communicating with the robot.
   * @throws {RequestIdDoesNotExistError} The request id provided is incorrect.
   */
  get_status(request_id, args) {
    const request = new data_acquisition.GetStatusRequest().setRequestId(request_id);
    return this.call(this._stub.getStatus, request, null, _get_status_error, args);
  }

  /**
   * Get information from a DAQ service to list it's capabilities - which data, metadata,
   * or processing the DAQ service will perform.
   * @param {Object} [args] Extra arguments for controlling RPC details.
   * @returns {Promise<data_acquisition.GetServiceInfoResponse.Capabilities>} The GetServiceInfoResponse message,
   * which contains all the different capabilities.
   * @throws {RpcError} Problem communicating with the robot.
   */
  get_service_info(args) {
    const request = new data_acquisition.GetServiceInfoRequest();
    return this.call(this._stub.getServiceInfo, request, _get_service_info_capabilities, common_header_errors, args);
  }

  /**
   * Cancel a data acquisition based on the request id.
   * @param {number} request_id The request id associated with an AcquireData request.
   * @param {Object} [args] Extra arguments for controlling RPC details.
   * @returns {Promise<data_acquisition.CancelAcquisitionResponse>} If the RPC is successful, then it will return the
   * full status response, which includes the status as well as other information about any possible errors.
   * @throws {RpcError} Problem communicating with the robot.
   * @throws {CancellationFailedError} The data acquisitions associated with the request id were unable to be cancelled.
   * @throws {RequestIdDoesNotExistError} The request id provided is incorrect.
   */
  cancel_acquisition(request_id, args) {
    const request = new data_acquisition.CancelAcquisitionRequest().setRequestId(request_id);
    return this.call(this._stub.cancelAcquisition, request, null, _cancel_acquisition_error, args);
  }
}

const _ACQUIRE_DATA_STATUS_TO_ERROR = {
  STATUS_OK: [null, null],
  STATUS_UNKNOWN_CAPTURE_TYPE: error_pair(UnknownCaptureTypeError),
};

const _GET_STATUS_STATUS_TO_ERROR = {
  STATUS_REQUEST_ID_DOES_NOT_EXIST: error_pair(RequestIdDoesNotExistError),
};

const _CANCEL_ACQUISITION_STATUS_TO_ERROR = {
  STATUS_REQUEST_ID_DOES_NOT_EXIST: error_pair(RequestIdDoesNotExistError),
  STATUS_FAILED_TO_CANCEL: error_pair(CancellationFailedError),
};

function metadata_to_proto(metadata) {
  let metadata_proto = null;
  if (metadata instanceof data_acquisition.Metadata) {
    metadata_proto = metadata;
  } else if (metadata instanceof Object) {
    metadata_proto = new data_acquisition.Metadata();
    metadata_proto.setData(metadata);
  }
  return metadata_proto;
}

function acquire_data_error(response) {
  return error_factory(
    response,
    response.getStatus(),
    Object.keys(data_acquisition.AcquireDataResponse.Status),
    _ACQUIRE_DATA_STATUS_TO_ERROR,
  );
}

function _get_status_error(response) {
  return error_factory(
    response,
    response.getStatus(),
    Object.keys(data_acquisition.GetStatusResponse.Status),
    _GET_STATUS_STATUS_TO_ERROR,
  );
}

function _cancel_acquisition_error(response) {
  return error_factory(
    response,
    response.getStatus(),
    Object.keys(data_acquisition.CancelAcquisitionResponse.Status),
    _CANCEL_ACQUISITION_STATUS_TO_ERROR,
  );
}

function _get_service_info_capabilities(response) {
  return response.getCapabilities();
}

function get_request_id(response) {
  return response.getRequestId();
}

module.exports = {
  DataAcquisitionClient,
  DataAcquisitionResponseError,
  RequestIdDoesNotExistError,
  UnknownCaptureTypeError,
  CancellationFailedError,
};