TI SensorTag

From eLinux.org
Jump to: navigation, search


Find my files here.

Overview

BBB interfaceing with Adafruit's BLE Module

The TI BLE SensorTag is a portable low-power module that uses Blueooth Low Energy (BLE, Bluetooth 4.0) and various sensors to communicate data to any BLE reciever.

TI Sensortag Wiki


The python scripts that interface with the BeagleBone Black were gently modified from msaunby's Raspberry Pi Scripts.


This project consists of interfacing the TI Sensortag with node.js's image rendering capabilities to make an IMU with graphical display.

System Outline

  • The Beaglebone Black runs BoneServer.js which creates a webserver on port 9090.
  • The user selects the ballAndCube picture, which launches ballAndCube.html, ballAndCube.js, and the sensortag.py locally on the server (i.e. the BLE dongle is connected to the server not the client).
  • The sensortag.py python script outputs data, which is handled as an event streamer by node.js, to stream the data to ballAndCube.js which renders object, and interprets the Gyroscope/Accelerometer Data.
  • Since the python script, which uses gattool, runs in its own process, the data that it receives is streamed without buffering to the client's ballAndCube.js script.

Necessary Modifications

  • Retrieve the original python BLE scripts from this github repo.
  • Execute the sensortag.py script to determine if it executes properly (i.e. your linux distribution comes with pexpect); if not, retrieve pexpect online (I used v3.2)
  • Retrive the BLE Address of your sensorTag. Press the side button on your sensorTag so that it enters discovery mode.
  $hciconfig hci0 up
  $hciconfig
  hci0: ...
        UP RUNNING
        ...
  $hcitool lescan
  LE Scan ...
  90:59:AF:0B:84:57 (unknown)
  90:59:AF:0B:84:57 SensorTag
  ^^^ Is the BLE address of your sensorTag
  • Modify sensortag.py with your given bluetooth_adr (see main), socket communication code, and 2's complement code.
   import socket
   import os, os.path
   ...
   # 2's complement as per StackOverflow post
   def twos_comp(val, bits):
        """compute the 2's compliment of int value val"""
        if( (val&(1<<(bits-1))) != 0 ):
            val = val - (1<<bits)
        return val
   
   ...
   
   # In accelerometer function
   # User "client.send( )" instead of print, to send data to your node.js server
   client.send( "A " + str(xyz[0]) + " " + str(xyz[1]) + " " + str(xyz[2]) )
   # the "A", serves as a tag to distinguish between each of the sensors
   
   ...
   
   # In gyroscope function
   # join two bytes to form 16 bit number, make unsigned.
   dx = twos_comp(  (  (v[1]<<8) + v[0] ) , 16 ) + 65536 
   dy =  twos_comp(  (  (v[3]<<8) + v[2]  ), 16 ) + 65536
   dz =  twos_comp( (  (v[5]<<8) + v[4]  ), 16 ) + 65536
   client.send( "G " + str( dx )+ " " + str( dy ) + " " + str(dz)  )
   
   ...
   
   # In main():
   global client
   soc_fd = "/tmp/py_soc" 
   # ^^ Unix socket file that python makes, modify as necessary
   client = socket.socket( socket.AF_UNIX, socket.SOCK_STREAM )
   client.connect( soc_fd )
   # ^^ sets up a Unix stream socket @ soc_fd (see socket man pages)
   
   ...
   # comment out any sensor init code for sensors you don't wish to use
   ...
   except (KeyboardInterrupt, SystemExit):
        # cleanup code 
       client.close()
       os.remove( soc_fd )
       raise
   except:
       pass
  • Modify BoneServer.js by appending said code:
   function sensorInit( socket ) { 
       /////////////////////////////////////////////////////////////////////////////
       // Executing Python script that communicates with Sensortag
       /////////////////////////////////////////////////////////////////////////////
    
       var net = require('net');
   
       // upon connection to ballAndCube.js via 'SensorTag' message, run sensorTag python script
       socket.on('SensorTag', function () { 
           
           // This server listens on a Unix socket at /tmp/py_soc
           var unixServer = net.createServer(function(client) {
               // Send data as it's recieved 
               client.on( 'data', function(data) {
                   var chunk;
                   // Prints recieved data to console
                   //console.log ("data: ", data.toString() );
                   socket.emit( 'SensorTag' , data.toString() );
               } );
   
               client.on('end', function() {
                   console.log('client disconnected');
               });
           });
            //location of the python socket as specified in sensortag.py 
           unixServer.listen('/tmp/py_soc');
   
            // EXEC python sensortag.py at given location (NOTE SPECIFIC TO YOUR LOCATION)
           var exec = require('child_process').exec, child;
   
           child = exec(  '~/ece497/exercises/realtime/sensortag.py' , function ( error, stdout, stderr) {	
       	    console.log( stdout );
                       if (error !== null) {
       		    console.log('exec error: ' + error );
       	        }	
           } );
       } );
   }

