UNPKG

9.9 kBJavaScriptView Raw
1var Board = require("./board");
2var Expander = require("./expander");
3var Pins = Board.Pins;
4var Collection = require("./mixins/collection");
5var Fn = require("./fn");
6var Emitter = require("events").EventEmitter;
7var util = require("util");
8
9var priv = new Map();
10
11
12var Controllers = {
13 PCA9685: {
14 initialize: {
15 value: function(opts) {
16 var state = priv.get(this);
17
18 this.address = opts.address || 0x40;
19 this.pwmRange = opts.pwmRange || [544, 2400];
20 this.frequency = opts.frequency || 50;
21
22 state.expander = Expander.get({
23 address: this.address,
24 controller: this.controller,
25 bus: this.bus,
26 pwmRange: this.pwmRange,
27 frequency: this.frequency,
28 });
29
30 this.pin = state.expander.normalize(opts.pin);
31 }
32 },
33 write: {
34 writable: true,
35 value: function(pin, microseconds) {
36 var state = priv.get(this);
37 state.expander.servoWrite(pin, microseconds);
38 }
39 }
40 },
41 DEFAULT: {
42 initialize: {
43 value: function(opts) {
44
45 // When in debug mode, if pin is not a PWM pin, emit an error
46 if (opts.debug && !this.board.pins.isServo(this.pin)) {
47 Board.Pins.Error({
48 pin: this.pin,
49 type: "PWM",
50 via: "Servo",
51 });
52 }
53
54 this.io.servoConfig(this.pin, this.pwmRange[0], this.pwmRange[1]);
55 }
56 },
57 write: {
58 writable: true,
59 value: function(pin, microseconds) {
60 microseconds |= 0;
61 this.io.servoWrite(pin, microseconds);
62 }
63 }
64 }
65};
66
67var Devices = {
68 FORWARD: {
69 deviceName: {
70 get: function() {
71 return "FORWARD";
72 }
73 },
74 dir: {
75 value: function(speed, dir) {
76 if (dir.name === "forward") {
77 return this.speed(speed);
78 }
79 }
80 }
81 },
82 FORWARD_REVERSE: {
83 deviceName: {
84 get: function() {
85 return "FORWARD_REVERSE";
86 }
87 },
88 dir: {
89 value: function(speed, dir) {
90 if (dir.name === "forward") {
91 return this.speed(Fn.fscale(speed, 0, 100, this.neutral, this.range[1]));
92 } else {
93 return this.speed(Fn.fscale(speed, 0, 100, this.neutral, this.range[0]));
94 }
95 }
96 }
97 },
98 FORWARD_BRAKE_REVERSE: {
99 deviceName: {
100 get: function() {
101 return "FORWARD_BRAKE_REVERSE";
102 }
103 },
104 dir: {
105 value: function(speed, dir) {
106
107 /*
108 As far as I can tell, this isn't possible.
109
110 To enable reverse, the brakes must first be applied,
111 but it's not nearly as simple as it sounds since there
112 appears to be a timing factor that differs across
113 speed controllers.
114 */
115
116 if (dir.name === "forward") {
117 this.speed(Fn.fscale(speed, 0, 100, this.neutral, this.range[1]));
118 } else {
119 this.speed(Fn.fscale(speed, 0, 100, this.neutral, this.range[0]));
120 }
121 }
122 }
123 }
124};
125
126/**
127 * ESC
128 * @constructor
129 *
130 * @param {Object} opts Options: pin, range
131 * @param {Number} pin Pin number
132 */
133
134function ESC(opts) {
135 if (!(this instanceof ESC)) {
136 return new ESC(opts);
137 }
138
139 var controller = null;
140 var pinValue;
141 var device;
142 var state = {
143 // All speed history for this ESC
144 // history = [
145 // {
146 // timestamp: Date.now(),
147 // speed: speed
148 // }
149 // ];
150 history: [],
151 value: 0
152 };
153
154 Board.Component.call(
155 this, opts = Board.Options(opts)
156 );
157
158 priv.set(this, state);
159
160 this.startAt = typeof opts.startAt !== "undefined" ? opts.startAt : null;
161 this.neutral = opts.neutral;
162 this.range = opts.range || [0, 100];
163 this.pwmRange = opts.pwmRange || [544, 2400];
164 this.interval = null;
165
166 // StandardFirmata on Arduino allows controlling
167 // servos from analog pins.
168 // If we're currently operating with an Arduino
169 // and the user has provided an analog pin name
170 // (eg. "A0", "A5" etc.), parse out the numeric
171 // value and capture the fully qualified analog
172 // pin number.
173 if (typeof opts.controller === "undefined" && Pins.isFirmata(this)) {
174 if (typeof pinValue === "string" && pinValue[0] === "A") {
175 pinValue = this.io.analogPins[+pinValue.slice(1)];
176 }
177
178 pinValue = +pinValue;
179
180 // If the board's default pin normalization
181 // came up with something different, use the
182 // the local value.
183 if (!Number.isNaN(pinValue) && this.pin !== pinValue) {
184 this.pin = pinValue;
185 }
186 }
187
188 // Allow users to pass in custom device types
189 device = typeof opts.device === "string" ?
190 Devices[opts.device] : opts.device;
191
192 if (!device) {
193 device = Devices.FORWARD;
194 }
195
196 if (opts.controller && typeof opts.controller === "string") {
197 controller = Controllers[opts.controller.toUpperCase()];
198 } else {
199 controller = opts.controller;
200 }
201
202 if (!controller) {
203 controller = Controllers.DEFAULT;
204 }
205
206 Object.defineProperties(this, Object.assign({}, device, controller, {
207 value: {
208 get: function() {
209 return state.value;
210 }
211 },
212 history: {
213 get: function() {
214 return state.history.slice(-5);
215 }
216 },
217 last: {
218 get: function() {
219 return state.history[state.history.length - 1] || {
220 last: null
221 };
222 }
223 }
224 }));
225
226 this.initialize(opts);
227
228 if (this.deviceName !== "FORWARD") {
229 if (Number.isNaN(+this.neutral)) {
230 throw new Error("Directional speed controllers require a neutral point from 0-100 (number)");
231 }
232
233 this.startAt = this.neutral;
234 }
235
236 // Match either null or undefined, but not 0
237 if (this.startAt !== null && this.startAt !== undefined) {
238 this.speed(this.startAt);
239 }
240}
241
242util.inherits(ESC, Emitter);
243
244/**
245 * speed
246 *
247 * Set the ESC's speed
248 *
249 * @param {Float} speed 0...100 (full range)
250 *
251 * @return {ESC} instance
252 */
253
254ESC.prototype.speed = function(speed) {
255 var state = priv.get(this);
256 var history = state.history;
257 var noInterval = false;
258 var steps = 0;
259 var lspeed, hspeed;
260
261 speed = Fn.constrain(speed, this.range[0], this.range[1]);
262
263 if (this.interval) {
264 // Bail out if speed is the same as whatever was
265 // last _provided_
266 if (this.value === speed) {
267 return this;
268 } else {
269 clearInterval(this.interval);
270 this.interval = null;
271 }
272 }
273
274 state.value = speed;
275
276 // This is the very first speed command being received.
277 // Safe to assume that the ESC and Brushless motor are
278 // not yet moving.
279 if (history.length === 0) {
280 noInterval = true;
281 }
282
283 // Bail out if speed is the same as whatever was
284 // last _written_
285
286 if (this.last.speed === speed) {
287 return this;
288 }
289
290 lspeed = this.last.speed;
291 hspeed = speed;
292 steps = Math.ceil(Math.abs(lspeed - hspeed));
293
294 if (!steps || steps === 1) {
295 noInterval = true;
296 }
297
298 if (noInterval) {
299 this.write(this.pin, Fn.fscale(speed, 0, 100, this.pwmRange[0], this.pwmRange[1]));
300
301 history.push({
302 timestamp: Date.now(),
303 speed: speed
304 });
305 return this;
306 }
307
308 var throttle = lspeed;
309
310 this.interval = setInterval(function() {
311
312 if (hspeed > throttle) {
313 throttle++;
314 } else {
315 throttle--;
316 }
317
318 this.write(this.pin, Fn.fscale(throttle, 0, 100, this.pwmRange[0], this.pwmRange[1]));
319
320 history.push({
321 timestamp: Date.now(),
322 speed: throttle
323 });
324
325 if (steps) {
326 steps--;
327
328 if (!steps) {
329 clearInterval(this.interval);
330 this.interval = null;
331 }
332 }
333 }.bind(this), 1);
334
335 return this;
336};
337
338
339/**
340 * brake Stop the ESC by hitting the brakes ;)
341 * @return {Object} instance
342 */
343ESC.prototype.brake = function() {
344 var state = priv.get(this);
345 var speed = this.neutral || 0;
346
347 this.speed(speed);
348
349 state.history.push({
350 timestamp: Date.now(),
351 speed: speed
352 });
353
354 return this;
355};
356
357[
358 /**
359 * forward Set forward speed
360 * fwd Set forward speed
361 *
362 * @param {Number} 0-100, 0 is stopped, 100 is fastest
363 * @return {Object} this
364 */
365 {
366 name: "forward",
367 abbr: "fwd",
368 value: 1
369 },
370 /**
371 * reverse Set revese speed
372 * rev Set revese speed
373 *
374 * @param {Number} 0-100, 0 is stopped, 100 is fastest
375 * @return {Object} this
376 */
377 {
378 name: "reverse",
379 abbr: "rev",
380 value: 0
381 }
382].forEach(function(dir) {
383 var method = function(speed) {
384 this.dir(speed, dir);
385 return this;
386 };
387
388 ESC.prototype[dir.name] = ESC.prototype[dir.abbr] = method;
389});
390
391
392/**
393 * stop Stop the ESC
394 * @return {Object} instance
395 */
396ESC.prototype.stop = function() {
397 var state = priv.get(this);
398 var history = state.history;
399 var speed = this.type === "bidirectional" ? this.neutral : 0;
400
401 this.write(this.pin, Fn.fscale(speed, 0, 100, this.pwmRange[0], this.pwmRange[1]));
402
403 history.push({
404 timestamp: Date.now(),
405 speed: speed
406 });
407
408 return this;
409};
410
411/**
412 * ESC.Collection()
413 * new ESC.Collection()
414 *
415 * Constructs an Array-like instance of all escs
416 */
417function ESCs(numsOrObjects) {
418 if (!(this instanceof ESCs)) {
419 return new ESCs(numsOrObjects);
420 }
421
422 Object.defineProperty(this, "type", {
423 value: ESC
424 });
425
426 Collection.call(this, numsOrObjects);
427}
428
429util.inherits(ESCs, Collection);
430
431/**
432 *
433 * ESCs, speed(0-100%)
434 *
435 * set all escs to the specified speed from 0-100%
436 *
437 * eg. array.min();
438
439 * ESCs, min()
440 *
441 * set all escs to the minimum throttle
442 *
443 * eg. array.min();
444
445 * ESCs, max()
446 *
447 * set all escs to the maximum throttle
448 *
449 * eg. array.max();
450
451 * ESCs, stop()
452 *
453 * stop all escs
454 *
455 * eg. array.stop();
456 */
457
458Collection.installMethodForwarding(
459 ESCs.prototype, ESC.prototype
460);
461
462
463// Assign ESCs Collection class as static "method" of ESC.
464// TODO: Eliminate .Array for 1.0.0
465ESC.Array = ESCs;
466ESC.Collection = ESCs;
467
468/* istanbul ignore else */
469if (!!process.env.IS_TEST_MODE) {
470 ESC.Controllers = Controllers;
471 ESC.purge = function() {
472 priv.clear();
473 };
474}
475
476module.exports = ESC;