Skip to content
Snippets Groups Projects
README.md 7.24 KiB
Newer Older
Jake Read's avatar
Jake Read committed
## LTV 1200 Ventilator Display 

The LTV-1200 is an (ageing) ventilator, unpopular in practice due to its lack of display. 

Luckily, the thing has an RS232 port which streams data in roughly 10ms intervals

We were able to read packets off of this line using a [Raspberry Pi](https://www.raspberrypi.org/), [node.js](https://nodejs.org/en/) and a small RS232 tranciever circuit from [pololu](https://www.pololu.com/product/126). 

![video](log/2020-11-18_vent-display-mvp.mp4)

Jake Read's avatar
Jake Read committed
### Using the Vent Display
Jake Read's avatar
Jake Read committed

**1:** power on the raspberry pi by plugging the micro usb cable in, using i.e. a phone charger. make sure the HDMI cable is connected to a display when you power on.  
Jake Read's avatar
Jake Read committed
**2:** there is a smaller circuit attached to the raspberry pi, that's the RS232 adapter. attached to *it* is a cable called an "RJ11" - similar to the kind found in corded phone headsets. this should plug into the ltv's "com port" - and the connections between it and the raspberry pi should be made to match the photos below  
Jake Read's avatar
Jake Read committed
**2:** open the 'terminal' application, and run these commands:
- `cd vent/code` (navigates the terminal to the code we will run)
Jake Read's avatar
Jake Read committed
- `node ltv` (tells node.js to run the ventilator UI server)  
Jake Read's avatar
Jake Read committed
**3:** the script should output two IP addresses. open a browser (ostensibly anywhere, but this will work best on the raspberry pi itself), and nagivate to either of these IP addresses in the browser. the UI should open, and if the ventilator is running, it will display data. 

Jake Read's avatar
Jake Read committed
### RS232 -> Raspberry Pi Connections 

![c1](log/2021-04-20_raspberry-pi-rs232-01.jpg)
![c2](log/2021-04-20_raspberry-pi-rs232-02.jpg)

Jake Read's avatar
Jake Read committed
### How it Works

Jake Read's avatar
Jake Read committed
The LTV's [serial protocol is well documented by the OEM](doc/11024-J_LTV-LTM_Serial_Communication_Protocol_For_Customers.pdf):
Jake Read's avatar
Jake Read committed

| - | type | electrical | baud rate | data bits | parity | stop bits |
| --- | --- | --- | --- | --- | --- | --- |
| spec | uart | R232 | 60096 | 8 | none | 2 |
| permitted | " | " | 57600 | 8 | none | 1 |

The LTV will spew "real time" packets from the RS232 port on a regular 10ms interval, *even if you donot ask anything of it* - this just happens all the time. This means code does not have to handshake with the device, there is no state in the connection, etc. Data emerges. This is the way. 

Packets are simple as well, delineating them is easiest to document with this code snippet (but is also well documented in their serial protocol doc linked above). Structure is like:

| b0 | b1 | b2 | b3:n | bn |
| --- | --- | --- | --- | --- |
| start = 255 | length | packet type | data | CRC |

```javascript
let packet = new Uint8Array(255)
let pi = 0	// packet indice (where inside of, during parse)
let pl = 0 	// packet length (expected len)
let ip = false // in packet (delineation state)

parser.on('data', (data) => {
    // read if 
    if(ip){
        if(pi == 0){
            // length byte 
            pl = data[0]
        }
        // store all bytes, increment ptr 
        packet[pi] = data[0]
        pi ++ 
        if(pi > pl + 1){
            onPacket(packet)    // pass, then ptr swap to new... shouldn't leak back 
            packet = new Uint8Array(255) 
            pi = 0 
            pl = 0 
            ip = false 
        }
    }
    // start byte / 0xFA
    if(data[0] == 250 && !ip){
        ip = true 
        pi = 0
    }
})
```

I only ever read "real time" packets from the device, using this structure:

```javascript
// switch on the packet type:
switch(pck[1]){
    case 1:
        //realtime data 
        //[2] uint8 insp state (table of 18 states)
        //[3,4] int16 prox pres (-5 to 120)
        //[5,6] int16 xdcr flow (-200 to 200) 
        //[7,8] int16 volume (1ml res) (0 to 3000)
        let data = {
            inspState: TS.read('uint8', pck, 2),
            proxPres: TS.read('int16', pck, 3),
            xdcrFlow: TS.read('int16', pck, 5),
            volume: TS.read('int16', pck, 7)
        }
        // seems like this works, I guess timestamp these things now and keep them around locally...
        // then run a server, client should req. the local store ? 
        store.push([indice, data.inspState, data.proxPres, data.xdcrFlow, data.volume])
        indice ++ 
        // occasionally lop off 1000 entries, so as not to explode local memory 
        if(store.length > 3000){
            console.log("rollover")
            store = store.slice(-2000)
        }
        //console.log(data)
        break;
    default:
        //not plotting others currently 
        break;
}
```

To pull types from data bytes like this:

```javascript
TS.read = (type, buffer, start) => {
    switch(type){
        case 'uint8':
            return buffer[start]
        case 'uint16':
            return (buffer[start] & 255 | buffer[start + 1] << 8)
        case 'int16':
            return new Int16Array(buffer.slice(start, start + 2).buffer)[0]
        case 'uint32':
            return (buffer[start] | buffer[start + 1] << 8 | buffer[start + 2] << 16 | buffer[start + 3] << 3)
        default:
            console.log("bad type to read")
            break;
    }
}
```

### Making the Connection

The Raspberry Pi has a hardware serial port, and runs linux. This means we can write our code in javascript (making it easy to update / modify / distribute) while still operating otherwise PITA serialports. Also, javascript is a wonderful language to write servers and clients in. 

The serialport needs an RS232 interface, I have also drawn this circuit to connect the raspberry pi's TTL UART to the RS232 connection on the vent:

![circuit](log/2021-01-01_backpack-routed.png)

The circuit is available in the repository, designed in eagle [here](circuit).

### Server / Client Architecture

To make the web app, I launch [ltv.js](code/ltv.js) on the Raspberry Pi which is connected to the LTV. Configuring the Raspberry Pi's serialport can be somewhat cumbersome, I have notes on that in [the log](log/log.md). 

This initalizes a server on the raspberry pi, and announces its availability on the raspberry pi's terminal. The IP address and port presented can be navigated to with any browser (or phone) and the following will happen:

- the client will request index.html, 
- the client will load the script referenced in index.html, 
    - *this script* will request that the server start a sub-process: `code/local/ltv-bridge.js`
- the `ltv-bridge.js` process connects to the ltv via the serialport, and to the client via a websocket
    - websocket coordinates are delivered to the client automatically by `ltv.js`
- when the `ltv-bridge` delineates a "realtime" packet on the serialport, it will button this up into a javascript object and whip it across the websocket to the client
- the client will add this data to a series of plots in the UI and redraw them

### What is Not Done

We have not tested this for long durations.  

The UI is a scratchpad, and could use work. Recommended settings for the UI were:

- Horizontal access should be time - 3-6 seconds (width of X axis in plots)
- Tidal volume is always positive so 0-1000 ml would cover all contingencies.
- Flow (inspiratory < 100 L/min) and expiratory flow < 150 L/min.
- Pressure also typically positive, scale is 0 - 80 should cover every possibility - most patients are ventilated at less than 45 cm H2O.

### Contact

If you are implementing one of these, and would like our input, simply post an issue on this repository.