Microsoft Case study on SanketLife

Agatsa has developed a small electrocardiogram (ECG) monitor called Sanket that is generating interest because of its potential use in remote locations, among other uses. Microsoft teamed up with Agatsa in an IoT hackfest to build a cloud back-end service that can work with Sanket. This report describes the solution.

Customer

Agatsa is a startup based in India that is building affordable medical devices and developing an ecosystem of care that will help people make informed decisions about their health. It currently has 10 employees, including its two cofounders.

Agatsa has developed “Sanket”—a credit card-sized ECG monitor. The device can be held between the thumbs for 15 seconds to get a clinical-grade ECG. No lead or gel is required. Sanket has a corresponding app on Android and iOS. The app displays and records the ECG sent to it via a Bluetooth-based Sanket device.

Figure 1. Sanket ECG device

Commercial applications

Sanket is being reviewed by a major oil company in India and by Security Forces of India for use in remote locations such as Ladakh because the device doesn’t need a specific power input (works on a small battery) and it even works without Internet connectivity. Security Forces can now profile the soldiers serving in remote locations, with basic tools like Agatsa and weight-monitoring devices.

Most of the data collection drives and UAT drives have been done in medical institutions such as Fortis healthcare, Medanta Medicity, and multiple government hospitals in the National Capital Region.

This is the team that was involved with the project:

  • Brijraj Singh – Senior Technical Evangelist, Microsoft, DX India
  • Surbhi Jain – Audience Marketing, Microsoft, DX India
  • Saurabh Kirtani – Technical Evangelist, Microsoft, DX India
  • Pranav Sah – iOS Developer, Agatsa
  • Akashdeep – Android Developer, Agatsa
  • Abhinav Chaudhary – NodeJS Developer, Agatsa
  • Rahul Rastogi – Founder, Agatsa

Pain points

Agatsa is an electronic device company and their focus for the past few years was on developing Sanket and bringing accuracy to their algorithms.

Sanket is a custom device that records minute vibrations of heartbeats via the patient’s thumbs and generates an electric signal that is sent to an Android/iOS app using Bluetooth. The mobile app records these electronic signals and sends them to a function in the cloud (currently a Python code running in Heroku), which converts this signal to an understandable pattern of ECG.

The data collection and aggregation strategy was missing in the implementation. They have a Parse-based implementation on which they store customer profiles and the PDF files of ECGs. While the raw data that produces the ECG is not stored anywhere, the PDF files generated on the phone app are uploaded in Parse.

The current solution has a few challenges:

  • The raw data from which the ECG is deduced is discarded, and isn’t kept anywhere.
  • Since raw data is not pushed to the cloud, further analytics are not possible on the cloud—especially for historical review or predictive analysis.
  • Apart from customer profiles, no other information related to the devices is available on the server side for further processing.
  • There are no means to find out how many devices have been active since what time and how many ECGs have been taken with them.
  • The information about ECG files is in Parse but no reporting tools are available for the same.
  • Parse is being discontinued by Facebook and Agatsa wants a more scalable platform on which to store data that can easily be used for analytics and reporting tools and can be fed to machine learning systems in the future.

Agatsa envisions a platform over which ECG data and other vitals like body temperature, blood pressure, weight, and height can be captured and analyzed for preventive care. Agatsa also wants to connect patients to doctors and hospitals based on their medical conditions and help patients make informed decisions on the basis of analysis over various parameters.

Solution

Step 1: Evaluating ECG device capabilities

Sanket is a custom device that currently has only Bluetooth capabilities and thus it can only communicate with a Bluetooth-enabled device (Android and iOS mobile phones). An ECG data packet is about 90 KB in size with all 8-12 lead worth of data. The user needs to hold the device in his/her hand for up to 15 seconds to get the ECG. The data packet of ECG is sent in one go to cloud after the data of all leads is collected.

Step 2: Determining Hub device connectivity to the cloud

The biggest hurdle we faced was the iOS device connectivity to Azure IoT Hub. Since there is no IoT Hub SDK for iOS, we had to create a REST API wrapper (written in NodeJS) that the iOS device can connect to for creating device identities and sending device-to-cloud messages.

Step 3: Building the end-to-end flow

