-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathestimote-nearable.js
141 lines (124 loc) · 5.13 KB
/
estimote-nearable.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// Estimote Nearable packet is broadcast as Manufacturer Specific Data,
// with Estimote's Company Identifier, i.e., 0x015d.
// https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers
// Once you obtain the Manufacturer Specific Data, here's how to parse them into
// an Estimote Nearable packet.
function parseEstimoteNearablePacket(data) { // data is a 0-indexed byte array/buffer
// note that depending on the BLE-scanning library you use, the Company ID
// might or might not be part of the Manufacturer Specific Data, and might
// instead be a separate property
//
// most of the time, it's simply the first two bytes of the data, and that's
// what we're assuming here
var companyId = data.readUInt16LE(0);
// Company ID must be Estimote's
if (companyId != 0x015d) { return; }
// byte 2 is "which exactly Estimote packet this is"
// for a Nearable packet version 1 (currently the only one), the value is 0x01
var frameType = data.readUInt8(2);
if (frameType != 0x01) { return; }
// Nearable identifier, this matches the identifier you see in Estimote Cloud
// bytes 3–10 (8 bytes total)
var nearableId = data.toString('hex', 3, 11);
// byte 11 = hardware version
// byte 12 = firmware version
// ***** TEMPERATURE
// byte 13 and the first 4 bits of byte 14 is the temperature in signed,
// fixed-point format, with 4 decimal places
var temperatureRawValue = data.readUInt16LE(13) & 0x0fff
if (temperatureRawValue > 2047) {
// convert a 12-bit unsigned integer to a signed one
temperatureRawValue = temperatureRawValue - 4096;
}
var temperature = temperatureRawValue / 16.0;
// byte 15, 7th bit = is the nearable moving or not
var isMoving = (data.readUInt8(15) & 0b01000000) != 0;
// ***** BATTERY VOLTAGE
// byte 15, 8th bit = voltage measurement type
// we measure voltage under stress (i.e., sticker is broadcasting) and when
// idle, to better understand the state of the battery
// the results are reported interchangably, i.e., one packet will come with
// the 'stress' measurement, another with 'idle', and so on and so forth
var voltageType = (data.readUInt8(15) & 0b10000000) != 0 ? 'stress' : 'idle';
// voltage RAW_VALUE = byte 14, 4 upper bits + byte 15, 6 lower bits
// RAW_VALUE is a 10-bit unsigned integer
// (3 * 1.2 * RAW_VALUE) / 1023) = actual voltage
var voltageRawValue = ((data.readUInt8(14) & 0b11110000) >> 4)
| ((data.readUInt8(15) & 0b00111111) << 4);
var voltage = (3 * 1.2 * voltageRawValue) / 1023;
// ***** ACCELERATION
// byte 16 => acceleration RAW_VALUE on the X axis
// byte 17 => acceleration RAW_VALUE on the Y axis
// byte 18 => acceleration RAW_VALUE on the Z axis
// RAW_VALUE is a signed (two's complement) 8-bit integer
// RAW_VALUE * 15.625 = acceleration in milli-"g-unit" (http://www.helmets.org/g.htm)
var acceleration = {
x: data.readInt8(16) * 15.625,
y: data.readInt8(17) * 15.625,
z: data.readInt8(18) * 15.625
};
// ***** MOTION STATE DURATION
// byte 19 => "current" motion state duration
// byte 20 => "previous" motion state duration
// e.g., if the beacon is currently still, "current" will state how long
// it's been still and "previous" will state how long it's previously been
// in motion before it stopped moving
//
// motion state duration is composed of two parts:
// - lower 6 bits is a NUMBER (unsigned 6-bit integer)
// - upper 2 bits is a unit:
// - 0b00 ("0") => seconds
// - 0b01 ("1") => minutes
// - 0b10 ("2") => hours
// - 0b11 ("3") => days if NUMBER is < 32
// if it's >= 32, then it's "NUMBER - 32" weeks
var parseMotionStateDuration = function(byte) {
var number = byte & 0b00111111;
var unitCode = (byte & 0b11000000) >> 6;
var unit;
if (unitCode == 0) {
unit = 'seconds';
} else if (unitCode == 1) {
unit = 'minutes';
} else if (unitCode == 2) {
unit = 'hours';
} else if (unitCode == 3 && number < 32) {
unit = 'days';
} else {
unit = 'weeks';
number = number - 32;
}
return {number: number, unit: unit};
}
var motionStateDuration = {
current: parseMotionStateDuration(data.readUInt8(19)),
previous: parseMotionStateDuration(data.readUInt8(20))
};
return {
nearableId,
temperature,
isMoving, motionStateDuration, acceleration,
voltageType, voltage
};
}
// example how to scan & parse Estimote Nearable packets with noble
var noble = process.platform === 'darwin' ? require('noble-mac') : require('noble');
noble.on('stateChange', function(state) {
console.log('state has changed', state);
if (state == 'poweredOn') {
var allowDuplicates = true;
noble.startScanning([], allowDuplicates, function(error) {
if (error) {
console.log('error starting scanning', error);
} else {
console.log('started scanning');
}
});
}
});
noble.on('discover', function(peripheral) {
var data = peripheral.advertisement.manufacturerData;
if (!data) { return; }
var nearablePacket = parseEstimoteNearablePacket(data);
if (nearablePacket) { console.log(nearablePacket); }
});