├── .eslintrc.json ├── .gitignore ├── .travis.yml ├── README.md ├── bin └── bluetooth-mqtt-proxy.js ├── config.json ├── index.js ├── lib ├── cli.js ├── debug.js ├── index.js └── resolve.js └── package.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "rules": { 4 | "global-require": 0, 5 | "import/no-dynamic-require": 0 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log 4 | .node-version 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | env: 5 | - NODE_ENV=test 6 | script: 7 | - npm run lint 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bluetooth MQTT Proxy 2 | 3 | Runs a bluetooth LE service that forwards messages to a MQTT service. 4 | 5 | ## Setup 6 | 7 | ```sh 8 | $ npm install bluetooth-mqtt-proxy -g 9 | ``` 10 | 11 | ## Config 12 | 13 | ```json 14 | { 15 | "name": "BLE2MQTT", 16 | "uuid": "70B3FB84-748F-4BA1-A162-28F19F068CD2", 17 | "host": "mqtt://localhost", 18 | "characteristics": [ 19 | { 20 | "uuid": "3DC74FD3-73C4-4252-89CA-ACF5197636DC", 21 | "topic": "presence" 22 | }, 23 | { 24 | "uuid": "3D54BCE8-FAC4-447D-8100-70C15F4D7219", 25 | "topic": "foo" 26 | } 27 | ] 28 | } 29 | ``` 30 | 31 | ## Running 32 | 33 | ```sh 34 | $ bluetooth-mqtt-proxy -c=/path/to/config.json 35 | ``` 36 | 37 | ## Debug 38 | 39 | ```sh 40 | DEBUG=bluetooth-mqtt-proxy* bluetooth-mqtt-proxy -c=/path/to/config.json 41 | ``` 42 | 43 | ## Puck.js client 44 | 45 | [Puck.js](http://www.puck-js.com/) is a JavaScript microcontroller you can program wirelessly. 46 | Here's a basic client implementation: 47 | 48 | ```javascript 49 | const blink = (led) => { 50 | const duration = 300; 51 | setTimeout(() => { 52 | led.write(true); 53 | setTimeout(() => { 54 | led.write(false); 55 | }, duration); 56 | }, duration); 57 | }; 58 | 59 | // As specified in config.json 60 | const name = 'BLE2MQTT'; 61 | const serviceUUID = '70b3fb84-748f-4ba1-a162-28f19f068cd2'; 62 | const topicUUID = '3DC74FD3-73C4-4252-89CA-ACF5197636DC'; 63 | 64 | // When button is clicked 65 | setWatch(() => { 66 | 67 | // Look for bluetooth-mqtt-proxy service 68 | NRF 69 | .requestDevice({ timeout: 5000, filters: [{ namePrefix: name }] }) 70 | .then((device) => { 71 | 72 | // Blink green to indicate service was found 73 | blink(LED2); 74 | 75 | // Connect to service 76 | device.gatt 77 | .connect() 78 | .then(gatt => gatt.getPrimaryService(serviceUUID)) 79 | .then(service => service.getCharacteristic(topicUUID)) 80 | .then((characteristic) => { 81 | 82 | // Send message to service, then blink blue 83 | characteristic.writeValue("Hello world!"); 84 | blink(LED3); 85 | device.gatt.disconnect(); 86 | }); 87 | }) 88 | .catch((error) => { 89 | 90 | // Error, blink red 91 | console.log(error); 92 | blink(LED1); 93 | }); 94 | }, BTN, { edge: 'rising', debounce: 50, repeat: true }); 95 | ``` 96 | -------------------------------------------------------------------------------- /bin/bluetooth-mqtt-proxy.js: -------------------------------------------------------------------------------- 1 | #! /usr/local/bin/node --harmony 2 | 3 | const bluetoothMQTTProxy = require('../'); 4 | const { processArgv } = require('../lib/cli'); 5 | 6 | const argsMap = { 7 | config: ['-c', '--config'], 8 | }; 9 | const args = processArgv(argsMap); 10 | 11 | bluetoothMQTTProxy(args); 12 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BLE2MQTT", 3 | "uuid": "70B3FB84-748F-4BA1-A162-28F19F068CD2", 4 | "host": "mqtt://butterfly.local", 5 | "characteristics": [ 6 | { 7 | "uuid": "3DC74FD3-73C4-4252-89CA-ACF5197636DC", 8 | "topic": "presence" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const bleno = require('bleno'); 2 | const peripheral = require('./lib'); 3 | const { resolvePath } = require('./lib/resolve'); 4 | const debug = require('./lib/debug')('main'); 5 | 6 | module.exports = (args) => { 7 | const config = Object.assign({}, require(resolvePath(args.config)), args); 8 | bleno.on('accept', () => debug('Accepted')); 9 | bleno.on('disconnect', () => debug('Disconnected')); 10 | bleno.on('stateChange', (state) => { 11 | debug('state change'); 12 | if (state === 'poweredOn') { 13 | debug('powered on'); 14 | peripheral(config); 15 | } 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | const processArgv = (knownArgs) => { 2 | const SEPARATOR = '='; 3 | const argsMap = {}; 4 | const args = {}; 5 | 6 | Object.keys(knownArgs).forEach((arg) => { 7 | knownArgs[arg].forEach((name) => { 8 | argsMap[name] = arg; 9 | }); 10 | }); 11 | 12 | process.argv.slice(2).forEach((arg) => { 13 | const [name, value] = arg.split(SEPARATOR); 14 | const key = argsMap[name] ? argsMap[name] : name; 15 | args[key] = value !== undefined ? value : true; 16 | }); 17 | 18 | return args; 19 | }; 20 | 21 | module.exports = { processArgv }; 22 | -------------------------------------------------------------------------------- /lib/debug.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug'); 2 | const { name } = require('../package'); 3 | 4 | module.exports = file => debug(`${name}#${file}`); 5 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const mqtt = require('mqtt'); 2 | const bleno = require('bleno'); 3 | const debug = require('./debug')('service'); 4 | 5 | module.exports = (config) => { 6 | const client = mqtt.connect(config.host); 7 | 8 | client.on('connect', () => { 9 | debug(`mqtt client connected to ${config.host}`); 10 | 11 | const characteristics = config.characteristics.map((characteristic) => { 12 | const { uuid, topic } = characteristic; 13 | const properties = ['read', 'write']; 14 | const onReadRequest = (offset, callback) => { 15 | callback(bleno.Characteristic.RESULT_SUCCESS, new Buffer([0])); 16 | }; 17 | const onWriteRequest = (data, offset, withoutResponse, callback) => { 18 | const publish = data.toString(); 19 | debug(`publishing ${publish} to ${topic}`); 20 | client.publish(topic, publish); 21 | callback(bleno.Characteristic.RESULT_SUCCESS); 22 | }; 23 | 24 | return new bleno.Characteristic({ uuid, properties, onReadRequest, onWriteRequest }); 25 | }); 26 | 27 | const { uuid, name } = config; 28 | const service = new bleno.PrimaryService({ uuid, characteristics }); 29 | bleno.startAdvertising(name, [uuid], (error) => { 30 | if (!error) { 31 | bleno.setServices([service]); 32 | } else { 33 | debug('error', error); 34 | } 35 | }); 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /lib/resolve.js: -------------------------------------------------------------------------------- 1 | const { resolve, join } = require('path'); 2 | 3 | const resolvePath = path => join(path[0] === '~' ? join(process.env.HOME, path.slice(1)) : resolve(path)); 4 | 5 | module.exports = { resolvePath }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bluetooth-mqtt-proxy", 3 | "version": "0.0.4", 4 | "description": "Bluetooth MQTT Proxy", 5 | "main": "index.js", 6 | "bin": { 7 | "bluetooth-mqtt-proxy": "./bin/bluetooth-mqtt-proxy.js" 8 | }, 9 | "scripts": { 10 | "lint": "./node_modules/.bin/eslint .", 11 | "test": "./node_modules/.bin/mocha *.test.js **/*.test.js", 12 | "preversion": "npm run lint", 13 | "patch-release": "npm version patch && npm publish && git push --follow-tags", 14 | "minor-release": "npm version minor && npm publish && git push --follow-tags", 15 | "major-release": "npm version major && npm publish && git push --follow-tags" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/flochtililoch/bluetooth-mqtt-proxy.git" 20 | }, 21 | "author": "flochtililoch", 22 | "license": "ISC", 23 | "dependencies": { 24 | "bleno": "^0.4.1", 25 | "debug": "^2.6.0", 26 | "mqtt": "^2.3.0" 27 | }, 28 | "devDependencies": { 29 | "babel-eslint": "^7.1.1", 30 | "eslint": "^3.14.1", 31 | "eslint-config-airbnb-base": "^11.0.1", 32 | "eslint-plugin-import": "^2.2.0", 33 | "mocha": "^3.2.0" 34 | } 35 | } 36 | --------------------------------------------------------------------------------