Paul Langdon
Published © GPL3+

Love Connection

Connect on the next level, haptic, gestures and bio-feedback make a bond only a dead battery can break.

AdvancedWork in progress4 hours954
Love Connection

Things used in this project

Hardware components

Hexiwear
NXP Hexiwear
×1

Software apps and online services

Evothings Studio
Evothings Studio

Story

Read more

Custom parts and enclosures

Hexiband

Schematics

Component Diagram

Code

index.html

HTML
<!DOCTYPE html>

<html>

<head>

	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width, user-scalable=no,
		shrink-to-fit=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0" />

	<title>Love Connection</title>

	<style>
		@import 'ui/css/evothings-app.css';
	</style>

	<style>
		#connectButton {
			width: 100%;
			float: left;
		}

		#disconnectButton {
			width: 49%;
			float: right;
		}

		#deviceName {
			width: 100%;
			display: block;
			-webkit-box-sizing: border-box;
			box-sizing: border-box;
		}

	</style>

	<script>

	// Redirect console.log to Evothings Workbench.
	if (window.hyper && window.hyper.log) { console.log = hyper.log }
	</script>

	<!--script src="bundle.js"></script-->
	<script src="cordova.js"></script>
	<script src="libs/jquery/jquery.js"></script>
	<script src="libs/evothings/evothings.js"></script>
	<script src="libs/evothings/ui/ui.js"></script>
	<script src="libs/bleat/bluetooth.helpers.js"></script>
	<script src="libs/bleat/api.web-bluetooth.js"></script>
	<script src="libs/bleat/adapter.evothings.js"></script>
	<script src="libs/mqttws31.js"></script>
	<script src="bundles-of-love-app.js"></script>

</head>

<body ontouchstart=""><!-- ontouchstart="" enables low-delay CSS transitions. -->
	<header>
		<button class="back" onclick="history.back()">
			<img src="ui/images/arrow-left.svg" />
		</button>
		<img class="logotype" src="ui/images/logo.svg" alt="Evothings" />
	</header>

	<h1>Love Connection</h1>

	<h2>Enter your Love Connection device</h2>

	<input id="deviceName" value="Type It Here!" type="text" />

	<button id="connectButton" class="blue">
		Connect
	</button>

	<p id="info"></p>

	<canvas id="canvas" width="300" height="150"></canvas>

</body>

</html>

bundles-of-love-app.js

