diff --git a/lib/cc2650.js b/lib/cc2650.js index 59b5b9f..43b5c43 100644 --- a/lib/cc2650.js +++ b/lib/cc2650.js @@ -1,7 +1,6 @@ // http://processors.wiki.ti.com/index.php/CC2650_SensorTag_User's_Guide var NobleDevice = require('noble-device'); - var Common = require('./common'); var MPU9250_UUID = 'f000aa8004514000b000000000000000'; @@ -26,6 +25,15 @@ var LUXOMETER_CONFIG_UUID = 'f000aa7204514000b000000000000000' var LUXOMETER_DATA_UUID = 'f000aa7104514000b000000000000000'; var LUXOMETER_PERIOD_UUID = 'f000aa7304514000b000000000000000'; +// For the audio profile see here: +// http://processors.wiki.ti.com/index.php/BLESDK-2.2.x-CC2650RC_Developers_Guide#TI_Audio_Profile +var AUDIO_UUID = 'f000b00004514000b000000000000000'; +var AUDIO_CONFIG_UUID = 'f000b00104514000b000000000000000'; +var AUDIO_STREAM_UUID = 'f000b00204514000b000000000000000'; + +var fs = require('fs'); +var audioFileName = 'data/audio'; // File to store audio data + var CC2650SensorTag = function(peripheral) { NobleDevice.call(this, peripheral); Common.call(this); @@ -36,6 +44,7 @@ var CC2650SensorTag = function(peripheral) { this.onMPU9250ChangeBinded = this.onMPU9250Change.bind(this); this.onLuxometerChangeBinded = this.onLuxometerChange.bind(this); + this.onAudioChangeBinded = this.onAudioChange.bind(this); // audio added }; CC2650SensorTag.is = function(peripheral) { @@ -346,4 +355,43 @@ CC2650SensorTag.prototype.convertSimpleKeyData = function(data, callback) { callback(left, right, reedRelay); }; +// Listener on 'AudioChange' +CC2650SensorTag.prototype.onAudioChange = function(data) { + this.convertAudioData(data, function(audio) { + this.emit('AudioChange', audio); + }.bind(this)); +}; + +// Append audio data to a file +CC2650SensorTag.prototype.convertAudioData = function(data, callback) { + var audioData = Buffer.from(data); + if(audioData.length == 1) { + if(audioData.readUInt8(0) == 0x00) { // 0x00 Received -> means the end of audio transmission + callback(false); + } + } else { + fs.appendFile(audioFileName, audioData, function (err) { + if(err) throw err; + }); + } + callback(true); +}; + +// Audio notifications +CC2650SensorTag.prototype.notifyAudioConfig = function(callback) { + this.notifyCharacteristic(AUDIO_UUID, AUDIO_CONFIG_UUID, true, this.onAudioChangeBinded, callback); +}; + +CC2650SensorTag.prototype.unnotifyAudioConfig = function(callback) { + this.notifyCharacteristic(AUDIO_UUID, AUDIO_CONFIG_UUID, false, this.onAudioChangeBinded, callback); +}; + +CC2650SensorTag.prototype.notifyAudioStream = function(callback) { + this.notifyCharacteristic(AUDIO_UUID, AUDIO_STREAM_UUID, true, this.onAudioChangeBinded, callback); +}; + +CC2650SensorTag.prototype.unnotifyAudioStream = function(callback) { + this.notifyCharacteristic(AUDIO_UUID, AUDIO_STREAM_UUID, false, this.onAudioChangeBinded, callback); +}; + module.exports = CC2650SensorTag; diff --git a/scripts/audio.py b/scripts/audio.py new file mode 100644 index 0000000..34660a4 --- /dev/null +++ b/scripts/audio.py @@ -0,0 +1,225 @@ +''' +/* + * Filename: audio_frame_serial_print.py + * + * Description: This tool is used to decode audio frames from the + * CC2650ARC, the CC2650STK development kits and the CC2650 LaunchPad with + * CC3200AUDBOOST booster pack. These frames will saved to a wav file for + * playback. This script expects audio compressed in ADPCM format. + * + * Copyright (C) 2016-2017 Texas Instruments Incorporated - http://www.ti.com/ + * + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Texas Instruments Incorporated nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * +*/ +''' +from struct import unpack, pack +import struct +import wave +from serial import Serial +from serial import SerialException +from time import time +import time +import winsound + +tic1_stepsize_Lut = [ + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, + 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, + 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, + 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, + 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493,10442,11487,12635,13899, + 15289,16818,18500,20350,22385,24623,27086,29794, 32767 +] + +tic1_IndexLut = [ + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 +] + +SI_Dec = 0 +PV_Dec = 0 + +def tic1_DecodeSingle(nibble): + global SI_Dec + global PV_Dec + + step = tic1_stepsize_Lut[SI_Dec] + cum_diff = step>>3; + + SI_Dec += tic1_IndexLut[nibble]; + + if SI_Dec < 0: + SI_Dec = 0 + if SI_Dec > 88: + SI_Dec = 88; + + if nibble & 4: + cum_diff += step + if nibble & 2: + cum_diff += step>>1 + if nibble & 1: + cum_diff += step>>2; + + if nibble & 8: + if PV_Dec < (-32767+cum_diff): + PV_Dec = -32767 + else: + PV_Dec -= cum_diff + else: + if PV_Dec > (0x7fff-cum_diff): + PV_Dec = 0x7fff + else: + PV_Dec += cum_diff + + return PV_Dec; + +decoded = [] +buf = '' + +def decode_adpcm(_buf): + global decoded + global buf + global SI_Dec + global PV_Dec + + buf = _buf + + for b in _buf: + b,= unpack('B', b) + decoded.append(pack('h', tic1_DecodeSingle(b & 0xF))) + decoded.append(pack('h', tic1_DecodeSingle(b >> 4))) + + +def save_wav(): + global decoded + + filename = time.strftime("pdm_test_%Y-%m-%d_%H-%M-%S_adpcm") + + print "saving file" + w = wave.open("" + filename + ".wav", "w") + w.setnchannels(1) + w.setframerate(16000) + w.setsampwidth(2) + w.writeframes(''.join(decoded)) + w.close() + print "...DONE..." + + #clear stuff for next stream + SI_Dec = 0 + PV_Dec = 0 + decoded = [] + missedFrames = 0 + +indata = '' +inbuffer = '' +frameNum = 1 +bufLen = 100 + +lastByteTime = 0 + +prevSeqNum = 0 +missedFrames = 0 + +mFramesCount = 0 + +try: + ser = None + readSoFar = 0 + iter = 0 + frame = bytearray() + + with open('audio', 'rb') as f: # adjust file name here + while 1: + byte = f.read(1) + if not byte: + break + else: + frame.append(byte) + + str = '' + for i in frame: + str += chr(i) + + print len(str) + + while True: + start = bufLen*iter + end = bufLen+bufLen*iter + indata = str[start:end] + readSoFar = len(indata) + + if end > len(str) and len(decoded): + if time.time() - lastByteTime > 2: + #save wav file + save_wav() + print mFramesCount + print iter + exit(0) + elif indata: + inbuffer += indata + + if len(inbuffer) == bufLen: + seqNum, SI_received, PV_received = struct.unpack('BBh', inbuffer[0:4]) + seqNum = (seqNum >> 3) + print "Frame sequence number: %d" % seqNum + + print "HDR_1 local: %d, HDR_1 received: %d" % (SI_Dec, SI_received) + print "HDR_2 local: %d, HDR_2 received: %d" % (PV_Dec, PV_received) + + #always use received PV and SI + PV_Dec = PV_received + SI_Dec = SI_received + + if seqNum > prevSeqNum: + missedFrames = (seqNum - prevSeqNum -1) + else: + missedFrames = ((seqNum + 32) - prevSeqNum - 1) + + prevSeqNum = seqNum + + if missedFrames > 0: + mFramesCount += missedFrames + print "######################### MISSED #########################" + print missedFrames + print "##########################################################" + + decode_adpcm(inbuffer[4:]) + inbuffer = '' + readSoFar = 0 + iter += 1 + + lastByteTime = time.time() + +except SerialException as e: + print "Serial port error" + print e + +finally: + if ser is not None: ser.close() diff --git a/scripts/ble_setup.sh b/scripts/ble_setup.sh new file mode 100644 index 0000000..a0f43f0 --- /dev/null +++ b/scripts/ble_setup.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# BLE connection params +min_conn_interval=6 +max_conn_interval=6 +slave_latency=0 +supervision_timeout=100 + +# Set up BLE connection params +sudo bash -c 'echo '$min_conn_interval' > /sys/kernel/debug/bluetooth/hci0/conn_min_interval' +sudo bash -c 'echo '$max_conn_interval' > /sys/kernel/debug/bluetooth/hci0/conn_max_interval' +sudo bash -c 'echo '$slave_latency' > /sys/kernel/debug/bluetooth/hci0/conn_latency' +sudo bash -c 'echo '$supervision_timeout' > /sys/kernel/debug/bluetooth/hci0/supervision_timeout' + +# Check params +cat /sys/kernel/debug/bluetooth/hci0/conn_min_interval +cat /sys/kernel/debug/bluetooth/hci0/conn_max_interval +cat /sys/kernel/debug/bluetooth/hci0/conn_latency +cat /sys/kernel/debug/bluetooth/hci0/supervision_timeout diff --git a/two_tags.js b/two_tags.js new file mode 100644 index 0000000..d1be7da --- /dev/null +++ b/two_tags.js @@ -0,0 +1,94 @@ +var util = require('util'); +var async = require('async'); +var SensorTag = require('./index'); + +var address1 = "BLE_MAC_ADDRESS1"; // BLE MAC, e.g. 'a1e6f9af4c76' +var address2 = "BLE_MAC_ADDRESS2"; + +var tags = []; +var tags_status = []; + +SensorTag.discoverByUuid(address1, function(tag){ + console.log("found " + tag.uuid); + tags.push(tag); + tags_status.push(false); +}); + +SensorTag.discoverByUuid(address2, function(tag){ + console.log("found " + tag.uuid); + tags.push(tag); + tags_status.push(false); + +}); + +setTimeout(function(){ + console.log("Trying to connect to sensors %s", tags); + + tags.forEach(function(tag, i) { + tag.on('disconnect', function(callback) { + console.log('disconnected!'); + process.exit(0); + }); + tag.connectAndSetUp(function(callback) { + console.log("connected " + tag); + tags_status[i] = true; + + async.series([ + + // Send audio notifications in order to enable audio transmission + function(callback) { + console.log('TAG:#%s\tnotifyAudioConfig',tag.uuid); + tag.notifyAudioConfig(callback); + }, + function(callback) { + setTimeout(callback, 2000); + }, + + function(callback) { + console.log('TAG:#%s\tnotifyAudioStream',tag.uuid); + tag.notifyAudioStream(callback); + }, + function(callback) { + setTimeout(callback, 2000); + }, + + // Fetch audio + function(callback) { + console.log('readAudio'); + tag.on('AudioChange', function(audio) { + if(!audio) { + callback(); + } + }); + }, + + // Send audio de-notifications in order to disable audio transmission + function(callback) { + console.log('TAG:#%s\tunnotifyAudioStream',tag.uuid); + tag.unnotifyAudioStream(callback); + }, + + function(callback) { + setTimeout(callback, 2000); + }, + + function(callback) { + console.log('TAG:#%s\tunnotifyAudioConfig',tag.uuid); + tag.unnotifyAudioConfig(callback); + }, + + function(callback) { + setTimeout(callback, 2000); + }, + + // Disconnect + function(callback) { + console.log('disconnect', tag.uuid); + tag.disconnect(callback); + } + + ]); + }); + }); + +},4000); \ No newline at end of file