UNPKG

8.68 kBJavaScriptView Raw
1var Board = require("./board");
2var events = require("events");
3var util = require("util");
4var Fn = require("./fn");
5
6var sum = Fn.sum;
7var toFixed = Fn.toFixed;
8
9var priv = new Map();
10var axes = ["x", "y", "z"];
11
12var Controllers = {
13 ANALOG: {
14 initialize: {
15 value: function(opts, dataHandler) {
16 var pins = opts.pins || [],
17 sensitivity, resolution,
18 state = priv.get(this),
19 dataPoints = {};
20
21 if (opts.sensitivity === undefined) {
22 throw new Error("Expected a Sensitivity");
23 }
24
25 // 4.88mV / (0.167mV/dps * 2)
26 // 0.67 = 4X
27 // 0.167 = 1X
28 sensitivity = opts.sensitivity;
29 resolution = opts.resolution || 4.88;
30 state.K = resolution / sensitivity;
31
32 pins.forEach(function(pin, index) {
33 this.io.pinMode(pin, this.io.MODES.ANALOG);
34 this.io.analogRead(pin, function(data) {
35 var axis = axes[index];
36 dataPoints[axis] = data;
37 dataHandler(dataPoints);
38 }.bind(this));
39 }, this);
40 }
41 },
42 toNormal: {
43 value: function(raw) {
44 return raw >> 2;
45 }
46 },
47 toDegreesPerSecond: {
48 value: function(raw, rawCenter) {
49 var normal = this.toNormal(raw);
50 var center = this.toNormal(rawCenter);
51 var state = priv.get(this);
52
53 return ((normal - center) * state.K) | 0;
54 }
55 }
56 },
57 // http://www.invensense.com/mems/gyro/mpu6050.html
58 // Default to the +- 250 which has a 131 LSB/dps
59 MPU6050: {
60 initialize: {
61 value: function(opts, dataHandler) {
62 var IMU = require("./imu");
63 var state = priv.get(this),
64 driver = IMU.Drivers.get(this.board, "MPU6050", opts);
65
66 state.sensitivity = opts.sensitivity || 131;
67
68 driver.on("data", function(data) {
69 dataHandler(data.gyro);
70 });
71 }
72 },
73 toNormal: {
74 value: function(raw) {
75 return (raw >> 11) + 127;
76 }
77 },
78 toDegreesPerSecond: {
79 // Datasheet available at https://www.invensense.com/wp-content/uploads/2015/02/MPU-6000-Datasheet1.pdf
80 //
81 // From paragraph 6.1 at page 12
82 // Sensitivity scale factor
83 // FS_SEL=0 131 LSB/dps -> 0,007633588 dps/LSB
84 // FS_SEL=1 65.5 LSB/dps -> 0,015267176 dps/LSB
85 // FS_SEL=2 32.8 LSB/dps -> 0,00304878 dps/LSB
86 // FS_SEL=3 16.4 LSB/dps -> 0,06097561 dps/LSB
87 // Using 4 digits resolution
88 value: function(raw, rawCenter) {
89 var state = priv.get(this);
90
91 return toFixed((raw - rawCenter) / state.sensitivity, 4);
92 }
93 }
94 },
95 BNO055: {
96 initialize: {
97 value: function(opts, dataHandler) {
98 var IMU = require("./imu");
99 var state = priv.get(this),
100 driver = IMU.Drivers.get(this.board, "BNO055", opts);
101
102 // AF p.14, OUTPUT SIGNAL GYROSCOPE, set this to 16 as according to AF.51 the default for the unit register
103 // is degrees. and there may be a bug in the Ada fruit code as it has the setting to radians disabled
104 // but the sensitivity / scale set to 900 which is used for radian reps
105 state.sensitivity = 16;
106
107 driver.on("data", function(data) {
108 dataHandler(data.gyro);
109 });
110 }
111 },
112 toNormal: {
113 value: function(raw) {
114 return raw;
115 }
116 },
117 toDegreesPerSecond: {
118 // Datasheet available at https://cdn-shop.adafruit.com/datasheets/BST_BNO055_DS000_12.pdf
119 //
120 // From Tble 3-22 at page 33
121 // Gyroscope unit settings 1dps = 16 LSB -> resolution 0,0625 dps with +=2000 dps range
122 value: function(raw) {
123 var state = priv.get(this);
124 return toFixed(raw / state.sensitivity, 4);
125 }
126 }
127 },
128};
129
130function Gyro(opts) {
131 if (!(this instanceof Gyro)) {
132 return new Gyro(opts);
133 }
134
135 var controller = null;
136 var isCalibrated = false;
137 var sampleSize = 100;
138
139 var state = {
140 x: {
141 angle: 0,
142 value: 0,
143 previous: 0,
144 calibration: [],
145 stash: [0, 0, 0, 0, 0],
146 center: 0,
147 hasValue: false
148 },
149 y: {
150 angle: 0,
151 value: 0,
152 previous: 0,
153 calibration: [],
154 stash: [0, 0, 0, 0, 0],
155 center: 0,
156 hasValue: false
157 },
158 z: {
159 angle: 0,
160 value: 0,
161 previous: 0,
162 calibration: [],
163 stash: [0, 0, 0, 0, 0],
164 center: 0,
165 hasValue: false
166 }
167 };
168
169 Board.Component.call(
170 this, opts = Board.Options(opts)
171 );
172
173 if (opts.controller && typeof opts.controller === "string") {
174 controller = Controllers[opts.controller.toUpperCase()];
175 } else {
176 controller = opts.controller;
177 }
178
179 if (controller == null) {
180 controller = Controllers.ANALOG;
181 }
182
183 Board.Controller.call(this, controller, opts);
184
185 if (!this.toNormal) {
186 this.toNormal = opts.toNormal || function(raw) {
187 return raw;
188 };
189 }
190
191 if (!this.toDegreesPerSecond) {
192 this.toDegreesPerSecond = opts.toDegreesPerSecond || function(raw) {
193 return raw;
194 };
195 }
196
197 priv.set(this, state);
198
199 if (typeof this.initialize === "function") {
200 this.initialize(opts, function(data) {
201 var isChange = false;
202
203 Object.keys(data).forEach(function(axis) {
204 var value = data[axis];
205 var sensor = state[axis];
206
207 sensor.previous = sensor.value;
208 sensor.stash.shift();
209 sensor.stash.push(value);
210 sensor.hasValue = true;
211 sensor.value = (sum(sensor.stash) / 5) | 0;
212
213 if (!isCalibrated &&
214 (state.x.calibration.length === sampleSize &&
215 state.y.calibration.length === sampleSize &&
216 (this.z === undefined || state.z.calibration.length === sampleSize))) {
217
218 isCalibrated = true;
219 state.x.center = (sum(state.x.calibration) / sampleSize) | 0;
220 state.y.center = (sum(state.y.calibration) / sampleSize) | 0;
221 state.z.center = (sum(state.z.calibration) / sampleSize) | 0;
222
223 state.x.calibration.length = 0;
224 state.y.calibration.length = 0;
225 state.z.calibration.length = 0;
226 } else {
227 if (sensor.calibration.length < sampleSize) {
228 sensor.calibration.push(value);
229 }
230 }
231
232 if (sensor.previous !== sensor.value) {
233 isChange = true;
234 }
235 }, this);
236
237 if (isCalibrated) {
238 state.x.angle += this.rate.x / 100;
239 state.y.angle += this.rate.y / 100;
240 state.z.angle += this.rate.z / 100;
241
242 this.emit("data", {
243 x: this.x,
244 y: this.y,
245 z: this.z
246 });
247
248 if (isChange) {
249 this.emit("change", {
250 x: this.x,
251 y: this.y,
252 z: this.z
253 });
254 }
255 }
256 }.bind(this));
257 }
258
259 Object.defineProperties(this, {
260 isCalibrated: {
261 get: function() {
262 return isCalibrated;
263 },
264 set: function(value) {
265 if (typeof value === "boolean") {
266 isCalibrated = value;
267 }
268 }
269 },
270 pitch: {
271 get: function() {
272 return {
273 rate: toFixed(this.rate.y, 2),
274 angle: toFixed(state.y.angle, 2)
275 };
276 }
277 },
278 roll: {
279 get: function() {
280 return {
281 rate: toFixed(this.rate.x, 2),
282 angle: toFixed(state.x.angle, 2)
283 };
284 }
285 },
286 yaw: {
287 get: function() {
288 return {
289 rate: this.z !== undefined ? toFixed(this.rate.z, 2) : 0,
290 angle: this.z !== undefined ? toFixed(state.z.angle, 2) : 0
291 };
292 }
293 },
294 x: {
295 get: function() {
296 return toFixed(this.toNormal(state.x.value), 4);
297 }
298 },
299 y: {
300 get: function() {
301 return toFixed(this.toNormal(state.y.value), 4);
302 }
303 },
304 z: {
305 get: function() {
306 return state.z.hasValue ? toFixed(this.toNormal(state.z.value), 4) : undefined;
307 }
308 },
309 rate: {
310 get: function() {
311 var x = this.toDegreesPerSecond(state.x.value, state.x.center);
312 var y = this.toDegreesPerSecond(state.y.value, state.y.center);
313 var z = state.z.hasValue ?
314 this.toDegreesPerSecond(state.z.value, state.z.center) : 0;
315
316 return {
317 x: x,
318 y: y,
319 z: z
320 };
321 }
322 }
323 });
324}
325
326Object.defineProperties(Gyro, {
327 TK_4X: {
328 value: 0.67
329 },
330 TK_1X: {
331 value: 0.167
332 }
333});
334
335
336util.inherits(Gyro, events.EventEmitter);
337
338Gyro.prototype.recalibrate = function() {
339 this.isCalibrated = false;
340};
341
342/* istanbul ignore else */
343if (!!process.env.IS_TEST_MODE) {
344 Gyro.Controllers = Controllers;
345 Gyro.purge = function() {
346 priv.clear();
347 };
348}
349module.exports = Gyro;