Природа вируса такова, что очень сложно помешать его распространению. В Шри-Ланке, где я живу, мы столкнулись с такой же ситуацией, с которой столкнулись люди в других странах. Здесь я хочу рассказать о том, как программисты смогли оказать посильную помощь тем, кто сражается с болезнью лицом к лицу.
Риски, связанные с уходом за больными
Человек может быть заражён даже в том случае, если совершит небольшую ошибку. Врачам периодически приходится посещать инфекционные изоляторы для проверки жизненных показателей пациентов. После посещения изолятора нужно уничтожать защитные костюмы. И всё это — только для того, чтобы взглянуть на показатели, выводимые на экранах медицинских устройств.
К нам, с предложением разработать систему удалённого мониторинга пациентов, обратились представители органов здравоохранения. Существуют готовые решения для мониторинга, которые очень дороги. Но Шри-Ланка не такая уж и богатая страна.
Начало работы
Итак, мы (я и Кешара), провели некоторые исследования, и выяснили, что устройства, с которых нужно снимать показатели, прикроватные мониторы, используют для обмена медицинскими данными (вроде жизненных показателей) распространённый протокол HL7 (Health Level 7).
Мы потратили некоторое время на изучение этого протокола. Он показался нам немного странным. Мы никогда с ним раньше не работали. Для нас это была новая задача.
Вот как выглядят HL7-сообщения.
Пакет HL7
В разделе
Message
медицинские данные пациента представлены в виде, показанном ниже. <CR>
— это \r
— символ перевода строки, используемый для разделения сообщений.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 или Golang — это статически типизированный язык, синтаксис которого основан на синтаксисе языка C. Но Go характеризуется некоторыми дополнительными особенностями вроде наличия в языке сборщика мусора (как в Java), безопасной работы с типами и некоторых возможностей по динамической типизации. Go разработан в Google в 2007 году. Созданием языка занималась группа высококлассных специалистов. Это — Роберт Гризмер, Роб Пайк и Кен Томпсон».
Go создан с учётом поддержки многопоточного выполнения кода, соответствующие механизмы встроены в язык. В Go имеются так называемые горутины и каналы, использование которых позволяет программисту быстро и с минимальными усилиями разрабатывать программы, отличающиеся высоким уровнем параллелизма.
Поэтому мы решили выбрать Go. Если говорить о вставшей перед нами задаче, то мы полагали, что нам, при её решении, придётся столкнуться с необходимостью работы со множеством параллельно выполняемых задач. Кроме того, исполняемые файлы Go компилируются статически, что упрощает установку программ на больничные компьютеры, избавляя от необходимости заботы о зависимостях.
Мы искали хорошие библиотеки для поддержки работы с протоколом HL7, написанные на Go, и в итоге сочли подходящей вот эту. Её автор, кроме прочего, написал хороший материал о HL7.
Эта библиотека значительно облегчает работу с HL7-сообщениями.
Почему мы выбрали Vue?
Из документации Vue можно узнать следующее: «Vue (произносится /vju?/, примерно как view) — это прогрессивный фреймворк для создания пользовательских интерфейсов. В отличие от фреймворков-монолитов, Vue создан пригодным для постепенного внедрения».
Благодаря использованию Vue мы смогли без особого труда создавать приятные реактивные интерфейсы. Мы выбрали Vue из-за того, что это — мощное и удобное средство для создания интерфейсов. Мы, кроме того, в качестве UI-библиотеки, пользовались Vuetify.
Прикроватный монитор
После того, как мы изучили руководство по Mindray Bedside Monitor для программистов (в больнице много таких устройств, поэтому мы выбрали именно его), мы создали небольшой прототип для декодирования HL7-сообщений. Прототип умел правильно декодировать сообщения и корректно преобразовывать их в формат JSON. Мы сделали это, воспользовавшись Unsolicited Result Interface, описанном в руководстве.
Mindray uMec10
Но когда в нашем распоряжении оказалось реальное устройство, работать оно отказалось. После этого Кешара и я начали анализировать пакеты в Wireshark, пытаясь разобраться в том, чем, на самом деле, занято устройство. Как оказалось, устройство этот протокол не использовало. Оно использовало Realtime Result Interface — довольно старый интерфейс, который уже не поддерживался производителем.
Извлечение сообщений из HL7-пакетов
Вот как происходит извлечение HL7-сообщений из устройств. Мы использовали для этой задачи объект
bufio.Reader
, так как он предоставляет в распоряжение разработчика эффективные механизмы обработки потоков. Вместо того, чтобы каждый раз обращаться к сетевому уровню, Reader
позволил нам эффективно читать данные из TCP-соединения.func (d *Device) ProcessHL7Packet() (hl7.Message, error) {
// чтение начала сообщения, представленного байтом 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")
}
// чтение данных
payloadWithDelimiter, err := d.ReadBytes(byte(0x1C))
if err != nil {
return nil, fmt.Errorf("error reading payload: %s", err)
}
// проверка и обработка следующего байта в строке
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")
}
// пропустить последние 2 байта hl7-пакета
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 можно создать надёжную систему хранения данных для системы мониторинга. Кроме того, выбранная нами система поддерживает обработку больших объёмов входных данных, что было для нас плюсом.
В будущем мы, используя TimescaleDB, планируем создать систему аналитики, работающую в режиме реального времени. В результате PostgreSQL стала для нас идеальным выбором, так как TimescaleDB может быть установлена поверх PostgreSQL.
Мы разделили API и шлюз Health 24, руководствуясь соображениями, касающимися управления системой. При создании шлюза мы стремились к компактности и надёжности. Благодаря использованию Golang решать эту задачу было легко и приятно.
Выход в реальный мир
Прикроватные мониторы сообщают о своём присутствии, выдавая широковещательные UDP-сообщения. Нам нужно было захватывать и обрабатывать эти пакеты для извлечения из них данных, нужных для организации доступа к устройствам.
Мы, используя Go, создали отдельный сервис, предназначенный для обнаружения широковещательных UDP-передач и для регистрации новых устройств в системе. Следующим шагом нашей работы было подключение устройств к шлюзу. Мы создали на Go ещё один сервис, предназначенный для поддержки соответствующих TCP-соединений.
Обнаружение устройства
Так как шлюзу нужно, в роли клиента, подключиться к устройству, нам нужно было скоординировать и отключение устройств. Кроме того, на шлюзе нужно было контролировать состояние каждого монитора.
Благодаря использованию каналов Go, мы легко могли сохранять соответствующие данные в базе данных PostgreSQL для целей последующего анализа.
Каналы позволяют организовывать удобное взаимодействие между горутинами. Пользоваться ими было очень удобно.
Мой опыт работы над проектом Kache, который представляет собой размещаемую в памяти базу данных, совместимую с Redis, очень пригодился мне в деле решения различных сложностей, встававших перед нами в ходе работы.
Вывод жизненных показателей в режиме реального времени
Разрабатывая серверную часть проекта, мы параллельно занимались и его клиентской частью. Она была предназначена для врачей и была направлена на демонстрацию данных, считанных с прикроватных мониторов. Фронтендом, в основном, занимался Кешара. В результате очень хороший интерфейс системы был создан всего за 3 дня.
Мы, пользуясь Vuetify, создали макет интерфейса, который был похож на интерфейс прикроватного монитора.
Для управления состоянием приложения мы использовали Vuex и разработали сервис оповещений, учитывающий приоритет задач, используемый для оповещения сотрудников больницы при возникновении опасных ситуаций.
Мы соединили API и фронтенд с использованием Socket.io, что позволило нам создать эффективный канал коммуникации, данные по которому передаются в режиме реального времени.
Хочу ещё раз сказать спасибо Кешаре за отличную работу над фронтендом.
Панель мониторинга, выводящая данные в реальном времени
Развёртывание системы
Прикроватные мониторы отправляют в сеть большие объёмы данных. Мы решили использовать отдельную VLAN для устройств, и ещё одну VLAN для API. Цель этого заключалась в обеспечении возможности надёжной обработки трафика без создания перегрузки в больничной сети. Нам, кроме того, помогли наши преподаватели из университета — доктора Аситха Бандаранайке и Сунет Намаль Карунаратна.
Благодаря их поддержке мы смогли наладить надёжную сеть. Далее, мы запустили на сервере Ubuntu 18.04 и начали развёртывание системы.
Кешара многое сделал и в этой области, рискуя заразиться COVID-19 в больнице.
Кешара занимается развёртыванием системы в больнице
Вывод системы в продакшн
Вот видео, где показана работа с системой.
Доктор Викрамасингхе тестирует систему
Кешара и доктор Сударшана после развёртывания системы
Заявление об отказе от ответственности
Мы создали вышеописанный программный комплекс по запросу представителей органов здравоохранения. Этот проект не является коммерческим. Даже с учётом наличия в больнице этой системы, мы настоятельно рекомендуем врачам посещать пациентов и лично проверять показатели их жизнедеятельности.
Так как этот комплекс был разработан очень быстро, в условиях пандемии, мы выпустили его, оснастив лишь самым важным: возможностью мониторинга. Мы, кроме того, провели его длительное тестирование, используя в ходе испытаний множество устройств. До сих пор он показывал себя хорошо.
Это не указывает на то, что мы создали систему, которую уже невозможно улучшить. Мы постоянно работаем над её улучшением и исправляем ошибки, добиваясь высокой стабильности системы.
В результате мы обязаны предупредить врачей о том, чтобы они использовали бы эту систему с осторожностью.
Итоги
Медицинские работники, находящиеся на переднем крае сражения с вирусом, работают без отдыха. Мы должны оказывать им посильную помощь. Мы, студенты, изучающие компьютерные технологии, сделали всё, что могли, для того, чтобы их поддержать.
Система удалённого мониторинга состояния пациентов позволяет решать некоторые задачи без прямого контакта с пациентами, а значит — помогает врачам эффективнее и безопаснее делать свою работу.
Мы благодарим всех, кто разработал замечательные опенсорсные инструменты и библиотеки, без которых нашему проекту не суждено было бы увидеть свет.
Решение об использовании Golang было блестящей идеей. Использование этого языка позволило нам создать весьма стабильную систему всего за несколько дней. То же самое можно сказать и о Vue. Благодаря этому фреймворку мы смогли очень быстро создать качественный и удобный интерфейс.
Вместе мы сможем победить COVID-19.
Уважаемые читатели! Как вы подошли бы к построению системы, подобной той, о которой рассказано в этом материале?
gbg
Я бы включил режим максимальной паранойи.
Если пакеты от монитора не приходят в течении более чем полутора периодов, нужно бежать, орать, бить тревогу.
Если гуй не может допинговаться до бека в течении одной секунды — правильно, бежать-орать, выть сиреной
Если параметр резко скакнул — алярм.
Если бэк не видит свежих апдейтов в постгресе — алярм.
Если из сетевых интерфейсов полезли ошибки — алярм.
Если формат пакета нарушен — то же самое.
Короче, такая система должна быть увешана тестами, ассертами и проверками на все законы Мерфи, как атаман каракулем.