UNPKG

8.94 kBJavaScriptView Raw
1var Board = require("./board");
2var Collection = require("./mixins/collection");
3var EVS = require("./evshield");
4var Pins = Board.Pins;
5var Fn = require("./fn");
6var Emitter = require("events").EventEmitter;
7var util = require("util");
8
9// Button instance private data
10var priv = new Map();
11var aliases = {
12 down: ["down", "press"],
13 up: ["up", "release"]
14};
15
16
17
18var Controllers = {
19 DEFAULT: {
20 initialize: {
21 value: function(opts, dataHandler) {
22 var state = priv.get(this);
23
24 if (Pins.isFirmata(this) && typeof opts.pinValue === "string" && opts.pinValue[0] === "A") {
25 opts.pinValue = this.io.analogPins[+opts.pinValue.slice(1)];
26 }
27
28 this.pin = Number.isNaN(+opts.pinValue) ? opts.pinValue : +opts.pinValue;
29
30 this.io.pinMode(this.pin, this.io.MODES.INPUT);
31
32 // Enable the pullup resistor after setting pin mode
33 if (this.pullup) {
34 this.io.digitalWrite(this.pin, this.io.HIGH);
35 }
36
37 // Enable the pulldown resistor after setting pin mode
38 if (this.pulldown) {
39 this.io.digitalWrite(this.pin, this.io.LOW);
40 }
41
42 this.io.digitalRead(this.pin, function(data) {
43 if (data !== state.last) {
44 dataHandler(data);
45 }
46 });
47 }
48 },
49 toBoolean: {
50 value: function(raw) {
51 return raw === this.downValue;
52 }
53 }
54 },
55
56 TINKERKIT: {
57 initialize: {
58 value: function(opts, dataHandler) {
59 var state = priv.get(this);
60 var value = 0;
61
62 this.io.pinMode(this.pin, this.io.MODES.ANALOG);
63
64 this.io.analogRead(this.pin, function(data) {
65 data = data > 512 ? 1 : 0;
66
67 // This condition simulates digitalRead's
68 // behavior of limiting calls to changes in
69 // pin value.
70 /* istanbul ignore else */
71 if (data !== value && data !== state.last) {
72 dataHandler(data);
73 }
74
75 value = data;
76 });
77 }
78 },
79 toBoolean: {
80 value: function(raw) {
81 return raw === this.downValue;
82 }
83 }
84 },
85
86 EVS_EV3: {
87 initialize: {
88 value: function(opts, dataHandler) {
89 var state = priv.get(this);
90
91 state.previous = 0;
92 state.shield = EVS.shieldPort(opts.pin);
93 state.register = EVS.Touch;
94
95 state.ev3 = new EVS(Object.assign(opts, {
96 io: this.io
97 }));
98 state.ev3.setup(state.shield, EVS.Type_EV3_TOUCH);
99 state.ev3.read(state.shield, EVS.Touch, EVS.Touch_Bytes, function(data) {
100 var value = data[0];
101 // Since i2cRead is continuous regardless of the reading,
102 // and digitalRead is continuous but only called for changes
103 // in reading value, we need to suppress repeated calls to
104 // dataHandler by limiting to only changed values.
105 /* istanbul ignore else */
106 if (state.previous !== value) {
107 dataHandler(value);
108 }
109 state.previous = value;
110 });
111 }
112 },
113 toBoolean: {
114 value: function(raw) {
115 return raw === this.downValue;
116 }
117 }
118 },
119 EVS_NXT: {
120 initialize: {
121 value: function(opts, dataHandler) {
122 var state = priv.get(this);
123
124 state.previous = 0;
125 state.shield = EVS.shieldPort(opts.pin);
126
127 state.ev3 = new EVS(Object.assign(opts, {
128 io: this.io
129 }));
130 state.ev3.setup(state.shield, EVS.Type_ANALOG);
131 state.ev3.read(state.shield, state.shield.analog, EVS.Analog_Bytes, function(data) {
132 var value = data[0] | (data[1] << 8);
133 // Since i2cRead is continuous regardless of the reading,
134 // and digitalRead is continuous but only called for changes
135 // in reading value, we need to suppress repeated calls to
136 // dataHandler by limiting to only changed values.
137 value = value < 300 ? 1 : 0;
138
139 /* istanbul ignore else */
140 if (state.previous !== value) {
141 dataHandler(value);
142 }
143 state.previous = value;
144 });
145 }
146 },
147 toBoolean: {
148 value: function(raw) {
149 return raw === this.downValue;
150 }
151 }
152 }
153};
154
155/**
156 * Button
157 * @constructor
158 *
159 * five.Button();
160 *
161 * five.Button({
162 * pin: 10
163 * });
164 *
165 *
166 * @param {Object} opts [description]
167 *
168 */
169
170function Button(opts) {
171 if (!(this instanceof Button)) {
172 return new Button(opts);
173 }
174
175 var pinValue;
176 var raw;
177 var invert = false;
178 var downValue = 1;
179 var upValue = 0;
180 var controller = null;
181 var state = {
182 interval: null,
183 last: null
184 };
185
186 // Create a debounce boundary on event triggers
187 // this avoids button events firing on
188 // press noise and false positives
189 var trigger = Fn.debounce(function(key) {
190 aliases[key].forEach(function(type) {
191 this.emit(type);
192 }, this);
193 }, 7);
194
195 pinValue = typeof opts === "object" ? opts.pin : opts;
196
197 Board.Component.call(
198 this, opts = Board.Options(opts)
199 );
200
201 opts.pinValue = pinValue;
202
203 if (opts.controller && typeof opts.controller === "string") {
204 controller = Controllers[opts.controller.toUpperCase()];
205 } else {
206 controller = opts.controller;
207 }
208
209 if (controller == null) {
210 controller = Controllers.DEFAULT;
211 }
212
213 Board.Controller.call(this, controller, opts);
214
215 // `holdtime` is used by an interval to determine
216 // if the button has been released within a specified
217 // time frame, in milliseconds.
218 this.holdtime = opts.holdtime || 500;
219
220 // `opts.isPullup` is included as part of an effort to
221 // phase out "isFoo" options properties
222 this.pullup = opts.pullup || opts.isPullup || false;
223
224 this.pulldown = opts.pulldown || opts.isPulldown || false;
225
226 // Turns out some button circuits will send
227 // 0 for up and 1 for down, and some the inverse,
228 // so we can invert our function with this option.
229 // Default to invert in pullup mode, but use opts.invert
230 // if explicitly defined (even if false)
231 invert = typeof opts.invert !== "undefined" ?
232 opts.invert : (this.pullup || false);
233
234 if (invert) {
235 downValue = downValue ^ 1;
236 upValue = upValue ^ 1;
237 }
238
239 state.last = upValue;
240
241 // Create a "state" entry for privately
242 // storing the state of the button
243 priv.set(this, state);
244
245 Object.defineProperties(this, {
246 value: {
247 get: function() {
248 return Number(this.isDown);
249 }
250 },
251 invert: {
252 get: function() {
253 return invert;
254 },
255 set: function(value) {
256 invert = value;
257 downValue = invert ? 0 : 1;
258 upValue = invert ? 1 : 0;
259
260 state.last = upValue;
261 }
262 },
263 downValue: {
264 get: function() {
265 return downValue;
266 },
267 set: function(value) {
268 downValue = value;
269 upValue = value ^ 1;
270 invert = value ? true : false;
271
272 state.last = upValue;
273 }
274 },
275 upValue: {
276 get: function() {
277 return upValue;
278 },
279 set: function(value) {
280 upValue = value;
281 downValue = value ^ 1;
282 invert = value ? true : false;
283
284 state.last = downValue;
285 }
286 },
287 isDown: {
288 get: function() {
289 return this.toBoolean(raw);
290 }
291 }
292 });
293
294 /* istanbul ignore else */
295 if (typeof this.initialize === "function") {
296 this.initialize(opts, function(data) {
297 // Update the raw data value, which
298 // is used by isDown = toBoolean()
299 raw = data;
300
301 if (!this.isDown) {
302 /* istanbul ignore else */
303 if (state.interval) {
304 clearInterval(state.interval);
305 }
306 trigger.call(this, "up");
307 }
308
309 if (this.isDown) {
310 trigger.call(this, "down");
311
312 state.interval = setInterval(function() {
313 /* istanbul ignore else */
314 if (this.isDown) {
315 this.emit("hold");
316 }
317 }.bind(this), this.holdtime);
318 }
319
320 state.last = data;
321 }.bind(this));
322 }
323}
324
325util.inherits(Button, Emitter);
326
327
328/**
329 * Fired when the button is pressed down
330 *
331 * @event
332 * @name down
333 * @memberOf Button
334 */
335
336/**
337 * Fired when the button is held
338 *
339 * @event
340 * @name hold
341 * @memberOf Button
342 */
343
344/**
345 * Fired when the button is released
346 *
347 * @event
348 * @name up
349 * @memberOf Button
350 */
351
352
353/**
354 * Buttons()
355 * new Buttons()
356 */
357
358function Buttons(numsOrObjects) {
359 if (!(this instanceof Buttons)) {
360 return new Buttons(numsOrObjects);
361 }
362
363 Object.defineProperty(this, "type", {
364 value: Button
365 });
366
367 Collection.Emitter.call(this, numsOrObjects);
368}
369
370util.inherits(Buttons, Collection.Emitter);
371
372Collection.installMethodForwarding(
373 Buttons.prototype, Button.prototype
374);
375
376// Assign Buttons Collection class as static "method" of Button.
377Button.Collection = Buttons;
378
379/* istanbul ignore else */
380if (!!process.env.IS_TEST_MODE) {
381 Button.Controllers = Controllers;
382 Button.purge = function() {
383 priv.clear();
384 };
385}
386
387
388module.exports = Button;