Once we were able to make both Android and iOS devices talk to Azure IoT Hub, we created the simple IoT Hub-based back end as shown in the next diagram.

Figure 2. Data insertion architecture

Data insertion architecture

We realized there is a pressing need to do push notifications over devices to support scenarios of patients sharing their vital stats with doctors for review and hence we started using Azure Notification Hubs. We also dropped the Azure Active Directory implementation for now but we’ll certainly take it up in the future to support patient and doctors authentication and authorization.

Figure 3. Final architecture

Final architecture

Step 4: Configuring IoT Hub, API app, and Stream Analytics

We started writing code for both Android and iOS devices and started sending data to IoT Hub in a generic format for both devices. We also created a Stream Analytics job, which can do calculations over the ECG data (if there is ECG data in it), and store it in an Azure SQL Database table. The rest of the vitals such as temperature and blood pressure are stored as it is.

Following is the sample request call from the iOS device.

+(void) getSASTokenFromWebAppOnCompletion:(GetSAStokenCompletionHandler) completion {
    
    //create a default NSURLConfiguration
    NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
    //create a session
    NSURLSession *session = [NSURLSession sessionWithConfiguration:defaultConfiguration];
    //Define path
    NSString * completePath = @"https://<RESTAPIEndPoint>/sas?op=registerdevice";
    //create a URL request
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:completePath] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60];
    //customize the url request
    [request setHTTPMethod:@"GET"];
    
    NSURLSessionDataTask *fetchTokenTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//        NSLog(@"StatusCode (getSASTokenFromWebAppOnCompletion): %ld",(long)[(NSHTTPURLResponse*)response statusCode]);
        if(error) {
            //error found : Return (nil, error)
            if(completion)
                completion(nil,error);
        }else {
            //error NOT found : Return (response, nil)
            //check if the status code is 200
            if([(NSHTTPURLResponse*)response statusCode] != 200) {
                //if status code is not 200 for some weird reason. Then create internal error and send it back to program's main logic for better understanding.
                NSError *internalError = [[NSError alloc] initWithDomain:@"com.eezytutorials.iosTuts"
                code:[(NSHTTPURLResponse*)response statusCode] userInfo:@{
                                    NSLocalizedFailureReasonErrorKey:@"LocalizedFailureReason",
                                    NSLocalizedDescriptionKey:@"LocalizedDescription",
                                    NSLocalizedRecoverySuggestionErrorKey:@"LocalizedRecoverySuggestion",
                                    NSLocalizedRecoveryOptionsErrorKey:@"LocalizedRecoveryOptions",
                                    NSRecoveryAttempterErrorKey:@"RecoveryAttempter",
                                    NSHelpAnchorErrorKey:@"HelpAnchor",
                                    NSStringEncodingErrorKey:@"NSStringEncodingError",
                                    NSURLErrorKey:@"NSURLError",
                                    NSFilePathErrorKey:@"NSFilePathError"
                                    }];
                if(completion)
                    completion(nil,internalError);
            }else {
                //status code is 200
//                NSString *responseString = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
                if (completion) {
                    completion(data,nil);
                }
            }
        }
    }];
    [fetchTokenTask resume];
}

+(void) registerDeviceUsingSASToken:(NSData *)sasTokenReceived OnCompletion:(RegisterDeviceCompletionHandler) completion {

    NSString *sasTokenString = [[NSString alloc]initWithData:sasTokenReceived encoding:NSUTF8StringEncoding];
    NSString *deviceNumber = [SSKeychain passwordForService:@"device_alternative" account:@"SanketLife"];
    if(!deviceNumber)
        deviceNumber = [self createUUID];
    
    NSString *requestURI = [NSString stringWithFormat:@"https://<iothubendpoint>/devices/%@?api-version=2016-02-03",imeiNumber];
    NSLog(@"%@",requestURI);
    NSDictionary *jsonDict = [NSDictionary dictionaryWithObject:deviceNumber forKey:@"deviceId"];
    id jsonObject = [NSJSONSerialization dataWithJSONObject:jsonDict options:NSJSONWritingPrettyPrinted error:nil];
    NSString *contentLength = [NSString stringWithFormat:@"%lu",(unsigned long)[jsonObject length]];
    NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:requestURI]];
    [urlRequest setHTTPMethod:@"PUT"];
    [urlRequest setValue:sasTokenString forHTTPHeaderField:@"Authorization"];
    [urlRequest setValue:@"application/json; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
    [urlRequest setValue:contentLength forHTTPHeaderField:@"Content-Length"];
    [urlRequest setHTTPBody:jsonObject];
    
    NSLog(@"\n***************************\n%@  %@\n%@\n%@\n***************************",[urlRequest HTTPMethod],[[urlRequest URL] absoluteString],[urlRequest allHTTPHeaderFields],[[NSString alloc]initWithData:[urlRequest HTTPBody] encoding:NSUTF8StringEncoding]);
    
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    [[session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

        if(error) {
            //error found : Return (nil, error)
            if(completion)
                completion(nil,error);
        }else {
      
            //error NOT found : Return (response, nil)
            //check if the status code is 200
            if([(NSHTTPURLResponse*)response statusCode] != 200) {
                //if status code is not 200 for some weird reason. Then create internal error and send it back to program's main logic for better understanding.
                NSError *internalError = [[NSError alloc] initWithDomain:@"com.eezytutorials.iosTuts"
                code:[(NSHTTPURLResponse*)response statusCode] userInfo:@{
                        NSLocalizedFailureReasonErrorKey:[[(NSHTTPURLResponse*)response allHeaderFields] valueForKey:@"iothub-errorcode"],
                        NSLocalizedDescriptionKey:[[(NSHTTPURLResponse*)response allHeaderFields] valueForKey:@"iothub-errorcode"],
                        NSLocalizedRecoverySuggestionErrorKey:@"Try another Device ID",
                        NSLocalizedRecoveryOptionsErrorKey:@"LocalizedRecoveryOptions",
                        NSRecoveryAttempterErrorKey:@"RecoveryAttempter",
                        NSHelpAnchorErrorKey:@"HelpAnchor",
                        NSStringEncodingErrorKey:@"NSStringEncodingError",
                        NSURLErrorKey:@"NSURLError",
                        NSFilePathErrorKey:@"NSFilePathError"
                        }];
                if(completion)
                    completion(nil,internalError);
            }else {
                //status code is 200
                NSString *responseString = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
                if (completion) {
                    completion(responseString,nil);
                }
            }
        }

    }] resume];
}

+(void) sendMessageToEventHubUsingRestAPIUsingDeviceId:(NSString *)deviceID andSASToken:(NSData *)sasTokenReceived {
    
    //Get the sas token in string form
    NSString *sasTokenString = [[NSString alloc]initWithData:sasTokenReceived encoding:NSUTF8StringEncoding];
    //create the Request URI
    NSString *requestURI = [NSString stringWithFormat:@"https://<iothubendpoint>/devices/%@/messages/events?api-version=2016-02-03",deviceID];
    
    //JSON Message to be passed, call the collectVitals function to pack the vitals information in JSON
    NSDictionary *jsonDict = collectVitals();

    id jsonObject = [NSJSONSerialization dataWithJSONObject:jsonDict options:NSJSONWritingPrettyPrinted error:nil];
   //Create the URL Request
    NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:requestURI]];
    //customize the url request
    [urlRequest setHTTPMethod:@"POST"];
    [urlRequest setValue:sasTokenString forHTTPHeaderField:@"Authorization"];
    [urlRequest setHTTPBody:jsonObject];
    
    //create a default NSURLConfiguration
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    
    [[session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        NSLog(@"data: %@", data);
        NSLog(@"response: %@", response);
        NSLog(@"ERROR %@", error);
    }] resume];
}

Sample JSON Message Packet from device to the Azure IoT Hub

{
"data":{
"uid":4632746732,
"Vitals":[
           {
             "vital":"ECG",
             "metric":"Lead1",
             "values":[142,165,167,198],
             "timestamp":1478178407
           },
          {
            "vital":"Height",
            "metric":"inches",
            "values":[68],
            "timestamp":1478178532
         }
],
"deviceid":47324
}
}

The NodeJS-based web API has two functions—one to generate the authentication token for each device and the other to send the data to the IoT Hub using the authentication token. Every time the device boots, it sends an initial message to the IoT Hub to check token validity. If the token is expired, it will generate a new token valid for 10 days. The DeviceID is formatted with both the Bluetooth ID of the device and the iOS/Android device ID, like <bluetoothid>-<mobiledeviceid>

Stream Analytics

When a message enters Azure IoT Hub, one Stream Analytics casts the data to appropriate data types while another Stream Analytics job works on the RAW ECG data and converts it into a usable format and stores it in the Azure SQL Database. Later the data can be archived into the blob storage on the basis of expiration policy. (The Stream Analytics jobs couldn’t be shared because they have IP algorithm to process the Raw ECG data.)

Figure 4. Sanket Stream Analytics job that stores vitals to SQL Database

Stream Analytics job

Step 5. Sharing the reports with doctors

Agatsa is using the Notification Hub to register multiple devices against every user (doctors or patients) and allow the patients to share the ECG reports with doctors. The shared message will have deep-linking links with parameters that would in turn open a Power BI Embedded report in a web control in mobile app (the Power BI Embedded work is in progress).

Figure 5. Some pictures from the hackfest

Hackfest

Figures 6-9. Sample screens of the new Agatsa mobile app

Sample screen 1

Sample screen 2

Sample screen 3

Sample screen 4

Architecture

The schema of the solution architecture is explained above.

Device used and code artifacts

  • Sanket ECG device – Each Sanket device has a built-in token that, when paired with the Sanket app, can help decode the ECG data stream emitted by the Sanket device. Users can access the ECGs taken by themselves only or shared to them by the owner of that ECG only. The ECG is not a PDF file anymore. Rather, it’s rendered on the fly by the device as well as the web app (web app in progress).
  • Android and iOS devices
  • GitHub repo for iOS connectivity to IoT Hub – https://github.com/brijrajsingh/IOS-IOTHUb

Opportunities going forward

We will continue to work with Agatsa as we are expecting the iOS SDK for IoT Hub in few months. Once it is available, we’ll get the code refactored and depend completely on the SDK, and enjoy the cloud-to-device messaging capabilities. Agatsa is also working over a Wi-Fi-enabled device for which we’ll help implement a C-based version of an IoT Hub implementation.

On the cloud side, Agatsa is able to prepare different kinds of reports from the data in Azure SQL Database. They have also started working with Power BI Embedded reports. We’ll continue the engagement to support these efforts.

Conclusion

The Microsoft and Agatsa teams worked in tandem to enable cloud capabilities for the Sanket device. The new compute and data storage capabilities of the cloud would enhance the solution to use advanced analytics. We were able to iron out issues like iOS connectivity quickly with the use of IoT Hub REST API. The Agatsa team was quick to learn Stream Analytics and Azure IoT Hub.

The Agatsa team is happy to see the ease of deployment and integration with PaaS services. During the course of the hackfest, we also learned how their device works and how they have developed a very fine-tuned algorithm to prepare a 12-lead ECG from just the two sensors on the Sanket device. It was really cool to see how the data can even determine whether the patient is under stress or relaxed.

A quote from the Agatsa dev team:

“Choosing Azure IoT Hub was easily the best decision that we have made for Sanket. It allows us to securely connect our devices with cloud back ends. Being in healthcare sector security is paramount. As a mobile dev-centric team, we wouldn’t have thought it could be so much easier to send events over to the cloud and get them to the database with such ease. Earlier, it would have taken weeks for a dev to write APIs and send data to the cloud—now it’s just a matter of hours to set up everything and start sending the data. Not only that, Azure has a set of other tools such as Power BI, Stream Analytics, among others, that can be connected with Azure IoT Hub with minimum fuss, yielding maximum results. Now using Microsoft tech, the Sanket platform can have concurrently thousands of users, getting their ECG monitored in milliseconds.”

Resources

Sanket won the highly coveted Mashelkar Award for the year 2015 in the area of inclusive innovation. Sanket was also honored in the recent Healthcare Summit in Rajasthan. For more about Sanket, see the following:

SanketLife is a Patent Protected Product and many designs and IP patents have been filed around this as early as 2013 by Agatsa Software Pvt Ltd. The patents have been awarded and in published state.

US FDA in Progress

We are filing 510K US FDA notification in association with our US FDA counterpart FDASmart http://www.fdasmart.com/

Expected date for obtaining US FDA is end of 2017

We will keep you posted on this more more updates.

TOP
error:
error: Content is protected !!