Upon receiving a 'SensorTag' message, the above code initializes a Unix socket server and the sensortag.py script. The SensorTag message comes from ballAndCube.js

  • Call sensorInit in body of
   io.sockets.on('connection', function (socket) {
       ...
       sensorInit(socket)
       ...
   }
  • 'Modify BallAndCube.js to include your BLE initialization and callbacks:
   //Global Varibles added
   var Gx = 0, Gy = 0, Gz = 0;
   var Ax = 0, Ay = 0, Az = 0;
   var lastX = 300*0.5 - 50;
   var lastY = 300*0.5 - 50;
   var WIDTH = 800, HEIGHT = 600;
   var ENABLED_ACC = 1;
   var ENABLED_ACC_Y = 0;


   function connect() {
     if(firstconnect) {
      ...
      socket.on('SensorTag', sensorTagParse ); 
      // SensorTagParse is my callback for any SensorTag Data
      socket.emit( 'SensorTag' );
      ...


   function animate() {
   
       // note: three.js includes requestAnimationFrame shim
       requestAnimationFrame(animate);
   
       cube.position.y = lastY + 0.1*Ay;
       cube.position.x = lastX + 0.1*Ax;
   
       //Bind the cube to displaying within the frame
       if (cube.position.x > WIDTH/2 ){
           cube.position.x = WIDTH/2;
       }
       if (cube.position.x <  -1*WIDTH/2 ){
           cube.position.x = -1*WIDTH/2;
       }
   
       if (cube.position.y > HEIGHT/2 ){
           cube.position.y = HEIGHT/2;
       }
       if (cube.position.y < -HEIGHT/2 ){
           cube.position.y = -HEIGHT/2;
       }
   
       lastX = cube.position.x;
       lastY = cube.position.y;
       //console.log( "lastX = " + lastX );
   
       cube.rotation.y = cube.rotation.y + 3.14159*Gy/250;
       cube.rotation.x = cube.rotation.x + 3.14159*Gx/250;
       cube.rotation.z = cube.rotation.z + 3.14159*Gz/250;
   
       renderer.render(scene, camera);
   }
   function sensorTagParse( data ) {
       var gData, maximum;
       // check if received data is valid 
       if(data.value !== "null") {
           gdata = data.split(" ")
           // Data is in format "[ ID, X, Y, Z ] "
           switch( gdata[0] ) {
               case "G":
                   // We want a log funct to diminish effect of extreme measurements, but still record
                   // 11.09... is log( 2^15), so we are converting back to signed 
                   var y_tmp = (Math.log( gdata[1] ) - 11.09035489) * 100 ; 
                   if ( Math.abs(y_tmp) < 3 ){
                       // if measurement is insignificant, set to zero (i.e. calibrating)
                       Gy = 0;
                   } else {
                       if ( y_tmp < 0 ){
                           // if measurement is negative, move in the negative direction. Also cap max # of pixels moved at a time 
                           Gy = Math.max( y_tmp, -1) * 1.5;
                       } else {
                           Gy = Math.min( y_tmp, 1) * 1.5; // it's positive. also cap max number of pixels moved at a time
                       }
                   }
               // repeat the above process but for x
               var x_tmp = (Math.log( gdata[2] ) - 11.09035489) * 100 ; 
               
               if ( Math.abs(x_tmp) < 0.8 ) {
                   Gx = 0;
               } else {
                   if ( x_tmp < 0 ){
                       Gx = Math.max( x_tmp, -0.5) * 3;
                   } else {
                       Gx = Math.min( x_tmp, 0.5) * 3;
                   }
               }
               // repeat the above process but for z
               var z_tmp = (Math.log( gdata[3] ) - 11.09035489) * 100 ; 
       
               if ( Math.abs(z_tmp) < 3 ) {
                   Gz = 0;
               } else {
                   if ( z_tmp < 0 ){
                       Gz = Math.max( z_tmp, -1) * 1.5;
                   } else {
                       Gz = Math.min( z_tmp, 1) * 1.5;
                   }
               }  
       
               //console.log( "Gx = " + Gx + " Gy = " + Gy  + " Gz = " + Gz) ;
               break;
           case "A":
               if (ENABLED_ACC ){
                   var ax_tmp = gdata[1]
                   if ( ax_tmp < 0 ){
                       Ax = Math.max(ax_tmp*1000, -40);
                   }  else {
                       Ax = Math.min(ax_tmp*1000, 40);
                   }
                   var ay_tmp = gdata[2] - 0.109375;
                    
                   if ( ENABLED_ACC_Y ){
                       if ( Math.abs(ay_tmp) > 0.02 ){
                           if ( ay_tmp < 0 ) {
                               Ay = -1* Math.max(ay_tmp*100, -40);
                           }  else {
                               Ay = -1* Math.min(ay_tmp*100, 40);
                           } 
                       } else {
                           Ay = 0;
                       } 
                   }
                   
                   Az = gdata[3];
                   console.log( "Ax = " + Ax + " Ay = " + Ay + " Az = " + Az)
                   console.log( "Cposx = " + cube.position.x )
                   break;
               }
           }
       }
       status_update("\" + "live" + "\"");
   }

Communication Flow

BallAndCube123.jpg

  • The program is initialized by
   hciconfig hci0 up
   $ ./boneServer.js
  • As the sever initializes, point your browser to the web server on your beagle (i.e 192.168.7.2:9090)
  • Click on the ballAndCube image
  • Wait for communication scripts to initialize
  • You may need to press the side button on your sensorTag for the dongle and the sensorTag to 'discover' each other, and pair

Debug

  • If you get
   Error: listen EADDRINUSE

your socket may already be in use/binded to another instance of gattool. To resolve:

   $ rm /tmp/py_soc #or whatever address you've mapped your socket to
  • You may notice that the resolution of measurements is not too good, this is due partly in fact to the sensor refresh rate of 1Hz (1 sample/sec is transmitted). This can be resolved by changing the sensorTag firmware.