1 | var Board = require("./board");
|
2 | var Expander = require("./expander");
|
3 | var Pins = Board.Pins;
|
4 | var Collection = require("./mixins/collection");
|
5 | var Fn = require("./fn");
|
6 | var Emitter = require("events").EventEmitter;
|
7 | var util = require("util");
|
8 |
|
9 | var priv = new Map();
|
10 |
|
11 |
|
12 | var 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 |
|
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 |
|
67 | var 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 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
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 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 | function 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 |
|
144 |
|
145 |
|
146 |
|
147 |
|
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 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
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 |
|
181 |
|
182 |
|
183 | if (!Number.isNaN(pinValue) && this.pin !== pinValue) {
|
184 | this.pin = pinValue;
|
185 | }
|
186 | }
|
187 |
|
188 |
|
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 |
|
237 | if (this.startAt !== null && this.startAt !== undefined) {
|
238 | this.speed(this.startAt);
|
239 | }
|
240 | }
|
241 |
|
242 | util.inherits(ESC, Emitter);
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 | ESC.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 |
|
265 |
|
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 |
|
277 |
|
278 |
|
279 | if (history.length === 0) {
|
280 | noInterval = true;
|
281 | }
|
282 |
|
283 |
|
284 |
|
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 |
|
341 |
|
342 |
|
343 | ESC.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 |
|
360 |
|
361 |
|
362 |
|
363 |
|
364 |
|
365 | {
|
366 | name: "forward",
|
367 | abbr: "fwd",
|
368 | value: 1
|
369 | },
|
370 | |
371 |
|
372 |
|
373 |
|
374 |
|
375 |
|
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 |
|
394 |
|
395 |
|
396 | ESC.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 |
|
413 |
|
414 |
|
415 |
|
416 |
|
417 | function 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 |
|
429 | util.inherits(ESCs, Collection);
|
430 |
|
431 |
|
432 |
|
433 |
|
434 |
|
435 |
|
436 |
|
437 |
|
438 |
|
439 |
|
440 |
|
441 |
|
442 |
|
443 |
|
444 |
|
445 |
|
446 |
|
447 |
|
448 |
|
449 |
|
450 |
|
451 |
|
452 |
|
453 |
|
454 |
|
455 |
|
456 |
|
457 |
|
458 | Collection.installMethodForwarding(
|
459 | ESCs.prototype, ESC.prototype
|
460 | );
|
461 |
|
462 |
|
463 |
|
464 |
|
465 | ESC.Array = ESCs;
|
466 | ESC.Collection = ESCs;
|
467 |
|
468 |
|
469 | if (!!process.env.IS_TEST_MODE) {
|
470 | ESC.Controllers = Controllers;
|
471 | ESC.purge = function() {
|
472 | priv.clear();
|
473 | };
|
474 | }
|
475 |
|
476 | module.exports = ESC;
|