// Copyright (c) 2018 Intel Corporation. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
'use strict';
const rclnodejs = require('bindings')('rclnodejs');
const { Clock, ROSClock } = require('./clock.js');
const { ClockType } = Clock;
const Node = require('./node.js');
const { Parameter, ParameterType } = require('./parameter.js');
const Time = require('./time.js');
const USE_SIM_TIME_PARAM = 'use_sim_time';
const CLOCK_TOPIC = '/clock';
/**
* @class - Class representing a TimeSource in ROS
*/
class TimeSource {
/**
* Create a TimeSource.
* @param {Node} node - The node to be attached.
*/
constructor(node) {
this._node = node;
this._associatedClocks = [];
this._clockSubscription = undefined;
this._lastTimeSet = new Time(0, 0, ClockType.ROS_TIME);
this._isRosTimeActive = false;
if (this._node) {
this.attachNode(this._node);
}
}
get isRosTimeActive() {
return this._isRosTimeActive;
}
set isRosTimeActive(enabled) {
if (this.isRosTimeActive === enabled) return;
this._isRosTimeActive = enabled;
for (const clock in this._associatedClocks) {
clock.isRosTimeActive = enabled;
}
if (enabled) {
this._subscribeToClockTopic();
} else if (this._node && this._clockSubscription) {
this._node.destroySubscription(this._clockSubscription);
this._node._clockSubscription = null;
}
}
/**
* Return status that whether the ROS time is active.
* @name TimeSource#get:isRosTimeActive
* @function
* @return {boolean} Return true if the time is active, otherwise return false.
*/
get isRosTimeActive() {
return this._isRosTimeActive;
}
/**
* Set the status of time.
* @param {boolean} enabled - Set the ROS time to be active if enabled is true.
* @name TimeSource#set:isRosTimeActive
* @function
* @return {undefined}
*/
set isRosTimeActive(enabled) {
if (this._isRosTimeActive === enabled) return;
this._isRosTimeActive = enabled;
this._associatedClocks.forEach((clock) => {
clock.isRosTimeActive = enabled;
});
if (enabled) {
this._subscribeToClockTopic();
}
}
/**
* Attach the clock to a Node object.
* @param {Node} node - The node to be attached.
* @return {undefined}
*/
attachNode(node) {
if (!node instanceof rclnodejs.ShadowNode) {
throw new TypeError('Invalid argument, must be type of Node');
}
if (this._node) {
this.detachNode();
}
this._node = node;
if (!node.hasParameter(USE_SIM_TIME_PARAM)) {
node.declareParameter(
new Parameter(USE_SIM_TIME_PARAM, ParameterType.PARAMETER_BOOL, false)
);
}
const useSimTimeParam = node.getParameter(USE_SIM_TIME_PARAM);
if (useSimTimeParam.type !== ParameterType.PARAMETER_NOT_SET) {
if (useSimTimeParam.type === ParameterType.PARAMETER_BOOL) {
this._isRosTimeActive = useSimTimeParam.value;
} else {
node
.getLogger()
.error(
`Invalid type for parameter ${USE_SIM_TIME_PARAM} ${useSimTimeParam.type} should be bool`
);
}
} else {
node
.getLogger()
.debug(
`${USE_SIM_TIME_PARAM}' parameter not set, using wall time by default`
);
}
if (this.isRosTimeActive) {
this._subscribeToClockTopic();
}
node.addOnSetParametersCallback(this.onParameterEvent.bind(this));
}
/**
* Detach the node which the clock have attached.
* @return {undefined}
*/
detachNode() {
if (this._clockSubscription) {
if (!this._node) {
throw new Error(
'Unable to destroy previously created clock subscription'
);
}
this._node.destroySubscription(this._clockSubscription);
}
this._clockSubscription = undefined;
this._node = undefined;
}
/**
* Attach the clock to a TimeSource object.
* @param {Clock} clock - The node to be attached.
* @return {undefined}
*/
attachClock(clock) {
if (!(clock instanceof ROSClock)) {
throw new TypeError('Only clocks with type ROS_TIME can be attached.');
}
clock.rosTimeOverride = this._lastTimeSet;
clock.isRosTimeActive = this._isRosTimeActive;
this._associatedClocks.push(clock);
}
_clockCallback(msg) {
this._lastTimeSet = Time.fromMsg(msg);
this._associatedClocks.forEach((clock) => {
clock.rosTimeOverride = this._lastTimeSet;
});
}
_subscribeToClockTopic() {
if (!this._clockSubscription && this._node) {
this._clockSubscription = this._node.createSubscription(
'builtin_interfaces/msg/Time',
CLOCK_TOPIC,
this._clockCallback.bind(this)
);
}
}
onParameterEvent(parameters = []) {
for (const parameter of parameters) {
if (parameter.name === USE_SIM_TIME_PARAM) {
if (parameter.type === ParameterType.PARAMETER_BOOL) {
this.isRosTimeActive = parameter.value;
} else if (this._node) {
this._node
.getLogger()
.error(
`${USE_SIM_TIME_PARAM} parameter set to something besides a bool`
);
}
break;
}
}
return {
successful: true,
reason: '',
};
}
}
module.exports = TimeSource;