Newer
Older
## 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).

### How it Works
The LTV's [serial protocol is well documented by the OEM](doc/11024-J_LTV-LTM_Serial_Communication_Protocol_For_Customers.pdf):
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
| - | 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:

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.