1 | var Board = require("./board");
|
2 | var Fn = require("./fn");
|
3 | var events = require("events");
|
4 | var util = require("util");
|
5 | var Collection = require("./mixins/collection");
|
6 | var within = require("./mixins/within");
|
7 |
|
8 |
|
9 |
|
10 | var priv = new Map();
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 | function median(input) {
|
29 |
|
30 | var sorted = input.sort(function(a, b) {
|
31 | return a - b;
|
32 | });
|
33 | var len = sorted.length;
|
34 | var half = Math.floor(len / 2);
|
35 |
|
36 |
|
37 |
|
38 | return len % 2 ? sorted[half] : (sorted[half - 1] + sorted[half]) / 2;
|
39 | }
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 | function Sensor(opts) {
|
51 |
|
52 | if (!(this instanceof Sensor)) {
|
53 | return new Sensor(opts);
|
54 | }
|
55 |
|
56 |
|
57 | var resolution = 0x3FF;
|
58 | var raw = null;
|
59 | var last = -1;
|
60 | var samples = [];
|
61 |
|
62 | Board.Component.call(
|
63 | this, opts = Board.Options(opts)
|
64 | );
|
65 |
|
66 | if (!opts.type) {
|
67 | opts.type = "analog";
|
68 | }
|
69 |
|
70 | if (this.io.RESOLUTION &&
|
71 | (this.io.RESOLUTION.ADC &&
|
72 | (this.io.RESOLUTION.ADC !== resolution))) {
|
73 | resolution = this.io.RESOLUTION.ADC;
|
74 | }
|
75 |
|
76 |
|
77 | this.mode = opts.type === "digital" ?
|
78 | this.io.MODES.INPUT :
|
79 | this.io.MODES.ANALOG;
|
80 |
|
81 | this.io.pinMode(this.pin, this.mode);
|
82 |
|
83 |
|
84 |
|
85 | var state = {
|
86 | enabled: typeof opts.enabled === "undefined" ? true : opts.enabled,
|
87 | booleanBarrier: opts.type === "digital" ? 0 : null,
|
88 | intervalId: null,
|
89 | scale: null,
|
90 | value: 0,
|
91 | median: 0,
|
92 | freq: opts.freq || 25,
|
93 | previousFreq: opts.freq || 25,
|
94 | };
|
95 |
|
96 | priv.set(this, state);
|
97 |
|
98 |
|
99 | this.range = opts.range || [0, resolution];
|
100 | this.limit = opts.limit || null;
|
101 | this.threshold = opts.threshold === undefined ? 1 : opts.threshold;
|
102 | this.isScaled = false;
|
103 |
|
104 | this.io[opts.type + "Read"](this.pin, function(data) {
|
105 | raw = data;
|
106 |
|
107 |
|
108 | if (opts.type !== "digital") {
|
109 | samples.push(raw);
|
110 | }
|
111 | }.bind(this));
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 | var eventProcessing = function() {
|
118 | var err, boundary;
|
119 |
|
120 | err = null;
|
121 |
|
122 |
|
123 |
|
124 | if (opts.type === "digital") {
|
125 | this.emit("data", raw);
|
126 |
|
127 |
|
128 | if (last !== raw) {
|
129 | this.emit("change", raw);
|
130 | last = raw;
|
131 | }
|
132 | return;
|
133 | }
|
134 |
|
135 |
|
136 | if (samples.length > 0) {
|
137 |
|
138 | state.median = median(samples);
|
139 | }
|
140 |
|
141 | var roundMedian = Math.round(state.median);
|
142 |
|
143 | this.emit("data", roundMedian);
|
144 |
|
145 |
|
146 |
|
147 | if (state.median <= (last - this.threshold) || state.median >= (last + this.threshold)) {
|
148 | this.emit("change", roundMedian);
|
149 |
|
150 |
|
151 | last = state.median;
|
152 | }
|
153 |
|
154 | if (this.limit) {
|
155 | if (state.median <= this.limit[0]) {
|
156 | boundary = "lower";
|
157 | }
|
158 | if (state.median >= this.limit[1]) {
|
159 | boundary = "upper";
|
160 | }
|
161 |
|
162 | if (boundary) {
|
163 | this.emit("limit", {
|
164 | boundary: boundary,
|
165 | value: roundMedian
|
166 | });
|
167 | this.emit("limit:" + boundary, roundMedian);
|
168 | }
|
169 | }
|
170 |
|
171 |
|
172 | samples.length = 0;
|
173 | }.bind(this);
|
174 |
|
175 |
|
176 | Object.defineProperties(this, {
|
177 | raw: {
|
178 | get: function() {
|
179 | return raw;
|
180 | }
|
181 | },
|
182 | analog: {
|
183 | get: function() {
|
184 | if (opts.type === "digital") {
|
185 | return raw;
|
186 | }
|
187 |
|
188 | return raw === null ? 0 :
|
189 | Fn.map(this.raw, 0, resolution, 0, 255) | 0;
|
190 | },
|
191 | },
|
192 | constrained: {
|
193 | get: function() {
|
194 | if (opts.type === "digital") {
|
195 | return raw;
|
196 | }
|
197 |
|
198 | return raw === null ? 0 :
|
199 | Fn.constrain(this.raw, 0, 255);
|
200 | }
|
201 | },
|
202 | boolean: {
|
203 | get: function() {
|
204 | var state = priv.get(this);
|
205 | var booleanBarrier = state.booleanBarrier;
|
206 | var scale = state.scale || [0, resolution];
|
207 |
|
208 | if (booleanBarrier === null) {
|
209 | booleanBarrier = scale[0] + (scale[1] - scale[0]) / 2;
|
210 | }
|
211 |
|
212 | return this.value > booleanBarrier;
|
213 | }
|
214 | },
|
215 | scaled: {
|
216 | get: function() {
|
217 | var mapped, constrain;
|
218 |
|
219 | if (state.scale && raw !== null) {
|
220 | if (opts.type === "digital") {
|
221 |
|
222 |
|
223 | return state.scale[raw];
|
224 | }
|
225 |
|
226 | mapped = Fn.fmap(raw, this.range[0], this.range[1], state.scale[0], state.scale[1]);
|
227 | constrain = Fn.constrain(mapped, state.scale[0], state.scale[1]);
|
228 |
|
229 | return constrain;
|
230 | }
|
231 | return this.constrained;
|
232 | }
|
233 | },
|
234 | freq: {
|
235 | get: function() {
|
236 | return state.freq;
|
237 | },
|
238 | set: function(newFreq) {
|
239 | state.freq = newFreq;
|
240 | if (state.intervalId) {
|
241 | clearInterval(state.intervalId);
|
242 | }
|
243 |
|
244 | if (state.freq !== null) {
|
245 | state.intervalId = setInterval(eventProcessing, newFreq);
|
246 | }
|
247 | }
|
248 | },
|
249 | value: {
|
250 | get: function() {
|
251 | if (state.scale) {
|
252 | this.isScaled = true;
|
253 | return this.scaled;
|
254 | }
|
255 |
|
256 | return raw;
|
257 | }
|
258 | },
|
259 | resolution: {
|
260 | get: function() {
|
261 | return resolution;
|
262 | }
|
263 | }
|
264 | });
|
265 |
|
266 |
|
267 | if (!!process.env.IS_TEST_MODE) {
|
268 | Object.defineProperties(this, {
|
269 | state: {
|
270 | get: function() {
|
271 | return priv.get(this);
|
272 | }
|
273 | }
|
274 | });
|
275 | }
|
276 |
|
277 |
|
278 |
|
279 | if (state.enabled) {
|
280 | this.freq = state.freq;
|
281 | }
|
282 | }
|
283 |
|
284 | util.inherits(Sensor, events.EventEmitter);
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 |
|
297 |
|
298 |
|
299 |
|
300 |
|
301 |
|
302 | Object.assign(Sensor.prototype, within);
|
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 |
|
309 |
|
310 | Sensor.prototype.enable = function() {
|
311 | var state = priv.get(this);
|
312 |
|
313 |
|
314 | if (!state.enabled) {
|
315 | this.freq = state.freq || state.previousFreq;
|
316 | }
|
317 |
|
318 | return this;
|
319 | };
|
320 |
|
321 |
|
322 |
|
323 |
|
324 |
|
325 |
|
326 |
|
327 | Sensor.prototype.disable = function() {
|
328 | var state = priv.get(this);
|
329 |
|
330 |
|
331 | if (state.enabled) {
|
332 | state.enabled = false;
|
333 | state.previousFreq = state.freq;
|
334 | this.freq = null;
|
335 | }
|
336 |
|
337 | return this;
|
338 | };
|
339 |
|
340 |
|
341 |
|
342 |
|
343 |
|
344 |
|
345 |
|
346 |
|
347 |
|
348 |
|
349 |
|
350 |
|
351 | Sensor.prototype.scale = function(low, high) {
|
352 | this.isScaled = true;
|
353 |
|
354 | priv.get(this).scale = Array.isArray(low) ?
|
355 | low : [low, high];
|
356 |
|
357 | return this;
|
358 | };
|
359 |
|
360 |
|
361 |
|
362 |
|
363 |
|
364 |
|
365 |
|
366 |
|
367 |
|
368 | Sensor.prototype.scaleTo = function(low, high) {
|
369 | var scale = Array.isArray(low) ? low : [low, high];
|
370 | return Fn.map(this.raw, 0, this.resolution, scale[0], scale[1]);
|
371 | };
|
372 |
|
373 |
|
374 |
|
375 |
|
376 |
|
377 |
|
378 |
|
379 |
|
380 |
|
381 | Sensor.prototype.fscaleTo = function(low, high) {
|
382 | var scale = Array.isArray(low) ? low : [low, high];
|
383 | return Fn.fmap(this.raw, 0, this.resolution, scale[0], scale[1]);
|
384 | };
|
385 |
|
386 |
|
387 |
|
388 |
|
389 |
|
390 |
|
391 |
|
392 |
|
393 |
|
394 | Sensor.prototype.booleanAt = function(barrier) {
|
395 | priv.get(this).booleanBarrier = barrier;
|
396 | return this;
|
397 | };
|
398 |
|
399 |
|
400 |
|
401 |
|
402 |
|
403 |
|
404 |
|
405 |
|
406 |
|
407 | function Sensors(numsOrObjects) {
|
408 | if (!(this instanceof Sensors)) {
|
409 | return new Sensors(numsOrObjects);
|
410 | }
|
411 |
|
412 | Object.defineProperty(this, "type", {
|
413 | value: Sensor
|
414 | });
|
415 |
|
416 | Collection.Emitter.call(this, numsOrObjects);
|
417 | }
|
418 |
|
419 | util.inherits(Sensors, Collection.Emitter);
|
420 |
|
421 |
|
422 | Collection.installMethodForwarding(
|
423 | Sensors.prototype, Sensor.prototype
|
424 | );
|
425 |
|
426 |
|
427 | Sensor.Collection = Sensors;
|
428 |
|
429 |
|
430 | if (!!process.env.IS_TEST_MODE) {
|
431 | Sensor.purge = function() {
|
432 | priv.clear();
|
433 | };
|
434 | }
|
435 |
|
436 |
|
437 | module.exports = Sensor;
|