Saturday 7 May 2011

Telemedicine demo application

Description

The Telmedicine application demonstrates the integration between video telecommunication systems and Electronic Health Record Systems(EHR).

The specific use case that I will be demonstrating is a patient to doctor video conversation, where there are numerous doctors connected to the system, when the patient calls the main line the application routes the call based on medical history and availability, and when the doctor receives the call, his browser automatically opens up and shows the medical file for the patient that called.

Technologies

The server platform I chose is Mobicents which is a Telco and messaging platform, and for the client phones I chose Jitsi a multi-protocol client. Both were chosen because they are stable, robust,Java based, and Open Source solutions.


LAN setup considerations

One of the goals of the TM app is that it is easy to install and demo, therefore the app needed to work on a LAN without any public servers, but still work properly on a public network. To accommodate this requirement, I needed to take a few configuration issues into account:
  • Make sure you bind MSS to the LAN IP using -b, otherwise MSS will bind to localhost and wont receive packets from your clients.
  • There are 3 ways to implement the dialogue between clients :
    • Registrar only : if you only define the SIP url , one the initial invite is sent Jitsi will attempt to communicate directly with the other client , which means that you wont be able to log, trace, or customize further messages.
    • Registrar with Proxy : If you define a proxy for the clients, they will send all of their traffic to the server , but will reject messages arriving directly from the other client, which means that the server will need to replace the SIP headers (which is essentially what B2BUA does).
    • B2BUA with proxy: In this mode the clients send all their requests, while the server customizes the headers so that each client this its talking to the other client directly. This was the model I chose since it works well with Jitsi and allows me to track all traffic on the server.
  • Turn off all extra features in Jitsi that can generate unneeded traffic and make debugging difficult including encryption, presence, and keep-alive, STUN, etc..
  • The most robust approach to configuring the domains and proxy would have been to use DNS names and add the required entries in the hosts file, but the name resolution library that Jitsi uses does not process the hosts file, and therefore cannot resolve the DNS name to IP. To bypass this issue you should use either a server that has a public DNS record, or use IP addresses for the domain and proxy, for example shay@192.168.1.100.
  • For the app to run on any local LAN(and not for a specific IP range) as well as on public WAN ,without any code or configuration changes, it needed to be domain agnostic and without any hard coded domain info. The way I achieved this is by mapping and processing only the user name part of the sip URL and removing the domain part. This means that users and their names have a 1:1 relationship rather then users and their full sip URL.
  • The sip connector listens on port 5080 which allows you to run a client and a server on the same machine.
  • Make sure you turn off any firewalls since you turned off all firewall bypass mechanism above :) .
  • Turns out my VPN was mapped to the same LAN range which caused connection issues when it was on.
  • On Jitsi as well as many other clients, a video conversation starts off as a voice one , and adds video upon request , which means that you will have to click your video button to start sharing your cam. It also means that the server had to handle RE-INVITE sip messages because they are a major part of the SIP dialogue. This is another reason to use B2BUA so the server can handle these in-dialogue requests.

Electronic Health Record (EHR) integration

 

Emulating the EHR was done by creating two HTML server static pages that represent patient files for two patients david and rogers. If the application is working properly when the doctor receives a a call from one of these patients , their medical files are going to open up automatically in the browser. The images  above and below this paragraph are screenshots of the patient files.

Jitsi customization

In order for the the Doctor’s SIP phone to know which URL points to the patient file ,the server inserts a custom SIP header to the invite message , Jitsi looks for this header and if it finds the header , the URL is extracted and the browser launched.

I initially looked for the least invasive mode to add this functionality to Jitsi by developing a plug-in and hook my own code. The two main approaches were :

  • OSGi events . Since Jitsi is based on Felix and follows the OSGi spec , I attempted to use OSGi events but unfortunately they are not implemented by Jitsi.
  • SIP Message processors. The SIP stack that Jitsi uses allows for dynamic registration of SIP message event handlers, but Jitsi only allows for one processor at any given time and new ones just overwrite the previous ones.
**update - Emil from Jitsi provided the below info:
"jain-sip only allows for a single SipListener and its addListener() method throws a TooManyListenersException if you try to add a second one.
Jitsi on the other hand uses a SipStackSharing class which multiplexes
events over multiple providers. Each provider also uses the notion of
MethodProcessors that allow multiple classes to register when for a
particular kind of requests."  , which is the right way to implement the Jitsi extension.


Because neither of the less “intrusive“methods worked i ended up customizing the Invite handling code inside Jitsi.

Here is the code i added to CallPeerSipImpl on function entry


public CallPeerSipImpl processInvite(SipProvider jainSipProvider,
                  ServerTransaction serverTran) {
            Request invite = serverTran.getRequest();
            // shay
            SIPHeader header = (SIPHeader) invite.getHeader("pfile");
            if (header!=null) {
                  String url = header.getHeaderValue();
                  if (url.isEmpty()) {
                        url = "www.google.com";
                  }    
                  else
                  {
                        try {
                              java.awt.Desktop.getDesktop().browse(java.net.URI.create(url));
                        } catch (IOException e) {
                              e.printStackTrace();
                        }
                  }
            }
 .......function continues


MSS

Thanks to the great utility class B2BUAHelper , the code for the Servlet is very simple the init method sets-up 2 maps . One for call forwarding and one for patient file mappings. The reason we need a map for call forwarding is becuse patients do not have the direct number for the specialist they are contacting. The server redirects all calls from “doctor” to “receiver” .
The “unique” aspect of the Servlet is the handling of REINVITE within the invite handler.

Here is the DAR config for the Servlet:
INVITE=("org.mobicents.servlet.sip.example.CallForwardingB2BUAApplication", "DAR\:From", "ORIGINATING", "", "NO_ROUTE", "0")
REGISTER=("org.mobicents.servlet.sip.example.CallForwardingB2BUAApplication", "DAR\:From", "ORIGINATING", "", "NO_ROUTE", "0")