如何在三天内使用Go和Vue创建实时Covid-19患者监护系统 -kasvith.me


作为医护人员,我们的前线人员必须在隔离病房中徘徊,以不时检查患者的生命体征。还涉及接触后处置防护装备。这些接触只是为了检查医疗设备上的读数。卫生当局要求开发隔离病房的远程监控系统。有昂贵的软件可以远程监视它们。但是斯里兰卡可能没有那么多钱花这么多钱。
因此,我们(我和Keshara)做了一些背景研究,发现这些设备通常支持称为HL7(健康等级7)的通用协议来交换生命体征等医学数据。
我们研究了HL7协议一段时间。有点奇怪。我们从未使用过该协议。这是一种新的体验。
HL7消息的框架如下

HL7封包
消息部分内的患者医疗数据如下所示打包,《CR》是\r用于分离消息的回车
HL7消息样本:

MSH|^~\&|||||||ORU^R01|103|P|2.3.1|<CR>
PID|||14140f00-7bbc-0478-11122d2d02000000||WEERASINGHE^KESHARA||19960714|M|<CR>
PV1||I|^^ICU&1&3232237756&4601&&1|||||||||||||||A|||<CR>
OBR||||Mindray Monitor|||0|<CR>
OBX||NM|52^||189.0||||||F<CR>
OBX||NM|51^||100.0||||||F<CR>
OBX||ST|2301^||2||||||F<CR>
OBX||CE|2302^Blood||0^N||||||F<CR>
OBX||CE|2303^Paced||2^||||||F<CR>
OBX||ST|2308^BedNoStr||BED-001||||||F<CR>

好吧,这看起来很奇怪吧?我们也感到。这被称为Pipehat格式,用于|分隔段。我在这里不会谈论太多协议。您可以在互联网上找到大量资源。
我们发现了一些很酷的库,它们用不同的语言编写来处理HL7消息。

为什么使用Go
Go是为并发而构建的,它以语言本身的一流公民的身份来支持他们。Go具有goroutines和通道,可让程序员以最少的努力快速开发高度并发的程序。
因此,我们决定去Golang。对于这项任务,我们认为我们将不得不处理许多并发任务。另外,Go二进制文件是静态构建的,因此可以轻松在医院系统上安装软件,而无需添加其他依赖项。
我们一直在寻找用Go编写的好的库,并发现此是一个很好的。它的作者也撰写了一篇有关HL7 的精彩博客文章。它支持轻松选择和解析消息。

为什么选择VueJS
在VueJS中,我们可以轻松地创建漂亮的反应式UI。我们已经使用它,因为您已经知道它非常棒,简单而强大。我们还将Vuetify用于UI库

我们有一个真正的设备
了解Mindray床边监护仪的程序员指南后(这在医院很普遍),我们制作了一个小的原型来解码hl7消息。它可以正确解码hl7消息并将数据正确转换为JSON。我们使用《程序员手册》中定义的“未经请求的结果接口”进行了此操作。
但是,当我们亲身体验真正的设备时,它实际上是行不通的。因此,我和Keshara开始在Wireshark中分析数据包,以查看设备实际在做什么。因此,我们发现它根本不是在谈论此协议。它使用的是实时结果接口,该接口很旧,制造商无法维护。

让我们从HL7中提取一条消息
从设备中提取HL7消息的过程如下。我们使用bufio.Reader该任务是因为它具有处理输入流的有效方法。与其每次都访问网络层,不如让Reader有效地从基础TCP连接中读取数据。

func (d *Device) ProcessHL7Packet() (hl7.Message, error) {
    // read message start 0x0B
    b, err := d.ReadByte()
    if err != nil {
        return nil, fmt.Errorf(
"error reading start byte: %s", err)
    }
    if b != byte(0x0B) {
        return nil, fmt.Errorf(
"invalid header")
    }

   
// read payload
    payloadWithDelimiter, err := d.ReadBytes(byte(0x1C))
    if err != nil {
        return nil, fmt.Errorf(
"error reading payload: %s", err)
    }

   
// just verify and process next byte on the line
    b, err = d.ReadByte()
    if err != nil {
        return nil, fmt.Errorf(
"error reading end byte %s", err)
    }
    if b != byte(0x0D) {
        return nil, fmt.Errorf(
"invalid message end")
    }

   
// skip last two bytes from the hl7 packet
    payload := payloadWithDelimiter[:len(payloadWithDelimiter)-1]
    log.Debugf(
"Length of payload %d\n", len(payload))
    m, _, err := hl7.ParseMessage(payload)
    if err != nil {
        return nil, fmt.Errorf(
"error parsing hl7: %s\n", err)
    }
    return m, err
}

系统架构

系统设计以长期可靠的方式完成。我们精心选择了适合该任务的最佳工具。
我们选择的数据库是PostgreSQL,因为它稳定且可靠。通过HA设置,我们可以为监视系统创建一个很好的可靠数据库系统。此外,PG还支持高吞吐量数据提取,这是一个加号。
将来使用TimeScaleDB时,我们也会将其用于实时分析。因此,PG是最佳的整体选择,因为将来可以将TimeScale安装在它上面。
我们出于管理目的将网关和API分开。Gateway的设计轻巧且坚固。感谢GoLang,这是一次很酷的体验。

走向真实世界
床头监视器通过UDP协议广播了它的存在。我们必须捕获UDP数据包并对其进行处理,以提取必要的详细信息才能访问监视设备。
我们创建了一个单独的Go Service,以检测UDP广播并在系统中注册新设备。下一阶段是从网关连接设备内部的数据服务器。我们在Go中创建了另一个服务来处理这些TCP连接。

设备发现
由于网关需要作为客户端连接到设备,因此我们也必须协调客户端断开连接。另外,我们还必须在网关中的每个监视器状态上保留选项卡。
使用Go Channels,我们可以轻松地将Alarms保存到PostgreSQL数据库中,以供以后分析之用。
通道允许goroutine之间无互斥的通信,而不会感到痛苦。使用它们真棒。
我在开发称为Kache的Redis兼容内存数据库中的经验为我们解决许多关键问题提供了很多帮助。

实时显示生命体征
我们同时开始开发一个良好的前端应用程序,以显示医务人员设备的实时结果。Keshara做了UI部分的繁重工作,我觉得它很棒。在短短三天内,我们就完成了一个非常好的UI。
Vuetify开始,我们研究了类似于床头显示器界面的自定义布局。
使用Vuex进行状态管理,我们还开发了基于优先级的警报服务,可在任何紧急情况下向员工发出警报。
我们使用Socket.io连接了API和Frontend ,这使我们能够创建有效的通信渠道来实时交付结果。
我必须再次感谢Keshara在UI开发过程中所做的努力。

部署方式
这些设备正在以高吞吐量发送数据。我们决定为设备使用单独的VLAN,为API使用另一个VLAN,以处理流量,而不会淹没医院网络。我们的大学讲师Asitha Bandaranayake [url=http://www.ce.pdn.ac.lk/academic-staff/suneth-namal-karunarathna/]博士[/url]和Suneth Namal Karunarathna [url=http://www.ce.pdn.ac.lk/academic-staff/asitha-bandaranayake/]博士[/url]也为我们提供了帮助
在他们的支持下,我们能够建立一个坚实的网络。接下来,我们启动了Ubuntu 18.04盒子并开始部署系统。
Keshara在这里也进行了繁重的工作,冒着可能感染COVID患者的医院冒着生命危险。

点击标题见原文