commit 3f9e2853b03975d87e7282d76fdb6920ed01e2ad Author: mmcwilliams Date: Mon Aug 21 21:11:07 2017 -0400 Start project with blootstrap (pre publishing) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..2034e74 --- /dev/null +++ b/Readme.md @@ -0,0 +1,2 @@ +# intval3 + diff --git a/index.js b/index.js new file mode 100644 index 0000000..49cede5 --- /dev/null +++ b/index.js @@ -0,0 +1,45 @@ +'use strict' + +const ble = require('./lib/blootstrap') +const express = require('express') +const app = express() +const gpio = require('gpio') +const gpio4 = gpio.export(4, { + direction: 'out', + interval: 100, + ready : () => { + } +}) +const PORT = process.env.PORT || 6699 +const APPNAME = 'my_project' + +function blink (req, res, next) { + console.log('Blinking!') + gpio4.set(1) + setTimeout(() => { + gpio4.set(0) + res.send('

You blinked!

') + next() + }, 1000) +} + +function index (req, res, next) { + res.send( + `

Welcome to my app!

+
+ +
`) + next() +} + +ble.on('data', (str) => { + console.log(str) + blink() +}) + +app.get('/', index) +app.all('/blink', blink) + +app.listen(PORT, () => { + console.log(`${APPNAME} listening on port ${PORT}!`) +}) \ No newline at end of file diff --git a/lib/blootstrap/index.js b/lib/blootstrap/index.js new file mode 100644 index 0000000..33d9da0 --- /dev/null +++ b/lib/blootstrap/index.js @@ -0,0 +1,32 @@ +'use strict' + +const ipc = require('node-ipc') + +function capitalize (s) { + return s[0].toUpperCase() + s.slice(1) +} + +class Blootstrap { + constructor () { + this._onData = () => {} + ipc.connectTo('blootstrap_ble', () => { + ipc.of.blootstrap_ble.on('connect', () => { + ipc.log(`Connected to the blootstrap_ble service`) + + }) + ipc.of.blootstrap_ble.on('data', data => { + const str = data.toString() + ipc.log(str) + this._onData(str) + }) + ipc.of.blootstrap_ble.on('disconnect', () => { + ipc.log(`Disconnected from the blootstrap_ble service`) + }) + }) + } + on (eventName, callback) { + this[`_on${capitalize(eventName)}`] = callback + } +} + +module.exports = new Blootstrap() \ No newline at end of file diff --git a/lib/wifi/index.js b/lib/wifi/index.js new file mode 100644 index 0000000..9688153 --- /dev/null +++ b/lib/wifi/index.js @@ -0,0 +1,90 @@ +'use strict' + +const networkPattern = /network[\s\S]*?=[\s\S]*?{([\s\S]*?)}/gi +const quoteRe = new RegExp('"', 'g') + +const filePath = '/etc/wpa_supplicant/wpa_supplicant.conf' +const reconfigure = '/sbin/wpa_cli reconfigure' +const refresh = '/sbin/ifdown wlan0 && /sbin/ifup --force wlan0' +const iwlist = '/sbin/iwlist wlan0 scanning | grep "ESSID:"' +const iwgetid = '/sbin/iwgetid' + +const exec = require('child_process').exec +const fs = require('fs') + +class wifi { + constructor () { + this._callback = () => {} + this._entry = null + } + list (callback) { + exec(iwlist, (err, stdout, stderr) => { + if (err) { + console.error(err) + return callback(err) + } + const lines = stdout.split('\n') + const output = [] + let line + for (let l of lines) { + line = l.replace('ESSID:', '').trim() + if (line != '""') { + line = line.replace(quoteRe, '') + output.push(line) + } + } + return callback(null, output) + }) + } + _readConfigCb (err, data) { + if (err) { + console.error(err) + return this._callback(err) + } + if (data.search(networkPattern) === -1) { + data += `\n${this._entry}` + } else { + data = data.replace(networkPattern, entry) + } + fs.writeFile(filePath, data, 'utf8', this._writeConfigCb) + } + _writeConfigCb (err) { + if (err) { + console.error(err) + return this._callback(err) + } + exec(reconfigure, this._reconfigureCb) + } + _reconfigureCb (err, stdout, stderr) { + if (err) { + console.error(err) + return this._callback(err) + } + console.log('Wifi reconfigured') + exec(refresh, this._refreshCb) + } + _refreshCb (err, stdout, stderr) { + if (err) { + console.error(err) + return this._callback(err) + } + console.log('Wifi refreshed') + this._callback(null, { ssid : ssid, pwd : pwd.length }) + this._callback = () => {} + } + setNetwork (ssid, pwd, callback) { + this._entry = `network={\n\tssid="${ssid}"\n\tpsk="${pwd}"\n}\n` + this._callback = callback + fs.readFile(filePath, 'utf8', this._readConfigCb) + } + getNetwork (callback) { + exec(iwgetid, (err, stdout, stderr) => { + if (err) { + return callback(err) + } + callback(null, stdout) + }) + } +} + +module.exports = new wifi() \ No newline at end of file diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..a89f37d --- /dev/null +++ b/nginx.conf @@ -0,0 +1,47 @@ +#blootstrap nginx conf + +#uncomment and modify following files for ssl +#server { + + #listen 80; + #server_name my_project; + #return 301 https://$server_name$request_uri; + +#} + +server { + listen 80; + #listen 443 ssl; + + #ssl on; + #ssl_certificate {{SSL_CERT_PATH}}; + #ssl_certificate_key {{SSL_KEY_PATH}}; + + #ssl_session_timeout 5m; + #ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2; + #ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES"; + #ssl_prefer_server_ciphers on; + + #server_name my_project; + + location / { + proxy_pass http://127.0.0.1:6699/; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + gzip on; + gzip_comp_level 9; + gzip_types text/plain text/html text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript application/json; + } + #uncomment for static file servers + #location /static/ { + #uncomment to turn on caching + #expires modified 1y; + #access_log off; + #add_header Cache-Control "public"; + #gzip on; + #gzip_comp_level 9; + #gzip_types text/plain text/html text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript application/json; + #alias /var/node/blootstrap/static/; + #} +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..2e5c8c9 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "intval3", + "version": "0.0.1", + "description": "Intervalometer for the Bolex", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/sixteenmillimeter/intval3.git" + }, + "jshintConfig": { + "esversion": 6, + "strict": "global", + "node": true, + "asi": true + }, + "author": "sixteenmillimeter", + "license": "MIT", + "bugs": { + "url": "https://github.com/sixteenmillimeter/intval3/issues" + }, + "homepage": "https://github.com/sixteenmillimeter/intval3#readme", + "dependencies": { + "bleno": "^0.4.2", + "gpio": "^0.2.7", + "node-ipc": "^9.1.0", + "restify": "^5.2.0", + "uuid": "^3.1.0" + } +} diff --git a/process.json b/process.json new file mode 100644 index 0000000..5e3c28f --- /dev/null +++ b/process.json @@ -0,0 +1,12 @@ +{ + "apps" : [ + { + "name" : "ble", + "script" : "./services/bluetooth/index.js", + "watch" : false, + "env" : { + "BLENO_DEVICE_NAME" : "intval3" + } + } + ] +} \ No newline at end of file diff --git a/scripts/blootstrap-deps.sh b/scripts/blootstrap-deps.sh new file mode 100644 index 0000000..90a92f3 --- /dev/null +++ b/scripts/blootstrap-deps.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +echo "Running blootstrap install script" +apt-get update +apt-get install git ufw nginx -y + +echo "Installing node.js dependencies.." +apt-get install nodejs npm -y +npm install -g n +n latest +npm install -g npm@latest +npm install -g pm2 + +echo "Installing bluetooth dependencies..." +apt-get install bluetooth bluez libbluetooth-dev libudev-dev -y + +echo "Finished installing blootstrap dependencies" \ No newline at end of file diff --git a/scripts/blootstrap-install.sh b/scripts/blootstrap-install.sh new file mode 100644 index 0000000..7348138 --- /dev/null +++ b/scripts/blootstrap-install.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +echo "Running blootstrap install script" +apt-get update +apt-get install git ufw nginx -y + +echo "Installing node.js dependencies.." +apt-get install nodejs npm -y +npm install -g n +n latest +npm install -g npm@latest +npm install -g pm2 + +echo "Installing bluetooth dependencies..." +apt-get install bluetooth bluez libbluetooth-dev libudev-dev -y + +echo "Configuring ufw (firewall)..." +ufw default deny incoming +ufw default allow outgoing +ufw allow ssh +ufw allow http +ufw allow https +ufw enable + +echo "Installing blootstrap project..." +mkdir /var/node +cd /var/node +wget https://github.com/mattmcw/blootstrap/archive/master.zip +unzip master.zip -d blootstrap/ +rm master.zip + +cd blootstrap +npm install +pm2 start process.json + +echo "Finished installing blootstrap" \ No newline at end of file diff --git a/services/bluetooth/index.js b/services/bluetooth/index.js new file mode 100644 index 0000000..ce79b70 --- /dev/null +++ b/services/bluetooth/index.js @@ -0,0 +1,116 @@ +'use strict' + +const ipc = require('node-ipc') +const bleno = require('bleno') +const util = require('util') +const wifi = require('../../lib/wifi') + +const BLENO_DEVICE_NAME = process.env.BLENO_DEVICE_NAME || 'my_project' +const DEVICE_ID = process.env.DEVICE_ID || 'my_project_id' +const SERVICE_ID = process.env.SERVICE_ID || 'blootstrap' +const CHAR_ID = process.env.CHAR_ID || 'blootstrapwifi' + +const chars = [] + +ipc.config.id = 'blootstrap_ble' +ipc.config.retry = 1500 +ipc.config.rawBuffer = true +ipc.config.encoding = 'hex' + +function createChar(name, uuid, prop, write, read) { + function characteristic () { + bleno.Characteristic.call(this, { + uuid : uuid, + properties: prop + }) + } + util.inherits(characteristic, bleno.Characteristic) + if (prop.indexOf('read')) { + //data, offset, withoutResponse, callback + characteristic.prototype.onReadRequest = read + } + if (prop.indexOf('write')) { + characteristic.prototype.onWriteRequest = write + } + char.push(new characteristic()) +} + +function onWifiWrite (data, offset, withoutResponse, callback) { + let result + let utf8 + let obj + let ssid + let pwd + if (offset) { + console.warn(`Offset scenario`) + result = bleno.Characteristic.RESULT_ATTR_NOT_LONG + return callback(result) + } + utf8 = data.toString('utf8') + obj = JSON.parse(utf8) + ssid = obj.ssid + pwd = obj.pwd + console.log(`Connecting to AP: ${ssid}...`) + return wifi.setNetwork(ssid, pwd, (err, data) => { + if (err) { + console.error('Error configuring wifi', err) + result = bleno.Characteristic.RESULT_UNLIKELY_ERROR + return callback(result) + } + console.log(`Connected to ${ssid}`) + result = bleno.Characteristic.RESULT_SUCCESS + return callback(result) + }) +} + +function onWifiRead (offset, callback) { + const result = bleno.Characteristic.RESULT_SUCCESS + + callback(result, data.slice(offset, data.length)) +} + +console.log('Starting bluetooth service') + +bleno.on('stateChange', state => { + console.log('on -> stateChange: ' + state) + if (state === 'poweredOn') { + console.log('Started advertising blootstrap services') + bleno.startAdvertising(BLENO_DEVICE_NAME, [DEVICE_ID]) + } else { + bleno.stopAdvertising() + } +}) + +bleno.on('advertisingStart', err => { + console.log('on -> advertisingStart: ' + (err ? 'error ' + err : 'success')) + if (!err) { + bleno.setServices([ + new bleno.PrimaryService({ + uuid : SERVICE_ID, //hardcoded across panels + characteristics : chars + }) + ]) + } +}) + +bleno.on('accept', clientAddress => { + console.log(`${clientAddress} accepted`) +}) + +bleno.on('disconnect', clientAddress => { + console.log(`${clientAddress} disconnected`) +}) + +ipc.serve(() => { + ipc.server.on('connect', socket => { + ipc.log('Client connected to socket') + }) + ipc.server.on('disconnect', () => { + ipc.log('Client disconnected from socket') + }) + ipc.server.on('data', (data, socket) => { + ipc.server.emit(socket, JSON.stringify({})) + }) +}) + +ipc.server.start() \ No newline at end of file