Streaming Mist Location Data using the WebSocket API

I recently worked on a project where the customer was trying to track BLE asset tags using the Juniper/Mist location solution.

I know that we can leverage the WebSocket API from Mist to be able to retrieve close-to-realtime location data, so I wrote a quick script before going onsite one day to test it out.

I wanted to see how fast we could retrieve the location data from the Mist Cloud using the WebSocket API.

USAGE

Once you will have configured the configuration variable, you should be able to run the script. (see the requirements section for more details)

In this example, I am tracking an asset tag called “Francois_ID”. Here is what the output will look like:

As you can see, we are getting one location update every 6 seconds in average.

There is a delay of about 30 seconds between the moment the tag is moved and the moment that the location received via WebSocket is updated (which is very similar to what we see on the Mist Dashboard).

REQUIREMENTS

Before running this script you have to perform the following actions:

  • Create an environmental variable called MIST_TOKEN that will contain your Token value
  • Retrieve your Mist Site ID
  • Retrieve the Map ID of the map used to track the asset tags
  • Retrieve the name of the asset tag you are tracking (use the same name as the one define on the Mist Dashboard)

Once you have these pieces of information, you can change the global variables located at the beginning of the script:

# Defining Global Variables (Configurations)
Token = os.getenv('MIST_TOKEN')
SiteId = "YOUR_SITE_ID"
MapId = "YOUR_MAP_ID"
AssetTagName = "ASSET_TAG_NAME"

Before running the script, you also need to install the websocket-client python library. You can use the following command to do it:

pip install websocket-client

THE CODE

import json
import websocket
import time
from datetime import datetime, timedelta
import os
 
# Defining Global Variables (Configurations)
Token = os.getenv('MIST_TOKEN')
SiteId = "YOUR_SITE_ID"
MapId = "YOUR_MAP_ID"
AssetTagName = ""
TimeDelta = datetime.now()
 
 
def print_asset_tag_message(message):
    """Format the message printed out."""
    print(f"* Received message for asset: {message['data']['name']} - {message['data']['mac']}")
    fulltime = time.gmtime(message['data']['_time'])
    dt = datetime.fromtimestamp(message['data']['_time'])
    hours = time.strftime("%H:%M:%S", fulltime)
 
    global TimeDelta
    NewTimeDelta = dt - TimeDelta
    TimeDelta = dt
 
    print(f"\tTIME: {hours}")
    print(f"\tDELTA: {NewTimeDelta.total_seconds()} sec")
    print(f"\tX: {message['data']['x']}")
    print(f"\tY: {message['data']['y']}")
    #print(json.dumps(message, indent=4, sort_keys=True))
    print()
 
 
def on_message(ws, message):
    """Executed whe a new message arrives."""
    message = json.loads(message)
    if 'data' not in message:
        print(json.dumps(message, indent=4, sort_keys=True))
    else:
        message['data'] = json.loads(message['data'])
        if (AssetTagName != "") and (message['data']['name'].find(AssetTagName) != -1):
            print_asset_tag_message(message)
        elif AssetTagName == "":
            print_asset_tag_message(message)
 
 
def on_error(ws, error):
    """Message printed when an error occurs."""
    print(error)
 
 
def on_close(ws):
    """Actions performed when the socket is closed."""
    print(f"* Websocket closed")
 
 
def on_open(ws):
    """Use to subscribe to a specific endpoint."""
    print('* Opening WebSocket...')
    print('* Subscribing to channel:')
    ep = f'/sites/{SiteId}/stats/maps/{MapId}/assets'
    print(json.dumps({'subscribe': ep}, indent=4, sort_keys=True))
    ws.send(json.dumps({'subscribe': ep}))
 
 
def main():
    """GET LOCATION DATA FROM ASSET TAGS."""
    header = [f'Authorization: Token {Token}']
 
    ws = websocket.WebSocketApp(
        'wss://api-ws.mist.com/api-ws/v1/stream',
        header=header,
        on_message=on_message,
        on_error=on_error,
        on_close=on_close
    )
 
    ws.on_open = on_open
    ws.run_forever()
 
 
if __name__ == '__main__':
    start_time = time.time()
    print('** GETTING LOCATION DATA FOR ASSET TAGS...\n')
    main()
    run_time = time.time() - start_time
    print(f"\n** Time to run: {round(run_time, 2)} sec")

Feel free to try it for yourself!

Leave a Reply

Your email address will not be published. Required fields are marked *