• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

RobotWebTools / rclnodejs / 19071410104

04 Nov 2025 02:08PM UTC coverage: 83.072% (+0.4%) from 82.711%
19071410104

Pull #1320

github

web-flow
Merge 9cad4567e into 3ad842cc4
Pull Request #1320: feat: add structured error handling with class error hierarchy

1032 of 1365 branches covered (75.6%)

Branch coverage included in aggregate %.

161 of 239 new or added lines in 25 files covered. (67.36%)

29 existing lines in 1 file now uncovered.

2354 of 2711 relevant lines covered (86.83%)

459.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

57.76
/lib/time_source.js
1
// Copyright (c) 2018 Intel Corporation. All rights reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
'use strict';
16

17
const rclnodejs = require('./native_loader.js');
26✔
18
const { Clock, ROSClock } = require('./clock.js');
26✔
19
const { ClockType } = Clock;
26✔
20
const { Parameter, ParameterType } = require('./parameter.js');
26✔
21
const Time = require('./time.js');
26✔
22
const { TypeValidationError, OperationError } = require('./errors.js');
26✔
23

24
const USE_SIM_TIME_PARAM = 'use_sim_time';
26✔
25
const CLOCK_TOPIC = '/clock';
26✔
26

27
/**
28
 * @class - Class representing a TimeSource in ROS
29
 */
30

