├── index.js ├── .gitignore ├── .gitmodules ├── addon.cpp ├── index.d.ts ├── .travis.yml ├── package.json ├── README.md ├── nodeimu.h ├── testSync.js ├── test.js ├── binding.gyp └── nodeimu.cpp /index.js: -------------------------------------------------------------------------------- 1 | module.exports = exports = require('./out/NodeIMU'); 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | RTIMULib.ini 3 | node_modules 4 | npm-debug.log 5 | out/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "RTIMULib2"] 2 | path = RTIMULib2 3 | url = https://github.com/rupnikj/RTIMULib2.git 4 | ignore = dirty 5 | -------------------------------------------------------------------------------- /addon.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "nodeimu.h" 3 | #include 4 | 5 | NAN_MODULE_INIT(Init) { 6 | NodeIMU::Init(target); 7 | } 8 | 9 | NODE_MODULE(NodeIMU, Init) 10 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | interface Vector { 2 | x: number; 3 | y: number; 4 | z: number; 5 | } 6 | 7 | interface Data { 8 | timestamp: Date; 9 | 10 | accel: Vector; 11 | gyro: Vector; 12 | compass: Vector; 13 | fusionPose: Vector; 14 | 15 | temperature: number; 16 | pressure: number; 17 | humidity: number; 18 | tiltHeading: number; 19 | } 20 | 21 | type Callback = (error: string | null, data: Data) => void; 22 | 23 | type CallbackFunction = (args: Callback) => void 24 | 25 | export class IMU { 26 | getValue: CallbackFunction; 27 | getValueSync: () => Data; 28 | } 29 | 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | sudo: false 4 | language: cpp 5 | addons: 6 | apt: 7 | sources: 8 | - ubuntu-toolchain-r-test 9 | packages: 10 | - g++-4.8 11 | env: 12 | matrix: 13 | - TRAVIS_NODE_VERSION="6" 14 | - TRAVIS_NODE_VERSION="8" 15 | - TRAVIS_NODE_VERSION="10" 16 | - TRAVIS_NODE_VERSION="12" 17 | install: 18 | - rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install $TRAVIS_NODE_VERSION 19 | - if [[ $TRAVIS_NODE_VERSION == "0.8" ]]; then npm install npm@2 && node_modules/.bin/npm install npm; else npm install npm; fi 20 | - mv node_modules npm 21 | - npm/.bin/npm --version 22 | - if [[ $TRAVIS_OS_NAME == "linux" ]]; then export CXX=g++-4.8; fi 23 | - $CXX --version 24 | - npm/.bin/npm install 25 | - node_modules/.bin/node-gyp rebuild 26 | script: "true" 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodeimu", 3 | "version": "2.1.12", 4 | "description": "Native addon for accessing IMU/pressure/humidity/temperature sensors using RTIMULib2", 5 | "license": "BSD-2-Clause-FreeBSD", 6 | "author": "Jan Rupnik ", 7 | "contributors": [ 8 | { 9 | "name": "Jan Rupnik", 10 | "email": "jan.rupnik@ijs.si" 11 | }, 12 | { 13 | "name": "Stéphane Lavirotte", 14 | "email": "stephane@lavirotte.com" 15 | } 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/rupnikj/nodeimu" 20 | }, 21 | "main": "./index.js", 22 | "types": "./index.d.ts", 23 | "dependencies": { 24 | "nan": "^2.14.0", 25 | "node-gyp": "^6.1.0" 26 | }, 27 | "keywords": [ 28 | "raspberry", 29 | "pi", 30 | "sense", 31 | "hat", 32 | "RTIMULib", 33 | "IMU", 34 | "pressure", 35 | "humidity", 36 | "temperature" 37 | ], 38 | "readmeFilename": "README.md" 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | nodeimu 2 | ========= 3 | 4 | [![NPM Version][npm-image]][npm-url] 5 | [![Linux Build][travis-linux-image]][travis-linux-url] 6 | 7 | 8 | Nodejs bindings for accessing IMU/pressure/humidity/temperature data using [RTIMULib2](https://github.com/richards-tech/RTIMULib2.git). The addon uses [nodejs/nan](https://github.com/nodejs/nan.git). It has been tested on [Sense HAT](https://www.raspberrypi.org/products/sense-hat/) and on [GrovePi+](http://www.dexterindustries.com/grovepi/) for Raspberry Pi. 9 | 10 | ## Install 11 | 12 | ``` 13 | git clone https://github.com/rupnikj/nodeimu --recursive && cd nodeimu 14 | npm install node-gyp -g 15 | npm install 16 | ``` 17 | 18 | ## Test 19 | 20 | ``` 21 | node test.js 22 | node testSync.js 23 | ``` 24 | 25 | [npm-image]: https://img.shields.io/npm/v/nodeimu.svg 26 | [npm-url]: https://npmjs.org/package/nodeimu 27 | [travis-linux-image]: https://img.shields.io/travis/rupnikj/nodeimu/master.svg?label=linux 28 | [travis-linux-url]: https://travis-ci.org/rupnikj/nodeimu 29 | -------------------------------------------------------------------------------- /nodeimu.h: -------------------------------------------------------------------------------- 1 | #ifndef NODEIMU_H 2 | #define NODEIMU_H 3 | 4 | #include "RTIMULib.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // tested on node 0.12.7 LSM9DS1 (IMU), LPS25H (pressure), HTS221 (humidity) 11 | 12 | class NodeIMU : public Nan::ObjectWrap { 13 | public: 14 | static NAN_MODULE_INIT(Init) { 15 | v8::Local tpl = Nan::New(New); 16 | tpl->SetClassName(Nan::New("IMU").ToLocalChecked()); 17 | tpl->InstanceTemplate()->SetInternalFieldCount(1); 18 | 19 | Nan::SetPrototypeMethod(tpl, "getValue", GetValue); 20 | Nan::SetPrototypeMethod(tpl, "getValueSync", GetValueSync); 21 | 22 | Nan::Set(target, Nan::New("IMU").ToLocalChecked(), 23 | Nan::GetFunction(tpl).ToLocalChecked()); 24 | } 25 | 26 | 27 | private: 28 | explicit NodeIMU(); 29 | 30 | ~NodeIMU(); 31 | 32 | static NAN_METHOD(New); 33 | 34 | static NAN_METHOD(GetValue); 35 | 36 | static NAN_METHOD(GetValueSync); 37 | 38 | RTIMU *imu; 39 | RTIMUSettings *settings; 40 | RTPressure *pressure; 41 | RTHumidity *humidity; 42 | 43 | }; 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /testSync.js: -------------------------------------------------------------------------------- 1 | var util = require('util') 2 | var nodeimu = require('./index.js'); 3 | var IMU = new nodeimu.IMU(); 4 | 5 | var num = 0; 6 | var numStop = 100; 7 | 8 | console.time("sync"); 9 | 10 | var print_vector3 = function(name, data) { 11 | var sx = data.x >= 0 ? ' ' : ''; 12 | var sy = data.y >= 0 ? ' ' : ''; 13 | var sz = data.z >= 0 ? ' ' : ''; 14 | return util.format('%s: %s%s %s%s %s%s ', name, sx, data.x.toFixed(4), sy, data.y.toFixed(4), sz, data.z.toFixed(4)); 15 | } 16 | 17 | function dispAccel() { 18 | var tic = new Date(); 19 | var data = IMU.getValueSync(); 20 | var toc = new Date(); 21 | 22 | var str = data.timestamp.toISOString() + " "; 23 | str += print_vector3("Accel", data.accel) 24 | // str += print_vector3("Gyro", data.gyro) 25 | // str += print_vector3("Compass", data.compass) 26 | // str += print_vector3("Fusion", data.fusionPose) 27 | 28 | var str2 = ""; 29 | if (data.temperature && data.pressure && data.humidity) { 30 | var str2 = util.format(' %s %s %s', data.temperature.toFixed(4), data.pressure.toFixed(4), data.humidity.toFixed(4)); 31 | } 32 | console.log(str + str2); 33 | num++; 34 | if (num == numStop) { 35 | console.timeEnd("sync"); 36 | } else { 37 | setTimeout(dispAccel, 50 - (toc - tic)); 38 | } 39 | } 40 | 41 | dispAccel(); 42 | 43 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var util = require('util') 2 | var nodeimu = require('./index.js'); 3 | var IMU = new nodeimu.IMU(); 4 | 5 | var num = 0; 6 | var numStop = 100; 7 | 8 | console.time("async"); 9 | 10 | var print_vector3 = function(name, data) { 11 | var sx = data.x >= 0 ? ' ' : ''; 12 | var sy = data.y >= 0 ? ' ' : ''; 13 | var sz = data.z >= 0 ? ' ' : ''; 14 | return util.format('%s: %s%s %s%s %s%s ', name, sx, data.x.toFixed(4), sy, data.y.toFixed(4), sz, data.z.toFixed(4)); 15 | } 16 | 17 | var headingCorrection = function(heading, offset) { 18 | if (typeof offset ==='undefined') 19 | offset = 0; 20 | 21 | // Once you have your heading, you must then add your 'Declination Angle', which is the 'Error' of the magnetic field in your location. 22 | // Find yours here: http://www.magnetic-declination.com/ 23 | var declinationAngle = 0.03106686; 24 | 25 | heading += declinationAngle + offset; 26 | 27 | // Correct for when signs are reversed. 28 | if (heading < 0) 29 | heading += 2 * Math.PI; 30 | 31 | // Check for wrap due to addition of declination. 32 | if (heading > 2 * Math.PI) 33 | heading -= 2 * Math.PI; 34 | 35 | return heading; 36 | } 37 | 38 | var headingToDegree = function(heading) { 39 | // Convert radians to degrees for readability. 40 | return heading * 180 / Math.PI; 41 | } 42 | 43 | var tic = new Date(); 44 | var callb = function (e, data) { 45 | var toc = new Date(); 46 | 47 | if (e) { 48 | console.log(e); 49 | return; 50 | } 51 | 52 | var str = data.timestamp.toISOString() + " "; 53 | str += print_vector3('Accel', data.accel) 54 | // str += print_vector3('Gyro', data.gyro) 55 | // str += print_vector3('Compass', data.compass) 56 | // str += print_vector3('Fusion', data.fusionPose) 57 | str += util.format('TiltHeading: %s ', headingToDegree(headingCorrection(data.tiltHeading, Math.PI / 2)).toFixed(0)); 58 | 59 | var str2 = ""; 60 | if (data.temperature && data.pressure && data.humidity) { 61 | var str2 = util.format('%s %s %s', data.temperature.toFixed(4), data.pressure.toFixed(4), data.humidity.toFixed(4)); 62 | } 63 | if ((num % 10) == 0) { 64 | console.log(str + str2); 65 | } 66 | 67 | num++; 68 | if (num == numStop) { 69 | console.timeEnd("async"); 70 | } else { 71 | setTimeout(function() { tic = new Date(); IMU.getValue(callb); } , 20 - (toc - tic)); 72 | } 73 | } 74 | 75 | IMU.getValue(callb); 76 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'target_defaults': { 3 | 'default_configuration': 'Release', 4 | 'conditions': [ 5 | ['OS == "win"', { 6 | 'msbuild_toolset': 'v120', 7 | }], 8 | ], 9 | }, 10 | 'targets' : [ 11 | { 12 | 'target_name' : 'NodeIMU', 13 | 'sources' : [ 14 | './addon.cpp', 15 | './nodeimu.cpp', 16 | './RTIMULib2/RTIMULib/RTMath.cpp', 17 | './RTIMULib2/RTIMULib/RTIMUHal.cpp', 18 | './RTIMULib2/RTIMULib/RTFusion.cpp', 19 | './RTIMULib2/RTIMULib/RTFusionKalman4.cpp', 20 | './RTIMULib2/RTIMULib/RTFusionRTQF.cpp', 21 | './RTIMULib2/RTIMULib/RTIMUSettings.cpp', 22 | './RTIMULib2/RTIMULib/RTIMUAccelCal.cpp', 23 | './RTIMULib2/RTIMULib/RTIMUMagCal.cpp', 24 | './RTIMULib2/RTIMULib/IMUDrivers/RTIMU.cpp', 25 | './RTIMULib2/RTIMULib/IMUDrivers/RTIMUNull.cpp', 26 | './RTIMULib2/RTIMULib/IMUDrivers/RTIMUMPU9150.cpp', 27 | './RTIMULib2/RTIMULib/IMUDrivers/RTIMUMPU9250.cpp', 28 | './RTIMULib2/RTIMULib/IMUDrivers/RTIMUGD20HM303D.cpp', 29 | './RTIMULib2/RTIMULib/IMUDrivers/RTIMUGD20M303DLHC.cpp', 30 | './RTIMULib2/RTIMULib/IMUDrivers/RTIMUGD20HM303DLHC.cpp', 31 | './RTIMULib2/RTIMULib/IMUDrivers/RTIMULSM9DS0.cpp', 32 | './RTIMULib2/RTIMULib/IMUDrivers/RTIMULSM9DS1.cpp', 33 | './RTIMULib2/RTIMULib/IMUDrivers/RTIMUBMX055.cpp', 34 | './RTIMULib2/RTIMULib/IMUDrivers/RTIMUBNO055.cpp', 35 | './RTIMULib2/RTIMULib/IMUDrivers/RTPressure.cpp', 36 | './RTIMULib2/RTIMULib/IMUDrivers/RTPressureBMP180.cpp', 37 | './RTIMULib2/RTIMULib/IMUDrivers/RTPressureLPS25H.cpp', 38 | './RTIMULib2/RTIMULib/IMUDrivers/RTPressureMS5611.cpp', 39 | './RTIMULib2/RTIMULib/IMUDrivers/RTPressureMS5637.cpp', 40 | './RTIMULib2/RTIMULib/IMUDrivers/RTHumidity.cpp', 41 | './RTIMULib2/RTIMULib/IMUDrivers/RTHumidityHTS221.cpp', 42 | './RTIMULib2/RTIMULib/IMUDrivers/RTHumidityHTU21D.cpp' 43 | ], 44 | 'include_dirs': [ 45 | './RTIMULib2/RTIMULib', 46 | " 3 | 4 | using namespace v8; 5 | 6 | NodeIMU::NodeIMU() { 7 | settings = new RTIMUSettings("RTIMULib"); 8 | imu = RTIMU::createIMU(settings); 9 | pressure = RTPressure::createPressure(settings); 10 | humidity = RTHumidity::createHumidity(settings); 11 | 12 | if ((imu == NULL) || (imu->IMUType() == RTIMU_TYPE_NULL)) { 13 | printf("No IMU found\n"); 14 | exit(1); 15 | } 16 | 17 | imu->IMUInit(); 18 | if (pressure != NULL) { pressure->pressureInit(); } 19 | if (humidity != NULL) { humidity->humidityInit(); } 20 | 21 | imu->setSlerpPower((float)0.02); 22 | imu->setGyroEnable(true); 23 | imu->setAccelEnable(true); 24 | imu->setCompassEnable(true); 25 | } 26 | 27 | NodeIMU::~NodeIMU() { 28 | delete imu; 29 | delete settings; 30 | delete pressure; 31 | delete humidity; 32 | } 33 | 34 | NAN_METHOD(NodeIMU::New) { 35 | NodeIMU* obj = new NodeIMU(); 36 | obj->Wrap(info.This()); 37 | info.GetReturnValue().Set(info.This()); 38 | } 39 | 40 | void AddRTVector3ToResult(v8::Local& result, RTVector3 data, const char* name) { 41 | Nan::HandleScope(); 42 | 43 | v8::Local field = Nan::New(); 44 | Nan::Set(field, Nan::New("x").ToLocalChecked(), Nan::New(data.x())); 45 | Nan::Set(field, Nan::New("y").ToLocalChecked(), Nan::New(data.y())); 46 | Nan::Set(field, Nan::New("z").ToLocalChecked(), Nan::New(data.z())); 47 | 48 | Nan::Set(result, Nan::New(name).ToLocalChecked(), field); 49 | } 50 | 51 | void PutMeasurement(const RTIMU_DATA& imuData, const bool pressure, const bool humidity, v8::Local& result) { 52 | Nan::HandleScope(); 53 | 54 | Nan::Set(result, Nan::New("timestamp").ToLocalChecked(), Nan::New(0.001 * (double)imuData.timestamp).ToLocalChecked()); 55 | 56 | AddRTVector3ToResult(result, imuData.accel, "accel"); 57 | AddRTVector3ToResult(result, imuData.gyro, "gyro"); 58 | AddRTVector3ToResult(result, imuData.compass, "compass"); 59 | AddRTVector3ToResult(result, imuData.fusionPose, "fusionPose"); 60 | 61 | Nan::Set(result, Nan::New("tiltHeading").ToLocalChecked(), Nan::New(RTMath::poseFromAccelMag(imuData.accel, imuData.compass).z())); 62 | 63 | if (pressure) { 64 | Nan::Set(result, Nan::New("pressure").ToLocalChecked(), Nan::New(imuData.pressure)); 65 | Nan::Set(result, Nan::New("temperature").ToLocalChecked(), Nan::New(imuData.temperature)); 66 | } 67 | if (humidity) { 68 | Nan::Set(result, Nan::New("humidity").ToLocalChecked(), Nan::New(imuData.humidity)); 69 | } 70 | } 71 | 72 | class SensorWorker : public Nan::AsyncWorker { 73 | public: 74 | SensorWorker(Nan::Callback *callback, RTIMU* imu, RTPressure *pressure, RTHumidity *humidity) 75 | : AsyncWorker(callback), d_imu(imu), d_pressure(pressure), d_humidity(humidity) {} 76 | ~SensorWorker() {} 77 | 78 | // Executed inside the worker-thread. 79 | // It is not safe to access V8, or V8 data structures 80 | // here, so everything we need for input and output 81 | // should go on `this`. 82 | void Execute() { 83 | if (d_imu->IMURead()) { 84 | d_imuData = d_imu->getIMUData(); 85 | if (d_pressure != NULL) { d_pressure->pressureRead(d_imuData); } 86 | if (d_humidity != NULL) { d_humidity->humidityRead(d_imuData); } 87 | } 88 | } 89 | 90 | // Executed when the async work is complete 91 | // this function will be run inside the main event loop 92 | // so it is safe to use V8 again 93 | void HandleOKCallback() { 94 | Nan::HandleScope scope; 95 | 96 | v8::Local result = Nan::New(); 97 | PutMeasurement(d_imuData, d_pressure != NULL,d_humidity != NULL, result); 98 | 99 | v8::Local argv[] = { 100 | Nan::Null(), result 101 | }; 102 | 103 | callback->Call(2, argv); 104 | } 105 | 106 | private: 107 | RTIMU* d_imu; 108 | RTPressure *d_pressure; 109 | RTHumidity *d_humidity; 110 | 111 | RTIMU_DATA d_imuData; 112 | }; 113 | 114 | NAN_METHOD(NodeIMU::GetValue) { 115 | NodeIMU* obj = Nan::ObjectWrap::Unwrap(info.This()); 116 | Nan::Callback *callback = new Nan::Callback(info[0].As()); 117 | Nan::AsyncQueueWorker(new SensorWorker(callback, obj->imu, obj->pressure, obj->humidity)); 118 | } 119 | 120 | NAN_METHOD(NodeIMU::GetValueSync) { 121 | NodeIMU* obj = Nan::ObjectWrap::Unwrap(info.This()); 122 | if (obj->imu->IMURead()) { 123 | RTIMU_DATA imuData = obj->imu->getIMUData(); 124 | bool pressure = (obj->pressure != NULL); 125 | bool humidity = (obj->humidity != NULL); 126 | if (pressure) { obj->pressure->pressureRead(imuData); } 127 | if (humidity) { obj->humidity->humidityRead(imuData); } 128 | 129 | v8::Local result = Nan::New(); 130 | PutMeasurement(imuData, pressure, humidity, result); 131 | 132 | info.GetReturnValue().Set(result); 133 | } 134 | } 135 | --------------------------------------------------------------------------------