Building a realtime ECG device backend with Agatsa

Customer

Fig 0 - Sanket ECG Device

Agatsa Technologies is a startup targeted towards building affordable medical devices and developing an ecosystem of care including the ability for people to make informed decisions about their health. Agatsa has developed “Sanket” – a credit card sized ECG monitor. The device can be held between thumbs for 15 seconds to get clinical grade ECG. No Lead or Gel is required for the same. Sanket has a corresponding App on Android and iOS. The app displays and records ECG sent to it via Bluetooth based Sanket device.

Agatsa Technologies is a startup targeted towards building affordable medical devices and developing an ecosystem of care including the ability for people to make informed decisions about their health. Agatsa has developed “Sanket” – a credit card sized ECG monitor. The device can be held between thumbs for 15 seconds to get clinical grade ECG. No Lead or Gel is required for the same. Sanket has a corresponding App on Android and iOS. The app displays and records ECG sent to it via Bluetooth based Sanket device.

Agatsa currently has 10 employees, including 2 co-founders.

Commercial Applications Sanket is being reviewed by Indian Oil and Border Security Force (BSF) for usage in remote locations like Ladakh as the device doesn’t need a specific power input (works on a small battery) and even works without Internet connectivity. BSF can now profile the soldiers serving in the 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 like Fortis healthcare and Medanta Medicity. Sanket is being used at present by 25 cardiologists in the National Capital region.

Sanket is a winner of the highly coveted Mashelkar award for the year 2015, in area of inclusive innovation, Sanket was also awarded in the recently organized Healthcare Summit in Rajasthan.

  1. Published in Medical Journals - http://medical.adrpublications.com/index.php/JoARMST/article/view/678
  2. Winner of Anjani Mashelkar Award 2015-http://award.ilcindia.org/winner-2015.aspx 3.Featured under top 10 Indian innovations by Business Outlook-http://www.outlookbusiness.com/specials/indian-innovation/heart-of-the-matter-2974 4.Tata Trusts Press release-http://tatatrusts.org/article/inside/tripura-healthcare-education 5.Featured in Business Standard-http://www.business-standard.com/article/current-affairs/life-saving-signals-from-the-heart-116062001254_1.html 6.Marico Foundation-https://www.youtube.com/watch?v=qIXFQaKL_F0 7.http://www.theweek.in/theweek/more/internet-of-things.html 8.https://inc42.com/startups/sanket/ 9.The Economic Times- http://economictimes.indiatimes.com/small-biz/startups/noida-based-agatsa-software-develops-credit-card-sized-healthcare-device-sanket/articleshow/49241627.cms 10.The Network India-http://www.networkedindia.com/2015/08/14/sanket-the-pocket-sized-mobile-heart-monitor-thats-revolutionising-cardiac-care/

This is the team that was involved with the project:

  • Brijraj Singh – Microsoft, Sr. Technical Evangelist, DX India
  • Surbhi Jain – Microsoft, Audience Marketing, DX India
  • Shweta Gupta - Microsoft, Sr. SDE
  • Saurabh Kirtani – Microsoft, Technical Evangelist, DX India
  • Pranav - IOS Dev, Agatsa
  • Akash - Android Dev, Agatsa
  • Abhinav - NodeJS Dev, Agatsa
  • Rahul Rastogi - Founder, Agatsa

Pain points

Agatsa is mostly an electronic device company and their focus for the past few years was developing the device and bringing accuracy to their algorithms. Sanket is a custom device that records minute vibrations of heart beat in patient’s thumbs, and generates an electric signal, which is sent over to Android/iOS App using Bluetooth. The mobile app records these electronic signals and sends them over to a function in 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 profile and the PDF files of ECG. While raw data which produces the ECG is discarded; PDF files generated on the Phone App is uploaded in Parse.

The current solution has a few problems:

  • The raw data from which 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 profile, no other information related to devices is available on server side for further processing.
  • There is no means to find out how many devices have been active since what time and how many ECGs have been taken using them
  • The information about ECG files is in PARSE but there are no reporting tools available for the same.
  • PARSE is being dis-continued by facebook and Agatsa wants a better scalable platform to store data that can easily plug with analytics and reporting tools and have capabilities to work with Machine learning systems in 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 wants to connect patients to doctors and hospitals based on their medical conditions and help the patient take informed decisions on the basis of analysis over various parameters.

Solution

Step 1: Evaluation of ECG device capabilities

Sanket is a custom device which for now has only bluetooth capabilities, and thus it can only communicate with a bluetooth enabled device, in this case its the android and ios mobile phones.

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 are no SDKs, 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 backend as shown in the diagram below.

Fig 1 - Data insertion architecture

Fig 2 - 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, and wrote a stream analytics job. This stream Analytics job can do calculations over the ECG data (if there is ECG data in it), and dump it in an Azure SQL database Table, the rest of the vitals like temperature and blood pressure is dumped 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://sastokengen.azurewebsites.net/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 *imeiNumber = [SSKeychain passwordForService:@"imei_alternative" account:@"SanketLife"];
    NSLog(@"imeiNumber : %@",imeiNumber);
    if(!imeiNumber)
        imeiNumber = [self createUUID];
    NSLog(@"imeiNumber : %@",imeiNumber);
    
    NSString *requestURI = [NSString stringWithFormat:@"https://agatsaiothub1.azure-devices.net/devices/%@?api-version=2016-02-03",imeiNumber];
    NSLog(@"%@",requestURI);
    NSDictionary *jsonDict = [NSDictionary dictionaryWithObject:imeiNumber 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://agatsaiothub1.azure-devices.net/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 IOT hub using the authentication token. Every time the device boots it sends an initial message to 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 Bluetooth id of the device and the iOS/android device id like -

Stream Analytics

When a message enters Azure IOT Hub, Stream Analytics casts the data to appropriate data types, while another SA job works on the RAW ECG data and converts it into usable format and dumps it in to azure SQL database, later the data can be archived into the blob storage on the basis of some expiration policy.
The SA Jobs couldn’t be shared because they have IP algorithm to process the Raw ECG data.

Step 6. Sharing the reports with Doctors

Agatsa is using the Notification hubs to register multiple devices against every user (doctors or patients) and allow the patients to share the ECG reports with Doctors using deep-linking links with parameters that would inturn open a PowerBI embedded report in a web control in Mobile app.

Fig 3 - Some pictures from Hackfest

Fig 4 - Some sample screens of new Agatsa mobile app

Architecture

Schema of the solution architecture explained above.

Device used & Code artifacts

Sanket ECG device 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, we are expecting the IOS SDK for IOT Hub in few months time, we will get the code refactored later and depend completely over the SDK and enjoy the cloud to device messaging capabilities. Agatsa is also working over the wifi enabled device, we’ll implement a C based version of same code in the device itself.

On the cloud side, Agatsa is being able to prepare different kind of reports from the data in Azure SQL, they are now working over PowerBI embedded reports.

Conclusion

Microsoft team and Agatsa team worked in tandem, we were able to iron out teething issues like IOS connectivity in a day, the Agatsa team was quick to learn the Stream analytics and Azure IOT Hub. Entire Architecture is PAAS based, hence the development was pretty quick.

We implemented a full backend architecture based on PAAS services, Agatsa team is really happy to see the ease of deployment and integration with PAAS services, during the course 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 2 sensors on Sanket device, we have also learned how same data can even find out if the patient is under stress or relaxed.