Setting up - Node.js
In this guide we will setup a simple consumer connection with the Terra WebSockets service. From a consumer connection, you will be able to read data from any wearables which are currently streaming to Terra through a producer connection.
For information on how to set up a wearable to start streaming data to the Terra WebSockets service through a producer connection, see Setting up a WebSocket Producer.
For deeper detail about the WebSockets streaming protocol, checkout our How-Tos: Using WebSockets blog post.
Installation
This guide will set up the WebSocket consumer in Node.js, however the same ideas can be easily applied to any other framework which has a WebSockets library available. Contact us at [email protected] if you wish to see examples written in other programming languages.
Install the ws
library.
npm install ws
You can now import it into your code:
const WebSocket = require("ws")
Initialise Connection
First, you will need to generate a "streaming developer (consumer) connection token" through the ws.tryterra.co/auth/developer API endpoint.
Let us declare a function initWS()
to perform all necessary steps to initialise a WebSocket connection. Any following code snippets in this guide should be written within this function.
const WebSocket = require("ws")
const WS_CONNECTION = "wss://ws.tryterra.co/connect";
function initWS(token) {
// following code goes here
// ...
}
initWS(token)
Initiating a connection to the Terra WebSockets service is as simple as:
const socket = new WebSocket(WS_CONNECTION)
We then need to implement EventListeners
on the socket
object to handle the possible events that could happen to our connection.
open
triggers when the connection first forms. Right now we simply printConnection Established
.close
triggers when the connection closes. There's usually a reason why it closed, which we can print.error
triggers when the crashes due to error. There's usually an error code, which we can print.
socket.addEventListener('open', function (event) {
console.log("Connection Established");
})
socket.addEventListener('close', function (event) {
console.log('close');
console.log(event.reason);
})
socket.addEventListener('error', function (event) {
console.log('error');
console.log(event);
})
Verify and Maintain the Connection
The Terra Streaming Protocol - necessary background info
After opening a WebSocket connection to the Terra, we can now send and receive messages to the Terra WebSockets service. According to the Terra Streaming Protocol, before we can start receiving data from users, we first need to
- verify the WebSocket connection with a Streaming Developer Token
- maintain a heartbeat signal to keep the WebSocket connection alive
All messages passed through a WebSocket connection with Terra must conform to a JSON schema defined by the Terra Streaming Protocol. In the protocol, all messages must have an "op" integer field which determines what type of message it is, followed by any number of optional fields specific to the message type.
{
"op": int,
...
}
Implementation
The message
event triggers when any data is received at the WebSocket connection. The following code sets up an event listener for the message
event and performs different functionality based on the specific message received. The msg
variable (line 18) contains the parsed message received from Terra.
var expectingHeartBeatAck = false
function heartBeat(){
if(!expectingHeartBeatAck){
var heartBeatPayload = JSON.stringify({
"op": 0
})
socket.send(heartBeatPayload)
console.log("↑ " + heartBeatPayload)
expectingHeartBeatAck = true
}
else socket.close()
}
socket.addEventListener('message', function (event) {
console.log("↓ " + event.data);
var msg = JSON.parse(event.data)
if (msg["op"] == 2){
heartBeat()
setInterval(heartBeat, message["d"]["heartbeat_interval"])
var payload = JSON.stringify(
{
"op": 3,
"d": {
"token": token,
"type": 1 //0 for user, 1 for developer
}
}
)
socket.send(payload)
}
if (msg["op"] == 1){
expectingHeartBeatAck = false
}
})
After opening a connection to Terra, the protocol dictates that we will immediately receive a Opcode 2
"HELLO" message from Terra in the form of:
{"op":2, "d":{"heartbeat_interval": 40000}}
This tells you to send a Opcode 0
"HEARTBEAT" message to Terra every 40 seconds, to signify that you still wish to keep the connection alive.
After sending a "HEARTBEAT" to Terra, we also expect to receive an Opcode 1
"HEARTBEAT_ACK", meaning that Terra knows you are still connected and will not close your connection. If we do not receive an acknowledgement within 40 seconds of sending a heartbeat, then we can assume the connection is dead. We can close the connection and open a new one. The heartBeat()
function implements this logic.
Before Terra will send us any user data, we also need to authenticate the connection by sending an Opcode 3
"IDENTIFY" message to Terra, which includes a valid a "Streaming Developer Token".
{
"op": 3,
"d": {
"token": token,
"type": 1 // 0 for producer, 1 for consumer
}
}
Any other messages we receive (such as those including user data) are simply printed, so we will see them in the console.
Testing the Code
Your final code should now look like this:
const WebSocket = require("ws")
const fetch = require("node-fetch-commonjs")
const WS_CONNECTION = "wss://ws.tryterra.co/connect"
async function getToken() {
const url = 'https://ws.tryterra.co/auth/developer'
const options = {
method: 'POST',
headers: {
accept: 'application/json',
'dev-id': 'DEV_ID',
'x-api-key': 'API_KEY',
}
}
try {
let tokenResp = await fetch(url, options)
return (await tokenResp.json())["token"]
}catch (e) {
console.error(e)
}
return ""
}
function initWS(token) {
const socket = new WebSocket(WS_CONNECTION)
socket.addEventListener('open', () =>
console.log("Connection Established")
)
socket.addEventListener('close', event => {
console.log('close')
console.log(event.reason)
})
socket.addEventListener('error', event => {
console.log('error')
console.log(event)
})
var expectingHeartBeatAck = false
function heartBeat() {
if (expectingHeartBeatAck) socket.close()
const heartBeatPayload = JSON.stringify({ "op": 0 })
socket.send(heartBeatPayload)
console.log("↑ " + heartBeatPayload)
expectingHeartBeatAck = true
}
socket.addEventListener('message', function (event) {
console.log("↓ " + event.data);
const msg = JSON.parse(event.data)
if (msg.op == 2){
heartBeat()
const interval = msg.d.heartbeat_interval
setInterval(heartBeat, interval)
var payload = JSON.stringify({
"op": 3,
"d": {
"token": token,
"type": 1 //0 for user, 1 for developer
}
})
socket.send(payload)
}
if (msg.op == 1) {
expectingHeartBeatAck = false
}
})
}
getToken().then(token => initWS(token))
If you have already setup wearables to stream data to Terra, running the code should give you an output similar to the following:
Connection Established
↓ {"op":2,"d":{"heartbeat_interval":40000}}
↑ {"op":0}
↑ {"op":3,"d":{"token":"dGVzdGluZ0VsbGlvdHQ.MU1fASa1nR9EpQWQx67xIN7veTFKFwudaB4HbN4kw9A","type":1}}
↓ {"op":1}
↓ {"op":4}
↑ {"op":0}
↓ {"op":1}
↓ {"op":5,"d":{"ts":"2022-06-01T14:47:08.055Z","val":86.0},"uid":"100b370c-d2be-42a2-b757-01fc85c41031","seq":12254,"t":"HEART_RATE"}
↓ {"op":5,"d":{"ts":"2022-06-01T14:47:08.701Z","val":87.0},"uid":"100b370c-d2be-42a2-b757-01fc85c41031","seq":12255,"t":"HEART_RATE"}
↓ {"op":5,"d":{"ts":"2022-06-01T14:47:09.698Z","val":86.0},"uid":"100b370c-d2be-42a2-b757-01fc85c41031","seq":12256,"t":"HEART_RATE"}
↓ {"op":5,"d":{"ts":"2022-06-01T14:47:11.041Z","val":86.0},"uid":"100b370c-d2be-42a2-b757-01fc85c41031","seq":12257,"t":"HEART_RATE"}
↓ {"op":5,"d":{"ts":"2022-06-01T14:47:12.049Z","val":86.0},"uid":"100b370c-d2be-42a2-b757-01fc85c41031","seq":12258,"t":"HEART_RATE"}
We can break the messages down:
- An initial
Hello
from the server withOpcode 2
followed by the script sending anOpcode 3
with the verification payload. The server then sends back anOpcode 4
meaning "Ready" - The
Opcode 0
andOpcode 1
to and from the server is simply the "heart beating" - Then there are many
Opcode 5
messages that contain data for a user with uid and their "HEART_RATE" values
If you don't have any active wearables streaming to Terra, you will still be able to see the heartbeat messages (Opcode 0
andOpcode 1
), but you won't see any Opcode 5
messages. To learn how to stream data to Terra, checkout Setting up a WebSocket Producer.
Congratulations! You have now set up a WebSocket consumer connection.
Updated about 1 year ago