i2c 0.2.5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,445 @@
1
+ const { describe, it, beforeEach, afterEach } = require('node:test');
2
+ const assert = require('node:assert/strict');
3
+ const I2C = require('../lib/i2c');
4
+
5
+ function createMockDevice(overrides) {
6
+ const noop = () => {};
7
+ return {
8
+ open: (path, cb) => setImmediate(() => cb(null)),
9
+ close: noop,
10
+ setAddress: noop,
11
+ scan: noop,
12
+ read: noop,
13
+ readByte: noop,
14
+ readBlock: noop,
15
+ write: noop,
16
+ writeByte: noop,
17
+ writeBlock: noop,
18
+ writeWord: noop,
19
+ // tracking
20
+ _calls: {},
21
+ ...overrides,
22
+ };
23
+ }
24
+
25
+ function trackCalls(device, method) {
26
+ const original = device[method];
27
+ const calls = [];
28
+ device[method] = function(...args) {
29
+ calls.push(args);
30
+ return original.apply(this, args);
31
+ };
32
+ device._calls[method] = calls;
33
+ return calls;
34
+ }
35
+
36
+ function createWire(address, mockDevice, options) {
37
+ return new Promise((resolve, reject) => {
38
+ function MockDeviceClass() { return mockDevice; }
39
+ const wire = new I2C(address, { _DeviceClass: MockDeviceClass, ...options });
40
+ wire.on('open', () => resolve(wire));
41
+ wire.on('error', reject);
42
+ });
43
+ }
44
+
45
+ describe('I2C', () => {
46
+ let wire;
47
+
48
+ afterEach(() => {
49
+ if (wire) {
50
+ wire.close();
51
+ wire = null;
52
+ }
53
+ });
54
+
55
+ describe('constructor', () => {
56
+ it('opens the device and sets address', async () => {
57
+ const md = createMockDevice();
58
+ const openCalls = trackCalls(md, 'open');
59
+ const addrCalls = trackCalls(md, 'setAddress');
60
+
61
+ wire = await createWire(0x18, md);
62
+ assert.equal(openCalls.length, 1);
63
+ assert.equal(openCalls[0][0], '/dev/i2c-1');
64
+ assert.equal(addrCalls.length, 1);
65
+ assert.equal(addrCalls[0][0], 0x18);
66
+ });
67
+
68
+ it('uses custom device path from options', async () => {
69
+ const md = createMockDevice();
70
+ const openCalls = trackCalls(md, 'open');
71
+
72
+ wire = await createWire(0x18, md, { device: '/dev/i2c-0' });
73
+ assert.equal(openCalls[0][0], '/dev/i2c-0');
74
+ });
75
+
76
+ it('emits error when open fails', async () => {
77
+ const md = createMockDevice({
78
+ open: (path, cb) => setImmediate(() => cb(new Error('No device'))),
79
+ });
80
+ const addrCalls = trackCalls(md, 'setAddress');
81
+
82
+ function MockDeviceClass() { return md; }
83
+ const wire2 = new I2C(0x18, { _DeviceClass: MockDeviceClass });
84
+ const err = await new Promise((resolve) => wire2.on('error', resolve));
85
+ assert.equal(err.message, 'No device');
86
+ assert.equal(addrCalls.length, 0);
87
+ wire2.close();
88
+ });
89
+ });
90
+
91
+ describe('setAddress', () => {
92
+ it('updates address on both JS and native side', async () => {
93
+ const md = createMockDevice();
94
+ const addrCalls = trackCalls(md, 'setAddress');
95
+
96
+ wire = await createWire(0x18, md);
97
+ wire.setAddress(0x20);
98
+ assert.equal(wire.address, 0x20);
99
+ assert.equal(addrCalls.length, 2);
100
+ assert.equal(addrCalls[1][0], 0x20);
101
+ });
102
+ });
103
+
104
+ describe('close', () => {
105
+ it('calls native close and removes exit listener', async () => {
106
+ const md = createMockDevice();
107
+ const closeCalls = trackCalls(md, 'close');
108
+
109
+ wire = await createWire(0x18, md);
110
+ const listenersBefore = process.listenerCount('exit');
111
+ wire.close();
112
+ assert.equal(closeCalls.length, 1);
113
+ assert.equal(process.listenerCount('exit'), listenersBefore - 1);
114
+ wire = null;
115
+ });
116
+
117
+ it('stops streaming on close', async () => {
118
+ const md = createMockDevice({
119
+ readBlock: (cmd, len, cb) => setImmediate(() => cb(null, Buffer.from([1]))),
120
+ });
121
+
122
+ wire = await createWire(0x18, md);
123
+ wire.stream(0x01, 1, 10);
124
+ wire.close();
125
+ assert.equal(wire._streaming, false);
126
+ assert.equal(wire._streamTimer, null);
127
+ wire = null;
128
+ });
129
+ });
130
+
131
+ describe('scan', () => {
132
+ it('filters negative values from scan results', async () => {
133
+ const md = createMockDevice({
134
+ scan: (cb) => {
135
+ const results = new Array(128).fill(-1);
136
+ results[0x18] = 0x18;
137
+ results[0x50] = 0x50;
138
+ setImmediate(() => cb(null, results));
139
+ },
140
+ });
141
+
142
+ wire = await createWire(0x18, md);
143
+ const devices = await new Promise((resolve, reject) => {
144
+ wire.scan((err, data) => err ? reject(err) : resolve(data));
145
+ });
146
+ assert.deepEqual(devices, [0x18, 0x50]);
147
+ });
148
+ });
149
+
150
+ describe('write', () => {
151
+ it('converts array to Buffer before writing', async () => {
152
+ const md = createMockDevice({
153
+ write: (buf, cb) => setImmediate(() => cb(null)),
154
+ });
155
+ const writeCalls = trackCalls(md, 'write');
156
+
157
+ wire = await createWire(0x18, md);
158
+ await new Promise((resolve, reject) => {
159
+ wire.write([0x01, 0x02], (err) => err ? reject(err) : resolve());
160
+ });
161
+ assert.ok(Buffer.isBuffer(writeCalls[0][0]));
162
+ assert.deepEqual([...writeCalls[0][0]], [0x01, 0x02]);
163
+ });
164
+
165
+ it('passes Buffer directly', async () => {
166
+ const md = createMockDevice({
167
+ write: (buf, cb) => setImmediate(() => cb(null)),
168
+ });
169
+ const writeCalls = trackCalls(md, 'write');
170
+ const buf = Buffer.from([0xFF]);
171
+
172
+ wire = await createWire(0x18, md);
173
+ await new Promise((resolve, reject) => {
174
+ wire.write(buf, (err) => err ? reject(err) : resolve());
175
+ });
176
+ assert.strictEqual(writeCalls[0][0], buf);
177
+ });
178
+
179
+ it('passes error to callback', async () => {
180
+ const md = createMockDevice({
181
+ write: (buf, cb) => setImmediate(() => cb(new Error('write failed'))),
182
+ });
183
+
184
+ wire = await createWire(0x18, md);
185
+ const err = await new Promise((resolve) => {
186
+ wire.write([0x01], (e) => resolve(e));
187
+ });
188
+ assert.equal(err.message, 'write failed');
189
+ });
190
+ });
191
+
192
+ describe('writeByte', () => {
193
+ it('writes a single byte', async () => {
194
+ const md = createMockDevice({
195
+ writeByte: (byte, cb) => setImmediate(() => cb(null)),
196
+ });
197
+ const calls = trackCalls(md, 'writeByte');
198
+
199
+ wire = await createWire(0x18, md);
200
+ await new Promise((resolve, reject) => {
201
+ wire.writeByte(0xAB, (err) => err ? reject(err) : resolve());
202
+ });
203
+ assert.equal(calls[0][0], 0xAB);
204
+ });
205
+ });
206
+
207
+ describe('writeBytes', () => {
208
+ it('converts array to Buffer and calls writeBlock', async () => {
209
+ const md = createMockDevice({
210
+ writeBlock: (cmd, buf, cb) => setImmediate(() => cb(null)),
211
+ });
212
+ const calls = trackCalls(md, 'writeBlock');
213
+
214
+ wire = await createWire(0x18, md);
215
+ await new Promise((resolve, reject) => {
216
+ wire.writeBytes(0x10, [0x01, 0x02, 0x03], (err) => err ? reject(err) : resolve());
217
+ });
218
+ assert.equal(calls[0][0], 0x10);
219
+ assert.ok(Buffer.isBuffer(calls[0][1]));
220
+ assert.deepEqual([...calls[0][1]], [0x01, 0x02, 0x03]);
221
+ });
222
+ });
223
+
224
+ describe('read', () => {
225
+ it('reads data from device', async () => {
226
+ const data = [1, 2, 3];
227
+ const md = createMockDevice({
228
+ read: (len, cb) => setImmediate(() => cb(null, data)),
229
+ });
230
+ const calls = trackCalls(md, 'read');
231
+
232
+ wire = await createWire(0x18, md);
233
+ const result = await new Promise((resolve, reject) => {
234
+ wire.read(3, (err, d) => err ? reject(err) : resolve(d));
235
+ });
236
+ assert.deepEqual(result, data);
237
+ assert.equal(calls[0][0], 3);
238
+ });
239
+ });
240
+
241
+ describe('readByte', () => {
242
+ it('reads a single byte', async () => {
243
+ const md = createMockDevice({
244
+ readByte: (cb) => setImmediate(() => cb(null, 0x42)),
245
+ });
246
+
247
+ wire = await createWire(0x18, md);
248
+ const data = await new Promise((resolve, reject) => {
249
+ wire.readByte((err, d) => err ? reject(err) : resolve(d));
250
+ });
251
+ assert.equal(data, 0x42);
252
+ });
253
+ });
254
+
255
+ describe('readBytes', () => {
256
+ it('reads a block of bytes from a register', async () => {
257
+ const buf = Buffer.from([0x10, 0x20, 0x30]);
258
+ const md = createMockDevice({
259
+ readBlock: (cmd, len, cb) => setImmediate(() => cb(null, buf)),
260
+ });
261
+ const calls = trackCalls(md, 'readBlock');
262
+
263
+ wire = await createWire(0x18, md);
264
+ const data = await new Promise((resolve, reject) => {
265
+ wire.readBytes(0x01, 3, (err, d) => err ? reject(err) : resolve(d));
266
+ });
267
+ assert.deepEqual(data, buf);
268
+ assert.equal(calls[0][0], 0x01);
269
+ assert.equal(calls[0][1], 3);
270
+ });
271
+ });
272
+
273
+ describe('stream', () => {
274
+ it('emits data events repeatedly and stops with stopStream', async () => {
275
+ let callCount = 0;
276
+ const md = createMockDevice({
277
+ readBlock: (cmd, len, cb) => {
278
+ callCount++;
279
+ const c = callCount;
280
+ setImmediate(() => cb(null, Buffer.from([c])));
281
+ },
282
+ });
283
+
284
+ wire = await createWire(0x18, md);
285
+ const received = await new Promise((resolve) => {
286
+ const items = [];
287
+ wire.on('data', (evt) => {
288
+ items.push(evt.data[0]);
289
+ if (items.length === 3) {
290
+ wire.stopStream();
291
+ resolve(items);
292
+ }
293
+ });
294
+ wire.stream(0x01, 1, 10);
295
+ });
296
+ assert.deepEqual(received, [1, 2, 3]);
297
+ assert.equal(wire._streaming, false);
298
+ });
299
+
300
+ it('emits error events on read failure', async () => {
301
+ const md = createMockDevice({
302
+ readBlock: (cmd, len, cb) => {
303
+ setImmediate(() => cb(new Error('read error')));
304
+ },
305
+ });
306
+
307
+ wire = await createWire(0x18, md);
308
+ const err = await new Promise((resolve) => {
309
+ wire.on('error', (e) => {
310
+ wire.stopStream();
311
+ resolve(e);
312
+ });
313
+ wire.stream(0x01, 1, 10);
314
+ });
315
+ assert.equal(err.message, 'read error');
316
+ });
317
+
318
+ it('includes metadata in data events', async () => {
319
+ const md = createMockDevice({
320
+ readBlock: (cmd, len, cb) => {
321
+ setImmediate(() => cb(null, Buffer.from([0xFF])));
322
+ },
323
+ });
324
+
325
+ wire = await createWire(0x18, md);
326
+ const evt = await new Promise((resolve) => {
327
+ wire.on('data', (e) => {
328
+ wire.stopStream();
329
+ resolve(e);
330
+ });
331
+ wire.stream(0x02, 1, 10);
332
+ });
333
+ assert.equal(evt.address, 0x18);
334
+ assert.equal(evt.cmd, 0x02);
335
+ assert.equal(evt.length, 1);
336
+ assert.equal(typeof evt.timestamp, 'number');
337
+ });
338
+
339
+ it('uses default delay of 100ms when not specified', async () => {
340
+ const md = createMockDevice({
341
+ readBlock: (cmd, len, cb) => {
342
+ setImmediate(() => cb(null, Buffer.from([1])));
343
+ },
344
+ });
345
+
346
+ wire = await createWire(0x18, md);
347
+ wire.on('data', () => wire.stopStream());
348
+ wire.stream(0x01, 1);
349
+ await new Promise((r) => setTimeout(r, 50));
350
+ });
351
+ });
352
+
353
+ describe('no listener leak', () => {
354
+ it('does not accumulate exit listeners across multiple instances', async () => {
355
+ const before = process.listenerCount('exit');
356
+ const instances = [];
357
+
358
+ for (let i = 0; i < 5; i++) {
359
+ const md = createMockDevice();
360
+ instances.push(await createWire(0x18 + i, md));
361
+ }
362
+
363
+ assert.equal(process.listenerCount('exit'), before + 5);
364
+
365
+ for (const inst of instances) {
366
+ inst.close();
367
+ }
368
+
369
+ assert.equal(process.listenerCount('exit'), before);
370
+ wire = null;
371
+ });
372
+ });
373
+
374
+ describe('double close safety', () => {
375
+ it('calling close() twice does not throw', async () => {
376
+ const md = createMockDevice();
377
+ const closeCalls = trackCalls(md, 'close');
378
+
379
+ wire = await createWire(0x18, md);
380
+ wire.close();
381
+ wire.close(); // should not throw or double-remove listener
382
+ assert.equal(closeCalls.length, 1); // native close called only once
383
+ wire = null;
384
+ });
385
+ });
386
+
387
+ describe('double stream guard', () => {
388
+ it('calling stream() twice does not start parallel loops', async () => {
389
+ let callCount = 0;
390
+ const md = createMockDevice({
391
+ readBlock: (cmd, len, cb) => {
392
+ callCount++;
393
+ setImmediate(() => cb(null, Buffer.from([callCount])));
394
+ },
395
+ });
396
+
397
+ wire = await createWire(0x18, md);
398
+ wire.on('data', () => {}); // consume events
399
+ wire.stream(0x01, 1, 50);
400
+ wire.stream(0x01, 1, 50); // should be ignored
401
+ await new Promise((r) => setTimeout(r, 30));
402
+ wire.stopStream();
403
+ // If double-stream started two loops, callCount would be > 1
404
+ assert.equal(callCount, 1);
405
+ });
406
+ });
407
+
408
+ describe('scan error handling', () => {
409
+ it('passes error to callback without crashing on undefined data', async () => {
410
+ const md = createMockDevice({
411
+ scan: (cb) => {
412
+ setImmediate(() => cb(new Error('scan failed')));
413
+ },
414
+ });
415
+
416
+ wire = await createWire(0x18, md);
417
+ const err = await new Promise((resolve) => {
418
+ wire.scan((err) => resolve(err));
419
+ });
420
+ assert.equal(err.message, 'scan failed');
421
+ });
422
+ });
423
+
424
+ describe('optional callbacks', () => {
425
+ it('write works without callback', async () => {
426
+ const md = createMockDevice({
427
+ write: (buf, cb) => setImmediate(() => cb(null)),
428
+ });
429
+
430
+ wire = await createWire(0x18, md);
431
+ wire.write([0x01]);
432
+ await new Promise((r) => setTimeout(r, 20));
433
+ });
434
+
435
+ it('read works without callback', async () => {
436
+ const md = createMockDevice({
437
+ read: (len, cb) => setImmediate(() => cb(null, [1])),
438
+ });
439
+
440
+ wire = await createWire(0x18, md);
441
+ wire.read(1);
442
+ await new Promise((r) => setTimeout(r, 20));
443
+ });
444
+ });
445
+ });
@@ -1,55 +0,0 @@
1
- Wire = require '../../main'
2
-
3
- # for AK8975
4
- # info: https://github.com/jrowberg/i2cdevlib/blob/master/Arduino/AK8975/AK8975.cpp
5
- # http://stackoverflow.com/questions/4768933/read-two-bytes-into-an-integer
6
-
7
- RANGE_BWIDTH = 0x14
8
- RANGE_BIT = 0x04
9
- RANGE_LENGTH = 0x02
10
- RANGE_2G = 0x00
11
- BANDWIDTH_BIT = 0x02
12
- BANDWIDTH_LENGTH = 0x03
13
- BW_25HZ = 0x00
14
- GET_ID = 0x00
15
-
16
- class Accelerometer
17
-
18
- constructor: (@address) ->
19
- @wire = new Wire @address
20
-
21
- @setRange()
22
- @setBandwidth()
23
-
24
- @wire.on 'data', (data) ->
25
- console.log data
26
-
27
- setRange: ->
28
- @wire.write(RANGE_BWIDTH, [RANGE_BIT, RANGE_LENGTH, RANGE_2G], null)
29
-
30
- testConnection: (callback) ->
31
- @getDeviceID (err, data) ->
32
- data[0] == 0b010
33
-
34
- getDeviceID: (callback) ->
35
- @wire.read GET_ID, 1, callback
36
-
37
- setBandwidth: ->
38
- @wire.write(RANGE_BWIDTH, [BANDWIDTH_BIT, BANDWIDTH_LENGTH, BW_25HZ], null)
39
-
40
- getHeading: ->
41
- @wire.write(0x0A, 0x1);
42
- setTimeout =>
43
- @wire.read 0x03, 6, (err, buffer) ->
44
- pos =
45
- x: ((buffer[1]) << 8) | buffer[0]
46
- y: ((buffer[3]) << 8) | buffer[2]
47
- z: ((buffer[5]) << 8) | buffer[4]
48
- console.log pos
49
- , 10
50
- getMotion: ->
51
- @wire.stream 0x02, 6, 100
52
-
53
-
54
- accel = new Accelerometer(56)
55
- accel.getHeading()
@@ -1,57 +0,0 @@
1
- Wire = require 'i2c'
2
- _ = require 'underscore'
3
-
4
- # BlinkM http://thingm.com/products/blinkm
5
- # firmware http://code.google.com/p/codalyze/wiki/CyzRgb
6
-
7
- TO_RGB = 0x6e
8
- GET_RGB = 0x67
9
- FADE_TO_RGB = 0x63
10
- FADE_TO_HSB = 0x68
11
- GET_ADDRESS = 0x61
12
- SET_ADDRESS = 0x41
13
- SET_FADE = 0x66
14
- GET_VERSION = 0x5a
15
- WRITE_SCRIPT = 0x57
16
- READ_SCRIPT = 0x52
17
- PLAY_SCRIPT = 0x70
18
- STOP_SCRIPT = 0x0f
19
-
20
- class Pixel
21
-
22
- address: 0x01
23
-
24
- constructor: (@address) ->
25
- @wire = new Wire(@address);
26
-
27
- off: ->
28
- @setRGB(0, 0, 0)
29
-
30
- getAddress: (callback) ->
31
- @_read GET_ADDRESS, 1, callback
32
-
33
- getVersion: (callback) ->
34
- @_read GET_VERSION, 1, callback
35
-
36
- setFadeSpeed: (speed) ->
37
- @_send SET_FADE, speed
38
-
39
- setRGB: (r, g, b) ->
40
- @_send TO_RGB, [r, g, b]
41
-
42
- getRGB: (callback) ->
43
- setTimeout =>
44
- @_read GET_RGB, 3, callback
45
- , 200
46
-
47
- fadeToRGB: (r, g, b) ->
48
- @_send FADE_TO_RGB, [r, g, b]
49
-
50
- fadeToHSB: (h, s, b) ->
51
- @_send FADE_TO_HSB, [h, s, b]
52
-
53
- _send: (cmd, values) ->
54
- @wire.writeBytes cmd, values
55
-
56
- _read: (cmd, length, callback) ->
57
- @wire.readBytes cmd, length, callback
package/lib/i2c.coffee DELETED
@@ -1,100 +0,0 @@
1
- _ = require 'underscore'
2
- wire = require '../build/Release/i2c'
3
- EventEmitter = require('events').EventEmitter
4
- tick = setImmediate || process.nextTick
5
-
6
- class i2c extends EventEmitter
7
-
8
- history: []
9
-
10
- constructor: (@address, @options = {}) ->
11
- _.defaults @options,
12
- debug: false
13
- device: "/dev/i2c-1"
14
-
15
- if @options.debug
16
- require('repl').start(
17
- prompt: "i2c > "
18
- ).context.wire = @
19
- process.stdin.emit 'data', '' # trigger repl
20
-
21
- process.on 'exit', => @close()
22
-
23
- @on 'data', (data) =>
24
- @history.push data
25
-
26
- @on 'error', (err) ->
27
- console.log "Error: #{err}"
28
-
29
- @open @options.device, (err) =>
30
- unless err then @setAddress @address
31
-
32
- scan: (callback) ->
33
- wire.scan (err, data) ->
34
- tick ->
35
- callback err, _.filter data, (num) -> return num >= 0
36
-
37
- setAddress: (address) ->
38
- wire.setAddress address
39
- @address = address
40
-
41
- open: (device, callback) ->
42
- wire.open device, (err) ->
43
- tick ->
44
- callback err
45
-
46
- close: ->
47
- wire.close()
48
-
49
- write: (buf, callback) ->
50
- @setAddress @address
51
- unless Buffer.isBuffer(buf) then buf = new Buffer(buf)
52
- wire.write buf, (err) ->
53
- tick ->
54
- callback err
55
-
56
- writeByte: (byte, callback) ->
57
- @setAddress @address
58
- wire.writeByte byte, (err) ->
59
- tick ->
60
- callback err
61
-
62
- writeBytes: (cmd, buf, callback) ->
63
- @setAddress @address
64
- unless Buffer.isBuffer(buf) then buf = new Buffer(buf)
65
- wire.writeBlock cmd, buf, (err) ->
66
- tick ->
67
- callback err
68
-
69
- read: (len, callback) ->
70
- @setAddress @address
71
- wire.read len, (err, data) ->
72
- tick ->
73
- callback err, data
74
-
75
- readByte: (callback) ->
76
- @setAddress @address
77
- wire.readByte (err, data) ->
78
- tick ->
79
- callback err, data
80
-
81
- readBytes: (cmd, len, callback) ->
82
- @setAddress @address
83
- wire.readBlock cmd, len, null, (err, actualBuffer) ->
84
- tick ->
85
- callback err, actualBuffer
86
-
87
- stream: (cmd, len, delay = 100) ->
88
- @setAddress @address
89
- wire.readBlock cmd, len, delay, (err, data) =>
90
- if err
91
- @emit 'error', err
92
- else
93
- @emit 'data',
94
- address : @address
95
- data : data
96
- cmd : cmd
97
- length : len
98
- timestamp : Date.now()
99
-
100
- module.exports = i2c