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