1 | var Board = require("./board");
|
2 | var Pins = Board.Pins;
|
3 | var Expander = require("./expander");
|
4 | var Emitter = require("events").EventEmitter;
|
5 | var util = require("util");
|
6 | var Collection = require("./mixins/collection");
|
7 | var Fn = require("./fn");
|
8 | var Animation = require("./animation");
|
9 |
|
10 |
|
11 | var priv = new Map();
|
12 |
|
13 | var Controllers = {
|
14 | PCA9685: {
|
15 | initialize: {
|
16 | value: function(opts) {
|
17 | var state = priv.get(this);
|
18 |
|
19 | this.address = opts.address || 0x40;
|
20 | this.pwmRange = opts.pwmRange || [450, 1850];
|
21 | this.frequency = opts.frequency || 50;
|
22 |
|
23 | state.expander = Expander.get({
|
24 | address: this.address,
|
25 | controller: this.controller,
|
26 | bus: this.bus,
|
27 | pwmRange: this.pwmRange,
|
28 | frequency: this.frequency,
|
29 | });
|
30 |
|
31 | this.pin = state.expander.normalize(opts.pin);
|
32 | }
|
33 | },
|
34 | update: {
|
35 | writable: true,
|
36 | value: function(degrees) {
|
37 | var state = priv.get(this);
|
38 | state.expander.servoWrite(this.pin, degrees);
|
39 | }
|
40 | }
|
41 | },
|
42 | Standard: {
|
43 | initialize: {
|
44 | value: function(opts) {
|
45 |
|
46 |
|
47 | if (opts.debug && !this.board.pins.isServo(this.pin)) {
|
48 | Board.Pins.Error({
|
49 | pin: this.pin,
|
50 | type: "PWM",
|
51 | via: "Servo",
|
52 | });
|
53 | }
|
54 |
|
55 | if (Array.isArray(opts.pwmRange)) {
|
56 | this.io.servoConfig(this.pin, opts.pwmRange[0], opts.pwmRange[1]);
|
57 | } else {
|
58 | this.io.pinMode(this.pin, this.mode);
|
59 | }
|
60 | }
|
61 | },
|
62 | update: {
|
63 | writable: true,
|
64 | value: function(degrees) {
|
65 |
|
66 | degrees |= 0;
|
67 |
|
68 |
|
69 | if (this.last && this.last.degrees === degrees) {
|
70 | return this;
|
71 | }
|
72 |
|
73 | this.io.servoWrite(this.pin, degrees);
|
74 | }
|
75 | }
|
76 | }
|
77 | };
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 | function Servo(opts) {
|
87 |
|
88 | if (!(this instanceof Servo)) {
|
89 | return new Servo(opts);
|
90 | }
|
91 |
|
92 | var history = [];
|
93 | var pinValue = typeof opts === "object" ? opts.pin : opts;
|
94 | var controller = null;
|
95 |
|
96 | Board.Component.call(
|
97 | this, opts = Board.Options(opts)
|
98 | );
|
99 |
|
100 | this.deadband = opts.deadband || [90, 90];
|
101 | this.fps = opts.fps || 100;
|
102 | this.offset = opts.offset || 0;
|
103 | this.range = opts.range || [0 - this.offset, 180 - this.offset];
|
104 | this.mode = this.io.MODES.SERVO;
|
105 | this.interval = null;
|
106 | this.value = null;
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 |
|
115 | if (typeof opts.controller === "undefined" && Pins.isFirmata(this)) {
|
116 | if (typeof pinValue === "string" && pinValue[0] === "A") {
|
117 | pinValue = this.io.analogPins[+pinValue.slice(1)];
|
118 | }
|
119 |
|
120 | pinValue = +pinValue;
|
121 |
|
122 |
|
123 |
|
124 |
|
125 | if (!Number.isNaN(pinValue) && this.pin !== pinValue) {
|
126 | this.pin = pinValue;
|
127 | }
|
128 | }
|
129 |
|
130 |
|
131 |
|
132 |
|
133 | this.type = opts.type || "standard";
|
134 |
|
135 |
|
136 |
|
137 | if (opts.isInverted) {
|
138 | console.warn("The 'isInverted' property has been renamed 'invert'");
|
139 | }
|
140 | this.invert = opts.isInverted || opts.invert || false;
|
141 |
|
142 |
|
143 |
|
144 | this.startAt = 90;
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 | if (opts.controller && typeof opts.controller === "string") {
|
155 | controller = Controllers[opts.controller.toUpperCase()];
|
156 | } else {
|
157 | controller = opts.controller;
|
158 | }
|
159 |
|
160 | if (controller == null) {
|
161 | controller = Controllers.Standard;
|
162 | }
|
163 |
|
164 | priv.set(this, {
|
165 | history: history
|
166 | });
|
167 |
|
168 | Board.Controller.call(this, controller, opts);
|
169 |
|
170 | Object.defineProperties(this, {
|
171 | history: {
|
172 | get: function() {
|
173 | return history.slice(-5);
|
174 | }
|
175 | },
|
176 | last: {
|
177 | get: function() {
|
178 | return history[history.length - 1];
|
179 | }
|
180 | },
|
181 | position: {
|
182 | get: function() {
|
183 | return history.length ? history[history.length - 1].degrees : -1;
|
184 | }
|
185 | }
|
186 | });
|
187 |
|
188 | this.initialize(opts);
|
189 |
|
190 |
|
191 |
|
192 | if (opts.startAt !== undefined) {
|
193 | this.startAt = opts.startAt;
|
194 | this.to(opts.startAt);
|
195 | }
|
196 |
|
197 |
|
198 | if (opts.center) {
|
199 | this.center();
|
200 | }
|
201 |
|
202 | if (opts.type === "continuous") {
|
203 | this.stop();
|
204 | }
|
205 | }
|
206 |
|
207 | util.inherits(Servo, Emitter);
|
208 |
|
209 |
|
210 |
|
211 |
|
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 |
|
225 |
|
226 | Servo.prototype.to = function(degrees, time, rate) {
|
227 |
|
228 | var state = priv.get(this);
|
229 | var options = {};
|
230 |
|
231 | if (typeof degrees === "object") {
|
232 |
|
233 | Object.assign(options, degrees);
|
234 |
|
235 | options.duration = degrees.duration || degrees.interval || 1000;
|
236 | options.cuePoints = degrees.cuePoints || [0, 1.0];
|
237 | options.keyFrames = degrees.keyFrames || [
|
238 | null,
|
239 | {
|
240 | value: typeof degrees.degrees === "number" ? degrees.degrees : this.startAt
|
241 | }
|
242 | ];
|
243 |
|
244 | options.oncomplete = function() {
|
245 |
|
246 | process.nextTick(function() {
|
247 | if (typeof degrees.oncomplete === "function") {
|
248 | degrees.oncomplete();
|
249 | }
|
250 | this.emit("move:complete");
|
251 | }.bind(this));
|
252 | }.bind(this);
|
253 |
|
254 |
|
255 | state.isRunning = true;
|
256 | state.animation = state.animation || new Animation(this);
|
257 | state.animation.enqueue(options);
|
258 |
|
259 | } else {
|
260 |
|
261 | var target = degrees;
|
262 |
|
263 |
|
264 | degrees = Fn.constrain(degrees, this.range[0], this.range[1]);
|
265 |
|
266 | if (typeof time !== "undefined") {
|
267 |
|
268 | options.duration = time;
|
269 | options.keyFrames = [null, {
|
270 | degrees: degrees
|
271 | }];
|
272 | options.fps = rate || this.fps;
|
273 |
|
274 | this.to(options);
|
275 |
|
276 | } else {
|
277 |
|
278 | this.value = degrees;
|
279 |
|
280 | degrees += this.offset;
|
281 |
|
282 | if (this.invert) {
|
283 | degrees = Fn.map(
|
284 | degrees,
|
285 | 0, 180,
|
286 | 180, 0
|
287 | );
|
288 | }
|
289 |
|
290 | this.update(degrees);
|
291 |
|
292 | if (state.history.length > 5) {
|
293 | state.history.shift();
|
294 | }
|
295 |
|
296 | state.history.push({
|
297 | timestamp: Date.now(),
|
298 | degrees: degrees,
|
299 | target: target
|
300 | });
|
301 | }
|
302 | }
|
303 |
|
304 |
|
305 | return this;
|
306 | };
|
307 |
|
308 |
|
309 |
|
310 |
|
311 |
|
312 |
|
313 |
|
314 |
|
315 | Servo.prototype[Animation.normalize] = function(keyFrames) {
|
316 |
|
317 | var last = this.last ? this.last.target : this.startAt;
|
318 |
|
319 |
|
320 | if (keyFrames[0] === null) {
|
321 | keyFrames[0] = {
|
322 | value: last
|
323 | };
|
324 | }
|
325 |
|
326 |
|
327 | if (typeof keyFrames[0] === "number") {
|
328 | keyFrames[0] = {
|
329 | value: last + keyFrames[0]
|
330 | };
|
331 | }
|
332 |
|
333 | return keyFrames.map(function(frame) {
|
334 | var value = frame;
|
335 |
|
336 |
|
337 | if (frame !== null) {
|
338 |
|
339 | if (typeof frame === "number") {
|
340 | frame = {
|
341 | step: value,
|
342 | };
|
343 | } else {
|
344 | if (typeof frame.degrees === "number") {
|
345 | frame.value = frame.degrees;
|
346 | delete frame.degrees;
|
347 | }
|
348 | if (typeof frame.copyDegrees === "number") {
|
349 | frame.copyValue = frame.copyDegrees;
|
350 | delete frame.copyDegrees;
|
351 | }
|
352 | }
|
353 |
|
354 |
|
355 | if (!frame.easing) {
|
356 | frame.easing = "linear";
|
357 | }
|
358 | }
|
359 | return frame;
|
360 | });
|
361 | };
|
362 |
|
363 |
|
364 |
|
365 |
|
366 |
|
367 |
|
368 | Servo.prototype[Animation.render] = function(position) {
|
369 | return this.to(position[0]);
|
370 | };
|
371 |
|
372 |
|
373 |
|
374 |
|
375 |
|
376 |
|
377 |
|
378 |
|
379 |
|
380 |
|
381 |
|
382 |
|
383 | Servo.prototype.step = function(degrees, time) {
|
384 | return this.to(this.last.target + degrees, time);
|
385 | };
|
386 |
|
387 |
|
388 |
|
389 |
|
390 | Servo.prototype.move = function(degrees, time) {
|
391 | console.warn("Servo.prototype.move has been renamed to Servo.prototype.to");
|
392 |
|
393 | return this.to(degrees, time);
|
394 | };
|
395 |
|
396 |
|
397 |
|
398 |
|
399 |
|
400 |
|
401 |
|
402 |
|
403 | Servo.prototype.min = function(time, rate) {
|
404 | return this.to(this.range[0], time, rate);
|
405 | };
|
406 |
|
407 |
|
408 |
|
409 |
|
410 |
|
411 |
|
412 |
|
413 | Servo.prototype.max = function(time, rate) {
|
414 | return this.to(this.range[1], time, rate);
|
415 | };
|
416 |
|
417 |
|
418 |
|
419 |
|
420 |
|
421 |
|
422 |
|
423 | Servo.prototype.center = function(time, rate) {
|
424 | return this.to(Math.abs((this.range[0] + this.range[1]) / 2), time, rate);
|
425 | };
|
426 |
|
427 |
|
428 |
|
429 |
|
430 | Servo.prototype.home = function() {
|
431 | return this.to(this.startAt);
|
432 | };
|
433 |
|
434 |
|
435 |
|
436 |
|
437 |
|
438 |
|
439 |
|
440 |
|
441 |
|
442 | Servo.prototype.sweep = function(opts) {
|
443 |
|
444 | var options = {
|
445 | keyFrames: [{
|
446 | value: this.range[0]
|
447 | }, {
|
448 | value: this.range[1]
|
449 | }],
|
450 | metronomic: true,
|
451 | loop: true,
|
452 | easing: "inOutSine"
|
453 | };
|
454 |
|
455 |
|
456 | if (Array.isArray(opts)) {
|
457 | options.keyFrames = rangeToKeyFrames(opts);
|
458 | } else {
|
459 | if (typeof opts === "object" && opts !== null) {
|
460 | Object.assign(options, opts);
|
461 |
|
462 | if (Array.isArray(options.range)) {
|
463 | options.keyFrames = rangeToKeyFrames(options.range);
|
464 | }
|
465 | }
|
466 | }
|
467 |
|
468 | return this.to(options);
|
469 | };
|
470 |
|
471 | function rangeToKeyFrames(range) {
|
472 | return range.map(function(value) {
|
473 | return { value: value };
|
474 | });
|
475 | }
|
476 |
|
477 |
|
478 |
|
479 |
|
480 |
|
481 | Servo.prototype.stop = function() {
|
482 | var state = priv.get(this);
|
483 |
|
484 | if (state.animation) {
|
485 | state.animation.stop();
|
486 | }
|
487 |
|
488 | if (this.type === "continuous") {
|
489 | this.to(
|
490 | this.deadband.reduce(function(a, b) {
|
491 | return Math.round((a + b) / 2);
|
492 | })
|
493 | );
|
494 | } else {
|
495 | clearInterval(this.interval);
|
496 | }
|
497 |
|
498 | return this;
|
499 | };
|
500 |
|
501 |
|
502 | ["clockWise", "cw", "counterClockwise", "ccw"].forEach(function(api) {
|
503 | Servo.prototype[api] = function(rate) {
|
504 | var range;
|
505 | rate = rate === undefined ? 1 : rate;
|
506 |
|
507 | if (this.type !== "continuous") {
|
508 | this.board.error(
|
509 | "Servo",
|
510 | "Servo.prototype." + api + " is only available for continuous servos"
|
511 | );
|
512 | }
|
513 | if (api === "cw" || api === "clockWise") {
|
514 | range = [rate, 0, 1, this.deadband[1] + 1, this.range[1]];
|
515 | } else {
|
516 | range = [rate, 0, 1, this.deadband[0] - 1, this.range[0]];
|
517 | }
|
518 | return this.to(Fn.scale.apply(null, range) | 0);
|
519 | };
|
520 | });
|
521 |
|
522 |
|
523 |
|
524 |
|
525 |
|
526 |
|
527 |
|
528 |
|
529 |
|
530 | Servo.Continuous = function(pinOrOpts) {
|
531 | var opts = {};
|
532 |
|
533 | if (typeof pinOrOpts === "object") {
|
534 | Object.assign(opts, pinOrOpts);
|
535 | } else {
|
536 | opts.pin = pinOrOpts;
|
537 | }
|
538 |
|
539 | opts.type = "continuous";
|
540 | return new Servo(opts);
|
541 | };
|
542 |
|
543 | Servo.Continuous.speeds = {
|
544 |
|
545 | "@4.8V": 0.23,
|
546 | "@5.0V": 0.17,
|
547 | "@6.0V": 0.18
|
548 | };
|
549 |
|
550 |
|
551 |
|
552 |
|
553 |
|
554 | function Servos(numsOrObjects) {
|
555 | if (!(this instanceof Servos)) {
|
556 | return new Servos(numsOrObjects);
|
557 | }
|
558 |
|
559 | Object.defineProperty(this, "type", {
|
560 | value: Servo
|
561 | });
|
562 |
|
563 | Collection.call(this, numsOrObjects);
|
564 | }
|
565 |
|
566 | util.inherits(Servos, Collection);
|
567 |
|
568 |
|
569 |
|
570 |
|
571 |
|
572 |
|
573 |
|
574 |
|
575 |
|
576 |
|
577 |
|
578 |
|
579 |
|
580 |
|
581 |
|
582 |
|
583 |
|
584 |
|
585 |
|
586 |
|
587 |
|
588 |
|
589 |
|
590 |
|
591 |
|
592 |
|
593 |
|
594 |
|
595 |
|
596 | Collection.installMethodForwarding(
|
597 | Servos.prototype, Servo.prototype
|
598 | );
|
599 |
|
600 |
|
601 |
|
602 |
|
603 |
|
604 |
|
605 |
|
606 | Servos.prototype[Animation.normalize] = function(keyFrameSet) {
|
607 | return keyFrameSet.map(function(keyFrames, index) {
|
608 | if (keyFrames !== null) {
|
609 | var servo = this[index];
|
610 |
|
611 |
|
612 | if (servo instanceof Servos) {
|
613 | servo = servo[0];
|
614 | }
|
615 |
|
616 | var last = servo.last ? servo.last.target : servo.startAt;
|
617 |
|
618 |
|
619 | if (keyFrames[0] === null) {
|
620 | keyFrames[0] = {
|
621 | value: last
|
622 | };
|
623 | }
|
624 |
|
625 | if (Array.isArray(keyFrames)) {
|
626 | if (keyFrames[0] === null) {
|
627 | keyFrameSet[index][0] = {
|
628 | value: last
|
629 | };
|
630 | }
|
631 | }
|
632 |
|
633 | return this[index][Animation.normalize](keyFrames);
|
634 | }
|
635 | return keyFrames;
|
636 | }, this);
|
637 | };
|
638 |
|
639 |
|
640 |
|
641 |
|
642 |
|
643 |
|
644 | Servos.prototype[Animation.render] = function(position) {
|
645 | return this.each(function(servo, i) {
|
646 | servo.to(position[i]);
|
647 | });
|
648 | };
|
649 |
|
650 |
|
651 |
|
652 |
|
653 | Servo.Array = Servos;
|
654 | Servo.Collection = Servos;
|
655 |
|
656 |
|
657 |
|
658 | Servo.prototype.write = Servo.prototype.move;
|
659 |
|
660 |
|
661 | if (!!process.env.IS_TEST_MODE) {
|
662 | Servo.Controllers = Controllers;
|
663 | Servo.purge = function() {
|
664 | priv.clear();
|
665 | };
|
666 | }
|
667 |
|
668 | module.exports = Servo;
|
669 |
|
670 |
|
671 |
|
672 |
|
673 |
|
674 |
|
675 |
|
676 |
|
677 |
|
678 |
|
679 |
|