31
class TimeSource {
32
  /**
33
   * Create a TimeSource.
34
   * @param {Node} node - The node to be attached.
35
   */
36
  constructor(node) {
37
    this._node = node;
693✔
38
    this._associatedClocks = [];
693✔
39
    this._clockSubscription = undefined;
693✔
40
    this._lastTimeSet = new Time(0n, 0n, ClockType.ROS_TIME);
693✔
41
    this._isRosTimeActive = false;
693✔
42

43
    if (this._node) {
693!
44
      this.attachNode(this._node);
693✔
45
    }
46
  }
47

48
  get isRosTimeActive() {
49
    return this._isRosTimeActive;
×
50
  }
51

52
  set isRosTimeActive(enabled) {
53
    if (this.isRosTimeActive === enabled) return;
×
54

55
    this._isRosTimeActive = enabled;
×
56
    for (const clock in this._associatedClocks) {
×
57
      clock.isRosTimeActive = enabled;
×
58
    }
59

60
    if (enabled) {
×
61
      this._subscribeToClockTopic();
×
62
    } else if (this._node && this._clockSubscription) {
×
63
      this._node.destroySubscription(this._clockSubscription);
×
64
      this._node._clockSubscription = null;
×
65
    }
66
  }
67

68
  /**
69
   * Return status that whether the ROS time is active.
70
   * @name TimeSource#get:isRosTimeActive
71
   * @function
72
   * @return {boolean} Return true if the time is active, otherwise return false.
73
   */
74

75
  get isRosTimeActive() {
76
    return this._isRosTimeActive;
696✔
77
  }
78

79
  /**
80
   * Set the status of time.
81
   * @param {boolean} enabled - Set the ROS time to be active if enabled is true.
82
   * @name TimeSource#set:isRosTimeActive
83
   * @function
84
   * @return {undefined}
85
   */
86

87
  set isRosTimeActive(enabled) {
88
    if (this._isRosTimeActive === enabled) return;
×
89

90
    this._isRosTimeActive = enabled;
×
91
    this._associatedClocks.forEach((clock) => {
×
92
      clock.isRosTimeActive = enabled;
×
93
    });
94
    if (enabled) {
×
95
      this._subscribeToClockTopic();
×
96
    }
97
  }
98

99
  /**
100
   * Attach the clock to a Node object.
101
   * @param {Node} node - The node to be attached.
102
   * @return {undefined}
103
   */
104
  attachNode(node) {
105
    if ((!node) instanceof rclnodejs.ShadowNode) {
694!
NEW
106
      throw new TypeValidationError('node', node, 'Node', {
×
107
        entityType: 'time source',
108
      });
109
    }
110

111
    if (this._node) {
694✔
112
      this.detachNode();
693✔
113
    }
114

115
    this._node = node;
694✔
116

117
    if (!node.hasParameter(USE_SIM_TIME_PARAM)) {
694✔
118
      node.declareParameter(
685✔
119
        new Parameter(USE_SIM_TIME_PARAM, ParameterType.PARAMETER_BOOL, false)
120
      );
121
    }
122

123
    const useSimTimeParam = node.getParameter(USE_SIM_TIME_PARAM);
694✔
124
    if (useSimTimeParam.type !== ParameterType.PARAMETER_NOT_SET) {
694!
125
      if (useSimTimeParam.type === ParameterType.PARAMETER_BOOL) {
694!
126
        this._isRosTimeActive = useSimTimeParam.value;
694✔
127
      } else {
128
        node
×
129
          .getLogger()
130
          .error(
131
            `Invalid type for parameter ${USE_SIM_TIME_PARAM} ${useSimTimeParam.type} should be bool`
132
          );
133
      }
134
    } else {
135
      node
×
136
        .getLogger()
137
        .debug(
138
          `${USE_SIM_TIME_PARAM}' parameter not set, using wall time by default`
139
        );
140
    }
141

142
    if (this.isRosTimeActive) {
694✔
143
      this._subscribeToClockTopic();
4✔
144
    }
145

146
    node.addOnSetParametersCallback(this.onParameterEvent.bind(this));
694✔
147
  }
148

149
  /**
150
   * Detach the node which the clock have attached.
151
   * @return {undefined}
152
   */
153
  detachNode() {
154
    if (this._clockSubscription) {
694✔
155
      if (!this._node) {
1!
NEW
156
        throw new OperationError(
×
157
          'Unable to destroy previously created clock subscription',
158
          {
159
            code: 'NO_NODE_ATTACHED',
160
            entityType: 'time source',
161
          }
162
        );
163
      }
164
      this._node.destroySubscription(this._clockSubscription);
1✔
165
    }
166
    this._clockSubscription = undefined;
694✔
167
    this._node = undefined;
694✔
168
  }
169

170
  /**
171
   * Attach the clock to a TimeSource object.
172
   * @param {Clock} clock - The node to be attached.
173
   * @return {undefined}
174
   */
175
  attachClock(clock) {
176
    if (!(clock instanceof ROSClock)) {
697✔
177
      throw new TypeValidationError('clock', clock, 'ROSClock', {
2✔
178
        entityType: 'time source',
179
      });
180
    }
181
    clock.rosTimeOverride = this._lastTimeSet;
695✔
182
    clock.isRosTimeActive = this._isRosTimeActive;
695✔
183
    this._associatedClocks.push(clock);
695✔
184
  }
185

186
  _clockCallback(msg) {
187
    this._lastTimeSet = Time.fromMsg(msg.clock);
4✔
188
    this._associatedClocks.forEach((clock) => {
4✔
189
      clock.rosTimeOverride = this._lastTimeSet;
4✔
190
    });
191
  }
192

193
  _subscribeToClockTopic() {
194
    if (!this._clockSubscription && this._node) {
4!
195
      this._clockSubscription = this._node.createSubscription(
4✔
196
        'rosgraph_msgs/msg/Clock',
197
        CLOCK_TOPIC,
198
        this._clockCallback.bind(this)
199
      );
200
    }
201
  }
202

203
  onParameterEvent(parameters = []) {
×
204
    for (const parameter of parameters) {
1,173✔
205
      if (parameter.name === USE_SIM_TIME_PARAM) {
1,173!
206
        if (parameter.type === ParameterType.PARAMETER_BOOL) {
×
207
          this.isRosTimeActive = parameter.value;
×
208
        } else if (this._node) {
×
209
          this._node
×
210
            .getLogger()
211
            .error(
212
              `${USE_SIM_TIME_PARAM} parameter set to something besides a bool`
213
            );
214
        }
215

216
        break;
×
217
      }
218
    }
219

220
    return {
1,173✔
221
      successful: true,
222
      reason: '',
223
    };
224
  }
225
}
226

227
module.exports = TimeSource;
26✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc