├── debian64bit-bin ├── libOpenCL.so └── libccurl.so ├── raspberrypi-bin └── libccurl.so ├── .gitmodules ├── iota-mam └── mam.js ├── readme.md ├── index.js ├── test.js ├── example.js └── ble-uart.js /debian64bit-bin/libOpenCL.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ojousima/ruuvi-nodejs/HEAD/debian64bit-bin/libOpenCL.so -------------------------------------------------------------------------------- /debian64bit-bin/libccurl.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ojousima/ruuvi-nodejs/HEAD/debian64bit-bin/libccurl.so -------------------------------------------------------------------------------- /raspberrypi-bin/libccurl.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ojousima/ruuvi-nodejs/HEAD/raspberrypi-bin/libccurl.so -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ruuvi.endpoints.js"] 2 | path = ruuvi.endpoints.js 3 | url = https://github.com/ruuvi/ruuvi.endpoints.js.git 4 | [submodule "iota.lib.js"] 5 | path = iota.lib.js 6 | url = https://github.com/iotaledger/iota.lib.js.git 7 | -------------------------------------------------------------------------------- /iota-mam/mam.js: -------------------------------------------------------------------------------- 1 | const MAM = require('./iota-mam'); 2 | const mam_create = MAM.cwrap('mam_create', 'string', ['string','string','number','number','number','number','number','number']); 3 | const mam_parse = MAM.cwrap('mam_parse', 'string', ['string', 'string', 'number']); 4 | 5 | module.exports = { 6 | 'mam_create': mam_create, 7 | 'mam_parse': mam_parse 8 | }; 9 | 10 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # NOTICE 2 | As of right now, changes in FFI interface prevent ccurl.interface.js from compiling in NodeJS 9+ 3 | Please use NodeJS 8.9.4 4 | 5 | # Usage 6 | Program searches for RuuviTag which has Nordic UART service enabled 7 | and automatically connects to first RuuviTag it finds. 8 | Please update the RuuviTag with [MAM-firmware](https://lab.ruuvi.com/distribution-packages/frankfurt_demo_dfu.zip). 9 | 10 | Sometimes there is issue with Bluetooth stack, and you'll need to run 11 | `sudo hciconfig hci0 reset`. Replace the HCI interface number if applicable. 12 | 13 | A simple command line interface is given. 14 | User is given option to setup temperature sensor for a single-shot measurement, 15 | continous measurement (once per second) and querying temperature in plain text. 16 | User can also send a MAM query, which will trigger reading environmental data 17 | and soon the environmental data is sent from RuuviTag with MAM coding. 18 | 19 | The example program publishes MAM data to tangle to fixed address defined in example.js. 20 | 21 | You can observe the state of RuuviTag by RED LED, if the led is on or blinking there is activity 22 | going on. 23 | 24 | After installing, run node example.js 25 | 26 | # Dependencies 27 | ## NodeJS, npm 28 | NOTE: This modifies your global NodeJS and NPM versions. 29 | Update NodeJS to 8.9.4 with instructions found at 30 | https://askubuntu.com/questions/426750/how-can-i-update-my-nodejs-to-the-latest-version . i.e. 31 | ``` 32 | sudo npm cache clean -f 33 | sudo npm install -g n 34 | sudo n 8.9.4 35 | sudo ln -sf /usr/local/n/versions/node/8.9.4/bin/node /usr/bin/node 36 | curl -0 -L https://npmjs.com/install.sh | sudo sh 37 | ``` 38 | 39 | This repository is tested on nodejs 8.9.4, check your version with `node --version` 40 | 41 | ## Noble 42 | [Noble repositry](https://github.com/sandeepmistry/noble) 43 | `npm install noble` 44 | `sudo setcap cap_net_raw+eip $(eval readlink -f \`which node\`)` 45 | 46 | ## iota.lib.js, ruuvi.endpoints.js ccurl.interface.js 47 | `git submodule update --init --recursive` 48 | 49 | `npm install iota.lib.js` 50 | 51 | `npm install ccurl.interface.js` 52 | 53 | `cp raspberrypi-bin/libbcurl.so node_modules/ccurl.interface.js` 54 | 55 | ## Sleep 56 | `npm install sleep` 57 | 58 | ## ccurl 59 | Install ccurl according to instructions published on [NPM](https://www.npmjs.com/package/ccurl.interface.js#https://github.com/iotaledger/ccurl). 60 | For your convenience binaries are provided for Raspberry Pi and 64-bit debian. 61 | 62 | # Licensing 63 | * Ruuvi / Ojousima: BSD-3 64 | * iota libs: MIT 65 | * BLE-UART: Unknown, please refer to [source](https://github.com/tigoe/BluetoothLE-Examples/issues/7) 66 | * Noble: MIT 67 | 68 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const IOTA = require('iota.lib.js'); 2 | const CCurl = require('ccurl.interface.js'); 3 | const { 4 | promisify 5 | } = require('util'); 6 | 7 | // not really used as we don't do any TX with value changes. 8 | const MWM = 15; 9 | let seed = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ9ABCDEFGHIJKLMNOPQRSTUVWXYZ9ABCDEFGHIJKLMNOPQRSTUVWXYZ9'; 10 | let targetAddress = '99999999999999999999999999999999999999999999999999999999999999999RUUVI9LOVES9IOTA'; 11 | let targetTag = 'ALL9THE9TAGGING9YOULL9NEED9'; 12 | let message = 'HSVKSZBJFAI9OUUXZSAYVPHENMYSMDOTXSVUISVGWN9DVUBYTNXCFQK9AAAWHFIBHLPRHXIXPX9HUCRUCILCKVZGYBOJEHGJFA9YFGIHNS99ILCOOKWV9FVLGVKCMPVPDZFXTKOBGVKXVTDLOVYCEYTTGRXVWBVFKFXHWQGQSQNESOEAMSSVBCOQVCWKEHHONFQKIBEHXYBNYIYBBXFZNBLGVHXPJTPSRB9FSVGBNVHADGGLKLIIARULVV9ALN99QTS9JUGTTFCNCXEGUZIUFWMXGYVK9YDKFVXMHDRNHRINXKKCIELUAGIDFUIANFNMSZQJGDYDZDQDURRLXHLYCDLFBRZYTQDJZUVAWXKWHGJHDUHETGNDSP9ABBIU9PAGZMLJKNCXTCZSQJXRBCEJ9PQPFLYKLUMEUGNOOTUFLABBBAPEWTWKTAMLMARRLSYRCWSYZRITTRLLLBP9XTRCFMJYYIZSZEVSXTVBFLAOENMPPKWZOUEBIORBVIWXVCTNKRYJMAXTKKMUR9IJDWEVHUBUJMQZIMAFSGZAIGHWHMOOLLINHNVJSIAEDUAO9ZZRABQ9AFRMEE9GLXHSMZIVZIZSSPHTYDKBFAMCWOPOEMFBJOJBPOVQCMUFPXHKQKQXPJKDTK9SK9LNGWYHCEZTBZWQDRCS9XKYHNXWSPHLEQSP9EODYIVQUJROCJWCPVDYJLZUGENRCUGKCLKALLQNGEVCGGNYZKHSZXADXABVRKJBGLHE9KRBSHODGRWQEGJZDL9OTITSAOUJSOGQYJRNIBCYWVZQTPAKULCGTYYHFPNHANGUR9IFY9SOKLMFWYZFEWZOEUXSU9JLXNWZ9ZPOGBQVWRBUQZVOAADABGWYLGSVIKEJBVARTCOUDOBFBFHSY9WNRYBHLGCAQKNZQNDYQMKOGECWIUVWIZ9ON9VFCLMODZELBZQGHLHWZRMDZXROMRWVAXUKCKVFU9AVTLKROICXLDBSPLCJGMXXTXTWLNIBQEQXWTNLABO9EWESSKZCAEQONSABZOCQJOYANPRVXBVYYUUNWAEHGRITYVZPDEMPQKRIRBQMHYHWXQXTXIUFPRJJRIORNUPFIIKVM9QOQVTLGXIRATSYXELMUAJNLLPMPCVSGJEEONRROUWWBBXC9XIHCOOMHYJHPYVXBRKAWB9KNZDUS9HEFLFSSWPPMFKROKJTXVFFLYJZRSJLCHYCDPEPNHDP9TBZGOPRKDLTNZRALWLIWKH9MSRVIFWLTDMNICQRW9HIQPQBG9WZZLCDOZYIDUSEIPEUGJ9ARCVNQZFVQRRUVPMQYQELADHDQFMBTRXDARMGHOQIJMGKJKGNMZAIGCTIP9VWLVOQVZHWDCOXOWLJHWKMELANENMMLYKL9Y9CI9QAOHHJSZBLAAIOPGQVPXLH9AZUUIWSMMNORKBNDSJVBCQLNMQN9E9QKAWIQUCFTVUEJKQKYDFXJZXGKQJXFD9XZUZZDSNVGJBYXCSWHDDDFIEHUXW9F9XQMOV9TVAPYHPLRNOBNWSIPJ9OLFZTAQEHXVAOYOVPBHYTMRNKVHDFZPAFRJEBZUXDPKSKJCBKLRYEWQUJSDJWEX9UJRRFHKE9AHBBNTTFIIIBHRFJVOMOEGB9VWZED9WADAPKFEAFVV99CAHRXD9RXRJDAULFRTKJLLTB9KRLCOM99BTBKDXMXRJOZSYXLSXDQQDEZ9NPCBJDVHARHHJCPGZTMJAV9PJSAEX9YILZBKUVAVNGFRWETNNFMRJYFXSOAOT9KDXTHAWABGZLWYLZVGABLSWUQGLAVWOFUAVQNAGLQWLGXCUSYXTWWCFWDHERSNXGFHKOEXWMBXBYASSBZGNVBOOPAWYBLDQMBXVBVARTDQPYCZLKWIVVJFLVINRDIIKLGEDWSLMOVLSYGDWZIAYRUPKJATMWBTJUADLJUJXI9XVXF9VXWGIIMEIESHZSCGUNAZBJFUXYJQ9KMBZVGJCOWOXGMTA9V9HI9FXILRKYQIFIGAGGCMYAQU9WKIHSOKKRFMWQZBGHHX9UARCSZJGLNGNMRNVBYMGXVRCSLRKMBWIWJVVWAHKAFQPJWOOMKQDOIKONZXKPZ9FXPNZUVT9ZVIQCBRECOQRZJPPYWQAPEWNVMSS9BPPOXCSOZFGP9NNRLQWEJWBKRXRDJAUGUCLZIVYYIPEH9YHR9VGNLFLTJQVYWXHVFKFFPANVMMKNUNUUFDLAVPQNNSGFGKID9CUQVMWWPBAWN9SPSTFDGUSYSCWTNCMNVPPJFITVKTAIQNHWPOKXCTJGTMPPFFBUIQIHPSOYPPZXLUKKCSGFQLRJLR9GOZIALKBIJOLTKDOTBHUSGMYM9WRPBUK9GIIJKWNTJLPDAXZYCKGLCRLRSVHTYYJNHOQEUVRFDRNJISJEEPAJANACSFPDOHGVNCSSGXAAHS9CYDYQBZOQZCFDOHBFDEVGAIF'; 13 | 14 | // sample MAM payload. 15 | 16 | // construct iota.lib.js instance 17 | let iota = new IOTA({ 18 | 'host': 'http://service.iotasupport.com', 19 | 'port': 14265 20 | }); 21 | 22 | const PCCurl = promisify(CCurl); 23 | const PprepareTransfers = promisify(iota.api.prepareTransfers); 24 | const PgetTransactionsToApprove = async depth => { 25 | return new Promise((resolve, reject) => { 26 | iota.api.getTransactionsToApprove(depth, function(err, suc) { 27 | if (err != undefined) { 28 | reject(err); 29 | } else { 30 | resolve(suc); 31 | } 32 | }); 33 | }); 34 | }; 35 | 36 | const PbroadcastTransactions = async trytes => { 37 | return new Promise((resolve, reject) => { 38 | iota.api.broadcastTransactions(trytes, function(err, suc) { 39 | if (err != undefined) { 40 | reject(err); 41 | } else { 42 | resolve(suc); 43 | } 44 | }); 45 | }); 46 | }; 47 | 48 | PprepareTransfers(seed, [{ 49 | address: targetAddress, 50 | value: 0, 51 | message: message, 52 | tag: targetTag 53 | }]).then(transactions => { 54 | return PgetTransactionsToApprove(5).then(toApprove => { 55 | return PCCurl(toApprove.trunkTransaction, toApprove.branchTransaction, MWM, transactions); 56 | }); 57 | }).then(bundle => PbroadcastTransactions(bundle)) 58 | .then(ret => console.log(ret)); 59 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var noble = require('noble'); 2 | var async = require('async'); 3 | 4 | var peripheralName = process.argv[2].toLowerCase(); 5 | 6 | noble.on('stateChange', function(state) { 7 | if (state === 'poweredOn') { 8 | noble.startScanning(); 9 | } else { 10 | noble.stopScanning(); 11 | } 12 | }); 13 | 14 | noble.on('discover', function(peripheral) { 15 | if (peripheral.advertisement.localName && peripheral.advertisement.localName.toLowerCase() === peripheralName) { 16 | noble.stopScanning(); 17 | 18 | console.log('peripheral with ID ' + peripheral.id + ' found'); 19 | var advertisement = peripheral.advertisement; 20 | 21 | var localName = advertisement.localName; 22 | var txPowerLevel = advertisement.txPowerLevel; 23 | var manufacturerData = advertisement.manufacturerData; 24 | var serviceData = advertisement.serviceData; 25 | var serviceUuids = advertisement.serviceUuids; 26 | 27 | if (localName) { 28 | console.log(' Local Name = ' + localName); 29 | } 30 | 31 | if (txPowerLevel) { 32 | console.log(' TX Power Level = ' + txPowerLevel); 33 | } 34 | 35 | if (manufacturerData) { 36 | console.log(' Manufacturer Data = ' + manufacturerData.toString('hex')); 37 | } 38 | 39 | if (serviceData) { 40 | console.log(' Service Data = ' + serviceData); 41 | } 42 | 43 | if (serviceUuids) { 44 | console.log(' Service UUIDs = ' + serviceUuids); 45 | } 46 | 47 | console.log(); 48 | 49 | explore(peripheral); 50 | } 51 | }); 52 | 53 | function explore(peripheral) { 54 | console.log('services and characteristics:'); 55 | 56 | peripheral.on('disconnect', function() { 57 | process.exit(0); 58 | }); 59 | 60 | peripheral.connect(function(error) { 61 | peripheral.discoverServices([], function(error, services) { 62 | var serviceIndex = 0; 63 | 64 | async.whilst( 65 | function () { 66 | return (serviceIndex < services.length); 67 | }, 68 | function(callback) { 69 | var service = services[serviceIndex]; 70 | var serviceInfo = service.uuid; 71 | 72 | if (service.name) { 73 | serviceInfo += ' (' + service.name + ')'; 74 | } 75 | console.log(serviceInfo); 76 | 77 | service.discoverCharacteristics([], function(error, characteristics) { 78 | var characteristicIndex = 0; 79 | 80 | async.whilst( 81 | function () { 82 | return (characteristicIndex < characteristics.length); 83 | }, 84 | function(callback) { 85 | var characteristic = characteristics[characteristicIndex]; 86 | var characteristicInfo = ' ' + characteristic.uuid; 87 | 88 | if (characteristic.name) { 89 | characteristicInfo += ' (' + characteristic.name + ')'; 90 | } 91 | 92 | async.series([ 93 | function(callback) { 94 | characteristic.discoverDescriptors(function(error, descriptors) { 95 | async.detect( 96 | descriptors, 97 | function(descriptor, callback) { 98 | return callback(descriptor.uuid === '2901'); 99 | }, 100 | function(userDescriptionDescriptor){ 101 | if (userDescriptionDescriptor) { 102 | userDescriptionDescriptor.readValue(function(error, data) { 103 | if (data) { 104 | characteristicInfo += ' (' + data.toString() + ')'; 105 | } 106 | callback(); 107 | }); 108 | } else { 109 | callback(); 110 | } 111 | } 112 | ); 113 | }); 114 | }, 115 | function(callback) { 116 | characteristicInfo += '\n properties ' + characteristic.properties.join(', '); 117 | 118 | if (characteristic.properties.indexOf('read') !== -1) { 119 | characteristic.read(function(error, data) { 120 | if (data) { 121 | var string = data.toString('ascii'); 122 | 123 | characteristicInfo += '\n value ' + data.toString('hex') + ' | \'' + string + '\''; 124 | } 125 | callback(); 126 | }); 127 | } else { 128 | callback(); 129 | } 130 | }, 131 | function() { 132 | console.log(characteristicInfo); 133 | characteristicIndex++; 134 | callback(); 135 | } 136 | ]); 137 | }, 138 | function(error) { 139 | serviceIndex++; 140 | callback(); 141 | } 142 | ); 143 | }); 144 | }, 145 | function (err) { 146 | peripheral.disconnect(); 147 | } 148 | ); 149 | }); 150 | }); 151 | }var peripheralIdOrAddress = process.argv[2].toLowerCase(); 152 | 153 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | const util = require('util'); 2 | const setImmediatePromise = util.promisify(setImmediate); 3 | var BleUart = require('./ble-uart'); 4 | var dataHandler = require('./ruuvi.endpoints.js/index.js') 5 | var tryteConverter = require('./iota.lib.js/lib/utils/asciiToTrytes.js') 6 | var MAM = require('./iota-mam/mam') 7 | var sleep = require('sleep'); 8 | var stdin = process.openStdin(); 9 | var singleShotTemperature = new Uint8Array([0x31,0x10,0x01,251,251,252,252,1,0,2,0]); 10 | var continuousTemperature = new Uint8Array([0x31,0x10,0x01,1,251,252,252,1,0,2,0]); 11 | var queryTemperature = new Uint8Array([0x31,0x10,0x05,0,0,0,0,0,0,0,0]); 12 | var queryMAM = new Uint8Array([0xE0,0x10,0x05,0,0,0,0,0,0,0,0]); //XXX PoC for hackathon, only destination matters 13 | 14 | const IOTA = require('iota.lib.js'); 15 | const CCurl = require('ccurl.interface.js'); 16 | const { 17 | promisify 18 | } = require('util'); 19 | 20 | // not really used as we don't do any TX with value changes. 21 | const MWM = 15; 22 | let seed = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ9ABCDEFGHIJKLMNOPQRSTUVWXYZ9ABCDEFGHIJKLMNOPQRSTUVWXYZ9'; 23 | let targetAddress = '99999999999999999999999999999999999999999999999999999999999999999RUUVI9LOVES9IOTA'; 24 | let targetTag = 'ALL9THE9TAGGING9YOULL9NEED9'; 25 | let message = 'HSVKSZBJFAI9OUUXZSAYVPHENMYSMDOTXSVUISVGWN9DVUBYTNXCFQK9AAAWHFIBHLPRHXIXPX9HUCRUCILCKVZGYBOJEHGJFA9YFGIHNS99ILCOOKWV9FVLGVKCMPVPDZFXTKOBGVKXVTDLOVYCEYTTGRXVWBVFKFXHWQGQSQNESOEAMSSVBCOQVCWKEHHONFQKIBEHXYBNYIYBBXFZNBLGVHXPJTPSRB9FSVGBNVHADGGLKLIIARULVV9ALN99QTS9JUGTTFCNCXEGUZIUFWMXGYVK9YDKFVXMHDRNHRINXKKCIELUAGIDFUIANFNMSZQJGDYDZDQDURRLXHLYCDLFBRZYTQDJZUVAWXKWHGJHDUHETGNDSP9ABBIU9PAGZMLJKNCXTCZSQJXRBCEJ9PQPFLYKLUMEUGNOOTUFLABBBAPEWTWKTAMLMARRLSYRCWSYZRITTRLLLBP9XTRCFMJYYIZSZEVSXTVBFLAOENMPPKWZOUEBIORBVIWXVCTNKRYJMAXTKKMUR9IJDWEVHUBUJMQZIMAFSGZAIGHWHMOOLLINHNVJSIAEDUAO9ZZRABQ9AFRMEE9GLXHSMZIVZIZSSPHTYDKBFAMCWOPOEMFBJOJBPOVQCMUFPXHKQKQXPJKDTK9SK9LNGWYHCEZTBZWQDRCS9XKYHNXWSPHLEQSP9EODYIVQUJROCJWCPVDYJLZUGENRCUGKCLKALLQNGEVCGGNYZKHSZXADXABVRKJBGLHE9KRBSHODGRWQEGJZDL9OTITSAOUJSOGQYJRNIBCYWVZQTPAKULCGTYYHFPNHANGUR9IFY9SOKLMFWYZFEWZOEUXSU9JLXNWZ9ZPOGBQVWRBUQZVOAADABGWYLGSVIKEJBVARTCOUDOBFBFHSY9WNRYBHLGCAQKNZQNDYQMKOGECWIUVWIZ9ON9VFCLMODZELBZQGHLHWZRMDZXROMRWVAXUKCKVFU9AVTLKROICXLDBSPLCJGMXXTXTWLNIBQEQXWTNLABO9EWESSKZCAEQONSABZOCQJOYANPRVXBVYYUUNWAEHGRITYVZPDEMPQKRIRBQMHYHWXQXTXIUFPRJJRIORNUPFIIKVM9QOQVTLGXIRATSYXELMUAJNLLPMPCVSGJEEONRROUWWBBXC9XIHCOOMHYJHPYVXBRKAWB9KNZDUS9HEFLFSSWPPMFKROKJTXVFFLYJZRSJLCHYCDPEPNHDP9TBZGOPRKDLTNZRALWLIWKH9MSRVIFWLTDMNICQRW9HIQPQBG9WZZLCDOZYIDUSEIPEUGJ9ARCVNQZFVQRRUVPMQYQELADHDQFMBTRXDARMGHOQIJMGKJKGNMZAIGCTIP9VWLVOQVZHWDCOXOWLJHWKMELANENMMLYKL9Y9CI9QAOHHJSZBLAAIOPGQVPXLH9AZUUIWSMMNORKBNDSJVBCQLNMQN9E9QKAWIQUCFTVUEJKQKYDFXJZXGKQJXFD9XZUZZDSNVGJBYXCSWHDDDFIEHUXW9F9XQMOV9TVAPYHPLRNOBNWSIPJ9OLFZTAQEHXVAOYOVPBHYTMRNKVHDFZPAFRJEBZUXDPKSKJCBKLRYEWQUJSDJWEX9UJRRFHKE9AHBBNTTFIIIBHRFJVOMOEGB9VWZED9WADAPKFEAFVV99CAHRXD9RXRJDAULFRTKJLLTB9KRLCOM99BTBKDXMXRJOZSYXLSXDQQDEZ9NPCBJDVHARHHJCPGZTMJAV9PJSAEX9YILZBKUVAVNGFRWETNNFMRJYFXSOAOT9KDXTHAWABGZLWYLZVGABLSWUQGLAVWOFUAVQNAGLQWLGXCUSYXTWWCFWDHERSNXGFHKOEXWMBXBYASSBZGNVBOOPAWYBLDQMBXVBVARTDQPYCZLKWIVVJFLVINRDIIKLGEDWSLMOVLSYGDWZIAYRUPKJATMWBTJUADLJUJXI9XVXF9VXWGIIMEIESHZSCGUNAZBJFUXYJQ9KMBZVGJCOWOXGMTA9V9HI9FXILRKYQIFIGAGGCMYAQU9WKIHSOKKRFMWQZBGHHX9UARCSZJGLNGNMRNVBYMGXVRCSLRKMBWIWJVVWAHKAFQPJWOOMKQDOIKONZXKPZ9FXPNZUVT9ZVIQCBRECOQRZJPPYWQAPEWNVMSS9BPPOXCSOZFGP9NNRLQWEJWBKRXRDJAUGUCLZIVYYIPEH9YHR9VGNLFLTJQVYWXHVFKFFPANVMMKNUNUUFDLAVPQNNSGFGKID9CUQVMWWPBAWN9SPSTFDGUSYSCWTNCMNVPPJFITVKTAIQNHWPOKXCTJGTMPPFFBUIQIHPSOYPPZXLUKKCSGFQLRJLR9GOZIALKBIJOLTKDOTBHUSGMYM9WRPBUK9GIIJKWNTJLPDAXZYCKGLCRLRSVHTYYJNHOQEUVRFDRNJISJEEPAJANACSFPDOHGVNCSSGXAAHS9CYDYQBZOQZCFDOHBFDEVGAIF'; 26 | 27 | // sample MAM payload. 28 | 29 | // construct iota.lib.js instance 30 | let iota = new IOTA({ 31 | 'host': 'http://service.iotasupport.com', 32 | 'port': 14265 33 | }); 34 | 35 | const PCCurl = promisify(CCurl); 36 | const PprepareTransfers = promisify(iota.api.prepareTransfers); 37 | const PgetTransactionsToApprove = async depth => { 38 | return new Promise((resolve, reject) => { 39 | iota.api.getTransactionsToApprove(depth, function(err, suc) { 40 | if (err != undefined) { 41 | reject(err); 42 | } else { 43 | resolve(suc); 44 | } 45 | }); 46 | }); 47 | }; 48 | 49 | const PbroadcastTransactions = async trytes => { 50 | return new Promise((resolve, reject) => { 51 | iota.api.broadcastTransactions(trytes, function(err, suc) { 52 | if (err != undefined) { 53 | reject(err); 54 | } else { 55 | resolve(suc); 56 | } 57 | }); 58 | }); 59 | }; 60 | 61 | 62 | https://stackoverflow.com/questions/6965107/converting-between-strings-and-arraybuffers 63 | function ab2str(buf) { 64 | return String.fromCharCode.apply(null, new Uint8Array(buf)); 65 | } 66 | 67 | 68 | 69 | // use a predefined UART service (nordic, redbear, laird, bluegiga) 70 | var bleSerial = new BleUart('nordic'); 71 | var reconnTimer = null; 72 | function reConn() { 73 | console.log("Connection failed. Retrying"); 74 | bleSerial.peripheral.disconnect(); 75 | bleSerial.scan('poweredOn'); 76 | } 77 | 78 | 79 | 80 | // optionally define a custom service 81 | // var uart = { 82 | // serviceUUID: '6e400001-b5a3-f393-e0a9-e50e24dcca9e', 83 | // txUUID: '6e400002-b5a3-f393-e0a9-e50e24dcca9e', 84 | // rxUUID: '6e400003-b5a3-f393-e0a9-e50e24dcca9e' 85 | // } 86 | // var bleSerial = new BleUart('foo', uart); 87 | 88 | // this function gets called when new data is received from 89 | // the Bluetooth LE serial service: 90 | bleSerial.on('data', function(data){ 91 | //console.log("Got new data: " + data); 92 | let message = dataHandler(data); 93 | if (message.ready){ 94 | let res = (ab2str(message.binary)).split('\n'); 95 | console.log(res[0]); 96 | console.log(res[1]); 97 | //if(message.type === "MAM") 98 | //{ 99 | let trytes = MAM.mam_parse(res[0], res[1], 1).split('\n'); 100 | //} 101 | console.log(trytes); 102 | let ascii = tryteConverter.fromTrytes(trytes[0]); 103 | console.log(ascii); 104 | 105 | PprepareTransfers(seed, [{ 106 | address: targetAddress, 107 | value: 0, 108 | message: res[0], 109 | tag: targetTag 110 | }]).then(transactions => { 111 | return PgetTransactionsToApprove(5).then(toApprove => { 112 | return PCCurl(toApprove.trunkTransaction, toApprove.branchTransaction, MWM, transactions); 113 | }); 114 | }).then(bundle => PbroadcastTransactions(bundle)) 115 | .then(ret => console.log(ret)); 116 | 117 | } 118 | }); 119 | 120 | // this function gets called when the program 121 | // establishes a connection with the remote BLE radio: 122 | bleSerial.on('connected', function(data){ 123 | console.log("Connected to BLE. Sending a hello message"); 124 | clearTimeout(reconnTimer); 125 | reconnTimer = null; 126 | timerExample(); 127 | //bleSerial.write("Hello BLE!"); 128 | //bleSerial.write([1,2,3,4,5]); 129 | 130 | 131 | //bleSerial.write(new Buffer([6,7,8,9])) 132 | }); 133 | 134 | bleSerial.on('preparing', function(){ 135 | if(reconnTimer == null){ 136 | console.log("setting up retry timer"); 137 | reconnTimer = setTimeout(reConn, 5000); 138 | } 139 | }); 140 | 141 | 142 | // thus function gets called if the radio successfully starts scanning: 143 | bleSerial.on('scanning', function(status){ 144 | console.log("radio status: " + status); 145 | }) 146 | 147 | async function timerExample() { 148 | //console.log('Before I/O callbacks'); 149 | await setImmediatePromise(); 150 | console.log('Connection ready.'); 151 | console.log('1) singleshot 2) continous 3) query. 4) Get MAM data'); 152 | //bleSerial.write(new Uint8Array([0x31,0x10,0x01,251,251,252,252,1,0,2,0])); 153 | //sleep.sleep(5); 154 | //bleSerial.write(new Uint8Array([0x31,0x10,0x05,0,0,0,0,0,0,0,0])); 155 | } 156 | 157 | stdin.on('data', function(chunk) { 158 | if(chunk == '1\n') 159 | { 160 | console.log("Single shot"); 161 | bleSerial.write(singleShotTemperature); 162 | } 163 | if(chunk == '2\n') 164 | { 165 | console.log("Continous measurement"); 166 | bleSerial.write(continuousTemperature); 167 | } 168 | if(chunk == '3\n') 169 | { 170 | console.log("Query data"); 171 | bleSerial.write(queryTemperature); 172 | } 173 | if(chunk == '4\n') 174 | { 175 | console.log("Asking for MAM"); 176 | bleSerial.write(queryMAM); 177 | } 178 | } 179 | ); 180 | 181 | -------------------------------------------------------------------------------- /ble-uart.js: -------------------------------------------------------------------------------- 1 | /* 2 | Noble UART service example 3 | This example uses Sandeep Mistry's noble library for node.js to 4 | read and write from Bluetooth LE characteristics. It looks for a UART 5 | characteristic based on a proprietary UART service by Nordic Semiconductor. 6 | You can see this service implemented in Adafruit's BLEFriend library. 7 | created 30 Nov 2015 8 | by Tom Igoe 9 | modified 1 Dec 2015 10 | by Don Coleman 11 | */ 12 | 13 | var noble = require('noble'); //noble library 14 | var util = require('util'); // utilities library 15 | 16 | // make an instance of the eventEmitter library: 17 | var EventEmitter = require('events').EventEmitter; 18 | 19 | // uuids are easier to read with dashes 20 | // this helper removes dashes so comparisons work 21 | var uuid = function(uuid_with_dashes) { 22 | return uuid_with_dashes.replace(/-/g, ''); 23 | }; 24 | 25 | // TX and RX are from Noble's perspective 26 | var knownUartServices = { 27 | nordic: { 28 | serviceUUID: '6e400001-b5a3-f393-e0a9-e50e24dcca9e', 29 | txUUID: '6e400002-b5a3-f393-e0a9-e50e24dcca9e', 30 | rxUUID: '6e400003-b5a3-f393-e0a9-e50e24dcca9e' 31 | }, 32 | redbear: { 33 | serviceUUID: '713d0000-503e-4c75-ba94-3148f18d941e', 34 | txUUID: '713d0003-503e-4c75-ba94-3148f18d941e', 35 | rxUUID: '713d0002-503e-4c75-ba94-3148f18d941e' 36 | }, 37 | laird: { 38 | serviceUUID: '569a1101-b87f-490c-92cb-11ba5ea5167c', 39 | txUUID: '569a2001-b87f-490c-92cb-11ba5ea5167c', 40 | rxUUID: '569a2000-b87f-490c-92cb-11ba5ea5167c' 41 | }, 42 | bluegiga: { 43 | serviceUUID: '1d5688de-866d-3aa4-ec46-a1bddb37ecf6', 44 | txUUID: 'af20fbac-2518-4998-9af7-af42540731b3', 45 | rxUUID: 'af20fbac-2518-4998-9af7-af42540731b3' 46 | } 47 | }; 48 | 49 | var checkService = function(name, uart) { 50 | var error = false; 51 | 52 | if (!name) { 53 | name = "UART Service"; 54 | } 55 | 56 | if (Object.keys(uart).length === 0) { 57 | // Maybe the user typo'd the service name? 58 | console.log("Expecting service name to be one of: " + Object.keys(knownUartServices).join(", ") + "."); 59 | console.log("Or pass serviceUUID, txUUID, & rxUUID in object literal as second argument."); 60 | error = true; 61 | } else { 62 | if (!uart.serviceUUID) { 63 | console.log("ERROR: Expecting serviceUUID for " + name); 64 | error = true; 65 | } 66 | if (!uart.txUUID) { 67 | console.log("ERROR: Expecting txUUID for " + name); 68 | error = true; 69 | } 70 | if (!uart.rxUUID) { 71 | console.log("ERROR: Expecting rxUUID for " + name); 72 | error = true; 73 | } 74 | } 75 | 76 | // TODO handle this better... 77 | if (error) { 78 | console.log(name + " " + JSON.stringify(uart)); 79 | process.exit(-1); 80 | } 81 | }; 82 | 83 | // constructor function, so you can call new BleUart(): 84 | var BleUart = function (name, options) { 85 | 86 | // get known service or empty object 87 | var uart = knownUartServices[name] || {}; 88 | // apply user over rides 89 | Object.assign(uart, options); 90 | 91 | checkService(name, uart); 92 | 93 | var serviceUUID = uuid(uart.serviceUUID); // the service you want 94 | var transmitUUID = uuid(uart.txUUID); // TX from noble's perspective 95 | var receiveUUID = uuid(uart.rxUUID); // RX from noble's perspective 96 | var receive, transmit; // transmit and receive BLE characteristics 97 | var writeWithoutResponse; // flag for write characteristic (based on Bluefruit version) 98 | var self = this; // reference to the instance of BleUart 99 | self.connected = false; // whether the remote peripheral's connected 100 | self.peripheral = null; // the remote peripheral as an object 101 | EventEmitter.call(self); // make a copy of EventEmitter so you can emit events 102 | self.setMaxListeners(20); 103 | 104 | // The scanning function: 105 | self.scan = function(state) { 106 | if (state === 'poweredOn') { // if the radio's on, scan for this service 107 | noble.startScanning([serviceUUID], false); 108 | } 109 | // emit a 'scanning' event: 110 | self.emit('scanning', state); 111 | } 112 | 113 | // the connect function: 114 | self.connect = function(peripheral) { 115 | self.peripheral = peripheral; 116 | peripheral.connect(); // start connection attempts 117 | 118 | // the connect function. This is local to the discovery function 119 | // because it needs to know the peripheral to discover services: 120 | function discover() { 121 | console.log("Discovering"); 122 | // once you know you have a peripheral with the desired 123 | // service, you can stop scanning for others: 124 | // get the service you want on this peripheral: 125 | peripheral.discoverServices(null,explore); 126 | } 127 | 128 | // called only when the peripheral has the service you're looking for: 129 | peripheral.on('connect', discover); 130 | // when a peripheral disconnects, run disconnect: 131 | peripheral.on('disconnect', self.disconnect); 132 | }; 133 | 134 | // the services and characteristics exploration function: 135 | // once you're connected, this gets run: 136 | function explore(error, services) { 137 | // this gets run by the for-loop at the end of the 138 | // explore function, below: 139 | console.log("Exploring"); 140 | function getCharacteristics(error, characteristics) { 141 | console.log("Reading characteristics"); 142 | 143 | characteristics.forEach(function(characteristic) { 144 | console.log(characteristic.toString()); 145 | if (characteristic.uuid === receiveUUID) { 146 | receive = characteristic; 147 | 148 | if (characteristic.properties.indexOf("notify") < 0) { 149 | console.log("ERROR: expecting " + characteristic.uuid + " to have 'notify' property."); 150 | } 151 | receive.notify(true); // turn on notifications 152 | 153 | receive.on('read', function(data, notification) { 154 | if (notification) { // if you got a notification 155 | self.emit('data', data); // emit a data event 156 | } 157 | }); 158 | } 159 | 160 | // separate *if* since some hardware uses the same characteristic for tx and rx 161 | if (characteristic.uuid === transmitUUID) { 162 | transmit = characteristic; 163 | // Older Adafruit hardware is writeWithoutResponse 164 | if (characteristic.properties.indexOf("writeWithoutResponse") > -1) { 165 | writeWithoutResponse = true; 166 | } else { 167 | writeWithoutResponse = false; 168 | } 169 | } 170 | }); 171 | 172 | // if you've got a valid transmit and receive characteristic, 173 | // then you're truly connected. Emit a connected event: 174 | if (transmit && receive) { 175 | self.connected = true; 176 | self.emit('connected', self.connected); 177 | } 178 | } // end of getCharacteristics() 179 | 180 | // iterate over the services discovered. If one matches 181 | // the UART service, look for its characteristics: 182 | for (var s in services) { 183 | console.log("Service found " + services[s].uuid + " comparing to " + serviceUUID + ": " + (services[s].uuid === serviceUUID)); 184 | if (services[s].uuid === serviceUUID) { 185 | console.log("Match " + services[s]); 186 | self.emit('preparing'); 187 | services[s].discoverCharacteristics(null, getCharacteristics); 188 | return; 189 | } 190 | } 191 | } 192 | 193 | // the BLE write function. If there's a valid transmit characteristic, 194 | /// then write data out to it as a Buffer: 195 | self.write = function(data) { 196 | if (transmit) { 197 | // you can only send at most 20 bytes in a Bluetooth LE pacet. 198 | // so slice the data into 20-byte chunks: 199 | while (data.length > 20) { 200 | var output = data.slice(0, 19); 201 | transmit.write(new Buffer(output), writeWithoutResponse); 202 | data = data.slice(20); 203 | } 204 | // send any remainder bytes less than the last 20: 205 | transmit.write(new Buffer(data), writeWithoutResponse); 206 | } 207 | }; 208 | 209 | // the BLE disconnect function: 210 | self.disconnect = function() { 211 | self.connected = false; 212 | self.discovering = false; 213 | }; 214 | 215 | // when the radio turns on, start scanning: 216 | noble.on('stateChange', self.scan); 217 | // if you discover a peripheral with the appropriate service, connect: 218 | noble.on('discover', self.connect); 219 | }; 220 | 221 | util.inherits(BleUart, EventEmitter); // BleUart inherits all the EventEmitter properties 222 | module.exports = BleUart; // export BleUart 223 | 224 | --------------------------------------------------------------------------------