JavaScript
// Using closure to avoid global name space conflicts.
;(() =>
{
'use strict';

// Timeinterval between acceleration fetch. 
const ACCELERATION_SAMPLE_PERIOD = 200; 
const MOTION_SERVICE_UUID = '2000';
const ACCELERATION_CHARACTERISTIC_UUID = '2001';


// BEWARE: These need to be edited! 
// Put In Your MQTT Credentials.
var host = 'm13.cloudmqtt.com';
var port = 1833;
var user = 'youUser';
var password = 'yourPass';


// Application object
var app = {};

// Name of the device the application will try to connect to. 
app.deviceName = 'YorDeviceName';

// The BLE GATT server.
app.gattServer = null;

// BLE objects
app.motionService = null;
app.accelerationCharacteristic = null;
app.accelerationSamples = []; 




// Initialise the application.
app.initialize = function() {

	document.addEventListener(
		'deviceready',
		app.onDeviceReady,
		false);
};

// When low level initialization is complete, this function is called.
app.onDeviceReady = function() {

	// Report status.
	app.showInfo('Enter BLE Love Connection device name and tap Connect');

	// Show the saved device name, if any.
	var name = localStorage.getItem('deviceName');
	if (name) {
		app.deviceName = name;
	}
	$('#deviceName').val(app.deviceName);

    app.pubTopic = '/love/' + device.uuid + '/evt'; // We publish to our own device topic
		app.subTopic = '/love/+/evt'; // We subscribe to all devices using "+" wildcard
		app.setupConnection(); //Make MQTT Connection
		
	// Register callback for connectButton
	$('#connectButton').click(app.onConnectButton);	
};

// Executed when the Connect/Disconnect button is pressed. 
app.onConnectButton = function() {
	
	if(app.gattServer) {
		
		app.disconnect();
	}
	else {

		app.connect();
	}
};

// Try to connect the smartphone to the GATT server.
app.connect = function() {

	// Get device name from text field.
	app.deviceName = $('#deviceName').val();

	// Save it for next time we use the app.
	localStorage.setItem('deviceName', app.deviceName);

	// Disconnect if connected.
	if (app.gattServer && app.gattServer.connected)
	{
		app.gattServer.disconnect();
		app.gattServer = null;
	}

	app.showInfo('Status: Scanning...');

	// Find and connect to device and get characteristics for LED read/write.
	bleat.requestDevice({

		filters:[{ name: app.deviceName }]
	})
	.then(device => {

		app.showInfo('Status: Found device: ' + device.name);

		// Connect to device.
		return device.gatt.connect();
	})
	.then(server => {

		app.showInfo('Status: Connected');
		
		// Save gatt server.
		app.gattServer = server;

		$('#connectButton').html('Disconnect').attr('class', 'red');

		app.timer = setInterval(function() {

			app.gattServer.getPrimaryService(MOTION_SERVICE_UUID).then(service => {
				
				app.motionService = service;
				
				return app.motionService.getCharacteristic(ACCELERATION_CHARACTERISTIC_UUID);
			})
			.then(characteristic => {

				app.accelerationCharacteristic = characteristic;
				
				return app.accelerationCharacteristic.readValue(); 
			})
			.then(accelerationBuffer => {

				// Parse acceleration variables. 
				var ax = accelerationBuffer.getInt16(0, true) / 100.0;
				var ay = accelerationBuffer.getInt16(2, true) / 100.0;
				var az = accelerationBuffer.getInt16(4, true) / 100.0;

				app.drawDiagram({ x: ax, y: ay, z: az });
				
				var args = app.build_args(ax, ay, az, new Date().valueOf());

				app.c.post(app.artikcloud, args, function(data, response) {            
					console.log(data);
				});

			})
			.catch(error => {

				app.showInfo(error);
			});

		}, ACCELERATION_SAMPLE_PERIOD);


	})
	.catch(error => {

		app.showInfo(error);
	});
};

// Disconnect smartphone from the GATT server.
app.disconnect = function() {

	// Disconnect from GATT server
	app.gattServer.disconnect();
	app.gattServer = null; 
	clearInterval(app.timer);

	// Update UI 
	app.clearDiagram();
	app.showInfo('Status: Disconnected');
	$('#connectButton').html('Connect').attr('class', 'blue');
};


// Print debug info to console and application UI.
app.showInfo = function(info) {

	document.getElementById('info').innerHTML = info;
	console.log(info);
};

app.clearDiagram = function() {

	var canvas = document.getElementById('canvas');
	var context = canvas.getContext('2d');

	// Clear diagram canvas.
	context.clearRect(0, 0, canvas.width, canvas.height);
	
	// Remove samples from array.
	app.accelerationSamples.length = 0;
};

// Draw diagram to canvas. 
app.drawDiagram = function(values) {

	var canvas = document.getElementById('canvas');
	var context = canvas.getContext('2d');

	// Add recent values.
	app.accelerationSamples.push(values);

	// Remove data points that do not fit the canvas.
	if (app.accelerationSamples.length > canvas.width) {

		app.accelerationSamples.splice(0, (app.accelerationSamples.length - canvas.width));
	}

	// Value is an accelerometer reading between -1 and 1.
	function calcDiagramY(value){

		// Return Y coordinate for this value.
		var diagramY = ((value * canvas.height) / 8) + (canvas.height / 2);
		return diagramY;
	}

	function drawLine(axis, color) {

		context.strokeStyle = color;
		context.beginPath();
		var lastDiagramY = calcDiagramY(
			app.accelerationSamples[app.accelerationSamples.length-1][axis]);
		context.moveTo(0, lastDiagramY);
		var x = 1;
		for (var i = app.accelerationSamples.length - 2; i >= 0; i--)
		{
			var y = calcDiagramY(app.accelerationSamples[i][axis]);
			context.lineTo(x, y);
			x++;
		}
		context.stroke();
	}
	
	function drawPartnerLine(axis, color, val) {

		context.strokeStyle = color;
		context.beginPath();
		context.moveTo(0, val);
		var x = 1;
		for (var i = app.accelerationSamples.length - 2; i >= 0; i--)
		{
			var y = calcDiagramY(app.accelerationSamples[i][axis]);
			context.lineTo(x, y);
			x++;
		}
		context.stroke();
	}
	

	// Clear background.
	context.clearRect(0, 0, canvas.width, canvas.height);

	// Draw lines.
	drawLine('x', '#f00');
	drawLine('y', '#0f0');
	drawLine('z', '#00f');
	
	// Publish
	if (app.connected) {
			var msg = JSON.stringify({ldata: {x:x, y:y, z:z}})
			app.publish(msg);
		}
};


app.setupConnection = function() {
  app.status("Connecting to " + host + ":" + port + " as " + device.uuid);
	app.client = new Paho.MQTT.Client(host, port, device.uuid);
	app.client.onConnectionLost = app.onConnectionLost;
	app.client.onMessageArrived = app.onMessageArrived;
	var options = {
    useSSL: true,
    onSuccess: app.onConnect,
    onFailure: app.onConnectFailure
  }
	app.client.connect(options);
}

app.publish = function(json) {
	message = new Paho.MQTT.Message(json);
	message.destinationName = app.pubTopic;
	app.client.send(message);
};

app.subscribe = function() {
	app.client.subscribe(app.subTopic);
	console.log("Subscribed: " + app.subTopic);
}

app.unsubscribe = function() {
	app.client.unsubscribe(app.subTopic);
	console.log("Unsubscribed: " + app.subTopic);
}

app.onMessageArrived = function(message) {
	var o = JSON.parse(message.payloadString);
	
	// Draw lines.
	drawPartnerLine('x', '#ff0', o.ldata.x);
	drawPartnerLine('y', '#0ff', o.ldata.y);
	drawPartnerLine('z', '#f0f', o.ldata.z);
	
}

app.onConnect = function(context) {
	app.subscribe();
	app.status("Connected!");
	app.connected = true;
}

app.onConnectFailure = function(e){
  console.log("Failed to connect: " + JSON.stringify(e));
}

app.onConnectionLost = function(responseObject) {
	app.status("Connection lost!");
	console.log("Connection lost: "+responseObject.errorMessage);
	app.connected = false;
}


// Initialize the app.
app.initialize();

})(); 

Credits

Paul Langdon

Paul Langdon

49 projects • 319 followers
Working as a cloud architect for an IoT hardware company

Comments