diff --git a/MDBT42Q/diract.js b/MDBT42Q/diract.js deleted file mode 100644 index 991aa00..0000000 --- a/MDBT42Q/diract.js +++ /dev/null @@ -1,303 +0,0 @@ -/** - * Copyright reelyActive 2017-2020 - * We believe in an open Internet of Things - * - * DirAct is jointly developed by reelyActive and Code Blue Consulting - */ - -// User-configurable constants -const INSTANCE_ID = [ 0x00, 0x00, 0x00, 0x01 ]; -const NAMESPACE_FILTER_ID = [ 0xc0, 0xde, 0xb1, 0x0e, 0x1d, - 0xd1, 0xe0, 0x1b, 0xed, 0x0c ]; -const RSSI_THRESHOLD = -85; -const SCAN_DURATION_MILLISECONDS = 200; -const SCAN_INTERVAL_MILLISECONDS = 4800; -const ADVERTISING_INTERVAL_MILLISECONDS = 500; -const DISABLE_SERIAL_SERVICE = false; - -// Eddystone protocol constants -const EDDYSTONE_UUID = 'feaa'; -const EDDYSTONE_UID_FRAME = 0x00; -const EDDYSTONE_NAMESPACE_OFFSET = 2; -const EDDYSTONE_NAMESPACE_LENGTH = 10; -const EDDYSTONE_INSTANCE_OFFSET = 14; - -// DirAct constants -const CODE_BLUE_MANUFACTURER_ID = 0x0583; -const DIRACT_FRAME = 0x01; -const DIRACT_DEFAULT_COUNT_LENGTH = 0x07; -const DIRACT_INSTANCE_LENGTH = 4; -const MAX_NUMBER_STRONGEST = 3; -const MAX_BATTERY_VOLTAGE = 3.0; -const MIN_BATTERY_VOLTAGE = 2.0; -const MAX_RSSI_TO_ENCODE = -28; -const MIN_RSSI_TO_ENCODE = -92; -const MAX_ACCELERATION_TO_ENCODE = 2; -const MAX_ACCELERATION_MAGNITUDE = 0x1f; -const INVALID_ACCELERATION_CODE = 0x20; -const SCAN_OPTIONS = { - filters: [ - { manufacturerData: { 0x0583: {} } }, - { services: [ EDDYSTONE_UUID ] } - ] -}; - -// Global variables -let devicesInRange = {}; -let sensorData = [ 0x82, 0x08, 0x3f ]; -let advertisingOptions = { - interval: ADVERTISING_INTERVAL_MILLISECONDS, - showName: false, - manufacturer: CODE_BLUE_MANUFACTURER_ID, - manufacturerData: [] -}; -let encodedBattery = 0x3f; -let cyclicCount = 0; - - -/** - * Encode the battery percentage. - * @return {Number} The battery percentage. - */ -function encodeBatteryPercentage() { - let voltage = NRF.getBattery(); - - if(voltage <= MIN_BATTERY_VOLTAGE) { - return 0x00; - } - if(voltage >= MAX_BATTERY_VOLTAGE) { - return 0x3f; - } - - return Math.round(0x3f * (voltage - MIN_BATTERY_VOLTAGE) / - (MAX_BATTERY_VOLTAGE - MIN_BATTERY_VOLTAGE)); -} - - -/** - * Update the sensor data (battery & acceleration) for the advertising packet. - */ -function updateSensorData() { - - // Update the battery measurement each time the cyclic count resets - if(cyclicCount === 0) { - encodedBattery = encodeBatteryPercentage(); - } - - sensorData[2] = ((INVALID_ACCELERATION_CODE << 6) & 0xc0) | - (encodedBattery & 0x3f); -} - - -/** - * Handle the given device discovered on scan and process further if - * Eddystone-UID or DirAct. - * @param {BluetoothDevice} device The discovered device. - */ -function handleDiscoveredDevice(device) { - let isEddystone = (device.hasOwnProperty('services') && - device.hasOwnProperty('serviceData') && - (device.services[0] === EDDYSTONE_UUID)); - let isCodeBlue = (device.hasOwnProperty('manufacturer') && - device.manufacturer === CODE_BLUE_MANUFACTURER_ID); - - if(isEddystone) { - let isEddystoneUID = (device.serviceData[EDDYSTONE_UUID][0] === - EDDYSTONE_UID_FRAME); - if(isEddystoneUID) { - handleEddystoneUidDevice(device.serviceData[EDDYSTONE_UUID], - device.rssi, device.id); - } - } - else if(device.manufacturer === CODE_BLUE_MANUFACTURER_ID) { - let isDirAct = (device.manufacturerData[0] === DIRACT_FRAME); - if(isDirAct) { - handleDirActDevice(device.manufacturerData, device.rssi, device.id); - } - } -} - - -/** - * Handle the given Eddystone-UID device, adding to the devices in range if - * it meets the filter criteria. - * @param {Array} serviceData The Eddystone service data. - * @param {Number} rssi The received signal strength. - * @param {String} deviceId The device's 48-bit advertiser address. - */ -function handleEddystoneUidDevice(serviceData, rssi, deviceId) { - for(let cByte = 0; cByte < EDDYSTONE_NAMESPACE_LENGTH; cByte++) { - if(serviceData[EDDYSTONE_NAMESPACE_OFFSET + cByte] !== - NAMESPACE_FILTER_ID[cByte]) { - return; - } - } - - let instance = []; - - for(let cByte = 0; cByte < DIRACT_INSTANCE_LENGTH; cByte++) { - instance.push(serviceData[EDDYSTONE_INSTANCE_OFFSET + cByte]); - } - - updateDevicesInRange(deviceId, instance, rssi); -} - - -/** - * Handle the given DirAct device, adding to the devices in range if - * it meets the filter criteria. - * @param {Array} manufacturerData The DirAct manufacturer data. - * @param {Number} rssi The received signal strength. - * @param {String} deviceId The device's 48-bit advertiser address. - */ -function handleDirActDevice(manufacturerData, rssi, deviceId) { - let instance = []; - - for(let cByte = 2; cByte <= 5; cByte++) { - instance.push(manufacturerData[cByte]); - } - - updateDevicesInRange(deviceId, instance, rssi); -} - - -/** - * Update the list of devices in range with the given device info. - * @param {String} deviceId The device's 48-bit advertiser address. - * @param {Array} instance The DirAct 4-byte instance id. - * @param {Number} rssi The received signal strength. - */ -function updateDevicesInRange(deviceId, instance, rssi) { - let isDeviceInList = devicesInRange.hasOwnProperty(deviceId); - - if(isDeviceInList) { - let device = devicesInRange[deviceId]; - device.rssi = (device.rssi + rssi) / 2; - } - else { - devicesInRange[deviceId] = { instance: instance, rssi: rssi }; - } -} - - -/** - * Determine the strongest devices in range, based on rssi. - * @param {Number} limit The number of devices to limit to. - * @return {Array} Array of the advertiser addresses of the strongest devices. - */ -function determineStrongestDevicesInRange(limit) { - let strongest = []; - let rssiList = []; - - for(let deviceId in devicesInRange) { - let rssi = devicesInRange[deviceId].rssi; - - if(strongest.length === 0) { - strongest.push(deviceId); - rssiList.push(rssi); - } - else { - for(let cStrongest = 0; cStrongest < strongest.length; cStrongest++) { - if(rssi > rssiList[cStrongest]) { - strongest.splice(cStrongest, 0, deviceId); - rssiList.splice(cStrongest, 0, rssi); - cStrongest = strongest.length; - } - else if((cStrongest < limit) && (cStrongest === (strongest.length - 1))) { - strongest.push(deviceId); - rssiList.push(rssi); - cStrongest = strongest.length; - } - } - } - } - - return strongest.slice(0, limit); -} - - -/** - * Encode the given RSSI. - * @param {Number} rssi The given RSSI. - * @return {Number} The encoded RSSI. - */ -function encodeRssi(rssi) { - rssi = Math.round(rssi); // TODO: do we need this? - - if(rssi >= MAX_RSSI_TO_ENCODE) { - return 0x3f; - } - if(rssi <= MIN_RSSI_TO_ENCODE) { - return 0x00; - } - return rssi - MIN_RSSI_TO_ENCODE; -} - - -/** - * Append the instance and rssi of the nearest devices to the given data array. - * @param {Array} data The manufacturerData array. - */ -function appendNearestDevices(data) { - let strongest = determineStrongestDevicesInRange(MAX_NUMBER_STRONGEST); - - strongest.forEach(function(deviceId) { - let device = devicesInRange[deviceId]; - - data.push(device.instance[0], device.instance[1], device.instance[2], - device.instance[3], encodeRssi(device.rssi)); - }); -} - - -/** - * Compile the DirAct manufacturer data. - */ -function compileManufacturerData() { - let data = [ - DIRACT_FRAME, - DIRACT_DEFAULT_COUNT_LENGTH, - INSTANCE_ID[0], INSTANCE_ID[1], INSTANCE_ID[2], INSTANCE_ID[3], - sensorData[0], sensorData[1], sensorData[2] - ]; - - appendNearestDevices(data); - - cyclicCount = (cyclicCount + 1) % 8; // Increment cyclic count - - data[1] = (cyclicCount << 5) + (data.length - 2); - - return data; -} - - -/** - * Stop scanning, update and advertise updated manufacturer data. - */ -function updateNearestAndAdvertise() { - NRF.setScan(); // Stop scanning - updateSensorData(); - advertisingOptions.manufacturerData = compileManufacturerData(); - NRF.setAdvertising({}, advertisingOptions); // Start advertising - setTimeout(diract, SCAN_INTERVAL_MILLISECONDS); // Schedule next scan -} - - -/** - * Start scanning and set timeout to stop and compile. - */ -function diract() { - devicesInRange = {}; // Reset previous results - NRF.setScan(handleDiscoveredDevice, SCAN_OPTIONS); // Start scanning... - setTimeout(updateNearestAndAdvertise, SCAN_DURATION_MILLISECONDS); // ...stop -} - - -// Begin DirAct execution -diract(); - - -// Optionally disable serial service -if(DISABLE_SERIAL_SERVICE) { - NRF.setServices({}, { uart: false }); -} \ No newline at end of file diff --git a/images/diract-aos8-filtering.png b/images/diract-aos8-filtering.png new file mode 100644 index 0000000..32bd9e1 Binary files /dev/null and b/images/diract-aos8-filtering.png differ diff --git a/images/diract-frame.png b/images/diract-frame.png new file mode 100644 index 0000000..c682e3b Binary files /dev/null and b/images/diract-frame.png differ diff --git a/images/diract-logo-black.png b/images/diract-logo-black.png new file mode 100644 index 0000000..949eff3 Binary files /dev/null and b/images/diract-logo-black.png differ diff --git a/images/diract-logo-white.png b/images/diract-logo-white.png new file mode 100644 index 0000000..0a52db8 Binary files /dev/null and b/images/diract-logo-white.png differ diff --git a/images/diract-physics.gif b/images/diract-physics.gif new file mode 100644 index 0000000..927e915 Binary files /dev/null and b/images/diract-physics.gif differ diff --git a/images/header.png b/images/header.png new file mode 100644 index 0000000..4dfa7d0 Binary files /dev/null and b/images/header.png differ diff --git a/images/how-diract-works-advertising.png b/images/how-diract-works-advertising.png new file mode 100644 index 0000000..099c753 Binary files /dev/null and b/images/how-diract-works-advertising.png differ diff --git a/images/how-diract-works-observing.png b/images/how-diract-works-observing.png new file mode 100644 index 0000000..712df9c Binary files /dev/null and b/images/how-diract-works-observing.png differ diff --git a/images/how-diract-works-processing.png b/images/how-diract-works-processing.png new file mode 100644 index 0000000..2cf95b4 Binary files /dev/null and b/images/how-diract-works-processing.png differ diff --git a/images/how-diract-works-ranking.png b/images/how-diract-works-ranking.png new file mode 100644 index 0000000..022a2cb Binary files /dev/null and b/images/how-diract-works-ranking.png differ diff --git a/images/icon.png b/images/icon.png new file mode 100644 index 0000000..6736f43 Binary files /dev/null and b/images/icon.png differ diff --git a/images/reelyactive-developers-logo-nav.png b/images/reelyactive-developers-logo-nav.png new file mode 100644 index 0000000..fc302a0 Binary files /dev/null and b/images/reelyactive-developers-logo-nav.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..d4c7d35 --- /dev/null +++ b/index.html @@ -0,0 +1,403 @@ + + +
+ + + + +Proximity interaction detection software for Bluetooth beacons
+
+
+ Learn how DirAct detects real-time proximity interactions, and why.
+DirAct observes what's in proximity and advertises this data for any device in range to interpret
+ +
+
+
+
+ DirAct can run on standard battery-powered beacons and other Bluetooth Low Energy devices
+Open source, MIT-licensed versions of the DirAct software written in JavaScript are maintained for Espruino devices, and are available on our GitHub at github.com/reelyactive/diract.
+The following tutorials provide step-by-step instructions to program devices with DirAct software:
+DirAct has been successfully ported to mass-produced beacons, such as the Minew E8, based on the same popular nRF52 chipset used by Espruino devices.
+ +DirAct is tightly integrated with Pareto Anywhere open source middleware
+The following tutorials provide step-by-step instructions to install and run Pareto Anywhere:
+For a lightweight software implementation see /diract-digester.
+ +Never (p)underestimate the importance of location, location, location!
+Anyone who has attempted precise ranging or real-time location using only the signal strength of Bluetooth (or other 2.4GHz signals) should question the effectiveness of this approach. That is because 2.4GHz signals are attenuated by water, which makes up over half of the human body. In other words:
+++Depending on where a transceiver is worn on the human body will greatly affect the signal strength of packets transmitted to, or received from, devices in proximity.
+
+ DirAct (which is derived from Directed interAction) takes advantage of the attenuating effect of the human body when it is implemented as a chest-worn badge. In this case, face-to-face interactions are clearly distinguished by relative signal strength alone.
+ +The implementation of DirAct
+The DirAct specification includes a definition of each DirAct frame. A single DirAct frame may reside in the payload of a BLE advertising packet. Observing the DirAct frame specification ensures that any device which receives such a packet may correctly interpret its semantics.
+The DirAct proximity frame is structured as follows. Each of the nearest elements is optional, meaning that the frame length may be 9, 14, 19 or 24 bytes depending on the number of compatible devices detected in proximity.
+
+ The DirAct digest frame shall be documented here in the near future.
+ +The DirAct logo is a trademark of reelyActive
+
+
+ Continue exploring our open architecture and all its applications.
+